diff options
369 files changed, 14112 insertions, 5770 deletions
diff --git a/Android.bp b/Android.bp index e5d4b8b87c88..75dfbb5d0fc3 100644 --- a/Android.bp +++ b/Android.bp @@ -324,6 +324,8 @@ java_library { "core/java/android/view/IOnKeyguardExitResult.aidl", "core/java/android/view/IPinnedStackController.aidl", "core/java/android/view/IPinnedStackListener.aidl", + "core/java/android/view/IRemoteAnimationRunner.aidl", + "core/java/android/view/IRemoteAnimationFinishedCallback.aidl", "core/java/android/view/IRotationWatcher.aidl", "core/java/android/view/IWallpaperVisibilityListener.aidl", "core/java/android/view/IWindow.aidl", @@ -464,6 +466,8 @@ java_library { "telecomm/java/com/android/internal/telecom/IInCallService.aidl", "telecomm/java/com/android/internal/telecom/ITelecomService.aidl", "telecomm/java/com/android/internal/telecom/RemoteServiceCallback.aidl", + "telephony/java/android/telephony/data/IDataService.aidl", + "telephony/java/android/telephony/data/IDataServiceCallback.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsCallSessionListener.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsCapabilityCallback.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsConfig.aidl", @@ -471,8 +475,6 @@ java_library { "telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl", @@ -492,6 +494,8 @@ java_library { "telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl", "telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl", "telephony/java/com/android/ims/internal/IImsMultiEndpoint.aidl", + "telephony/java/com/android/ims/internal/IImsRegistration.aidl", + "telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl", "telephony/java/com/android/ims/internal/IImsRcsFeature.aidl", "telephony/java/com/android/ims/internal/IImsService.aidl", "telephony/java/com/android/ims/internal/IImsServiceController.aidl", @@ -519,9 +523,21 @@ java_library { "telephony/java/com/android/internal/telephony/ITelephony.aidl", "telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl", "telephony/java/com/android/internal/telephony/IWapPushManager.aidl", + "telephony/java/com/android/internal/telephony/euicc/IAuthenticateServerCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/ICancelSessionCallback.aidl", "telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl", "telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl", "telephony/java/com/android/internal/telephony/euicc/IGetAllProfilesCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IGetEuiccChallengeCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IGetEuiccInfo1Callback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IGetEuiccInfo2Callback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IGetRulesAuthTableCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IListNotificationsCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/ILoadBoundProfilePackageCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IPrepareDownloadCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IRemoveNotificationFromListCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IRetrieveNotificationCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IRetrieveNotificationListCallback.aidl", "wifi/java/android/net/wifi/IWifiManager.aidl", "wifi/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl", "wifi/java/android/net/wifi/aware/IWifiAwareEventCallback.aidl", diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java index 5653a039a9ed..93a0fc314b7f 100644 --- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java +++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java @@ -190,7 +190,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PremeasuredText text = PremeasuredText.build( + final MeasuredText text = MeasuredText.build( generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); state.resumeTiming(); @@ -206,7 +206,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PremeasuredText text = PremeasuredText.build( + final MeasuredText text = MeasuredText.build( generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); state.resumeTiming(); @@ -222,7 +222,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PremeasuredText text = PremeasuredText.build( + final MeasuredText text = MeasuredText.build( generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); state.resumeTiming(); @@ -238,7 +238,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PremeasuredText text = PremeasuredText.build( + final MeasuredText text = MeasuredText.build( generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); state.resumeTiming(); @@ -254,7 +254,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PremeasuredText text = PremeasuredText.build( + final MeasuredText text = MeasuredText.build( generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT, LTR); state.resumeTiming(); diff --git a/api/current.txt b/api/current.txt index 4210db73556b..9398d72615f2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6357,6 +6357,7 @@ package android.app.admin { field public static final java.lang.String EXTRA_DISABLE_WARNING = "android.app.extra.DISABLE_WARNING"; field public static final java.lang.String EXTRA_LOCK_TASK_PACKAGE = "android.app.extra.LOCK_TASK_PACKAGE"; field public static final java.lang.String EXTRA_TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE = "android.app.extra.TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE"; + field public static final java.lang.String SUPPORT_TRANSFER_OWNERSHIP_META_DATA = "android.app.support_transfer_ownership"; } public class DeviceAdminService extends android.app.Service { @@ -6370,7 +6371,7 @@ package android.app.admin { method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName); method public void addUserRestriction(android.content.ComponentName, java.lang.String); method public boolean bindDeviceAdminServiceAsUser(android.content.ComponentName, android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle); - method public boolean clearApplicationUserData(android.content.ComponentName, java.lang.String, android.app.admin.DevicePolicyManager.OnClearApplicationUserDataListener, java.util.concurrent.Executor); + method public boolean clearApplicationUserData(android.content.ComponentName, java.lang.String, java.util.concurrent.Executor, android.app.admin.DevicePolicyManager.OnClearApplicationUserDataListener); method public void clearCrossProfileIntentFilters(android.content.ComponentName); method public deprecated void clearDeviceOwnerApp(java.lang.String); method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String); @@ -6400,12 +6401,14 @@ package android.app.admin { method public java.util.List<java.lang.String> getDelegatePackages(android.content.ComponentName, java.lang.String); method public java.util.List<java.lang.String> getDelegatedScopes(android.content.ComponentName, java.lang.String); method public java.lang.CharSequence getDeviceOwnerLockScreenInfo(); + method public java.lang.CharSequence getEndUserSessionMessage(android.content.ComponentName); method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName); method public java.util.List<java.lang.String> getKeepUninstalledPackages(android.content.ComponentName); method public int getKeyguardDisabledFeatures(android.content.ComponentName); method public int getLockTaskFeatures(android.content.ComponentName); method public java.lang.String[] getLockTaskPackages(android.content.ComponentName); method public java.lang.CharSequence getLongSupportMessage(android.content.ComponentName); + method public android.content.ComponentName getMandatoryBackupTransport(); method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName); method public long getMaximumTimeToLock(android.content.ComponentName); method public int getOrganizationColor(android.content.ComponentName); @@ -6434,6 +6437,7 @@ package android.app.admin { method public boolean getScreenCaptureDisabled(android.content.ComponentName); method public java.util.List<android.os.UserHandle> getSecondaryUsers(android.content.ComponentName); method public java.lang.CharSequence getShortSupportMessage(android.content.ComponentName); + method public java.lang.CharSequence getStartUserSessionMessage(android.content.ComponentName); method public boolean getStorageEncryption(android.content.ComponentName); method public int getStorageEncryptionStatus(); method public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy(); @@ -6496,6 +6500,7 @@ package android.app.admin { method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean); method public void setDelegatedScopes(android.content.ComponentName, java.lang.String, java.util.List<java.lang.String>); method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence); + method public void setEndUserSessionMessage(android.content.ComponentName, java.lang.CharSequence); method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String); method public void setKeepUninstalledPackages(android.content.ComponentName, java.util.List<java.lang.String>); method public boolean setKeyPairCertificate(android.content.ComponentName, java.lang.String, java.util.List<java.security.cert.Certificate>, boolean); @@ -6505,6 +6510,7 @@ package android.app.admin { method public void setLockTaskPackages(android.content.ComponentName, java.lang.String[]) throws java.lang.SecurityException; method public void setLogoutEnabled(android.content.ComponentName, boolean); method public void setLongSupportMessage(android.content.ComponentName, java.lang.CharSequence); + method public void setMandatoryBackupTransport(android.content.ComponentName, android.content.ComponentName); method public void setMasterVolumeMuted(android.content.ComponentName, boolean); method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int); method public void setMaximumTimeToLock(android.content.ComponentName, long); @@ -6538,6 +6544,7 @@ package android.app.admin { method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String); method public void setSecurityLoggingEnabled(android.content.ComponentName, boolean); method public void setShortSupportMessage(android.content.ComponentName, java.lang.CharSequence); + method public void setStartUserSessionMessage(android.content.ComponentName, java.lang.CharSequence); method public boolean setStatusBarDisabled(android.content.ComponentName, boolean); method public int setStorageEncryption(android.content.ComponentName, boolean); method public void setSystemSetting(android.content.ComponentName, java.lang.String, java.lang.String); @@ -7069,8 +7076,8 @@ package android.app.slice { public final class Slice implements android.os.Parcelable { ctor protected Slice(android.os.Parcel); - method public static android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri, java.util.List<android.app.slice.SliceSpec>); - method public static android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent, java.util.List<android.app.slice.SliceSpec>); + method public static deprecated android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri, java.util.List<android.app.slice.SliceSpec>); + method public static deprecated android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent, java.util.List<android.app.slice.SliceSpec>); method public int describeContents(); method public java.util.List<java.lang.String> getHints(); method public java.util.List<android.app.slice.SliceItem> getItems(); @@ -7156,7 +7163,10 @@ package android.app.slice { } public class SliceManager { + method public android.app.slice.Slice bindSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>); + method public android.app.slice.Slice bindSlice(android.content.Intent, java.util.List<android.app.slice.SliceSpec>); method public java.util.List<android.app.slice.SliceSpec> getPinnedSpecs(android.net.Uri); + method public java.util.Collection<android.net.Uri> getSliceDescendants(android.net.Uri); method public void pinSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>); method public void registerSliceCallback(android.net.Uri, android.app.slice.SliceManager.SliceCallback, java.util.List<android.app.slice.SliceSpec>); method public void registerSliceCallback(android.net.Uri, android.app.slice.SliceManager.SliceCallback, java.util.List<android.app.slice.SliceSpec>, android.os.Handler); @@ -7175,7 +7185,7 @@ package android.app.slice { method public final java.lang.String getType(android.net.Uri); method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues); method public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>); - method public deprecated android.app.slice.Slice onBindSlice(android.net.Uri); + method public java.util.Collection<android.net.Uri> onGetSliceDescendants(android.net.Uri); method public android.net.Uri onMapIntentToUri(android.content.Intent); method public void onSlicePinned(android.net.Uri); method public void onSliceUnpinned(android.net.Uri); @@ -11157,7 +11167,7 @@ package android.content.pm { field public static final java.lang.String FEATURE_USB_HOST = "android.hardware.usb.host"; field public static final java.lang.String FEATURE_VERIFIED_BOOT = "android.software.verified_boot"; field public static final java.lang.String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking"; - field public static final java.lang.String FEATURE_VR_MODE = "android.software.vr.mode"; + field public static final deprecated java.lang.String FEATURE_VR_MODE = "android.software.vr.mode"; field public static final java.lang.String FEATURE_VR_MODE_HIGH_PERFORMANCE = "android.hardware.vr.high_performance"; field public static final java.lang.String FEATURE_VULKAN_HARDWARE_COMPUTE = "android.hardware.vulkan.compute"; field public static final java.lang.String FEATURE_VULKAN_HARDWARE_LEVEL = "android.hardware.vulkan.level"; @@ -20885,7 +20895,6 @@ package android.inputmethodservice { method public int getMaxWidth(); method public java.lang.CharSequence getTextForImeAction(int); method public android.app.Dialog getWindow(); - method public void hideSoftInputFromInputMethod(int); method public void hideStatusIcon(); method public void hideWindow(); method public boolean isExtractViewShown(); @@ -20933,6 +20942,7 @@ package android.inputmethodservice { method public void onWindowHidden(); method public void onWindowShown(); method public void requestHideSelf(int); + method public void requestShowSelf(int); method public boolean sendDefaultEditorAction(boolean); method public void sendDownUpKeyEvents(int); method public void sendKeyChar(char); @@ -20945,7 +20955,6 @@ package android.inputmethodservice { method public void setInputMethodAndSubtype(java.lang.String, android.view.inputmethod.InputMethodSubtype); method public void setInputView(android.view.View); method public boolean shouldOfferSwitchingToNextInputMethod(); - method public void showSoftInputFromInputMethod(int); method public void showStatusIcon(int); method public void showWindow(boolean); method public void switchInputMethod(java.lang.String); @@ -26221,12 +26230,18 @@ package android.net { } public final class IpSecManager { - method public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException; - method public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; - method public void applyTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException; + method public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException; + method public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; + method public void applyTransportModeTransform(java.net.Socket, int, android.net.IpSecTransform) throws java.io.IOException; + method public void applyTransportModeTransform(java.net.DatagramSocket, int, android.net.IpSecTransform) throws java.io.IOException; + method public void applyTransportModeTransform(java.io.FileDescriptor, int, android.net.IpSecTransform) throws java.io.IOException; method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; - method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException; + method public void removeTransportModeTransforms(java.net.Socket) throws java.io.IOException; + method public void removeTransportModeTransforms(java.net.DatagramSocket) throws java.io.IOException; + method public void removeTransportModeTransforms(java.io.FileDescriptor) throws java.io.IOException; + field public static final int DIRECTION_IN = 0; // 0x0 + field public static final int DIRECTION_OUT = 1; // 0x1 } public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException { @@ -26249,18 +26264,15 @@ package android.net { public final class IpSecTransform implements java.lang.AutoCloseable { method public void close(); - field public static final int DIRECTION_IN = 0; // 0x0 - field public static final int DIRECTION_OUT = 1; // 0x1 } public static class IpSecTransform.Builder { ctor public IpSecTransform.Builder(android.content.Context); - method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; - method public android.net.IpSecTransform.Builder setAuthenticatedEncryption(int, android.net.IpSecAlgorithm); - method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm); - method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm); + method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; + method public android.net.IpSecTransform.Builder setAuthenticatedEncryption(android.net.IpSecAlgorithm); + method public android.net.IpSecTransform.Builder setAuthentication(android.net.IpSecAlgorithm); + method public android.net.IpSecTransform.Builder setEncryption(android.net.IpSecAlgorithm); method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int); - method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex); } public class LinkAddress implements android.os.Parcelable { @@ -26343,10 +26355,10 @@ package android.net { } public final class MacAddress implements android.os.Parcelable { - method public int addressType(); method public int describeContents(); method public static android.net.MacAddress fromBytes(byte[]); method public static android.net.MacAddress fromString(java.lang.String); + method public int getAddressType(); method public boolean isLocallyAssigned(); method public byte[] toByteArray(); method public java.lang.String toOuiString(); @@ -26356,7 +26368,6 @@ package android.net { field public static final int TYPE_BROADCAST = 3; // 0x3 field public static final int TYPE_MULTICAST = 2; // 0x2 field public static final int TYPE_UNICAST = 1; // 0x1 - field public static final int TYPE_UNKNOWN = 0; // 0x0 } public class MailTo { @@ -32465,6 +32476,7 @@ package android.os { field public static final java.lang.String DISALLOW_CONFIG_LOCALE = "no_config_locale"; field public static final java.lang.String DISALLOW_CONFIG_LOCATION_MODE = "no_config_location_mode"; field public static final java.lang.String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks"; + field public static final java.lang.String DISALLOW_CONFIG_SCREEN_TIMEOUT = "no_config_screen_timeout"; field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering"; field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn"; field public static final java.lang.String DISALLOW_CONFIG_WIFI = "no_config_wifi"; @@ -42299,20 +42311,9 @@ package android.text { method public boolean isAllowed(char); } - public abstract interface NoCopySpan { - } - - public static class NoCopySpan.Concrete implements android.text.NoCopySpan { - ctor public NoCopySpan.Concrete(); - } - - public abstract interface ParcelableSpan implements android.os.Parcelable { - method public abstract int getSpanTypeId(); - } - - public class PremeasuredText implements android.text.Spanned { - method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic); - method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic, int, int); + public class MeasuredText implements android.text.Spanned { + method public static android.text.MeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic); + method public static android.text.MeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic, int, int); method public char charAt(int); method public int getEnd(); method public android.text.TextPaint getPaint(); @@ -42331,6 +42332,17 @@ package android.text { method public java.lang.CharSequence subSequence(int, int); } + public abstract interface NoCopySpan { + } + + public static class NoCopySpan.Concrete implements android.text.NoCopySpan { + ctor public NoCopySpan.Concrete(); + } + + public abstract interface ParcelableSpan implements android.os.Parcelable { + method public abstract int getSpanTypeId(); + } + public class Selection { method public static boolean extendDown(android.text.Spannable, android.text.Layout); method public static boolean extendLeft(android.text.Spannable, android.text.Layout); @@ -47892,7 +47904,6 @@ package android.view { field public static final int FIRST_APPLICATION_WINDOW = 1; // 0x1 field public static final int FIRST_SUB_WINDOW = 1000; // 0x3e8 field public static final int FIRST_SYSTEM_WINDOW = 2000; // 0x7d0 - field public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 1L; // 0x1L field public static final int FLAGS_CHANGED = 4; // 0x4 field public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 1; // 0x1 field public static final int FLAG_ALT_FOCUSABLE_IM = 131072; // 0x20000 @@ -47930,6 +47941,9 @@ package android.view { field public static final int LAST_SUB_WINDOW = 1999; // 0x7cf field public static final int LAST_SYSTEM_WINDOW = 2999; // 0xbb7 field public static final int LAYOUT_CHANGED = 1; // 0x1 + field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 1; // 0x1 + field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0; // 0x0 + field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER = 2; // 0x2 field public static final int MEMORY_TYPE_CHANGED = 256; // 0x100 field public static final deprecated int MEMORY_TYPE_GPU = 2; // 0x2 field public static final deprecated int MEMORY_TYPE_HARDWARE = 1; // 0x1 @@ -47987,11 +48001,11 @@ package android.view { field public float buttonBrightness; field public float dimAmount; field public int flags; - field public long flags2; field public int format; field public int gravity; field public float horizontalMargin; field public float horizontalWeight; + field public int layoutInDisplayCutoutMode; field public deprecated int memoryType; field public java.lang.String packageName; field public int preferredDisplayModeId; @@ -48039,6 +48053,8 @@ package android.view.accessibility { method public void setPackageName(java.lang.CharSequence); method public void writeToParcel(android.os.Parcel, int); field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4 + field public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 16; // 0x10 + field public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 32; // 0x20 field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8 field public static final int CONTENT_CHANGE_TYPE_SUBTREE = 1; // 0x1 field public static final int CONTENT_CHANGE_TYPE_TEXT = 2; // 0x2 diff --git a/api/system-current.txt b/api/system-current.txt index 002c2bd9d08c..b7518a5ed958 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -430,6 +430,7 @@ package android.app.backup { method public java.lang.String getCurrentTransport(); method public boolean isAppEligibleForBackup(java.lang.String); method public boolean isBackupEnabled(); + method public boolean isBackupServiceActive(android.os.UserHandle); method public java.lang.String[] listAllTransports(); method public int requestBackup(java.lang.String[], android.app.backup.BackupObserver); method public int requestBackup(java.lang.String[], android.app.backup.BackupObserver, android.app.backup.BackupManagerMonitor, int); @@ -4454,6 +4455,8 @@ package android.telephony { method public deprecated void setDataEnabled(int, boolean); method public boolean setRadio(boolean); method public boolean setRadioPower(boolean); + method public void setSimPowerState(int); + method public void setSimPowerStateForSlot(int, int); method public deprecated void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean); method public void setVoiceActivationState(int); method public deprecated void silenceRinger(); @@ -4463,8 +4466,6 @@ package android.telephony { method public int[] supplyPukReportResult(java.lang.String, java.lang.String); method public void toggleRadioOnOff(); method public void updateServiceLocation(); - method public void setSimPowerState(int); - method public void setSimPowerStateForSlot(int, int); field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1 field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0 @@ -4535,6 +4536,38 @@ package android.telephony.data { field public static final int TYPE_COMMON = 0; // 0x0 } + public abstract class DataService extends android.app.Service { + method public abstract android.telephony.data.DataService.DataServiceProvider createDataServiceProvider(int); + field public static final java.lang.String DATA_SERVICE_EXTRA_SLOT_ID = "android.telephony.data.extra.SLOT_ID"; + field public static final java.lang.String DATA_SERVICE_INTERFACE = "android.telephony.data.DataService"; + } + + public class DataService.DataServiceProvider { + ctor public DataService.DataServiceProvider(int); + method public void deactivateDataCall(int, boolean, boolean, android.telephony.data.DataServiceCallback); + method public void getDataCallList(android.telephony.data.DataServiceCallback); + method public final int getSlotId(); + method public final void notifyDataCallListChanged(java.util.List<android.telephony.data.DataCallResponse>); + method protected void onDestroy(); + method public void setDataProfile(java.util.List<android.telephony.data.DataProfile>, boolean, android.telephony.data.DataServiceCallback); + method public void setInitialAttachApn(android.telephony.data.DataProfile, boolean, android.telephony.data.DataServiceCallback); + method public void setupDataCall(int, android.telephony.data.DataProfile, boolean, boolean, boolean, android.net.LinkProperties, android.telephony.data.DataServiceCallback); + } + + public class DataServiceCallback { + method public void onDataCallListChanged(java.util.List<android.telephony.data.DataCallResponse>); + method public void onDeactivateDataCallComplete(int); + method public void onGetDataCallListComplete(int, java.util.List<android.telephony.data.DataCallResponse>); + method public void onSetDataProfileComplete(int); + method public void onSetInitialAttachApnComplete(int); + method public void onSetupDataCallComplete(int, android.telephony.data.DataCallResponse); + field public static final int RESULT_ERROR_BUSY = 3; // 0x3 + field public static final int RESULT_ERROR_ILLEGAL_STATE = 4; // 0x4 + field public static final int RESULT_ERROR_INVALID_ARG = 2; // 0x2 + field public static final int RESULT_ERROR_UNSUPPORTED = 1; // 0x1 + field public static final int RESULT_SUCCESS = 0; // 0x0 + } + } package android.telephony.ims { @@ -4647,9 +4680,12 @@ package android.util { } public final class StatsManager { + method public boolean addConfiguration(java.lang.String, byte[], java.lang.String, java.lang.String); method public boolean addConfiguration(long, byte[], java.lang.String, java.lang.String); + method public byte[] getData(java.lang.String); method public byte[] getData(long); method public byte[] getMetadata(); + method public boolean removeConfiguration(java.lang.String); method public boolean removeConfiguration(long); } diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index ffe652f72256..2c35d413631b 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -186,7 +186,8 @@ LOCAL_SRC_FILES := \ tests/statsd_test_util.cpp \ tests/e2e/WakelockDuration_e2e_test.cpp \ tests/e2e/MetricConditionLink_e2e_test.cpp \ - tests/e2e/Attribution_e2e_test.cpp + tests/e2e/Attribution_e2e_test.cpp \ + tests/e2e/GaugeMetric_e2e_test.cpp LOCAL_STATIC_LIBRARIES := \ $(statsd_common_static_libraries) \ @@ -198,6 +199,24 @@ LOCAL_PROTOC_OPTIMIZE_TYPE := lite include $(BUILD_NATIVE_TEST) +############################## +# stats proto static java lib +############################## + +include $(CLEAR_VARS) +LOCAL_MODULE := statsdprotolite + +LOCAL_SRC_FILES := \ + src/stats_log.proto \ + src/statsd_config.proto \ + src/atoms.proto + +LOCAL_PROTOC_OPTIMIZE_TYPE := lite + +LOCAL_STATIC_JAVA_LIBRARIES := \ + platformprotoslite + +include $(BUILD_STATIC_JAVA_LIBRARY) statsd_common_src:= statsd_common_aidl_includes:= @@ -208,4 +227,4 @@ statsd_common_shared_libraries:= ############################## -include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index 09e10a13615f..301e3a535e6d 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -99,6 +99,7 @@ private: FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions); FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks); FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice); + FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent); }; diff --git a/cmds/statsd/src/dimension.h b/cmds/statsd/src/dimension.h index 845c13867e5a..d0f96a2abe6b 100644 --- a/cmds/statsd/src/dimension.h +++ b/cmds/statsd/src/dimension.h @@ -32,6 +32,10 @@ namespace statsd { const DimensionsValue* getSingleLeafValue(const DimensionsValue* value); DimensionsValue getSingleLeafValue(const DimensionsValue& value); +// Appends the leaf node to the parent tree. +void appendLeafNodeToParent(const Field& field, const DimensionsValue& value, + DimensionsValue* parentValue); + // Constructs the DimensionsValue protos from the FieldMatcher. Each DimensionsValue proto // represents a tree. When the input proto has repeated fields and the input "dimensions" wants // "ANY" locations, it will return multiple trees. diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 1a4888c31fff..ae47bd898cf9 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -114,6 +114,9 @@ GaugeMetricProducer::~GaugeMetricProducer() { void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) { flushIfNeededLocked(dumpTimeNs); + ProtoOutputStream pbOutput; + onDumpReportLocked(dumpTimeNs, &pbOutput); + parseProtoOutputStream(pbOutput, report); } void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index 08b0981bb14f..a0239fcd1127 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -141,6 +141,7 @@ private: FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions); FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks); FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice); + FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent); }; } // namespace statsd diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp index b0c31975c91c..dcb8eed1479d 100644 --- a/cmds/statsd/src/packages/UidMap.cpp +++ b/cmds/statsd/src/packages/UidMap.cpp @@ -471,10 +471,13 @@ const std::map<string, uint32_t> UidMap::sAidToUidMapping = {{"AID_ROOT", 0}, {"AID_AUTOMOTIVE_EVS", 1062}, {"AID_LOWPAN", 1063}, {"AID_HSM", 1064}, + {"AID_RESERVED_DISK", 1065}, + {"AID_STATSD", 1066}, + {"AID_INCIDENTD", 1067}, {"AID_SHELL", 2000}, {"AID_CACHE", 2001}, {"AID_DIAG", 2002}}; } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h index 09a43f5c881f..cee920038eef 100644 --- a/cmds/statsd/src/stats_log_util.h +++ b/cmds/statsd/src/stats_log_util.h @@ -43,6 +43,19 @@ int64_t TimeUnitToBucketSizeInMillis(TimeUnit unit); // Helper function to write PulledAtomStats to ProtoOutputStream void writePullerStatsToStream(const std::pair<int, StatsdStats::PulledAtomStats>& pair, util::ProtoOutputStream* protoOutput); + +template<class T> +bool parseProtoOutputStream(util::ProtoOutputStream& protoOutput, T* message) { + std::string pbBytes; + auto iter = protoOutput.data(); + while (iter.readBuffer() != NULL) { + size_t toRead = iter.currentToRead(); + pbBytes.append(reinterpret_cast<const char*>(iter.readBuffer()), toRead); + iter.rp()->move(toRead); + } + return message->ParseFromArray(pbBytes.c_str(), pbBytes.size()); +} + } // namespace statsd } // namespace os } // namespace android
\ No newline at end of file diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp new file mode 100644 index 000000000000..10a6c363eab3 --- /dev/null +++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp @@ -0,0 +1,193 @@ +// 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. + +#include <gtest/gtest.h> + +#include "src/StatsLogProcessor.h" +#include "src/stats_log_util.h" +#include "tests/statsd_test_util.h" + +#include <vector> + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +namespace { + +StatsdConfig CreateStatsdConfigForPushedEvent() { + StatsdConfig config; + *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); + *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); + + auto atomMatcher = CreateSimpleAtomMatcher("", android::util::APP_START_CHANGED); + *config.add_atom_matcher() = atomMatcher; + + auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); + *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ }); + *config.add_predicate() = isInBackgroundPredicate; + + auto gaugeMetric = config.add_gauge_metric(); + gaugeMetric->set_id(123456); + gaugeMetric->set_what(atomMatcher.id()); + gaugeMetric->set_condition(isInBackgroundPredicate.id()); + gaugeMetric->mutable_gauge_fields_filter()->set_include_all(false); + auto fieldMatcher = gaugeMetric->mutable_gauge_fields_filter()->mutable_fields(); + fieldMatcher->set_field(android::util::APP_START_CHANGED); + fieldMatcher->add_child()->set_field(3); // type (enum) + fieldMatcher->add_child()->set_field(4); // activity_name(str) + fieldMatcher->add_child()->set_field(7); // activity_start_msec(int64) + *gaugeMetric->mutable_dimensions() = + CreateDimensions(android::util::APP_START_CHANGED, {1 /* uid field */ }); + gaugeMetric->set_bucket(ONE_MINUTE); + + auto links = gaugeMetric->add_links(); + links->set_condition(isInBackgroundPredicate.id()); + auto dimensionWhat = links->mutable_dimensions_in_what(); + dimensionWhat->set_field(android::util::APP_START_CHANGED); + dimensionWhat->add_child()->set_field(1); // uid field. + auto dimensionCondition = links->mutable_dimensions_in_condition(); + dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); + dimensionCondition->add_child()->set_field(1); // uid field. + return config; +} + +std::unique_ptr<LogEvent> CreateAppStartChangedEvent( + const int uid, const string& pkg_name, AppStartChanged::TransitionType type, + const string& activity_name, const string& calling_pkg_name, const bool is_instant_app, + int64_t activity_start_msec, uint64_t timestampNs) { + auto logEvent = std::make_unique<LogEvent>( + android::util::APP_START_CHANGED, timestampNs); + logEvent->write(uid); + logEvent->write(pkg_name); + logEvent->write(type); + logEvent->write(activity_name); + logEvent->write(calling_pkg_name); + logEvent->write(is_instant_app); + logEvent->write(activity_start_msec); + logEvent->init(); + return logEvent; +} + +} // namespace + +TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { + auto config = CreateStatsdConfigForPushedEvent(); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + + int appUid1 = 123; + int appUid2 = 456; + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateMoveToBackgroundEvent(appUid1, bucketStartTimeNs + 15)); + events.push_back(CreateMoveToForegroundEvent(appUid1, bucketStartTimeNs + bucketSizeNs + 250)); + events.push_back(CreateMoveToBackgroundEvent(appUid1, bucketStartTimeNs + bucketSizeNs + 350)); + events.push_back(CreateMoveToForegroundEvent( + appUid1, bucketStartTimeNs + 2 * bucketSizeNs + 100)); + + + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::WARM, "activity_name1", "calling_pkg_name1", + true /*is_instant_app*/, 101 /*activity_start_msec*/, bucketStartTimeNs + 10)); + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::HOT, "activity_name2", "calling_pkg_name2", + true /*is_instant_app*/, 102 /*activity_start_msec*/, bucketStartTimeNs + 20)); + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::COLD, "activity_name3", "calling_pkg_name3", + true /*is_instant_app*/, 103 /*activity_start_msec*/, bucketStartTimeNs + 30)); + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::WARM, "activity_name4", "calling_pkg_name4", + true /*is_instant_app*/, 104 /*activity_start_msec*/, + bucketStartTimeNs + bucketSizeNs + 30)); + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::COLD, "activity_name5", "calling_pkg_name5", + true /*is_instant_app*/, 105 /*activity_start_msec*/, + bucketStartTimeNs + 2 * bucketSizeNs)); + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::HOT, "activity_name6", "calling_pkg_name6", + false /*is_instant_app*/, 106 /*activity_start_msec*/, + bucketStartTimeNs + 2 * bucketSizeNs + 10)); + + events.push_back(CreateMoveToBackgroundEvent(appUid2, bucketStartTimeNs + bucketSizeNs + 10)); + events.push_back(CreateAppStartChangedEvent( + appUid2, "app2", AppStartChanged::COLD, "activity_name7", "calling_pkg_name7", + true /*is_instant_app*/, 201 /*activity_start_msec*/, + bucketStartTimeNs + 2 * bucketSizeNs + 10)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 3 * bucketSizeNs, &reports); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); + EXPECT_EQ(gaugeMetrics.data_size(), 2); + + auto data = gaugeMetrics.data(0); + EXPECT_EQ(data.dimension().field(), android::util::APP_START_CHANGED); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1 /* uid field */); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid1); + EXPECT_EQ(data.bucket_info_size(), 3); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().type(), AppStartChanged::HOT); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_name(), "activity_name2"); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_start_msec(), 102L); + + EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().type(), AppStartChanged::WARM); + EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().activity_name(), "activity_name4"); + EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().activity_start_msec(), 104L); + + EXPECT_EQ(data.bucket_info(2).start_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(2).end_bucket_nanos(), bucketStartTimeNs + 3 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().type(), AppStartChanged::COLD); + EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().activity_name(), "activity_name5"); + EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().activity_start_msec(), 105L); + + data = gaugeMetrics.data(1); + EXPECT_EQ(data.dimension().field(), android::util::APP_START_CHANGED); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1 /* uid field */); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid2); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + 3 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().type(), AppStartChanged::COLD); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_name(), "activity_name7"); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_start_msec(), 201L); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp index 278335674130..fcdaafce6627 100644 --- a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp @@ -26,6 +26,8 @@ namespace statsd { #ifdef __ANDROID__ +namespace { + StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) { StatsdConfig config; *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); @@ -56,6 +58,8 @@ StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) return config; } +} // namespace + TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { ConfigKey cfgKey; for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE }) { @@ -94,6 +98,7 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { auto releaseEvent2 = CreateReleaseWakelockEvent( attributions2, "wl2", bucketStartTimeNs + 2 * bucketSizeNs - 15); + std::vector<std::unique_ptr<LogEvent>> events; events.push_back(std::move(screenTurnedOnEvent)); @@ -119,18 +124,8 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { auto data = reports.reports(0).metrics(0).duration_metrics().data(0); // Validate dimension value. - EXPECT_EQ(data.dimension().field(), - android::util::WAKELOCK_STATE_CHANGED); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); - // Attribution field. - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1); - // Uid only. - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).value_int(), 111); + ValidateAttributionUidDimension( + data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 111); // Validate bucket info. EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1); data = reports.reports(0).metrics(0).duration_metrics().data(0); @@ -147,18 +142,8 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2); data = reports.reports(0).metrics(0).duration_metrics().data(0); // Validate dimension value. - EXPECT_EQ(data.dimension().field(), - android::util::WAKELOCK_STATE_CHANGED); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); - // Attribution field. - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1); - // Uid only. - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).value_int(), 111); + ValidateAttributionUidDimension( + data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 111); // Two output buckets. // The wakelock holding interval in the 1st bucket starts from the screen off event and to // the end of the 1st bucket. @@ -167,6 +152,32 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { // The wakelock holding interval in the 2nd bucket starts at the beginning of the bucket and // ends at the second screen on event. EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL); + + events.clear(); + events.push_back(CreateScreenStateChangedEvent( + ScreenStateChanged::STATE_OFF, bucketStartTimeNs + 2 * bucketSizeNs + 90)); + events.push_back(CreateAcquireWakelockEvent( + attributions1, "wl3", bucketStartTimeNs + 2 * bucketSizeNs + 100)); + events.push_back(CreateReleaseWakelockEvent( + attributions1, "wl3", bucketStartTimeNs + 5 * bucketSizeNs + 100)); + sortLogEventsByTimestamp(&events); + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + reports.Clear(); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, &reports); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 6); + data = reports.reports(0).metrics(0).duration_metrics().data(0); + ValidateAttributionUidDimension( + data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 111); + // The last wakelock holding spans 4 buckets. + EXPECT_EQ((unsigned long long)data.bucket_info(2).duration_nanos(), bucketSizeNs - 100); + EXPECT_EQ((unsigned long long)data.bucket_info(3).duration_nanos(), bucketSizeNs); + EXPECT_EQ((unsigned long long)data.bucket_info(4).duration_nanos(), bucketSizeNs); + EXPECT_EQ((unsigned long long)data.bucket_info(5).duration_nanos(), 100UL); } } diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index e7882353bec0..718b2e177d52 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -19,6 +19,14 @@ namespace android { namespace os { namespace statsd { +AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId(name)); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(atomId); + return atom_matcher; +} + AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name, WakelockStateChanged::State state) { AtomMatcher atom_matcher; diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h index 1bbbd9a30540..7eb93b9d5fcd 100644 --- a/cmds/statsd/tests/statsd_test_util.h +++ b/cmds/statsd/tests/statsd_test_util.h @@ -23,6 +23,9 @@ namespace android { namespace os { namespace statsd { +// Create AtomMatcher proto to simply match a specific atom type. +AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId); + // Create AtomMatcher proto for acquiring wakelock. AtomMatcher CreateAcquireWakelockAtomMatcher(); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index aa099eb19d37..0a5b848e6220 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -16,6 +16,7 @@ package android.app; +import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; import static java.lang.Character.MIN_VALUE; import android.annotation.CallSuper; @@ -98,6 +99,7 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; +import android.view.RemoteAnimationDefinition; import android.view.SearchEvent; import android.view.View; import android.view.View.OnCreateContextMenuListener; @@ -857,6 +859,7 @@ public class Activity extends ContextThemeWrapper private boolean mHasCurrentPermissionsRequest; private boolean mAutoFillResetNeeded; + private boolean mAutoFillIgnoreFirstResumePause; /** The last autofill id that was returned from {@link #getNextAutofillId()} */ private int mLastAutofillId = View.LAST_APP_AUTOFILL_ID; @@ -1253,10 +1256,7 @@ public class Activity extends ContextThemeWrapper getApplication().dispatchActivityStarted(this); if (mAutoFillResetNeeded) { - AutofillManager afm = getAutofillManager(); - if (afm != null) { - afm.onVisibleForAutofill(); - } + getAutofillManager().onVisibleForAutofill(); } } @@ -1320,6 +1320,20 @@ public class Activity extends ContextThemeWrapper if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this); getApplication().dispatchActivityResumed(this); mActivityTransitionState.onResume(this, isTopOfTask()); + if (mAutoFillResetNeeded) { + if (!mAutoFillIgnoreFirstResumePause) { + View focus = getCurrentFocus(); + if (focus != null && focus.canNotifyAutofillEnterExitEvent()) { + // TODO: in Activity killed/recreated case, i.e. SessionLifecycleTest# + // testDatasetVisibleWhileAutofilledAppIsLifecycled: the View's initial + // window visibility after recreation is INVISIBLE in onResume() and next frame + // ViewRootImpl.performTraversals() changes window visibility to VISIBLE. + // So we cannot call View.notifyEnterOrExited() which will do nothing + // when View.isVisibleToUser() is false. + getAutofillManager().notifyViewEntered(focus); + } + } + } mCalled = true; } @@ -1681,6 +1695,19 @@ public class Activity extends ContextThemeWrapper protected void onPause() { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onPause " + this); getApplication().dispatchActivityPaused(this); + if (mAutoFillResetNeeded) { + if (!mAutoFillIgnoreFirstResumePause) { + if (DEBUG_LIFECYCLE) Slog.v(TAG, "autofill notifyViewExited " + this); + View focus = getCurrentFocus(); + if (focus != null && focus.canNotifyAutofillEnterExitEvent()) { + getAutofillManager().notifyViewExited(focus); + } + } else { + // reset after first pause() + if (DEBUG_LIFECYCLE) Slog.v(TAG, "autofill got first pause " + this); + mAutoFillIgnoreFirstResumePause = false; + } + } mCalled = true; } @@ -1871,6 +1898,10 @@ public class Activity extends ContextThemeWrapper mTranslucentCallback = null; mCalled = true; + if (mAutoFillResetNeeded) { + getAutofillManager().onInvisibleForAutofill(); + } + if (isFinishing()) { if (mAutoFillResetNeeded) { getAutofillManager().onActivityFinished(); @@ -6266,7 +6297,7 @@ public class Activity extends ContextThemeWrapper mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix); - final AutofillManager afm = getAutofillManager(); + final AutofillManager afm = mAutofillManager; if (afm != null) { afm.dump(prefix, writer); } else { @@ -6616,7 +6647,6 @@ public class Activity extends ContextThemeWrapper * to run as a {@link android.service.vr.VrListenerService} is not installed, or has * not been enabled in user settings. * - * @see android.content.pm.PackageManager#FEATURE_VR_MODE * @see android.content.pm.PackageManager#FEATURE_VR_MODE_HIGH_PERFORMANCE * @see android.service.vr.VrListenerService * @see android.provider.Settings#ACTION_VR_LISTENER_SETTINGS @@ -7120,13 +7150,23 @@ public class Activity extends ContextThemeWrapper } } - final void performResume() { + final void performResume(boolean followedByPause) { performRestart(true /* start */); mFragments.execPendingActions(); mLastNonConfigurationInstances = null; + if (mAutoFillResetNeeded) { + // When Activity is destroyed in paused state, and relaunch activity, there will be + // extra onResume and onPause event, ignore the first onResume and onPause. + // see ActivityThread.handleRelaunchActivity() + mAutoFillIgnoreFirstResumePause = followedByPause; + if (mAutoFillIgnoreFirstResumePause && DEBUG_LIFECYCLE) { + Slog.v(TAG, "autofill will ignore first pause when relaunching " + this); + } + } + mCalled = false; // mResumed is set by the instrumentation mInstrumentation.callActivityOnResume(this); @@ -7311,7 +7351,7 @@ public class Activity extends ContextThemeWrapper } } else if (who.startsWith(AUTO_FILL_AUTH_WHO_PREFIX)) { Intent resultData = (resultCode == Activity.RESULT_OK) ? data : null; - getAutofillManager().onAuthenticationResult(requestCode, resultData); + getAutofillManager().onAuthenticationResult(requestCode, resultData, getCurrentFocus()); } else { Fragment frag = mFragments.findFragmentByWho(who); if (frag != null) { @@ -7585,6 +7625,12 @@ public class Activity extends ContextThemeWrapper return !mStopped; } + /** @hide */ + @Override + public boolean isDisablingEnterExitEventForAutofill() { + return mAutoFillIgnoreFirstResumePause || !mResumed; + } + /** * If set to true, this indicates to the system that it should never take a * screenshot of the activity to be used as a representation while it is not in a started state. @@ -7659,6 +7705,22 @@ public class Activity extends ContextThemeWrapper } } + /** + * Registers remote animations per transition type for this activity. + * + * @param definition The remote animation definition that defines which transition whould run + * which remote animation. + * @hide + */ + @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS) + public void registerRemoteAnimations(RemoteAnimationDefinition definition) { + try { + ActivityManager.getService().registerRemoteAnimations(mToken, definition); + } catch (RemoteException e) { + Log.e(TAG, "Failed to call registerRemoteAnimations", e); + } + } + class HostCallbacks extends FragmentHostCallback<Activity> { public HostCallbacks() { super(Activity.this /*activity*/); diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 972ffcbf527f..db12c37f2c2d 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -324,4 +324,14 @@ public abstract class ActivityManagerInternal { * Returns if more users can be started without stopping currently running users. */ public abstract boolean canStartMoreUsers(); + + /** + * Sets the user switcher message for switching from {@link android.os.UserHandle#SYSTEM}. + */ + public abstract void setSwitchingFromSystemUserMessage(String switchingFromSystemUserMessage); + + /** + * Sets the user switcher message for switching to {@link android.os.UserHandle#SYSTEM}. + */ + public abstract void setSwitchingToSystemUserMessage(String switchingToSystemUserMessage); } diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index e61c5b7c78a1..4bcd677e1f4e 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -16,12 +16,14 @@ package android.app; +import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.INVALID_DISPLAY; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; @@ -44,6 +46,7 @@ import android.util.Pair; import android.util.Slog; import android.view.AppTransitionAnimationSpec; import android.view.IAppTransitionAnimationSpecsFuture; +import android.view.RemoteAnimationAdapter; import android.view.View; import android.view.ViewGroup; import android.view.Window; @@ -241,6 +244,8 @@ public class ActivityOptions { private static final String KEY_INSTANT_APP_VERIFICATION_BUNDLE = "android:instantapps.installerbundle"; private static final String KEY_SPECS_FUTURE = "android:activity.specsFuture"; + private static final String KEY_REMOTE_ANIMATION_ADAPTER + = "android:activity.remoteAnimationAdapter"; /** @hide */ public static final int ANIM_NONE = 0; @@ -268,6 +273,8 @@ public class ActivityOptions { public static final int ANIM_CLIP_REVEAL = 11; /** @hide */ public static final int ANIM_OPEN_CROSS_PROFILE_APPS = 12; + /** @hide */ + public static final int ANIM_REMOTE_ANIMATION = 13; private String mPackageName; private Rect mLaunchBounds; @@ -304,6 +311,7 @@ public class ActivityOptions { private int mRotationAnimationHint = -1; private Bundle mAppVerificationBundle; private IAppTransitionAnimationSpecsFuture mSpecsFuture; + private RemoteAnimationAdapter mRemoteAnimationAdapter; /** * Create an ActivityOptions specifying a custom animation to run when @@ -826,6 +834,20 @@ public class ActivityOptions { return opts; } + /** + * Create an {@link ActivityOptions} instance that lets the application control the entire + * animation using a {@link RemoteAnimationAdapter}. + * @hide + */ + @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS) + public static ActivityOptions makeRemoteAnimation( + RemoteAnimationAdapter remoteAnimationAdapter) { + final ActivityOptions opts = new ActivityOptions(); + opts.mRemoteAnimationAdapter = remoteAnimationAdapter; + opts.mAnimationType = ANIM_REMOTE_ANIMATION; + return opts; + } + /** @hide */ public boolean getLaunchTaskBehind() { return mAnimationType == ANIM_LAUNCH_TASK_BEHIND; @@ -922,6 +944,7 @@ public class ActivityOptions { mSpecsFuture = IAppTransitionAnimationSpecsFuture.Stub.asInterface(opts.getBinder( KEY_SPECS_FUTURE)); } + mRemoteAnimationAdapter = opts.getParcelable(KEY_REMOTE_ANIMATION_ADAPTER); } /** @@ -1070,6 +1093,11 @@ public class ActivityOptions { } /** @hide */ + public RemoteAnimationAdapter getRemoteAnimationAdapter() { + return mRemoteAnimationAdapter; + } + + /** @hide */ public static ActivityOptions fromBundle(Bundle bOptions) { return bOptions != null ? new ActivityOptions(bOptions) : null; } @@ -1309,6 +1337,7 @@ public class ActivityOptions { mAnimSpecs = otherOptions.mAnimSpecs; mAnimationFinishedListener = otherOptions.mAnimationFinishedListener; mSpecsFuture = otherOptions.mSpecsFuture; + mRemoteAnimationAdapter = otherOptions.mRemoteAnimationAdapter; } /** @@ -1403,7 +1432,9 @@ public class ActivityOptions { if (mAppVerificationBundle != null) { b.putBundle(KEY_INSTANT_APP_VERIFICATION_BUNDLE, mAppVerificationBundle); } - + if (mRemoteAnimationAdapter != null) { + b.putParcelable(KEY_REMOTE_ANIMATION_ADAPTER, mRemoteAnimationAdapter); + } return b; } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index e610ac4a318b..934b0f3cd4e4 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -166,6 +166,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.InetAddress; import java.text.DateFormat; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -220,6 +221,9 @@ public final class ActivityThread extends ClientTransactionHandler { // Whether to invoke an activity callback after delivering new configuration. private static final boolean REPORT_TO_ACTIVITY = true; + // Maximum number of recent tokens to maintain for debugging purposes + private static final int MAX_RECENT_TOKENS = 10; + /** * Denotes an invalid sequence number corresponding to a process state change. */ @@ -252,6 +256,8 @@ public final class ActivityThread extends ClientTransactionHandler { final H mH = new H(); final Executor mExecutor = new HandlerExecutor(mH); final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>(); + final ArrayDeque<Integer> mRecentTokens = new ArrayDeque<>(); + // List of new activities (via ActivityRecord.nextIdle) that should // be reported when next we idle. ActivityClientRecord mNewActivities = null; @@ -1752,9 +1758,11 @@ public final class ActivityThread extends ClientTransactionHandler { handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1, (IVoiceInteractor) ((SomeArgs) msg.obj).arg2); break; - case ATTACH_AGENT: - handleAttachAgent((String) msg.obj); + case ATTACH_AGENT: { + Application app = getApplication(); + handleAttachAgent((String) msg.obj, app != null ? app.mLoadedApk : null); break; + } case APPLICATION_INFO_CHANGED: mUpdatingSystemConfig = true; try { @@ -2166,6 +2174,18 @@ public final class ActivityThread extends ClientTransactionHandler { pw.println(String.format(format, objs)); } + @Override + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "mActivities:"); + + for (ArrayMap.Entry<IBinder, ActivityClientRecord> entry : mActivities.entrySet()) { + pw.println(prefix + " [token:" + entry.getKey().hashCode() + " record:" + + entry.getValue().toString() + "]"); + } + + pw.println(prefix + "mRecentTokens:" + mRecentTokens); + } + public static void dumpMemInfoTable(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin, boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly, int pid, String processName, @@ -2850,6 +2870,11 @@ public final class ActivityThread extends ClientTransactionHandler { r.setState(ON_CREATE); mActivities.put(r.token, r); + mRecentTokens.push(r.token.hashCode()); + + if (mRecentTokens.size() > MAX_RECENT_TOKENS) { + mRecentTokens.removeLast(); + } } catch (SuperNotCalledException e) { throw e; @@ -3071,7 +3096,7 @@ public final class ActivityThread extends ClientTransactionHandler { checkAndBlockForNetworkAccess(); deliverNewIntents(r, intents); if (resumed) { - r.activity.performResume(); + r.activity.performResume(false); r.activity.mTemporaryPause = false; } @@ -3246,11 +3271,23 @@ public final class ActivityThread extends ClientTransactionHandler { } } - static final void handleAttachAgent(String agent) { + private static boolean attemptAttachAgent(String agent, ClassLoader classLoader) { try { - VMDebug.attachAgent(agent); + VMDebug.attachAgent(agent, classLoader); + return true; } catch (IOException e) { - Slog.e(TAG, "Attaching agent failed: " + agent); + Slog.e(TAG, "Attaching agent with " + classLoader + " failed: " + agent); + return false; + } + } + + static void handleAttachAgent(String agent, LoadedApk loadedApk) { + ClassLoader classLoader = loadedApk != null ? loadedApk.getClassLoader() : null; + if (attemptAttachAgent(agent, classLoader)) { + return; + } + if (classLoader != null) { + attemptAttachAgent(agent, null); } } @@ -3681,7 +3718,7 @@ public final class ActivityThread extends ClientTransactionHandler { deliverResults(r, r.pendingResults); r.pendingResults = null; } - r.activity.performResume(); + r.activity.performResume(r.startsNotResumed); synchronized (mResourcesManager) { // If there is a pending local relaunch that was requested when the activity was @@ -4400,7 +4437,7 @@ public final class ActivityThread extends ClientTransactionHandler { checkAndBlockForNetworkAccess(); deliverResults(r, results); if (resumed) { - r.activity.performResume(); + r.activity.performResume(false); r.activity.mTemporaryPause = false; } } @@ -5542,12 +5579,16 @@ public final class ActivityThread extends ClientTransactionHandler { mCompatConfiguration = new Configuration(data.config); mProfiler = new Profiler(); + String agent = null; if (data.initProfilerInfo != null) { mProfiler.profileFile = data.initProfilerInfo.profileFile; mProfiler.profileFd = data.initProfilerInfo.profileFd; mProfiler.samplingInterval = data.initProfilerInfo.samplingInterval; mProfiler.autoStopProfiler = data.initProfilerInfo.autoStopProfiler; mProfiler.streamingOutput = data.initProfilerInfo.streamingOutput; + if (data.initProfilerInfo.attachAgentDuringBind) { + agent = data.initProfilerInfo.agent; + } } // send up app name; do this *before* waiting for debugger @@ -5597,6 +5638,10 @@ public final class ActivityThread extends ClientTransactionHandler { data.loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo); + if (agent != null) { + handleAttachAgent(agent, data.loadedApk); + } + /** * Switch this process to density compatibility mode if needed. */ diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index 45c0e0cdfb25..0f66652af76c 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -24,6 +24,7 @@ import android.os.IBinder; import com.android.internal.content.ReferrerIntent; +import java.io.PrintWriter; import java.util.List; /** @@ -121,4 +122,11 @@ public abstract class ClientTransactionHandler { * provided token. */ public abstract ActivityThread.ActivityClientRecord getActivityClient(IBinder token); + + /** + * Debugging output. + * @param pw {@link PrintWriter} to write logs to. + * @param prefix Prefix to prepend to output. + */ + public abstract void dump(PrintWriter pw, String prefix); } diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 696899f73b96..04ee77d764aa 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -65,6 +65,7 @@ import android.os.PersistableBundle; import android.os.StrictMode; import android.os.WorkSource; import android.service.voice.IVoiceInteractionSession; +import android.view.RemoteAnimationDefinition; import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.IResultReceiver; import com.android.internal.policy.IKeyguardDismissCallback; @@ -672,4 +673,9 @@ interface IActivityManager { * user unlock progress. */ boolean startUserInBackgroundWithListener(int userid, IProgressListener unlockProgressListener); + + /** + * Registers remote animations for a specific activity. + */ + void registerRemoteAnimations(in IBinder token, in RemoteAnimationDefinition definition); } diff --git a/core/java/android/app/ProfilerInfo.java b/core/java/android/app/ProfilerInfo.java index d5234278da7d..a295c4c61a8c 100644 --- a/core/java/android/app/ProfilerInfo.java +++ b/core/java/android/app/ProfilerInfo.java @@ -55,14 +55,24 @@ public class ProfilerInfo implements Parcelable { */ public final String agent; + /** + * Whether the {@link agent} should be attached early (before bind-application) or during + * bind-application. Agents attached prior to binding cannot be loaded from the app's APK + * directly and must be given as an absolute path (or available in the default LD_LIBRARY_PATH). + * Agents attached during bind-application will miss early setup (e.g., resource initialization + * and classloader generation), but are searched in the app's library search path. + */ + public final boolean attachAgentDuringBind; + public ProfilerInfo(String filename, ParcelFileDescriptor fd, int interval, boolean autoStop, - boolean streaming, String agent) { + boolean streaming, String agent, boolean attachAgentDuringBind) { profileFile = filename; profileFd = fd; samplingInterval = interval; autoStopProfiler = autoStop; streamingOutput = streaming; this.agent = agent; + this.attachAgentDuringBind = attachAgentDuringBind; } public ProfilerInfo(ProfilerInfo in) { @@ -72,6 +82,7 @@ public class ProfilerInfo implements Parcelable { autoStopProfiler = in.autoStopProfiler; streamingOutput = in.streamingOutput; agent = in.agent; + attachAgentDuringBind = in.attachAgentDuringBind; } /** @@ -110,6 +121,7 @@ public class ProfilerInfo implements Parcelable { out.writeInt(autoStopProfiler ? 1 : 0); out.writeInt(streamingOutput ? 1 : 0); out.writeString(agent); + out.writeBoolean(attachAgentDuringBind); } public static final Parcelable.Creator<ProfilerInfo> CREATOR = @@ -132,6 +144,7 @@ public class ProfilerInfo implements Parcelable { autoStopProfiler = in.readInt() != 0; streamingOutput = in.readInt() != 0; agent = in.readString(); + attachAgentDuringBind = in.readBoolean(); } @Override diff --git a/core/java/android/app/admin/ConnectEvent.java b/core/java/android/app/admin/ConnectEvent.java index f06a9257b7f8..d511c57b1f51 100644 --- a/core/java/android/app/admin/ConnectEvent.java +++ b/core/java/android/app/admin/ConnectEvent.java @@ -68,7 +68,7 @@ public final class ConnectEvent extends NetworkEvent implements Parcelable { @Override public String toString() { - return String.format("ConnectEvent(%s, %d, %d, %s)", mIpAddress, mPort, mTimestamp, + return String.format("ConnectEvent(%d, %s, %d, %d, %s)", mId, mIpAddress, mPort, mTimestamp, mPackageName); } diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index aa05b7630c9d..302d52f1ae72 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -467,6 +467,31 @@ public class DeviceAdminReceiver extends BroadcastReceiver { public static final String EXTRA_TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE = "android.app.extra.TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE"; + /** + * Name under which a device administration component indicates whether it supports transfer of + * ownership. This meta-data is of type <code>boolean</code>. A value of <code>true</code> + * allows this administrator to be used as a target administrator for a transfer. If the value + * is <code>false</code>, ownership cannot be transferred to this administrator. The default + * value is <code>false</code>. + * <p>This metadata is used to avoid ownership transfer migration to an administrator with a + * version which does not yet support it. + * <p>Usage: + * <pre> + * <receiver name="..." android:permission="android.permission.BIND_DEVICE_ADMIN"> + * <meta-data + * android:name="android.app.device_admin" + * android:resource="@xml/..." /> + * <meta-data + * android:name="android.app.support_transfer_ownership" + * android:value="true" /> + * </receiver> + * </pre> + * + * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle) + */ + public static final String SUPPORT_TRANSFER_OWNERSHIP_META_DATA = + "android.app.support_transfer_ownership"; + private DevicePolicyManager mManager; private ComponentName mWho; diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index e334aab7005e..9329d56a8de9 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -18,7 +18,6 @@ package android.app.admin; import android.annotation.CallbackExecutor; import android.annotation.ColorInt; -import android.annotation.Condemned; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -50,8 +49,6 @@ import android.graphics.Bitmap; import android.net.ProxyInfo; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerExecutor; import android.os.Parcelable; import android.os.PersistableBundle; import android.os.Process; @@ -8633,6 +8630,13 @@ public class DevicePolicyManager { * * <p> Backup service is off by default when device owner is present. * + * <p> If backups are made mandatory by specifying a non-null mandatory backup transport using + * the {@link DevicePolicyManager#setMandatoryBackupTransport} method, the backup service is + * automatically enabled. + * + * <p> If the backup service is disabled using this method after the mandatory backup transport + * has been set, the mandatory backup transport is cleared. + * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param enabled {@code true} to enable the backup service, {@code false} to disable it. * @throws SecurityException if {@code admin} is not a device owner. @@ -8664,6 +8668,43 @@ public class DevicePolicyManager { } /** + * Makes backups mandatory and enforces the usage of the specified backup transport. + * + * <p>When a {@code null} backup transport is specified, backups are made optional again. + * <p>Only device owner can call this method. + * <p>If backups were disabled and a non-null backup transport {@link ComponentName} is + * specified, backups will be enabled. + * + * @param admin admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param backupTransportComponent The backup transport layer to be used for mandatory backups. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public void setMandatoryBackupTransport( + @NonNull ComponentName admin, @Nullable ComponentName backupTransportComponent) { + try { + mService.setMandatoryBackupTransport(admin, backupTransportComponent); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Returns the backup transport which has to be used for backups if backups are mandatory or + * {@code null} if backups are not mandatory. + * + * @return a {@link ComponentName} of the backup transport layer to be used if backups are + * mandatory or {@code null} if backups are not mandatory. + */ + public ComponentName getMandatoryBackupTransport() { + try { + return mService.getMandatoryBackupTransport(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + + /** * Called by a device owner to control the network logging feature. * * <p> Network logs contain DNS lookup and connect() library call events. The following library @@ -8939,15 +8980,6 @@ public class DevicePolicyManager { } } - /** {@hide} */ - @Condemned - @Deprecated - public boolean clearApplicationUserData(@NonNull ComponentName admin, - @NonNull String packageName, @NonNull OnClearApplicationUserDataListener listener, - @NonNull Handler handler) { - return clearApplicationUserData(admin, packageName, listener, new HandlerExecutor(handler)); - } - /** * Called by the device owner or profile owner to clear application user data of a given * package. The behaviour of this is equivalent to the target application calling @@ -8958,14 +8990,14 @@ public class DevicePolicyManager { * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param packageName The name of the package which will have its user data wiped. - * @param listener A callback object that will inform the caller when the clearing is done. * @param executor The executor through which the listener should be invoked. + * @param listener A callback object that will inform the caller when the clearing is done. * @throws SecurityException if the caller is not the device owner/profile owner. * @return whether the clearing succeeded. */ public boolean clearApplicationUserData(@NonNull ComponentName admin, - @NonNull String packageName, @NonNull OnClearApplicationUserDataListener listener, - @NonNull @CallbackExecutor Executor executor) { + @NonNull String packageName, @NonNull @CallbackExecutor Executor executor, + @NonNull OnClearApplicationUserDataListener listener) { throwIfParentInstance("clearAppData"); Preconditions.checkNotNull(executor); try { @@ -9064,6 +9096,11 @@ public class DevicePolicyManager { * will be received in the * {@link DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)} callback. * + * <p>The incoming target administrator must have the + * {@link DeviceAdminReceiver#SUPPORT_TRANSFER_OWNERSHIP_META_DATA} <code>meta-data</code> tag + * included in its corresponding <code>receiver</code> component with a value of {@code true}. + * Otherwise an {@link IllegalArgumentException} will be thrown. + * * @param admin which {@link DeviceAdminReceiver} this request is associated with * @param target which {@link DeviceAdminReceiver} we want the new administrator to be * @param bundle data to be sent to the new administrator @@ -9080,4 +9117,84 @@ public class DevicePolicyManager { throw re.rethrowFromSystemServer(); } } + + /** + * Called by a device owner to specify the user session start message. This may be displayed + * during a user switch. + * <p> + * The message should be limited to a short statement or it may be truncated. + * <p> + * If the message needs to be localized, it is the responsibility of the + * {@link DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast + * and set a new version of this message accordingly. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with. + * @param startUserSessionMessage message for starting user session, or {@code null} to use + * system default message. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public void setStartUserSessionMessage( + @NonNull ComponentName admin, @Nullable CharSequence startUserSessionMessage) { + throwIfParentInstance("setStartUserSessionMessage"); + try { + mService.setStartUserSessionMessage(admin, startUserSessionMessage); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Called by a device owner to specify the user session end message. This may be displayed + * during a user switch. + * <p> + * The message should be limited to a short statement or it may be truncated. + * <p> + * If the message needs to be localized, it is the responsibility of the + * {@link DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast + * and set a new version of this message accordingly. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with. + * @param endUserSessionMessage message for ending user session, or {@code null} to use system + * default message. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public void setEndUserSessionMessage( + @NonNull ComponentName admin, @Nullable CharSequence endUserSessionMessage) { + throwIfParentInstance("setEndUserSessionMessage"); + try { + mService.setEndUserSessionMessage(admin, endUserSessionMessage); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Returns the user session start message. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public CharSequence getStartUserSessionMessage(@NonNull ComponentName admin) { + throwIfParentInstance("getStartUserSessionMessage"); + try { + return mService.getStartUserSessionMessage(admin); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Returns the user session end message. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public CharSequence getEndUserSessionMessage(@NonNull ComponentName admin) { + throwIfParentInstance("getEndUserSessionMessage"); + try { + return mService.getEndUserSessionMessage(admin); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index b692ffd95e72..531bef014c0b 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -123,4 +123,13 @@ public abstract class DevicePolicyManagerInternal { * @param userId User ID of the profile. */ public abstract void reportSeparateProfileChallengeChanged(@UserIdInt int userId); + + /** + * Check whether the user could have their password reset in an untrusted manor due to there + * being an admin which can call {@link #resetPassword} to reset the password without knowledge + * of the previous password. + * + * @param userId The user in question + */ + public abstract boolean canUserHaveUntrustedCredentialReset(@UserIdInt int userId); } diff --git a/core/java/android/app/admin/DnsEvent.java b/core/java/android/app/admin/DnsEvent.java index 4ddf13e07344..a2d704b86649 100644 --- a/core/java/android/app/admin/DnsEvent.java +++ b/core/java/android/app/admin/DnsEvent.java @@ -96,7 +96,7 @@ public final class DnsEvent extends NetworkEvent implements Parcelable { @Override public String toString() { - return String.format("DnsEvent(%s, %s, %d, %d, %s)", mHostname, + return String.format("DnsEvent(%d, %s, %s, %d, %d, %s)", mId, mHostname, (mIpAddresses == null) ? "NONE" : String.join(" ", mIpAddresses), mIpAddressesCount, mTimestamp, mPackageName); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 7154053f593c..eac7f7ed4b3e 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -359,6 +359,8 @@ interface IDevicePolicyManager { void setBackupServiceEnabled(in ComponentName admin, boolean enabled); boolean isBackupServiceEnabled(in ComponentName admin); + void setMandatoryBackupTransport(in ComponentName admin, in ComponentName backupTransportComponent); + ComponentName getMandatoryBackupTransport(); void setNetworkLoggingEnabled(in ComponentName admin, boolean enabled); boolean isNetworkLoggingEnabled(in ComponentName admin); @@ -389,4 +391,9 @@ interface IDevicePolicyManager { List<String> getDisallowedSystemApps(in ComponentName admin, int userId, String provisioningAction); void transferOwnership(in ComponentName admin, in ComponentName target, in PersistableBundle bundle); + + void setStartUserSessionMessage(in ComponentName admin, in CharSequence startUserSessionMessage); + void setEndUserSessionMessage(in ComponentName admin, in CharSequence endUserSessionMessage); + CharSequence getStartUserSessionMessage(in ComponentName admin); + CharSequence getEndUserSessionMessage(in ComponentName admin); } diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java index 3a6a5b20edf7..12f4483141f4 100644 --- a/core/java/android/app/backup/BackupManager.java +++ b/core/java/android/app/backup/BackupManager.java @@ -27,6 +27,7 @@ import android.os.Handler; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.util.Log; import android.util.Pair; @@ -387,6 +388,29 @@ public class BackupManager { } /** + * Report whether the backup mechanism is currently active. + * When it is inactive, the device will not perform any backup operations, nor will it + * deliver data for restore, although clients can still safely call BackupManager methods. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.BACKUP) + public boolean isBackupServiceActive(UserHandle user) { + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, + "isBackupServiceActive"); + checkServiceBinder(); + if (sService != null) { + try { + return sService.isBackupServiceActive(user.getIdentifier()); + } catch (RemoteException e) { + Log.e(TAG, "isBackupEnabled() couldn't connect"); + } + } + return false; + } + + /** * Enable/disable data restore at application install time. When enabled, app * installation will include an attempt to fetch the app's historical data from * the archival restore dataset (if any). When disabled, no such attempt will diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java index da81d19ca3cb..3558e3430470 100644 --- a/core/java/android/app/backup/BackupTransport.java +++ b/core/java/android/app/backup/BackupTransport.java @@ -55,6 +55,22 @@ public class BackupTransport { // Transport should ignore its own moratoriums for call with this flag set. public static final int FLAG_USER_INITIATED = 1; + /** + * For key value backup, indicates that the backup data is a diff from a previous backup. The + * transport must apply this diff to an existing backup to build the new backup set. + * + * @hide + */ + public static final int FLAG_INCREMENTAL = 1 << 1; + + /** + * For key value backup, indicates that the backup data is a complete set, not a diff from a + * previous backup. The transport should clear any previous backup when storing this backup. + * + * @hide + */ + public static final int FLAG_NON_INCREMENTAL = 1 << 2; + IBackupTransport mBinderImpl = new TransportImpl(); public IBinder getBinder() { @@ -231,12 +247,18 @@ public class BackupTransport { * {@link #TRANSPORT_OK}, {@link #finishBackup} will then be called to ensure the data * is sent and recorded successfully. * + * If the backup data is a diff against the previous backup then the flag {@link + * BackupTransport#FLAG_INCREMENTAL} will be set. Otherwise, if the data is a complete backup + * set then {@link BackupTransport#FLAG_NON_INCREMENTAL} will be set. Before P neither flag will + * be set regardless of whether the backup is incremental or not. + * * @param packageInfo The identity of the application whose data is being backed up. * This specifically includes the signature list for the package. * @param inFd Descriptor of file with data that resulted from invoking the application's * BackupService.doBackup() method. This may be a pipe rather than a file on * persistent media, so it may not be seekable. - * @param flags {@link BackupTransport#FLAG_USER_INITIATED} or 0. + * @param flags a combination of {@link BackupTransport#FLAG_USER_INITIATED}, {@link + * BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL}, or 0. * @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far), * {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} (to suppress backup of this * specific package, but allow others to proceed), diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index 792cb5f29f9c..f3ca74656e8c 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -294,7 +294,8 @@ interface IBackupManager { * * @param transport ComponentName of the service hosting the transport. This is different from * the transport's name that is returned by {@link BackupTransport#name()}. - * @param listener A listener object to get a callback on the transport being selected. + * @param listener A listener object to get a callback on the transport being selected. It may + * be {@code null}. * * @hide */ diff --git a/core/java/android/app/job/JobParameters.java b/core/java/android/app/job/JobParameters.java index 5053dc6fdf05..c71bf2e65731 100644 --- a/core/java/android/app/job/JobParameters.java +++ b/core/java/android/app/job/JobParameters.java @@ -70,6 +70,7 @@ public class JobParameters implements Parcelable { private final Network network; private int stopReason; // Default value of stopReason is REASON_CANCELED + private String debugStopReason; // Human readable stop reason for debugging. /** @hide */ public JobParameters(IBinder callback, int jobId, PersistableBundle extras, @@ -104,6 +105,14 @@ public class JobParameters implements Parcelable { } /** + * Reason onStopJob() was called on this job. + * @hide + */ + public String getDebugStopReason() { + return debugStopReason; + } + + /** * @return The extras you passed in when constructing this job with * {@link android.app.job.JobInfo.Builder#setExtras(android.os.PersistableBundle)}. This will * never be null. If you did not set any extras this will be an empty bundle. @@ -288,11 +297,13 @@ public class JobParameters implements Parcelable { network = null; } stopReason = in.readInt(); + debugStopReason = in.readString(); } /** @hide */ - public void setStopReason(int reason) { + public void setStopReason(int reason, String debugStopReason) { stopReason = reason; + this.debugStopReason = debugStopReason; } @Override @@ -323,6 +334,7 @@ public class JobParameters implements Parcelable { dest.writeInt(0); } dest.writeInt(stopReason); + dest.writeString(debugStopReason); } public static final Creator<JobParameters> CREATOR = new Creator<JobParameters>() { diff --git a/core/java/android/app/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java index 0fdc7c56fd01..9a50a009ce34 100644 --- a/core/java/android/app/servertransaction/ActivityLifecycleItem.java +++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java @@ -17,7 +17,9 @@ package android.app.servertransaction; import android.annotation.IntDef; +import android.os.Parcel; +import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -26,6 +28,7 @@ import java.lang.annotation.RetentionPolicy; * @hide */ public abstract class ActivityLifecycleItem extends ClientTransactionItem { + private String mDescription; @IntDef(prefix = { "UNDEFINED", "PRE_", "ON_" }, value = { UNDEFINED, @@ -53,4 +56,39 @@ public abstract class ActivityLifecycleItem extends ClientTransactionItem { /** A final lifecycle state that an activity should reach. */ @LifecycleState public abstract int getTargetState(); + + + protected ActivityLifecycleItem() { + } + + protected ActivityLifecycleItem(Parcel in) { + mDescription = in.readString(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mDescription); + } + + /** + * Sets a description that can be retrieved later for debugging purposes. + * @param description Description to set. + * @return The {@link ActivityLifecycleItem}. + */ + public ActivityLifecycleItem setDescription(String description) { + mDescription = description; + return this; + } + + /** + * Retrieves description if set through {@link #setDescription(String)}. + */ + public String getDescription() { + return mDescription; + } + + void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "target state:" + getTargetState()); + pw.println(prefix + "description: " + mDescription); + } } diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java index 08ad2f055774..fc078798f6b9 100644 --- a/core/java/android/app/servertransaction/ClientTransaction.java +++ b/core/java/android/app/servertransaction/ClientTransaction.java @@ -26,6 +26,7 @@ import android.os.RemoteException; import com.android.internal.annotations.VisibleForTesting; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -237,4 +238,12 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { result = 31 * result + Objects.hashCode(mLifecycleStateRequest); return result; } + + void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "mActivityToken:" + mActivityToken.hashCode()); + pw.println(prefix + "mLifecycleStateRequest:"); + if (mLifecycleStateRequest != null) { + mLifecycleStateRequest.dump(pw, prefix + " "); + } + } } diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java index 83da5f33c62a..cbcf6c750fed 100644 --- a/core/java/android/app/servertransaction/DestroyActivityItem.java +++ b/core/java/android/app/servertransaction/DestroyActivityItem.java @@ -76,12 +76,14 @@ public class DestroyActivityItem extends ActivityLifecycleItem { /** Write to Parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); dest.writeBoolean(mFinished); dest.writeInt(mConfigChanges); } /** Read from Parcel. */ private DestroyActivityItem(Parcel in) { + super(in); mFinished = in.readBoolean(); mConfigChanges = in.readInt(); } diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java index 880fef73c6f2..70a4755f99af 100644 --- a/core/java/android/app/servertransaction/PauseActivityItem.java +++ b/core/java/android/app/servertransaction/PauseActivityItem.java @@ -114,6 +114,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { /** Write to Parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); dest.writeBoolean(mFinished); dest.writeBoolean(mUserLeaving); dest.writeInt(mConfigChanges); @@ -122,6 +123,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { /** Read from Parcel. */ private PauseActivityItem(Parcel in) { + super(in); mFinished = in.readBoolean(); mUserLeaving = in.readBoolean(); mConfigChanges = in.readInt(); diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java index 9249c6e8ed54..ed90f2cb1013 100644 --- a/core/java/android/app/servertransaction/ResumeActivityItem.java +++ b/core/java/android/app/servertransaction/ResumeActivityItem.java @@ -113,6 +113,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem { /** Write to Parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); dest.writeInt(mProcState); dest.writeBoolean(mUpdateProcState); dest.writeBoolean(mIsForward); @@ -120,6 +121,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem { /** Read from Parcel. */ private ResumeActivityItem(Parcel in) { + super(in); mProcState = in.readInt(); mUpdateProcState = in.readBoolean(); mIsForward = in.readBoolean(); diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java index 5c5c3041344f..b814d1ae1392 100644 --- a/core/java/android/app/servertransaction/StopActivityItem.java +++ b/core/java/android/app/servertransaction/StopActivityItem.java @@ -83,12 +83,14 @@ public class StopActivityItem extends ActivityLifecycleItem { /** Write to Parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); dest.writeBoolean(mShowWindow); dest.writeInt(mConfigChanges); } /** Read from Parcel. */ private StopActivityItem(Parcel in) { + super(in); mShowWindow = in.readBoolean(); mConfigChanges = in.readInt(); } diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java index 5b0ea6b1f9d4..78b393a831f9 100644 --- a/core/java/android/app/servertransaction/TransactionExecutor.java +++ b/core/java/android/app/servertransaction/TransactionExecutor.java @@ -33,6 +33,8 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.List; /** @@ -122,6 +124,21 @@ public class TransactionExecutor { final IBinder token = transaction.getActivityToken(); final ActivityClientRecord r = mTransactionHandler.getActivityClient(token); + // TODO(b/71506345): Remove once root cause is found. + if (r == null) { + final StringWriter stringWriter = new StringWriter(); + final PrintWriter pw = new PrintWriter(stringWriter); + final String prefix = " "; + + pw.println("Lifecycle transaction does not have valid ActivityClientRecord."); + pw.println("Transaction:"); + transaction.dump(pw, prefix); + pw.println("Executor:"); + dump(pw, prefix); + + Slog.wtf(TAG, stringWriter.toString()); + } + // Cycle to the state right before the final requested state. cycleToPath(r, lifecycleItem.getTargetState(), true /* excludeLastState */); @@ -245,4 +262,9 @@ public class TransactionExecutor { private static void log(String message) { if (DEBUG_RESOLVER) Slog.d(TAG, message); } + + private void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "mTransactionHandler:"); + mTransactionHandler.dump(pw, prefix + " "); + } } diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java index b8fb2e34d083..27cd6e56dc0c 100644 --- a/core/java/android/app/slice/Slice.java +++ b/core/java/android/app/slice/Slice.java @@ -21,12 +21,10 @@ import android.annotation.Nullable; import android.annotation.StringDef; import android.app.PendingIntent; import android.app.RemoteInput; -import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; import android.content.IContentProvider; import android.content.Intent; -import android.content.pm.ResolveInfo; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Bundle; @@ -553,16 +551,11 @@ public final class Slice implements Parcelable { } /** - * Turns a slice Uri into slice content. - * - * @param resolver ContentResolver to be used. - * @param uri The URI to a slice provider - * @param supportedSpecs List of supported specs. - * @return The Slice provided by the app or null if none is given. - * @see Slice - */ - public static @Nullable Slice bindSlice(ContentResolver resolver, @NonNull Uri uri, - List<SliceSpec> supportedSpecs) { + * @deprecated TO BE REMOVED. + */ + @Deprecated + public static @Nullable Slice bindSlice(ContentResolver resolver, + @NonNull Uri uri, @NonNull List<SliceSpec> supportedSpecs) { Preconditions.checkNotNull(uri, "uri"); IContentProvider provider = resolver.acquireProvider(uri); if (provider == null) { @@ -590,60 +583,11 @@ public final class Slice implements Parcelable { } /** - * Turns a slice intent into slice content. Expects an explicit intent. If there is no - * {@link ContentProvider} associated with the given intent this will throw - * {@link IllegalArgumentException}. - * - * @param context The context to use. - * @param intent The intent associated with a slice. - * @param supportedSpecs List of supported specs. - * @return The Slice provided by the app or null if none is given. - * @see Slice - * @see SliceProvider#onMapIntentToUri(Intent) - * @see Intent + * @deprecated TO BE REMOVED. */ + @Deprecated public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent, - List<SliceSpec> supportedSpecs) { - Preconditions.checkNotNull(intent, "intent"); - Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null, - "Slice intent must be explicit " + intent); - ContentResolver resolver = context.getContentResolver(); - - // Check if the intent has data for the slice uri on it and use that - final Uri intentData = intent.getData(); - if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) { - return bindSlice(resolver, intentData, supportedSpecs); - } - // Otherwise ask the app - List<ResolveInfo> providers = - context.getPackageManager().queryIntentContentProviders(intent, 0); - if (providers == null) { - throw new IllegalArgumentException("Unable to resolve intent " + intent); - } - String authority = providers.get(0).providerInfo.authority; - Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) - .authority(authority).build(); - IContentProvider provider = resolver.acquireProvider(uri); - if (provider == null) { - throw new IllegalArgumentException("Unknown URI " + uri); - } - try { - Bundle extras = new Bundle(); - extras.putParcelable(SliceProvider.EXTRA_INTENT, intent); - extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS, - new ArrayList<>(supportedSpecs)); - final Bundle res = provider.call(resolver.getPackageName(), - SliceProvider.METHOD_MAP_INTENT, null, extras); - if (res == null) { - return null; - } - return res.getParcelable(SliceProvider.EXTRA_SLICE); - } catch (RemoteException e) { - // Arbitrary and not worth documenting, as Activity - // Manager will kill this process shortly anyway. - return null; - } finally { - resolver.releaseProvider(provider); - } + @NonNull List<SliceSpec> supportedSpecs) { + return context.getSystemService(SliceManager.class).bindSlice(intent, supportedSpecs); } } diff --git a/core/java/android/app/slice/SliceManager.java b/core/java/android/app/slice/SliceManager.java index 0c5f225d515e..74864cb1a371 100644 --- a/core/java/android/app/slice/SliceManager.java +++ b/core/java/android/app/slice/SliceManager.java @@ -17,17 +17,29 @@ package android.app.slice; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemService; +import android.content.ContentResolver; import android.content.Context; +import android.content.IContentProvider; +import android.content.Intent; +import android.content.pm.ResolveInfo; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.util.ArrayMap; +import android.util.Log; import android.util.Pair; +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; @@ -39,6 +51,8 @@ import java.util.concurrent.Executor; @SystemService(Context.SLICE_SERVICE) public class SliceManager { + private static final String TAG = "SliceManager"; + private final ISliceManager mService; private final Context mContext; private final ArrayMap<Pair<Uri, SliceCallback>, ISliceListener> mListenerLookup = @@ -224,6 +238,126 @@ public class SliceManager { } /** + * Obtains a list of slices that are descendants of the specified Uri. + * <p> + * Not all slice providers will implement this functionality, in which case, + * an empty collection will be returned. + * + * @param uri The uri to look for descendants under. + * @return All slices within the space. + * @see SliceProvider#onGetSliceDescendants(Uri) + */ + public @NonNull Collection<Uri> getSliceDescendants(@NonNull Uri uri) { + ContentResolver resolver = mContext.getContentResolver(); + IContentProvider provider = resolver.acquireProvider(uri); + try { + Bundle extras = new Bundle(); + extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri); + final Bundle res = provider.call(resolver.getPackageName(), + SliceProvider.METHOD_GET_DESCENDANTS, null, extras); + return res.getParcelableArrayList(SliceProvider.EXTRA_SLICE_DESCENDANTS); + } catch (RemoteException e) { + Log.e(TAG, "Unable to get slice descendants", e); + } finally { + resolver.releaseProvider(provider); + } + return Collections.emptyList(); + } + + /** + * Turns a slice Uri into slice content. + * + * @param uri The URI to a slice provider + * @param supportedSpecs List of supported specs. + * @return The Slice provided by the app or null if none is given. + * @see Slice + */ + public @Nullable Slice bindSlice(@NonNull Uri uri, @NonNull List<SliceSpec> supportedSpecs) { + Preconditions.checkNotNull(uri, "uri"); + ContentResolver resolver = mContext.getContentResolver(); + IContentProvider provider = resolver.acquireProvider(uri); + if (provider == null) { + throw new IllegalArgumentException("Unknown URI " + uri); + } + try { + Bundle extras = new Bundle(); + extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri); + extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS, + new ArrayList<>(supportedSpecs)); + final Bundle res = provider.call(resolver.getPackageName(), SliceProvider.METHOD_SLICE, + null, extras); + Bundle.setDefusable(res, true); + if (res == null) { + return null; + } + return res.getParcelable(SliceProvider.EXTRA_SLICE); + } catch (RemoteException e) { + // Arbitrary and not worth documenting, as Activity + // Manager will kill this process shortly anyway. + return null; + } finally { + resolver.releaseProvider(provider); + } + } + + /** + * Turns a slice intent into slice content. Expects an explicit intent. If there is no + * {@link android.content.ContentProvider} associated with the given intent this will throw + * {@link IllegalArgumentException}. + * + * @param intent The intent associated with a slice. + * @param supportedSpecs List of supported specs. + * @return The Slice provided by the app or null if none is given. + * @see Slice + * @see SliceProvider#onMapIntentToUri(Intent) + * @see Intent + */ + public @Nullable Slice bindSlice(@NonNull Intent intent, + @NonNull List<SliceSpec> supportedSpecs) { + Preconditions.checkNotNull(intent, "intent"); + Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null, + "Slice intent must be explicit " + intent); + ContentResolver resolver = mContext.getContentResolver(); + + // Check if the intent has data for the slice uri on it and use that + final Uri intentData = intent.getData(); + if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) { + return bindSlice(intentData, supportedSpecs); + } + // Otherwise ask the app + List<ResolveInfo> providers = + mContext.getPackageManager().queryIntentContentProviders(intent, 0); + if (providers == null) { + throw new IllegalArgumentException("Unable to resolve intent " + intent); + } + String authority = providers.get(0).providerInfo.authority; + Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(authority).build(); + IContentProvider provider = resolver.acquireProvider(uri); + if (provider == null) { + throw new IllegalArgumentException("Unknown URI " + uri); + } + try { + Bundle extras = new Bundle(); + extras.putParcelable(SliceProvider.EXTRA_INTENT, intent); + extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS, + new ArrayList<>(supportedSpecs)); + final Bundle res = provider.call(resolver.getPackageName(), + SliceProvider.METHOD_MAP_INTENT, null, extras); + if (res == null) { + return null; + } + return res.getParcelable(SliceProvider.EXTRA_SLICE); + } catch (RemoteException e) { + // Arbitrary and not worth documenting, as Activity + // Manager will kill this process shortly anyway. + return null; + } finally { + resolver.releaseProvider(provider); + } + } + + /** * Class that listens to changes in {@link Slice}s. */ public interface SliceCallback { diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java index 8483931ceaec..aa41f14d8cb5 100644 --- a/core/java/android/app/slice/SliceProvider.java +++ b/core/java/android/app/slice/SliceProvider.java @@ -36,6 +36,9 @@ import android.os.StrictMode.ThreadPolicy; import android.os.UserHandle; import android.util.Log; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -113,11 +116,19 @@ public abstract class SliceProvider extends ContentProvider { /** * @hide */ + public static final String METHOD_GET_DESCENDANTS = "get_descendants"; + /** + * @hide + */ public static final String EXTRA_INTENT = "slice_intent"; /** * @hide */ public static final String EXTRA_SLICE = "slice"; + /** + * @hide + */ + public static final String EXTRA_SLICE_DESCENDANTS = "slice_descendants"; private static final boolean DEBUG = false; @@ -139,14 +150,6 @@ public abstract class SliceProvider extends ContentProvider { * @see {@link Slice#HINT_PARTIAL} */ public Slice onBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs) { - return onBindSlice(sliceUri); - } - - /** - * @deprecated migrating to {@link #onBindSlice(Uri, List)} - */ - @Deprecated - public Slice onBindSlice(Uri sliceUri) { return null; } @@ -183,6 +186,20 @@ public abstract class SliceProvider extends ContentProvider { } /** + * Obtains a list of slices that are descendants of the specified Uri. + * <p> + * Implementing this is optional for a SliceProvider, but does provide a good + * discovery mechanism for finding slice Uris. + * + * @param uri The uri to look for descendants under. + * @return All slices within the space. + * @see SliceManager#getSliceDescendants(Uri) + */ + public @NonNull Collection<Uri> onGetSliceDescendants(@NonNull Uri uri) { + return Collections.emptyList(); + } + + /** * This method must be overridden if an {@link IntentFilter} is specified on the SliceProvider. * In that case, this method can be called and is expected to return a non-null Uri representing * a slice. Otherwise this will throw {@link UnsupportedOperationException}. @@ -290,10 +307,35 @@ public abstract class SliceProvider extends ContentProvider { "Slice binding requires the permission BIND_SLICE"); } handleUnpinSlice(uri); + } else if (method.equals(METHOD_GET_DESCENDANTS)) { + Uri uri = extras.getParcelable(EXTRA_BIND_URI); + Bundle b = new Bundle(); + b.putParcelableArrayList(EXTRA_SLICE_DESCENDANTS, + new ArrayList<>(handleGetDescendants(uri))); + return b; } return super.call(method, arg, extras); } + private Collection<Uri> handleGetDescendants(Uri uri) { + if (Looper.myLooper() == Looper.getMainLooper()) { + return onGetSliceDescendants(uri); + } else { + CountDownLatch latch = new CountDownLatch(1); + Collection<Uri>[] output = new Collection[1]; + Handler.getMain().post(() -> { + output[0] = onGetSliceDescendants(uri); + latch.countDown(); + }); + try { + latch.await(); + return output[0]; + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + private void handlePinSlice(Uri sliceUri) { if (Looper.myLooper() == Looper.getMainLooper()) { onSlicePinned(sliceUri); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index deb8dfb11094..44b4a33976d0 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2535,31 +2535,22 @@ public abstract class PackageManager { * Devices declaring this feature must include an application implementing a * {@link android.service.vr.VrListenerService} that can be targeted by VR applications via * {@link android.app.Activity#setVrModeEnabled}. + * @deprecated use {@link #FEATURE_VR_MODE_HIGH_PERFORMANCE} instead. */ + @Deprecated @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_VR_MODE = "android.software.vr.mode"; /** * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: - * The device implements {@link #FEATURE_VR_MODE} but additionally meets extra CDD requirements - * to provide a high-quality VR experience. In general, devices declaring this feature will - * additionally: - * <ul> - * <li>Deliver consistent performance at a high framerate over an extended period of time - * for typical VR application CPU/GPU workloads with a minimal number of frame drops for VR - * applications that have called - * {@link android.view.Window#setSustainedPerformanceMode}.</li> - * <li>Implement {@link #FEATURE_HIFI_SENSORS} and have a low sensor latency.</li> - * <li>Include optimizations to lower display persistence while running VR applications.</li> - * <li>Implement an optimized render path to minimize latency to draw to the device's main - * display.</li> - * <li>Include the following EGL extensions: EGL_ANDROID_create_native_client_buffer, - * EGL_ANDROID_front_buffer_auto_refresh, EGL_EXT_protected_content, - * EGL_KHR_mutable_render_buffer, EGL_KHR_reusable_sync, and EGL_KHR_wait_sync.</li> - * <li>Provide at least one CPU core that is reserved for use solely by the top, foreground - * VR application process for critical render threads while such an application is - * running.</li> - * </ul> + * The device implements an optimized mode for virtual reality (VR) applications that handles + * stereoscopic rendering of notifications, disables most monocular system UI components + * while a VR application has user focus and meets extra CDD requirements to provide a + * high-quality VR experience. + * Devices declaring this feature must include an application implementing a + * {@link android.service.vr.VrListenerService} that can be targeted by VR applications via + * {@link android.app.Activity#setVrModeEnabled}. + * and must meet CDD requirements to provide a high-quality VR experience. */ @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_VR_MODE_HIGH_PERFORMANCE diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index a18f22e1184b..6d6c02a47082 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -6285,6 +6285,31 @@ public class PackageParser { + " " + packageName + "}"; } + public String dumpState_temp() { + String flags = ""; + flags += ((applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 ? "U" : ""); + flags += ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 ? "S" : ""); + if ("".equals(flags)) { + flags = "-"; + } + String privFlags = ""; + privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0 ? "P" : ""); + privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0 ? "O" : ""); + privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0 ? "V" : ""); + if ("".equals(privFlags)) { + privFlags = "-"; + } + return "Package{" + + Integer.toHexString(System.identityHashCode(this)) + + " " + packageName + + ", ver:" + getLongVersionCode() + + ", path: " + codePath + + ", flags: " + flags + + ", privFlags: " + privFlags + + ", extra: " + (mExtras == null ? "<<NULL>>" : Integer.toHexString(System.identityHashCode(mExtras)) + "}") + + "}"; + } + @Override public int describeContents() { return 0; diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 3f6dd2e757ed..078958ad881a 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -179,6 +179,11 @@ public abstract class DisplayManagerInternal { public abstract void persistBrightnessSliderEvents(); /** + * Notifies the display manager that resource overlays have changed. + */ + public abstract void onOverlayChanged(); + + /** * Describes the requested power state of the display. * * This object is intended to describe the general characteristics of the diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index e941279f4d25..7528bc3989c2 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -1082,33 +1082,6 @@ public class InputMethodService extends AbstractInputMethodService { } /** - * Close/hide the input method's soft input area, so the user no longer - * sees it or can interact with it. This can only be called - * from the currently active input method, as validated by the given token. - * - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY}, - * {@link InputMethodManager#HIDE_NOT_ALWAYS} bit set. - */ - public void hideSoftInputFromInputMethod(int flags) { - mImm.hideSoftInputFromInputMethodInternal(mToken, flags); - } - - /** - * Show the input method's soft input area, so the user - * sees the input method window and can interact with it. - * This can only be called from the currently active input method, - * as validated by the given token. - * - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link InputMethodManager#SHOW_IMPLICIT} or - * {@link InputMethodManager#SHOW_FORCED} bit set. - */ - public void showSoftInputFromInputMethod(int flags) { - mImm.showSoftInputFromInputMethodInternal(mToken, flags); - } - - /** * Force switch to the last used input method and subtype. If the last input method didn't have * any subtypes, the framework will simply switch to the last input method with no subtype * specified. @@ -1738,7 +1711,7 @@ public class InputMethodService extends AbstractInputMethodService { // Rethrow the exception to preserve the existing behavior. Some IMEs may have directly // called this method and relied on this exception for some clean-up tasks. // TODO: Give developers a clear guideline of whether it's OK to call this method or - // InputMethodManager#showSoftInputFromInputMethod() should always be used instead. + // InputMethodService#requestShowSelf(int) should always be used instead. throw e; } finally { // TODO: Is it OK to set true when we get BadTokenException? @@ -2060,27 +2033,30 @@ public class InputMethodService extends AbstractInputMethodService { /** * Close this input method's soft input area, removing it from the display. - * The input method will continue running, but the user can no longer use - * it to generate input by touching the screen. - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY - * InputMethodManager.HIDE_IMPLICIT_ONLY} bit set. + * + * The input method will continue running, but the user can no longer use it to generate input + * by touching the screen. + * + * @see InputMethodManager#HIDE_IMPLICIT_ONLY + * @see InputMethodManager#HIDE_NOT_ALWAYS + * @param flags Provides additional operating flags. */ public void requestHideSelf(int flags) { - mImm.hideSoftInputFromInputMethod(mToken, flags); + mImm.hideSoftInputFromInputMethodInternal(mToken, flags); } - + /** - * Show the input method. This is a call back to the - * IMF to handle showing the input method. - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link InputMethodManager#SHOW_FORCED - * InputMethodManager.} bit set. + * Show the input method's soft input area, so the user sees the input method window and can + * interact with it. + * + * @see InputMethodManager#SHOW_IMPLICIT + * @see InputMethodManager#SHOW_FORCED + * @param flags Provides additional operating flags. */ - private void requestShowSelf(int flags) { - mImm.showSoftInputFromInputMethod(mToken, flags); + public void requestShowSelf(int flags) { + mImm.showSoftInputFromInputMethodInternal(mToken, flags); } - + private boolean handleBack(boolean doIt) { if (mShowInputRequested) { // If the soft input area is shown, back closes it and we diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl index d9b57db18071..790c80b1d934 100644 --- a/core/java/android/net/IIpSecService.aidl +++ b/core/java/android/net/IIpSecService.aidl @@ -31,7 +31,7 @@ import android.os.ParcelFileDescriptor; interface IIpSecService { IpSecSpiResponse allocateSecurityParameterIndex( - int direction, in String remoteAddress, int requestedSpi, in IBinder binder); + in String destinationAddress, int requestedSpi, in IBinder binder); void releaseSecurityParameterIndex(int resourceId); @@ -43,7 +43,7 @@ interface IIpSecService void deleteTransportModeTransform(int transformId); - void applyTransportModeTransform(in ParcelFileDescriptor socket, int transformId); + void applyTransportModeTransform(in ParcelFileDescriptor socket, int direction, int transformId); - void removeTransportModeTransform(in ParcelFileDescriptor socket, int transformId); + void removeTransportModeTransforms(in ParcelFileDescriptor socket); } diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java index 7d752e89e6f6..c69a4d4c0bee 100644 --- a/core/java/android/net/IpSecAlgorithm.java +++ b/core/java/android/net/IpSecAlgorithm.java @@ -256,13 +256,19 @@ public final class IpSecAlgorithm implements Parcelable { return getName().equals(AUTH_CRYPT_AES_GCM); } + // Because encryption keys are sensitive and userdebug builds are used by large user pools + // such as beta testers, we only allow sensitive info such as keys on eng builds. + private static boolean isUnsafeBuild() { + return Build.IS_DEBUGGABLE && Build.IS_ENG; + } + @Override public String toString() { return new StringBuilder() .append("{mName=") .append(mName) .append(", mKey=") - .append(Build.IS_DEBUGGABLE ? HexDump.toHexString(mKey) : "<hidden>") + .append(isUnsafeBuild() ? HexDump.toHexString(mKey) : "<hidden>") .append(", mTruncLenBits=") .append(mTruncLenBits) .append("}") diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java index f54ceb5c142a..80b0af33735b 100644 --- a/core/java/android/net/IpSecConfig.java +++ b/core/java/android/net/IpSecConfig.java @@ -32,59 +32,29 @@ public final class IpSecConfig implements Parcelable { // MODE_TRANSPORT or MODE_TUNNEL private int mMode = IpSecTransform.MODE_TRANSPORT; - // Needs to be valid only for tunnel mode // Preventing this from being null simplifies Java->Native binder - private String mLocalAddress = ""; + private String mSourceAddress = ""; // Preventing this from being null simplifies Java->Native binder - private String mRemoteAddress = ""; + private String mDestinationAddress = ""; // The underlying Network that represents the "gateway" Network // for outbound packets. It may also be used to select packets. private Network mNetwork; - /** - * This class captures the parameters that specifically apply to inbound or outbound traffic. - */ - public static class Flow { - // Minimum requirements for identifying a transform - // SPI identifying the IPsec flow in packet processing - // and a remote IP address - private int mSpiResourceId = IpSecManager.INVALID_RESOURCE_ID; - - // Encryption Algorithm - private IpSecAlgorithm mEncryption; - - // Authentication Algorithm - private IpSecAlgorithm mAuthentication; - - // Authenticated Encryption Algorithm - private IpSecAlgorithm mAuthenticatedEncryption; - - @Override - public String toString() { - return new StringBuilder() - .append("{mSpiResourceId=") - .append(mSpiResourceId) - .append(", mEncryption=") - .append(mEncryption) - .append(", mAuthentication=") - .append(mAuthentication) - .append(", mAuthenticatedEncryption=") - .append(mAuthenticatedEncryption) - .append("}") - .toString(); - } - - static boolean equals(IpSecConfig.Flow lhs, IpSecConfig.Flow rhs) { - if (lhs == null || rhs == null) return (lhs == rhs); - return (lhs.mSpiResourceId == rhs.mSpiResourceId - && IpSecAlgorithm.equals(lhs.mEncryption, rhs.mEncryption) - && IpSecAlgorithm.equals(lhs.mAuthentication, rhs.mAuthentication)); - } - } + // Minimum requirements for identifying a transform + // SPI identifying the IPsec SA in packet processing + // and a destination IP address + private int mSpiResourceId = IpSecManager.INVALID_RESOURCE_ID; + + // Encryption Algorithm + private IpSecAlgorithm mEncryption; + + // Authentication Algorithm + private IpSecAlgorithm mAuthentication; - private final Flow[] mFlow = new Flow[] {new Flow(), new Flow()}; + // Authenticated Encryption Algorithm + private IpSecAlgorithm mAuthenticatedEncryption; // For tunnel mode IPv4 UDP Encapsulation // IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE @@ -100,36 +70,37 @@ public final class IpSecConfig implements Parcelable { mMode = mode; } - /** Set the local IP address for Tunnel mode */ - public void setLocalAddress(String localAddress) { - mLocalAddress = localAddress; + /** Set the source IP addres for this IPsec transform */ + public void setSourceAddress(String sourceAddress) { + mSourceAddress = sourceAddress; } - /** Set the remote IP address for this IPsec transform */ - public void setRemoteAddress(String remoteAddress) { - mRemoteAddress = remoteAddress; + /** Set the destination IP address for this IPsec transform */ + public void setDestinationAddress(String destinationAddress) { + mDestinationAddress = destinationAddress; } - /** Set the SPI for a given direction by resource ID */ - public void setSpiResourceId(int direction, int resourceId) { - mFlow[direction].mSpiResourceId = resourceId; + /** Set the SPI by resource ID */ + public void setSpiResourceId(int resourceId) { + mSpiResourceId = resourceId; } - /** Set the encryption algorithm for a given direction */ - public void setEncryption(int direction, IpSecAlgorithm encryption) { - mFlow[direction].mEncryption = encryption; + /** Set the encryption algorithm */ + public void setEncryption(IpSecAlgorithm encryption) { + mEncryption = encryption; } - /** Set the authentication algorithm for a given direction */ - public void setAuthentication(int direction, IpSecAlgorithm authentication) { - mFlow[direction].mAuthentication = authentication; + /** Set the authentication algorithm */ + public void setAuthentication(IpSecAlgorithm authentication) { + mAuthentication = authentication; } - /** Set the authenticated encryption algorithm for a given direction */ - public void setAuthenticatedEncryption(int direction, IpSecAlgorithm authenticatedEncryption) { - mFlow[direction].mAuthenticatedEncryption = authenticatedEncryption; + /** Set the authenticated encryption algorithm */ + public void setAuthenticatedEncryption(IpSecAlgorithm authenticatedEncryption) { + mAuthenticatedEncryption = authenticatedEncryption; } + /** Set the underlying network that will carry traffic for this transform */ public void setNetwork(Network network) { mNetwork = network; } @@ -155,28 +126,28 @@ public final class IpSecConfig implements Parcelable { return mMode; } - public String getLocalAddress() { - return mLocalAddress; + public String getSourceAddress() { + return mSourceAddress; } - public int getSpiResourceId(int direction) { - return mFlow[direction].mSpiResourceId; + public int getSpiResourceId() { + return mSpiResourceId; } - public String getRemoteAddress() { - return mRemoteAddress; + public String getDestinationAddress() { + return mDestinationAddress; } - public IpSecAlgorithm getEncryption(int direction) { - return mFlow[direction].mEncryption; + public IpSecAlgorithm getEncryption() { + return mEncryption; } - public IpSecAlgorithm getAuthentication(int direction) { - return mFlow[direction].mAuthentication; + public IpSecAlgorithm getAuthentication() { + return mAuthentication; } - public IpSecAlgorithm getAuthenticatedEncryption(int direction) { - return mFlow[direction].mAuthenticatedEncryption; + public IpSecAlgorithm getAuthenticatedEncryption() { + return mAuthenticatedEncryption; } public Network getNetwork() { @@ -209,17 +180,13 @@ public final class IpSecConfig implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { out.writeInt(mMode); - out.writeString(mLocalAddress); - out.writeString(mRemoteAddress); + out.writeString(mSourceAddress); + out.writeString(mDestinationAddress); out.writeParcelable(mNetwork, flags); - out.writeInt(mFlow[IpSecTransform.DIRECTION_IN].mSpiResourceId); - out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mEncryption, flags); - out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthentication, flags); - out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthenticatedEncryption, flags); - out.writeInt(mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId); - out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mEncryption, flags); - out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication, flags); - out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthenticatedEncryption, flags); + out.writeInt(mSpiResourceId); + out.writeParcelable(mEncryption, flags); + out.writeParcelable(mAuthentication, flags); + out.writeParcelable(mAuthenticatedEncryption, flags); out.writeInt(mEncapType); out.writeInt(mEncapSocketResourceId); out.writeInt(mEncapRemotePort); @@ -231,22 +198,15 @@ public final class IpSecConfig implements Parcelable { private IpSecConfig(Parcel in) { mMode = in.readInt(); - mLocalAddress = in.readString(); - mRemoteAddress = in.readString(); + mSourceAddress = in.readString(); + mDestinationAddress = in.readString(); mNetwork = (Network) in.readParcelable(Network.class.getClassLoader()); - mFlow[IpSecTransform.DIRECTION_IN].mSpiResourceId = in.readInt(); - mFlow[IpSecTransform.DIRECTION_IN].mEncryption = - (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - mFlow[IpSecTransform.DIRECTION_IN].mAuthentication = - (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - mFlow[IpSecTransform.DIRECTION_IN].mAuthenticatedEncryption = - (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId = in.readInt(); - mFlow[IpSecTransform.DIRECTION_OUT].mEncryption = + mSpiResourceId = in.readInt(); + mEncryption = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication = + mAuthentication = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - mFlow[IpSecTransform.DIRECTION_OUT].mAuthenticatedEncryption = + mAuthenticatedEncryption = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); mEncapType = in.readInt(); mEncapSocketResourceId = in.readInt(); @@ -260,10 +220,10 @@ public final class IpSecConfig implements Parcelable { strBuilder .append("{mMode=") .append(mMode == IpSecTransform.MODE_TUNNEL ? "TUNNEL" : "TRANSPORT") - .append(", mLocalAddress=") - .append(mLocalAddress) - .append(", mRemoteAddress=") - .append(mRemoteAddress) + .append(", mSourceAddress=") + .append(mSourceAddress) + .append(", mDestinationAddress=") + .append(mDestinationAddress) .append(", mNetwork=") .append(mNetwork) .append(", mEncapType=") @@ -274,10 +234,14 @@ public final class IpSecConfig implements Parcelable { .append(mEncapRemotePort) .append(", mNattKeepaliveInterval=") .append(mNattKeepaliveInterval) - .append(", mFlow[OUT]=") - .append(mFlow[IpSecTransform.DIRECTION_OUT]) - .append(", mFlow[IN]=") - .append(mFlow[IpSecTransform.DIRECTION_IN]) + .append("{mSpiResourceId=") + .append(mSpiResourceId) + .append(", mEncryption=") + .append(mEncryption) + .append(", mAuthentication=") + .append(mAuthentication) + .append(", mAuthenticatedEncryption=") + .append(mAuthenticatedEncryption) .append("}"); return strBuilder.toString(); @@ -299,17 +263,18 @@ public final class IpSecConfig implements Parcelable { public static boolean equals(IpSecConfig lhs, IpSecConfig rhs) { if (lhs == null || rhs == null) return (lhs == rhs); return (lhs.mMode == rhs.mMode - && lhs.mLocalAddress.equals(rhs.mLocalAddress) - && lhs.mRemoteAddress.equals(rhs.mRemoteAddress) + && lhs.mSourceAddress.equals(rhs.mSourceAddress) + && lhs.mDestinationAddress.equals(rhs.mDestinationAddress) && ((lhs.mNetwork != null && lhs.mNetwork.equals(rhs.mNetwork)) || (lhs.mNetwork == rhs.mNetwork)) && lhs.mEncapType == rhs.mEncapType && lhs.mEncapSocketResourceId == rhs.mEncapSocketResourceId && lhs.mEncapRemotePort == rhs.mEncapRemotePort && lhs.mNattKeepaliveInterval == rhs.mNattKeepaliveInterval - && IpSecConfig.Flow.equals(lhs.mFlow[IpSecTransform.DIRECTION_OUT], - rhs.mFlow[IpSecTransform.DIRECTION_OUT]) - && IpSecConfig.Flow.equals(lhs.mFlow[IpSecTransform.DIRECTION_IN], - rhs.mFlow[IpSecTransform.DIRECTION_IN])); + && lhs.mSpiResourceId == rhs.mSpiResourceId + && IpSecAlgorithm.equals(lhs.mEncryption, rhs.mEncryption) + && IpSecAlgorithm.equals( + lhs.mAuthenticatedEncryption, rhs.mAuthenticatedEncryption) + && IpSecAlgorithm.equals(lhs.mAuthentication, rhs.mAuthentication)); } } diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index 34cfa9b2153d..2cda58c99a61 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -17,6 +17,7 @@ package android.net; import static com.android.internal.util.Preconditions.checkNotNull; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemService; import android.annotation.TestApi; @@ -33,6 +34,8 @@ import dalvik.system.CloseGuard; import java.io.FileDescriptor; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.Socket; @@ -53,6 +56,23 @@ public final class IpSecManager { private static final String TAG = "IpSecManager"; /** + * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute + * applies to traffic towards the host. + */ + public static final int DIRECTION_IN = 0; + + /** + * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute + * applies to traffic from the host. + */ + public static final int DIRECTION_OUT = 1; + + /** @hide */ + @IntDef(value = {DIRECTION_IN, DIRECTION_OUT}) + @Retention(RetentionPolicy.SOURCE) + public @interface PolicyDirection {} + + /** * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index. * * <p>No IPsec packet may contain an SPI of 0. @@ -125,7 +145,7 @@ public final class IpSecManager { */ public static final class SecurityParameterIndex implements AutoCloseable { private final IIpSecService mService; - private final InetAddress mRemoteAddress; + private final InetAddress mDestinationAddress; private final CloseGuard mCloseGuard = CloseGuard.get(); private int mSpi = INVALID_SECURITY_PARAMETER_INDEX; private int mResourceId = INVALID_RESOURCE_ID; @@ -164,14 +184,14 @@ public final class IpSecManager { } private SecurityParameterIndex( - @NonNull IIpSecService service, int direction, InetAddress remoteAddress, int spi) + @NonNull IIpSecService service, InetAddress destinationAddress, int spi) throws ResourceUnavailableException, SpiUnavailableException { mService = service; - mRemoteAddress = remoteAddress; + mDestinationAddress = destinationAddress; try { IpSecSpiResponse result = mService.allocateSecurityParameterIndex( - direction, remoteAddress.getHostAddress(), spi, new Binder()); + destinationAddress.getHostAddress(), spi, new Binder()); if (result == null) { throw new NullPointerException("Received null response from IpSecService"); @@ -216,25 +236,23 @@ public final class IpSecManager { } /** - * Reserve a random SPI for traffic bound to or from the specified remote address. + * Reserve a random SPI for traffic bound to or from the specified destination address. * * <p>If successful, this SPI is guaranteed available until released by a call to {@link * SecurityParameterIndex#close()}. * - * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT} - * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress + * @param destinationAddress the destination address for traffic bearing the requested SPI. + * For inbound traffic, the destination should be an address currently assigned on-device. * @return the reserved SecurityParameterIndex - * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated - * for this user - * @throws SpiUnavailableException indicating that a particular SPI cannot be reserved + * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are + * currently allocated for this user */ - public SecurityParameterIndex allocateSecurityParameterIndex( - int direction, InetAddress remoteAddress) throws ResourceUnavailableException { + public SecurityParameterIndex allocateSecurityParameterIndex(InetAddress destinationAddress) + throws ResourceUnavailableException { try { return new SecurityParameterIndex( mService, - direction, - remoteAddress, + destinationAddress, IpSecManager.INVALID_SECURITY_PARAMETER_INDEX); } catch (SpiUnavailableException unlikely) { throw new ResourceUnavailableException("No SPIs available"); @@ -242,26 +260,27 @@ public final class IpSecManager { } /** - * Reserve the requested SPI for traffic bound to or from the specified remote address. + * Reserve the requested SPI for traffic bound to or from the specified destination address. * * <p>If successful, this SPI is guaranteed available until released by a call to {@link * SecurityParameterIndex#close()}. * - * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT} - * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress + * @param destinationAddress the destination address for traffic bearing the requested SPI. + * For inbound traffic, the destination should be an address currently assigned on-device. * @param requestedSpi the requested SPI, or '0' to allocate a random SPI * @return the reserved SecurityParameterIndex - * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated - * for this user - * @throws SpiUnavailableException indicating that the requested SPI could not be reserved + * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are + * currently allocated for this user + * @throws {@link #SpiUnavailableException} indicating that the requested SPI could not be + * reserved */ public SecurityParameterIndex allocateSecurityParameterIndex( - int direction, InetAddress remoteAddress, int requestedSpi) + InetAddress destinationAddress, int requestedSpi) throws SpiUnavailableException, ResourceUnavailableException { if (requestedSpi == IpSecManager.INVALID_SECURITY_PARAMETER_INDEX) { throw new IllegalArgumentException("Requested SPI must be a valid (non-zero) SPI"); } - return new SecurityParameterIndex(mService, direction, remoteAddress, requestedSpi); + return new SecurityParameterIndex(mService, destinationAddress, requestedSpi); } /** @@ -269,14 +288,14 @@ public final class IpSecManager { * * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When - * the transform is removed from the socket by calling {@link #removeTransportModeTransform}, + * the transform is removed from the socket by calling {@link #removeTransportModeTransforms}, * unprotected traffic can resume on that socket. * * <p>For security reasons, the destination address of any traffic on the socket must match the * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any * other IP address will result in an IOException. In addition, reads and writes on the socket * will throw IOException if the user deactivates the transform (by calling {@link - * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}. + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}. * * <h4>Rekey Procedure</h4> * @@ -287,15 +306,14 @@ public final class IpSecManager { * in-flight packets have been received. * * @param socket a stream socket + * @param direction the policy direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param transform a transport mode {@code IpSecTransform} * @throws IOException indicating that the transform could not be applied - * @hide */ - public void applyTransportModeTransform(Socket socket, IpSecTransform transform) + public void applyTransportModeTransform( + Socket socket, int direction, IpSecTransform transform) throws IOException { - try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) { - applyTransportModeTransform(pfd, transform); - } + applyTransportModeTransform(socket.getFileDescriptor$(), direction, transform); } /** @@ -303,14 +321,14 @@ public final class IpSecManager { * * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When - * the transform is removed from the socket by calling {@link #removeTransportModeTransform}, + * the transform is removed from the socket by calling {@link #removeTransportModeTransforms}, * unprotected traffic can resume on that socket. * * <p>For security reasons, the destination address of any traffic on the socket must match the * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any * other IP address will result in an IOException. In addition, reads and writes on the socket * will throw IOException if the user deactivates the transform (by calling {@link - * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}. + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}. * * <h4>Rekey Procedure</h4> * @@ -321,15 +339,13 @@ public final class IpSecManager { * in-flight packets have been received. * * @param socket a datagram socket + * @param direction the policy direction either DIRECTION_IN or DIRECTION_OUT * @param transform a transport mode {@code IpSecTransform} * @throws IOException indicating that the transform could not be applied - * @hide */ - public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform) - throws IOException { - try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket)) { - applyTransportModeTransform(pfd, transform); - } + public void applyTransportModeTransform( + DatagramSocket socket, int direction, IpSecTransform transform) throws IOException { + applyTransportModeTransform(socket.getFileDescriptor$(), direction, transform); } /** @@ -337,14 +353,14 @@ public final class IpSecManager { * * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When - * the transform is removed from the socket by calling {@link #removeTransportModeTransform}, + * the transform is removed from the socket by calling {@link #removeTransportModeTransforms}, * unprotected traffic can resume on that socket. * * <p>For security reasons, the destination address of any traffic on the socket must match the * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any * other IP address will result in an IOException. In addition, reads and writes on the socket * will throw IOException if the user deactivates the transform (by calling {@link - * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}. + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}. * * <h4>Rekey Procedure</h4> * @@ -355,24 +371,17 @@ public final class IpSecManager { * in-flight packets have been received. * * @param socket a socket file descriptor + * @param direction the policy direction either DIRECTION_IN or DIRECTION_OUT * @param transform a transport mode {@code IpSecTransform} * @throws IOException indicating that the transform could not be applied */ - public void applyTransportModeTransform(FileDescriptor socket, IpSecTransform transform) + public void applyTransportModeTransform( + FileDescriptor socket, int direction, IpSecTransform transform) throws IOException { // We dup() the FileDescriptor here because if we don't, then the ParcelFileDescriptor() - // constructor takes control and closes the user's FD when we exit the method - // This is behaviorally the same as the other versions, but the PFD constructor does not - // dup() automatically, whereas PFD.fromSocket() and PDF.fromDatagramSocket() do dup(). + // constructor takes control and closes the user's FD when we exit the method. try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) { - applyTransportModeTransform(pfd, transform); - } - } - - /* Call down to activate a transform */ - private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) { - try { - mService.applyTransportModeTransform(pfd, transform.getResourceId()); + mService.applyTransportModeTransform(pfd, direction, transform.getResourceId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -396,75 +405,56 @@ public final class IpSecManager { /** * Remove an IPsec transform from a stream socket. * - * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed - * regardless of the state of the transform. Removing a transform from a socket allows the - * socket to be reused for communication in the clear. + * <p>Once removed, traffic on the socket will not be encrypted. Removing transforms from a + * socket allows the socket to be reused for communication in the clear. * * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling * {@link IpSecTransform#close()}, then communication on the socket will fail until this method * is called. * * @param socket a socket that previously had a transform applied to it - * @param transform the IPsec Transform that was previously applied to the given socket * @throws IOException indicating that the transform could not be removed from the socket - * @hide */ - public void removeTransportModeTransform(Socket socket, IpSecTransform transform) + public void removeTransportModeTransforms(Socket socket) throws IOException { - try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) { - removeTransportModeTransform(pfd, transform); - } + removeTransportModeTransforms(socket.getFileDescriptor$()); } /** * Remove an IPsec transform from a datagram socket. * - * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed - * regardless of the state of the transform. Removing a transform from a socket allows the - * socket to be reused for communication in the clear. + * <p>Once removed, traffic on the socket will not be encrypted. Removing transforms from a + * socket allows the socket to be reused for communication in the clear. * * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling * {@link IpSecTransform#close()}, then communication on the socket will fail until this method * is called. * * @param socket a socket that previously had a transform applied to it - * @param transform the IPsec Transform that was previously applied to the given socket * @throws IOException indicating that the transform could not be removed from the socket - * @hide */ - public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform) + public void removeTransportModeTransforms(DatagramSocket socket) throws IOException { - try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket)) { - removeTransportModeTransform(pfd, transform); - } + removeTransportModeTransforms(socket.getFileDescriptor$()); } /** * Remove an IPsec transform from a socket. * - * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed - * regardless of the state of the transform. Removing a transform from a socket allows the - * socket to be reused for communication in the clear. + * <p>Once removed, traffic on the socket will not be encrypted. Removing transforms from a + * socket allows the socket to be reused for communication in the clear. * * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling * {@link IpSecTransform#close()}, then communication on the socket will fail until this method * is called. * * @param socket a socket that previously had a transform applied to it - * @param transform the IPsec Transform that was previously applied to the given socket * @throws IOException indicating that the transform could not be removed from the socket */ - public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform) + public void removeTransportModeTransforms(FileDescriptor socket) throws IOException { try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) { - removeTransportModeTransform(pfd, transform); - } - } - - /* Call down to remove a transform */ - private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) { - try { - mService.removeTransportModeTransform(pfd, transform.getResourceId()); + mService.removeTransportModeTransforms(pfd); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java index 102ba6d94faa..7b9b4830929d 100644 --- a/core/java/android/net/IpSecTransform.java +++ b/core/java/android/net/IpSecTransform.java @@ -38,13 +38,11 @@ import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; /** - * This class represents an IPsec transform, which comprises security associations in one or both - * directions. + * This class represents a transform, which roughly corresponds to an IPsec Security Association. * * <p>Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform} - * object encapsulates the properties and state of an inbound and outbound IPsec security - * association. That includes, but is not limited to, algorithm choice, key material, and allocated - * system resources. + * object encapsulates the properties and state of an IPsec security association. That includes, + * but is not limited to, algorithm choice, key material, and allocated system resources. * * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the * Internet Protocol</a> @@ -52,23 +50,6 @@ import java.net.InetAddress; public final class IpSecTransform implements AutoCloseable { private static final String TAG = "IpSecTransform"; - /** - * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute - * applies to traffic towards the host. - */ - public static final int DIRECTION_IN = 0; - - /** - * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute - * applies to traffic from the host. - */ - public static final int DIRECTION_OUT = 1; - - /** @hide */ - @IntDef(value = {DIRECTION_IN, DIRECTION_OUT}) - @Retention(RetentionPolicy.SOURCE) - public @interface TransformDirection {} - /** @hide */ public static final int MODE_TRANSPORT = 0; @@ -170,7 +151,7 @@ public final class IpSecTransform implements AutoCloseable { * * <p>Deactivating a transform while it is still applied to a socket will result in errors on * that socket. Make sure to remove transforms by calling {@link - * IpSecManager#removeTransportModeTransform}. Note, removing an {@code IpSecTransform} from a + * IpSecManager#removeTransportModeTransforms}. Note, removing an {@code IpSecTransform} from a * socket will not deactivate it (because one transform may be applied to multiple sockets). * * <p>It is safe to call this method on a transform that has already been deactivated. @@ -272,85 +253,49 @@ public final class IpSecTransform implements AutoCloseable { private IpSecConfig mConfig; /** - * Set the encryption algorithm for the given direction. - * - * <p>If encryption is set for a direction without also providing an SPI for that direction, - * creation of an {@code IpSecTransform} will fail when attempting to build the transform. + * Set the encryption algorithm. * * <p>Encryption is mutually exclusive with authenticated encryption. * - * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied. */ - public IpSecTransform.Builder setEncryption( - @TransformDirection int direction, IpSecAlgorithm algo) { + public IpSecTransform.Builder setEncryption(@NonNull IpSecAlgorithm algo) { // TODO: throw IllegalArgumentException if algo is not an encryption algorithm. - mConfig.setEncryption(direction, algo); + Preconditions.checkNotNull(algo); + mConfig.setEncryption(algo); return this; } /** - * Set the authentication (integrity) algorithm for the given direction. - * - * <p>If authentication is set for a direction without also providing an SPI for that - * direction, creation of an {@code IpSecTransform} will fail when attempting to build the - * transform. + * Set the authentication (integrity) algorithm. * * <p>Authentication is mutually exclusive with authenticated encryption. * - * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied. */ - public IpSecTransform.Builder setAuthentication( - @TransformDirection int direction, IpSecAlgorithm algo) { + public IpSecTransform.Builder setAuthentication(@NonNull IpSecAlgorithm algo) { // TODO: throw IllegalArgumentException if algo is not an authentication algorithm. - mConfig.setAuthentication(direction, algo); + Preconditions.checkNotNull(algo); + mConfig.setAuthentication(algo); return this; } /** - * Set the authenticated encryption algorithm for the given direction. - * - * <p>If an authenticated encryption algorithm is set for a given direction without also - * providing an SPI for that direction, creation of an {@code IpSecTransform} will fail when - * attempting to build the transform. + * Set the authenticated encryption algorithm. * - * <p>The Authenticated Encryption (AE) class of algorithms are also known as Authenticated - * Encryption with Associated Data (AEAD) algorithms, or Combined mode algorithms (as - * referred to in <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>). + * <p>The Authenticated Encryption (AE) class of algorithms are also known as + * Authenticated Encryption with Associated Data (AEAD) algorithms, or Combined mode + * algorithms (as referred to in + * <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>). * * <p>Authenticated encryption is mutually exclusive with encryption and authentication. * - * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to * be applied. */ - public IpSecTransform.Builder setAuthenticatedEncryption( - @TransformDirection int direction, IpSecAlgorithm algo) { - mConfig.setAuthenticatedEncryption(direction, algo); - return this; - } - - /** - * Set the SPI for the given direction. - * - * <p>Because IPsec operates at the IP layer, this 32-bit identifier uniquely identifies - * packets to a given destination address. To prevent SPI collisions, values should be - * reserved by calling {@link IpSecManager#allocateSecurityParameterIndex}. - * - * <p>If the SPI and algorithms are omitted for one direction, traffic in that direction - * will not be encrypted or authenticated. - * - * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} - * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed - * traffic - */ - public IpSecTransform.Builder setSpi( - @TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) { - if (spi.getResourceId() == INVALID_RESOURCE_ID) { - throw new IllegalArgumentException("Invalid SecurityParameterIndex"); - } - mConfig.setSpiResourceId(direction, spi.getResourceId()); + public IpSecTransform.Builder setAuthenticatedEncryption(@NonNull IpSecAlgorithm algo) { + Preconditions.checkNotNull(algo); + mConfig.setAuthenticatedEncryption(algo); return this; } @@ -363,7 +308,8 @@ public final class IpSecTransform implements AutoCloseable { * @hide */ @SystemApi - public IpSecTransform.Builder setUnderlyingNetwork(Network net) { + public IpSecTransform.Builder setUnderlyingNetwork(@NonNull Network net) { + Preconditions.checkNotNull(net); mConfig.setNetwork(net); return this; } @@ -382,7 +328,8 @@ public final class IpSecTransform implements AutoCloseable { * encapsulated traffic. In the case of IKEv2, this should be port 4500. */ public IpSecTransform.Builder setIpv4Encapsulation( - IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) { + @NonNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) { + Preconditions.checkNotNull(localSocket); mConfig.setEncapType(ENCAP_ESPINUDP); if (localSocket.getResourceId() == INVALID_RESOURCE_ID) { throw new IllegalArgumentException("Invalid UdpEncapsulationSocket"); @@ -419,24 +366,33 @@ public final class IpSecTransform implements AutoCloseable { * will not affect any network traffic until it has been applied to one or more sockets. * * @see IpSecManager#applyTransportModeTransform - * @param remoteAddress the remote {@code InetAddress} of traffic on sockets that will use - * this transform + * @param sourceAddress the source {@code InetAddress} of traffic on sockets that will use + * this transform; this address must belong to the Network used by all sockets that + * utilize this transform; if provided, then only traffic originating from the + * specified source address will be processed. + * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed + * traffic * @throws IllegalArgumentException indicating that a particular combination of transform * properties is invalid - * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms are - * active + * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms + * are active * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI * collides with an existing transform * @throws IOException indicating other errors */ - public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress) + public IpSecTransform buildTransportModeTransform( + @NonNull InetAddress sourceAddress, + @NonNull IpSecManager.SecurityParameterIndex spi) throws IpSecManager.ResourceUnavailableException, IpSecManager.SpiUnavailableException, IOException { - if (remoteAddress == null) { - throw new IllegalArgumentException("Remote address may not be null or empty!"); + Preconditions.checkNotNull(sourceAddress); + Preconditions.checkNotNull(spi); + if (spi.getResourceId() == INVALID_RESOURCE_ID) { + throw new IllegalArgumentException("Invalid SecurityParameterIndex"); } mConfig.setMode(MODE_TRANSPORT); - mConfig.setRemoteAddress(remoteAddress.getHostAddress()); + mConfig.setSourceAddress(sourceAddress.getHostAddress()); + mConfig.setSpiResourceId(spi.getResourceId()); // FIXME: modifying a builder after calling build can change the built transform. return new IpSecTransform(mContext, mConfig).activate(); } @@ -445,26 +401,33 @@ public final class IpSecTransform implements AutoCloseable { * Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some * parameters have interdependencies that are checked at build time. * - * @param localAddress the {@link InetAddress} that provides the local endpoint for this + * @param sourceAddress the {@link InetAddress} that provides the source address for this * IPsec tunnel. This is almost certainly an address belonging to the {@link Network} * that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}. - * @param remoteAddress the {@link InetAddress} representing the remote endpoint of this - * IPsec tunnel. + * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed + * traffic * @throws IllegalArgumentException indicating that a particular combination of transform * properties is invalid. + * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms + * are active + * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI + * collides with an existing transform + * @throws IOException indicating other errors * @hide */ public IpSecTransform buildTunnelModeTransform( - InetAddress localAddress, InetAddress remoteAddress) { - if (localAddress == null) { - throw new IllegalArgumentException("Local address may not be null or empty!"); - } - if (remoteAddress == null) { - throw new IllegalArgumentException("Remote address may not be null or empty!"); + @NonNull InetAddress sourceAddress, + @NonNull IpSecManager.SecurityParameterIndex spi) + throws IpSecManager.ResourceUnavailableException, + IpSecManager.SpiUnavailableException, IOException { + Preconditions.checkNotNull(sourceAddress); + Preconditions.checkNotNull(spi); + if (spi.getResourceId() == INVALID_RESOURCE_ID) { + throw new IllegalArgumentException("Invalid SecurityParameterIndex"); } - mConfig.setLocalAddress(localAddress.getHostAddress()); - mConfig.setRemoteAddress(remoteAddress.getHostAddress()); mConfig.setMode(MODE_TUNNEL); + mConfig.setSourceAddress(sourceAddress.getHostAddress()); + mConfig.setSpiResourceId(spi.getResourceId()); return new IpSecTransform(mContext, mConfig); } diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java index d6992aaede5f..287bdc88dd3e 100644 --- a/core/java/android/net/MacAddress.java +++ b/core/java/android/net/MacAddress.java @@ -17,6 +17,7 @@ package android.net; import android.annotation.IntDef; +import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; @@ -60,7 +61,7 @@ public final class MacAddress implements Parcelable { }) public @interface MacAddressType { } - /** Indicates a MAC address of unknown type. */ + /** @hide Indicates a MAC address of unknown type. */ public static final int TYPE_UNKNOWN = 0; /** Indicates a MAC address is a unicast address. */ public static final int TYPE_UNICAST = 1; @@ -92,7 +93,7 @@ public final class MacAddress implements Parcelable { * * @return the int constant representing the MAC address type of this MacAddress. */ - public @MacAddressType int addressType() { + public @MacAddressType int getAddressType() { if (equals(BROADCAST_ADDRESS)) { return TYPE_BROADCAST; } @@ -120,12 +121,12 @@ public final class MacAddress implements Parcelable { /** * @return a byte array representation of this MacAddress. */ - public byte[] toByteArray() { + public @NonNull byte[] toByteArray() { return byteAddrFromLongAddr(mAddr); } @Override - public String toString() { + public @NonNull String toString() { return stringAddrFromLongAddr(mAddr); } @@ -133,7 +134,7 @@ public final class MacAddress implements Parcelable { * @return a String representation of the OUI part of this MacAddress made of 3 hexadecimal * numbers in [0,ff] joined by ':' characters. */ - public String toOuiString() { + public @NonNull String toOuiString() { return String.format( "%02x:%02x:%02x", (mAddr >> 40) & 0xff, (mAddr >> 32) & 0xff, (mAddr >> 24) & 0xff); } @@ -197,7 +198,7 @@ public final class MacAddress implements Parcelable { if (!isMacAddress(addr)) { return TYPE_UNKNOWN; } - return MacAddress.fromBytes(addr).addressType(); + return MacAddress.fromBytes(addr).getAddressType(); } /** @@ -211,7 +212,7 @@ public final class MacAddress implements Parcelable { * * @hide */ - public static byte[] byteAddrFromStringAddr(String addr) { + public static @NonNull byte[] byteAddrFromStringAddr(String addr) { Preconditions.checkNotNull(addr); String[] parts = addr.split(":"); if (parts.length != ETHER_ADDR_LEN) { @@ -239,7 +240,7 @@ public final class MacAddress implements Parcelable { * * @hide */ - public static String stringAddrFromByteAddr(byte[] addr) { + public static @NonNull String stringAddrFromByteAddr(byte[] addr) { if (!isMacAddress(addr)) { return null; } @@ -291,7 +292,7 @@ public final class MacAddress implements Parcelable { // Internal conversion function equivalent to stringAddrFromByteAddr(byteAddrFromLongAddr(addr)) // that avoids the allocation of an intermediary byte[]. - private static String stringAddrFromLongAddr(long addr) { + private static @NonNull String stringAddrFromLongAddr(long addr) { return String.format("%02x:%02x:%02x:%02x:%02x:%02x", (addr >> 40) & 0xff, (addr >> 32) & 0xff, @@ -310,7 +311,7 @@ public final class MacAddress implements Parcelable { * @return the MacAddress corresponding to the given String representation. * @throws IllegalArgumentException if the given String is not a valid representation. */ - public static MacAddress fromString(String addr) { + public static @NonNull MacAddress fromString(@NonNull String addr) { return new MacAddress(longAddrFromStringAddr(addr)); } @@ -322,7 +323,7 @@ public final class MacAddress implements Parcelable { * @return the MacAddress corresponding to the given byte array representation. * @throws IllegalArgumentException if the given byte array is not a valid representation. */ - public static MacAddress fromBytes(byte[] addr) { + public static @NonNull MacAddress fromBytes(@NonNull byte[] addr) { return new MacAddress(longAddrFromByteAddr(addr)); } @@ -336,7 +337,7 @@ public final class MacAddress implements Parcelable { * * @hide */ - public static MacAddress createRandomUnicastAddress() { + public static @NonNull MacAddress createRandomUnicastAddress() { return createRandomUnicastAddress(BASE_GOOGLE_MAC, new Random()); } @@ -352,7 +353,7 @@ public final class MacAddress implements Parcelable { * * @hide */ - public static MacAddress createRandomUnicastAddress(MacAddress base, Random r) { + public static @NonNull MacAddress createRandomUnicastAddress(MacAddress base, Random r) { long addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong()); addr = addr | LOCALLY_ASSIGNED_MASK; addr = addr & ~MULTICAST_MASK; diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java index 1a3ce9124b48..5df168d20586 100644 --- a/core/java/android/net/Network.java +++ b/core/java/android/net/Network.java @@ -357,13 +357,13 @@ public class Network implements Parcelable { // Multiple Provisioning Domains API recommendations, as made by the // IETF mif working group. // - // The HANDLE_MAGIC value MUST be kept in sync with the corresponding + // The handleMagic value MUST be kept in sync with the corresponding // value in the native/android/net.c NDK implementation. if (netId == 0) { return 0L; // make this zero condition obvious for debugging } - final long HANDLE_MAGIC = 0xfacade; - return (((long) netId) << 32) | HANDLE_MAGIC; + final long handleMagic = 0xcafed00dL; + return (((long) netId) << 32) | handleMagic; } // implement the Parcelable interface diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 81c49a339d53..9ef26a9f5a5b 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -29,7 +29,6 @@ import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.os.RemoteException; import android.os.UserHandle; -import android.telephony.SubscriptionPlan; import android.util.DebugUtils; import android.util.Pair; @@ -329,7 +328,7 @@ public class NetworkPolicyManager { * to access network when the device is idle or in battery saver mode. Otherwise, false. */ public static boolean isProcStateAllowedWhileIdleOrPowerSaveMode(int procState) { - return procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; + return procState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; } /** @@ -337,7 +336,7 @@ public class NetworkPolicyManager { * to access network when the device is in data saver mode. Otherwise, false. */ public static boolean isProcStateAllowedWhileOnRestrictBackground(int procState) { - return procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; + return procState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; } public static String resolveNetworkId(WifiConfiguration config) { diff --git a/core/java/android/net/metrics/WakeupStats.java b/core/java/android/net/metrics/WakeupStats.java index 7277ba34534b..bb36536fe2ce 100644 --- a/core/java/android/net/metrics/WakeupStats.java +++ b/core/java/android/net/metrics/WakeupStats.java @@ -80,7 +80,7 @@ public class WakeupStats { break; } - switch (ev.dstHwAddr.addressType()) { + switch (ev.dstHwAddr.getAddressType()) { case MacAddress.TYPE_UNICAST: l2UnicastCount++; break; diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 21974843613f..7b0c153f5234 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -238,6 +238,20 @@ public class UserManager { public static final String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display"; /** + * Specifies if a user is disallowed from changing screen off timeout. + * + * <p>The default value is <code>false</code>. + * + * <p>This user restriction has no effect on managed profiles. + * <p>Key for user restrictions. + * <p>Type: Boolean + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_CONFIG_SCREEN_TIMEOUT = "no_config_screen_timeout"; + + /** * Specifies if a user is disallowed from enabling the * "Unknown Sources" setting, that allows installation of apps from unknown sources. * The default value is <code>false</code>. diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index 070b8c1b0008..839a8bf42b10 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -394,4 +394,32 @@ public final class StorageVolume implements Parcelable { parcel.writeString(mFsUuid); parcel.writeString(mState); } + + /** {@hide} */ + public static final class ScopedAccessProviderContract { + + private ScopedAccessProviderContract() { + throw new UnsupportedOperationException("contains constants only"); + } + + public static final String AUTHORITY = "com.android.documentsui.scopedAccess"; + + public static final String TABLE_PACKAGES = "packages"; + public static final String TABLE_PERMISSIONS = "permissions"; + + public static final String COL_PACKAGE = "package_name"; + public static final String COL_VOLUME_UUID = "volume_uuid"; + public static final String COL_DIRECTORY = "directory"; + public static final String COL_GRANTED = "granted"; + + public static final String[] TABLE_PACKAGES_COLUMNS = new String[] { COL_PACKAGE }; + public static final String[] TABLE_PERMISSIONS_COLUMNS = + new String[] { COL_PACKAGE, COL_VOLUME_UUID, COL_DIRECTORY, COL_GRANTED }; + + public static final int TABLE_PACKAGES_COL_PACKAGE = 0; + public static final int TABLE_PERMISSIONS_COL_PACKAGE = 0; + public static final int TABLE_PERMISSIONS_COL_VOLUME_UUID = 1; + public static final int TABLE_PERMISSIONS_COL_DIRECTORY = 2; + public static final int TABLE_PERMISSIONS_COL_GRANTED = 3; + } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 850aedd517a9..24e56c0598b2 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6830,7 +6830,7 @@ public final class Settings { * @hide */ public static final int SHOW_ROTATION_SUGGESTIONS_DEFAULT = - SHOW_ROTATION_SUGGESTIONS_DISABLED; + SHOW_ROTATION_SUGGESTIONS_ENABLED; /** * Read only list of the service components that the current user has explicitly allowed to @@ -9852,6 +9852,15 @@ public final class Settings { public static final String FORCED_APP_STANDBY_ENABLED = "forced_app_standby_enabled"; /** + * Whether or not to enable Forced App Standby on small battery devices. + * Type: int (0 for false, 1 for true) + * Default: 0 + * @hide + */ + public static final String FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED + = "forced_app_standby_for_small_battery_enabled"; + + /** * Whether or not Network Watchlist feature is enabled. * Type: int (0 for false, 1 for true) * Default: 0 @@ -11300,6 +11309,13 @@ public final class Settings { */ public static final String ZRAM_ENABLED = "zram_enabled"; + + /** + * Whether smart replies in notifications are enabled. + * @hide + */ + public static final String ENABLE_SMART_REPLIES_IN_NOTIFICATIONS = + "enable_smart_replies_in_notifications"; } /** diff --git a/core/java/android/security/keystore/RecoveryData.aidl b/core/java/android/security/keystore/KeychainProtectionParameter.aidl index 4200de1637a8..1e2c365d4e69 100644 --- a/core/java/android/security/keystore/RecoveryData.aidl +++ b/core/java/android/security/keystore/KeychainProtectionParameter.aidl @@ -17,4 +17,4 @@ package android.security.keystore; /* @hide */ -parcelable RecoveryData; +parcelable KeychainProtectionParameter; diff --git a/core/java/android/security/keystore/RecoveryMetadata.java b/core/java/android/security/keystore/KeychainProtectionParameter.java index 3f0945557a5f..2319ef5e3129 100644 --- a/core/java/android/security/keystore/RecoveryMetadata.java +++ b/core/java/android/security/keystore/KeychainProtectionParameter.java @@ -28,12 +28,26 @@ import java.lang.annotation.RetentionPolicy; import java.util.Arrays; /** - * Helper class with data necessary to recover Keystore on a new device. - * It defines UI shown to the user and a way to derive a cryptographic key from user output. + * A {@link KeychainSnapshot} is protected with a key derived from the user's lock screen. This + * class wraps all the data necessary to derive the same key on a recovering device: + * + * <ul> + * <li>UI parameters for the user's lock screen - so that if e.g., the user was using a pattern, + * the recovering device can display the pattern UI to the user when asking them to enter + * the lock screen from their previous device. + * <li>The algorithm used to derive a key from the user's lock screen, e.g. SHA-256 with a salt. + * </ul> + * + * <p>As such, this data is sent along with the {@link KeychainSnapshot} when syncing the current + * version of the keychain. + * + * <p>For now, the recoverable keychain only supports a single layer of protection, which is the + * user's lock screen. In the future, the keychain will support multiple layers of protection + * (e.g. an additional keychain password, along with the lock screen). * * @hide */ -public final class RecoveryMetadata implements Parcelable { +public final class KeychainProtectionParameter implements Parcelable { /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_LOCKSCREEN, TYPE_CUSTOM_PASSWORD}) @@ -88,7 +102,7 @@ public final class RecoveryMetadata implements Parcelable { * @link {#clearSecret} to overwrite its value in memory. * @hide */ - public RecoveryMetadata(@UserSecretType int userSecretType, + public KeychainProtectionParameter(@UserSecretType int userSecretType, @LockScreenUiFormat int lockScreenUiFormat, @NonNull KeyDerivationParams keyDerivationParams, @NonNull byte[] secret) { @@ -98,7 +112,7 @@ public final class RecoveryMetadata implements Parcelable { mSecret = Preconditions.checkNotNull(secret); } - private RecoveryMetadata() { + private KeychainProtectionParameter() { } @@ -141,10 +155,10 @@ public final class RecoveryMetadata implements Parcelable { } /** - * Builder for creating {@link RecoveryMetadata}. + * Builder for creating {@link KeychainProtectionParameter}. */ public static class Builder { - private RecoveryMetadata mInstance = new RecoveryMetadata(); + private KeychainProtectionParameter mInstance = new KeychainProtectionParameter(); /** * Sets user secret type. @@ -198,14 +212,14 @@ public final class RecoveryMetadata implements Parcelable { /** - * Creates a new {@link RecoveryMetadata} instance. + * Creates a new {@link KeychainProtectionParameter} instance. * The instance will include default values, if {@link setSecret} * or {@link setUserSecretType} were not called. * * @return new instance * @throws NullPointerException if some required fields were not set. */ - public @NonNull RecoveryMetadata build() { + @NonNull public KeychainProtectionParameter build() { if (mInstance.mUserSecretType == null) { mInstance.mUserSecretType = TYPE_LOCKSCREEN; } @@ -235,14 +249,14 @@ public final class RecoveryMetadata implements Parcelable { Arrays.fill(mSecret, (byte) 0); } - public static final Parcelable.Creator<RecoveryMetadata> CREATOR = - new Parcelable.Creator<RecoveryMetadata>() { - public RecoveryMetadata createFromParcel(Parcel in) { - return new RecoveryMetadata(in); + public static final Parcelable.Creator<KeychainProtectionParameter> CREATOR = + new Parcelable.Creator<KeychainProtectionParameter>() { + public KeychainProtectionParameter createFromParcel(Parcel in) { + return new KeychainProtectionParameter(in); } - public RecoveryMetadata[] newArray(int length) { - return new RecoveryMetadata[length]; + public KeychainProtectionParameter[] newArray(int length) { + return new KeychainProtectionParameter[length]; } }; @@ -260,7 +274,7 @@ public final class RecoveryMetadata implements Parcelable { /** * @hide */ - protected RecoveryMetadata(Parcel in) { + protected KeychainProtectionParameter(Parcel in) { mUserSecretType = in.readInt(); mLockScreenUiFormat = in.readInt(); mKeyDerivationParams = in.readTypedObject(KeyDerivationParams.CREATOR); diff --git a/core/java/android/security/keystore/RecoveryMetadata.aidl b/core/java/android/security/keystore/KeychainSnapshot.aidl index 8e342b485044..b35713f329d6 100644 --- a/core/java/android/security/keystore/RecoveryMetadata.aidl +++ b/core/java/android/security/keystore/KeychainSnapshot.aidl @@ -17,4 +17,4 @@ package android.security.keystore; /* @hide */ -parcelable RecoveryMetadata; +parcelable KeychainSnapshot; diff --git a/core/java/android/security/keystore/RecoveryData.java b/core/java/android/security/keystore/KeychainSnapshot.java index 897aa18a0e18..71a808a41f57 100644 --- a/core/java/android/security/keystore/RecoveryData.java +++ b/core/java/android/security/keystore/KeychainSnapshot.java @@ -25,42 +25,48 @@ import com.android.internal.util.Preconditions; import java.util.List; /** - * Helper class which returns data necessary to recover keys. - * Contains + * A snapshot of a version of the keystore. Two events can trigger the generation of a new snapshot: * * <ul> - * <li>Snapshot version. - * <li>Recovery metadata with UI and key derivation parameters. - * <li>List of application keys encrypted by recovery key. - * <li>Encrypted recovery key. + * <li>The user's lock screen changes. (A key derived from the user's lock screen is used to + * protected the keychain, which is why this forces a new snapshot.) + * <li>A key is added to or removed from the recoverable keychain. * </ul> * + * <p>The snapshot data is also encrypted with the remote trusted hardware's public key, so even + * the recovery agent itself should not be able to decipher the data. The recovery agent sends an + * instance of this to the remote trusted hardware whenever a new snapshot is generated. During a + * recovery flow, the recovery agent retrieves a snapshot from the remote trusted hardware. It then + * sends it to the framework, where it is decrypted using the user's lock screen from their previous + * device. + * * @hide */ -public final class RecoveryData implements Parcelable { +public final class KeychainSnapshot implements Parcelable { private int mSnapshotVersion; - private List<RecoveryMetadata> mRecoveryMetadata; - private List<EntryRecoveryData> mEntryRecoveryData; + private List<KeychainProtectionParameter> mKeychainProtectionParams; + private List<WrappedApplicationKey> mEntryRecoveryData; private byte[] mEncryptedRecoveryKeyBlob; /** * @hide * Deprecated, consider using builder. */ - public RecoveryData( + public KeychainSnapshot( int snapshotVersion, - @NonNull List<RecoveryMetadata> recoveryMetadata, - @NonNull List<EntryRecoveryData> entryRecoveryData, + @NonNull List<KeychainProtectionParameter> keychainProtectionParams, + @NonNull List<WrappedApplicationKey> wrappedApplicationKeys, @NonNull byte[] encryptedRecoveryKeyBlob) { mSnapshotVersion = snapshotVersion; - mRecoveryMetadata = - Preconditions.checkCollectionElementsNotNull(recoveryMetadata, "recoveryMetadata"); - mEntryRecoveryData = Preconditions.checkCollectionElementsNotNull(entryRecoveryData, - "entryRecoveryData"); + mKeychainProtectionParams = + Preconditions.checkCollectionElementsNotNull(keychainProtectionParams, + "keychainProtectionParams"); + mEntryRecoveryData = Preconditions.checkCollectionElementsNotNull(wrappedApplicationKeys, + "wrappedApplicationKeys"); mEncryptedRecoveryKeyBlob = Preconditions.checkNotNull(encryptedRecoveryKeyBlob); } - private RecoveryData() { + private KeychainSnapshot() { } @@ -75,15 +81,15 @@ public final class RecoveryData implements Parcelable { /** * UI and key derivation parameters. Note that combination of secrets may be used. */ - public @NonNull List<RecoveryMetadata> getRecoveryMetadata() { - return mRecoveryMetadata; + public @NonNull List<KeychainProtectionParameter> getKeychainProtectionParams() { + return mKeychainProtectionParams; } /** * List of application keys, with key material encrypted by * the recovery key ({@link #getEncryptedRecoveryKeyBlob}). */ - public @NonNull List<EntryRecoveryData> getEntryRecoveryData() { + public @NonNull List<WrappedApplicationKey> getWrappedApplicationKeys() { return mEntryRecoveryData; } @@ -94,22 +100,22 @@ public final class RecoveryData implements Parcelable { return mEncryptedRecoveryKeyBlob; } - public static final Parcelable.Creator<RecoveryData> CREATOR = - new Parcelable.Creator<RecoveryData>() { - public RecoveryData createFromParcel(Parcel in) { - return new RecoveryData(in); + public static final Parcelable.Creator<KeychainSnapshot> CREATOR = + new Parcelable.Creator<KeychainSnapshot>() { + public KeychainSnapshot createFromParcel(Parcel in) { + return new KeychainSnapshot(in); } - public RecoveryData[] newArray(int length) { - return new RecoveryData[length]; + public KeychainSnapshot[] newArray(int length) { + return new KeychainSnapshot[length]; } }; /** - * Builder for creating {@link RecoveryData}. + * Builder for creating {@link KeychainSnapshot}. */ public static class Builder { - private RecoveryData mInstance = new RecoveryData(); + private KeychainSnapshot mInstance = new KeychainSnapshot(); /** * Snapshot version for given account. @@ -128,8 +134,9 @@ public final class RecoveryData implements Parcelable { * @param recoveryMetadata The UI and key derivation parameters * @return This builder. */ - public Builder setRecoveryMetadata(@NonNull List<RecoveryMetadata> recoveryMetadata) { - mInstance.mRecoveryMetadata = recoveryMetadata; + public Builder setKeychainProtectionParams( + @NonNull List<KeychainProtectionParameter> recoveryMetadata) { + mInstance.mKeychainProtectionParams = recoveryMetadata; return this; } @@ -139,7 +146,7 @@ public final class RecoveryData implements Parcelable { * @param entryRecoveryData List of application keys * @return This builder. */ - public Builder setEntryRecoveryData(List<EntryRecoveryData> entryRecoveryData) { + public Builder setWrappedApplicationKeys(List<WrappedApplicationKey> entryRecoveryData) { mInstance.mEntryRecoveryData = entryRecoveryData; return this; } @@ -157,13 +164,13 @@ public final class RecoveryData implements Parcelable { /** - * Creates a new {@link RecoveryData} instance. + * Creates a new {@link KeychainSnapshot} instance. * * @return new instance * @throws NullPointerException if some required fields were not set. */ - public @NonNull RecoveryData build() { - Preconditions.checkCollectionElementsNotNull(mInstance.mRecoveryMetadata, + @NonNull public KeychainSnapshot build() { + Preconditions.checkCollectionElementsNotNull(mInstance.mKeychainProtectionParams, "recoveryMetadata"); Preconditions.checkCollectionElementsNotNull(mInstance.mEntryRecoveryData, "entryRecoveryData"); @@ -178,7 +185,7 @@ public final class RecoveryData implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { out.writeInt(mSnapshotVersion); - out.writeTypedList(mRecoveryMetadata); + out.writeTypedList(mKeychainProtectionParams); out.writeByteArray(mEncryptedRecoveryKeyBlob); out.writeTypedList(mEntryRecoveryData); } @@ -186,11 +193,11 @@ public final class RecoveryData implements Parcelable { /** * @hide */ - protected RecoveryData(Parcel in) { + protected KeychainSnapshot(Parcel in) { mSnapshotVersion = in.readInt(); - mRecoveryMetadata = in.createTypedArrayList(RecoveryMetadata.CREATOR); + mKeychainProtectionParams = in.createTypedArrayList(KeychainProtectionParameter.CREATOR); mEncryptedRecoveryKeyBlob = in.createByteArray(); - mEntryRecoveryData = in.createTypedArrayList(EntryRecoveryData.CREATOR); + mEntryRecoveryData = in.createTypedArrayList(WrappedApplicationKey.CREATOR); } @Override diff --git a/core/java/android/security/keystore/RecoveryManager.java b/core/java/android/security/keystore/RecoveryManager.java index 99bd284e4d80..bddf3e849182 100644 --- a/core/java/android/security/keystore/RecoveryManager.java +++ b/core/java/android/security/keystore/RecoveryManager.java @@ -99,11 +99,11 @@ public class RecoveryManager { * @return Data necessary to recover keystore. * @hide */ - public @NonNull RecoveryData getRecoveryData(@NonNull byte[] account) + @NonNull public KeychainSnapshot getRecoveryData(@NonNull byte[] account) throws RecoveryManagerException { try { - RecoveryData recoveryData = mBinder.getRecoveryData(account); - return recoveryData; + KeychainSnapshot keychainSnapshot = mBinder.getRecoveryData(account); + return keychainSnapshot; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { @@ -136,7 +136,7 @@ public class RecoveryManager { * version. Version zero is used, if no snapshots were created for the account. * * @return Map from recovery agent accounts to snapshot versions. - * @see RecoveryData#getSnapshotVersion + * @see KeychainSnapshot#getSnapshotVersion * @hide */ public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions() @@ -156,7 +156,7 @@ public class RecoveryManager { /** * Server parameters used to generate new recovery key blobs. This value will be included in - * {@code RecoveryData.getEncryptedRecoveryKeyBlob()}. The same value must be included + * {@code KeychainSnapshot.getEncryptedRecoveryKeyBlob()}. The same value must be included * in vaultParams {@link #startRecoverySession} * * @param serverParams included in recovery key blob. @@ -230,11 +230,11 @@ public class RecoveryManager { * Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them * is necessary to recover data. * - * @param secretTypes {@link RecoveryMetadata#TYPE_LOCKSCREEN} or {@link - * RecoveryMetadata#TYPE_CUSTOM_PASSWORD} + * @param secretTypes {@link KeychainProtectionParameter#TYPE_LOCKSCREEN} or {@link + * KeychainProtectionParameter#TYPE_CUSTOM_PASSWORD} */ public void setRecoverySecretTypes( - @NonNull @RecoveryMetadata.UserSecretType int[] secretTypes) + @NonNull @KeychainProtectionParameter.UserSecretType int[] secretTypes) throws RecoveryManagerException { try { mBinder.setRecoverySecretTypes(secretTypes); @@ -247,12 +247,12 @@ public class RecoveryManager { /** * Defines a set of secret types used for end-to-end keystore encryption. Knowing all of them is - * necessary to generate RecoveryData. + * necessary to generate KeychainSnapshot. * * @return list of recovery secret types - * @see RecoveryData + * @see KeychainSnapshot */ - public @NonNull @RecoveryMetadata.UserSecretType int[] getRecoverySecretTypes() + @NonNull public @KeychainProtectionParameter.UserSecretType int[] getRecoverySecretTypes() throws RecoveryManagerException { try { return mBinder.getRecoverySecretTypes(); @@ -271,7 +271,8 @@ public class RecoveryManager { * @return list of recovery secret types * @hide */ - public @NonNull @RecoveryMetadata.UserSecretType int[] getPendingRecoverySecretTypes() + @NonNull + public @KeychainProtectionParameter.UserSecretType int[] getPendingRecoverySecretTypes() throws RecoveryManagerException { try { return mBinder.getPendingRecoverySecretTypes(); @@ -285,14 +286,14 @@ public class RecoveryManager { /** * Method notifies KeyStore that a user-generated secret is available. This method generates a * symmetric session key which a trusted remote device can use to return a recovery key. Caller - * should use {@link RecoveryMetadata#clearSecret} to override the secret value in + * should use {@link KeychainProtectionParameter#clearSecret} to override the secret value in * memory. * * @param recoverySecret user generated secret together with parameters necessary to regenerate * it on a new device. * @hide */ - public void recoverySecretAvailable(@NonNull RecoveryMetadata recoverySecret) + public void recoverySecretAvailable(@NonNull KeychainProtectionParameter recoverySecret) throws RecoveryManagerException { try { mBinder.recoverySecretAvailable(recoverySecret); @@ -326,7 +327,7 @@ public class RecoveryManager { @NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams, @NonNull byte[] vaultChallenge, - @NonNull List<RecoveryMetadata> secrets) + @NonNull List<KeychainProtectionParameter> secrets) throws RecoveryManagerException { try { byte[] recoveryClaim = @@ -352,13 +353,13 @@ public class RecoveryManager { * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session. * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob * and session. KeyStore only uses package names from the application info in {@link - * EntryRecoveryData}. Caller is responsibility to perform certificates check. + * WrappedApplicationKey}. Caller is responsibility to perform certificates check. * @return Map from alias to raw key material. */ public Map<String, byte[]> recoverKeys( @NonNull String sessionId, @NonNull byte[] recoveryKeyBlob, - @NonNull List<EntryRecoveryData> applicationKeys) + @NonNull List<WrappedApplicationKey> applicationKeys) throws RecoveryManagerException { try { return (Map<String, byte[]>) mBinder.recoverKeys( diff --git a/core/java/android/security/keystore/EntryRecoveryData.aidl b/core/java/android/security/keystore/WrappedApplicationKey.aidl index c6c20e337bc8..a6294fee03b3 100644 --- a/core/java/android/security/keystore/EntryRecoveryData.aidl +++ b/core/java/android/security/keystore/WrappedApplicationKey.aidl @@ -17,4 +17,4 @@ package android.security.keystore; /* @hide */ -parcelable EntryRecoveryData; +parcelable WrappedApplicationKey; diff --git a/core/java/android/security/keystore/EntryRecoveryData.java b/core/java/android/security/keystore/WrappedApplicationKey.java index aaca3fe8b2cf..522bb9557b8d 100644 --- a/core/java/android/security/keystore/EntryRecoveryData.java +++ b/core/java/android/security/keystore/WrappedApplicationKey.java @@ -35,16 +35,16 @@ import com.android.internal.util.Preconditions; * * @hide */ -public final class EntryRecoveryData implements Parcelable { +public final class WrappedApplicationKey implements Parcelable { private String mAlias; // The only supported format is AES-256 symmetric key. private byte[] mEncryptedKeyMaterial; /** - * Builder for creating {@link EntryRecoveryData}. + * Builder for creating {@link WrappedApplicationKey}. */ public static class Builder { - private EntryRecoveryData mInstance = new EntryRecoveryData(); + private WrappedApplicationKey mInstance = new WrappedApplicationKey(); /** * Sets Application-specific alias of the key. @@ -70,19 +70,19 @@ public final class EntryRecoveryData implements Parcelable { } /** - * Creates a new {@link EntryRecoveryData} instance. + * Creates a new {@link WrappedApplicationKey} instance. * * @return new instance * @throws NullPointerException if some required fields were not set. */ - public @NonNull EntryRecoveryData build() { + @NonNull public WrappedApplicationKey build() { Preconditions.checkNotNull(mInstance.mAlias); Preconditions.checkNotNull(mInstance.mEncryptedKeyMaterial); return mInstance; } } - private EntryRecoveryData() { + private WrappedApplicationKey() { } @@ -90,7 +90,7 @@ public final class EntryRecoveryData implements Parcelable { * Deprecated - consider using Builder. * @hide */ - public EntryRecoveryData(@NonNull String alias, @NonNull byte[] encryptedKeyMaterial) { + public WrappedApplicationKey(@NonNull String alias, @NonNull byte[] encryptedKeyMaterial) { mAlias = Preconditions.checkNotNull(alias); mEncryptedKeyMaterial = Preconditions.checkNotNull(encryptedKeyMaterial); } @@ -109,14 +109,14 @@ public final class EntryRecoveryData implements Parcelable { return mEncryptedKeyMaterial; } - public static final Parcelable.Creator<EntryRecoveryData> CREATOR = - new Parcelable.Creator<EntryRecoveryData>() { - public EntryRecoveryData createFromParcel(Parcel in) { - return new EntryRecoveryData(in); + public static final Parcelable.Creator<WrappedApplicationKey> CREATOR = + new Parcelable.Creator<WrappedApplicationKey>() { + public WrappedApplicationKey createFromParcel(Parcel in) { + return new WrappedApplicationKey(in); } - public EntryRecoveryData[] newArray(int length) { - return new EntryRecoveryData[length]; + public WrappedApplicationKey[] newArray(int length) { + return new WrappedApplicationKey[length]; } }; @@ -132,7 +132,7 @@ public final class EntryRecoveryData implements Parcelable { /** * @hide */ - protected EntryRecoveryData(Parcel in) { + protected WrappedApplicationKey(Parcel in) { mAlias = in.readString(); mEncryptedKeyMaterial = in.createByteArray(); } diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 20cd90679195..b7b2b2de35e5 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -55,6 +55,7 @@ import android.util.Log; import android.widget.RemoteViews; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import java.lang.annotation.Retention; @@ -1543,7 +1544,11 @@ public abstract class NotificationListenerService extends Service { return mShowBadge; } - private void populate(String key, int rank, boolean matchesInterruptionFilter, + /** + * @hide + */ + @VisibleForTesting + public void populate(String key, int rank, boolean matchesInterruptionFilter, int visibilityOverride, int suppressedVisualEffects, int importance, CharSequence explanation, String overrideGroupKey, NotificationChannel channel, ArrayList<String> overridePeople, diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index bf4b6ac5194f..aa97b2aba749 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1917,10 +1917,10 @@ public abstract class Layout { private static float measurePara(TextPaint paint, CharSequence text, int start, int end, TextDirectionHeuristic textDir) { - MeasuredText mt = null; + MeasuredParagraph mt = null; TextLine tl = TextLine.obtain(); try { - mt = MeasuredText.buildForBidi(text, start, end, textDir, mt); + mt = MeasuredParagraph.buildForBidi(text, start, end, textDir, mt); final char[] chars = mt.getChars(); final int len = chars.length; final Directions directions = mt.getDirections(0, len); diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java new file mode 100644 index 000000000000..c93e0365d58d --- /dev/null +++ b/core/java/android/text/MeasuredParagraph.java @@ -0,0 +1,677 @@ +/* + * 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 android.text; + +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Paint; +import android.text.AutoGrowArray.ByteArray; +import android.text.AutoGrowArray.FloatArray; +import android.text.AutoGrowArray.IntArray; +import android.text.Layout.Directions; +import android.text.style.MetricAffectingSpan; +import android.text.style.ReplacementSpan; +import android.util.Pools.SynchronizedPool; + +import dalvik.annotation.optimization.CriticalNative; + +import libcore.util.NativeAllocationRegistry; + +import java.util.Arrays; + +/** + * MeasuredParagraph provides text information for rendering purpose. + * + * The first motivation of this class is identify the text directions and retrieving individual + * character widths. However retrieving character widths is slower than identifying text directions. + * Thus, this class provides several builder methods for specific purposes. + * + * - buildForBidi: + * Compute only text directions. + * - buildForMeasurement: + * Compute text direction and all character widths. + * - buildForStaticLayout: + * This is bit special. StaticLayout also needs to know text direction and character widths for + * line breaking, but all things are done in native code. Similarly, text measurement is done + * in native code. So instead of storing result to Java array, this keeps the result in native + * code since there is no good reason to move the results to Java layer. + * + * In addition to the character widths, some additional information is computed for each purposes, + * e.g. whole text length for measurement or font metrics for static layout. + * + * MeasuredParagraph is NOT a thread safe object. + * @hide + */ +public class MeasuredParagraph { + private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC'; + + private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( + MeasuredParagraph.class.getClassLoader(), nGetReleaseFunc(), 1024); + + private MeasuredParagraph() {} // Use build static functions instead. + + private static final SynchronizedPool<MeasuredParagraph> sPool = new SynchronizedPool<>(1); + + private static @NonNull MeasuredParagraph obtain() { // Use build static functions instead. + final MeasuredParagraph mt = sPool.acquire(); + return mt != null ? mt : new MeasuredParagraph(); + } + + /** + * Recycle the MeasuredParagraph. + * + * Do not call any methods after you call this method. + */ + public void recycle() { + release(); + sPool.release(this); + } + + // The casted original text. + // + // This may be null if the passed text is not a Spanned. + private @Nullable Spanned mSpanned; + + // The start offset of the target range in the original text (mSpanned); + private @IntRange(from = 0) int mTextStart; + + // The length of the target range in the original text. + private @IntRange(from = 0) int mTextLength; + + // The copied character buffer for measuring text. + // + // The length of this array is mTextLength. + private @Nullable char[] mCopiedBuffer; + + // The whole paragraph direction. + private @Layout.Direction int mParaDir; + + // True if the text is LTR direction and doesn't contain any bidi characters. + private boolean mLtrWithoutBidi; + + // The bidi level for individual characters. + // + // This is empty if mLtrWithoutBidi is true. + private @NonNull ByteArray mLevels = new ByteArray(); + + // The whole width of the text. + // See getWholeWidth comments. + private @FloatRange(from = 0.0f) float mWholeWidth; + + // Individual characters' widths. + // See getWidths comments. + private @Nullable FloatArray mWidths = new FloatArray(); + + // The span end positions. + // See getSpanEndCache comments. + private @Nullable IntArray mSpanEndCache = new IntArray(4); + + // The font metrics. + // See getFontMetrics comments. + private @Nullable IntArray mFontMetrics = new IntArray(4 * 4); + + // The native MeasuredParagraph. + // See getNativePtr comments. + // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead. + private /* Maybe Zero */ long mNativePtr = 0; + private @Nullable Runnable mNativeObjectCleaner; + + // Associate the native object to this Java object. + private void bindNativeObject(/* Non Zero*/ long nativePtr) { + mNativePtr = nativePtr; + mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr); + } + + // Decouple the native object from this Java object and release the native object. + private void unbindNativeObject() { + if (mNativePtr != 0) { + mNativeObjectCleaner.run(); + mNativePtr = 0; + } + } + + // Following two objects are for avoiding object allocation. + private @NonNull TextPaint mCachedPaint = new TextPaint(); + private @Nullable Paint.FontMetricsInt mCachedFm; + + /** + * Releases internal buffers. + */ + public void release() { + reset(); + mLevels.clearWithReleasingLargeArray(); + mWidths.clearWithReleasingLargeArray(); + mFontMetrics.clearWithReleasingLargeArray(); + mSpanEndCache.clearWithReleasingLargeArray(); + } + + /** + * Resets the internal state for starting new text. + */ + private void reset() { + mSpanned = null; + mCopiedBuffer = null; + mWholeWidth = 0; + mLevels.clear(); + mWidths.clear(); + mFontMetrics.clear(); + mSpanEndCache.clear(); + unbindNativeObject(); + } + + /** + * Returns the characters to be measured. + * + * This is always available. + */ + public @NonNull char[] getChars() { + return mCopiedBuffer; + } + + /** + * Returns the paragraph direction. + * + * This is always available. + */ + public @Layout.Direction int getParagraphDir() { + return mParaDir; + } + + /** + * Returns the directions. + * + * This is always available. + */ + public Directions getDirections(@IntRange(from = 0) int start, // inclusive + @IntRange(from = 0) int end) { // exclusive + if (mLtrWithoutBidi) { + return Layout.DIRS_ALL_LEFT_TO_RIGHT; + } + + final int length = end - start; + return AndroidBidi.directions(mParaDir, mLevels.getRawArray(), start, mCopiedBuffer, start, + length); + } + + /** + * Returns the whole text width. + * + * This is available only if the MeasureText is computed with computeForMeasurement. + * Returns 0 in other cases. + */ + public @FloatRange(from = 0.0f) float getWholeWidth() { + return mWholeWidth; + } + + /** + * Returns the individual character's width. + * + * This is available only if the MeasureText is computed with computeForMeasurement. + * Returns empty array in other cases. + */ + public @NonNull FloatArray getWidths() { + return mWidths; + } + + /** + * Returns the MetricsAffectingSpan end indices. + * + * If the input text is not a spanned string, this has one value that is the length of the text. + * + * This is available only if the MeasureText is computed with computeForStaticLayout. + * Returns empty array in other cases. + */ + public @NonNull IntArray getSpanEndCache() { + return mSpanEndCache; + } + + /** + * Returns the int array which holds FontMetrics. + * + * This array holds the repeat of top, bottom, ascent, descent of font metrics value. + * + * This is available only if the MeasureText is computed with computeForStaticLayout. + * Returns empty array in other cases. + */ + public @NonNull IntArray getFontMetrics() { + return mFontMetrics; + } + + /** + * Returns the native ptr of the MeasuredParagraph. + * + * This is available only if the MeasureText is computed with computeForStaticLayout. + * Returns 0 in other cases. + */ + public /* Maybe Zero */ long getNativePtr() { + return mNativePtr; + } + + /** + * Generates new MeasuredParagraph for Bidi computation. + * + * If recycle is null, this returns new instance. If recycle is not null, this fills computed + * result to recycle and returns recycle. + * + * @param text the character sequence to be measured + * @param start the inclusive start offset of the target region in the text + * @param end the exclusive end offset of the target region in the text + * @param textDir the text direction + * @param recycle pass existing MeasuredParagraph if you want to recycle it. + * + * @return measured text + */ + public static @NonNull MeasuredParagraph buildForBidi(@NonNull CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @NonNull TextDirectionHeuristic textDir, + @Nullable MeasuredParagraph recycle) { + final MeasuredParagraph mt = recycle == null ? obtain() : recycle; + mt.resetAndAnalyzeBidi(text, start, end, textDir); + return mt; + } + + /** + * Generates new MeasuredParagraph for measuring texts. + * + * If recycle is null, this returns new instance. If recycle is not null, this fills computed + * result to recycle and returns recycle. + * + * @param paint the paint to be used for rendering the text. + * @param text the character sequence to be measured + * @param start the inclusive start offset of the target region in the text + * @param end the exclusive end offset of the target region in the text + * @param textDir the text direction + * @param recycle pass existing MeasuredParagraph if you want to recycle it. + * + * @return measured text + */ + public static @NonNull MeasuredParagraph buildForMeasurement(@NonNull TextPaint paint, + @NonNull CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @NonNull TextDirectionHeuristic textDir, + @Nullable MeasuredParagraph recycle) { + final MeasuredParagraph mt = recycle == null ? obtain() : recycle; + mt.resetAndAnalyzeBidi(text, start, end, textDir); + + mt.mWidths.resize(mt.mTextLength); + if (mt.mTextLength == 0) { + return mt; + } + + if (mt.mSpanned == null) { + // No style change by MetricsAffectingSpan. Just measure all text. + mt.applyMetricsAffectingSpan( + paint, null /* spans */, start, end, 0 /* native static layout ptr */); + } else { + // There may be a MetricsAffectingSpan. Split into span transitions and apply styles. + int spanEnd; + for (int spanStart = start; spanStart < end; spanStart = spanEnd) { + spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class); + MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, + MetricAffectingSpan.class); + spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); + mt.applyMetricsAffectingSpan( + paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */); + } + } + return mt; + } + + /** + * Generates new MeasuredParagraph for StaticLayout. + * + * If recycle is null, this returns new instance. If recycle is not null, this fills computed + * result to recycle and returns recycle. + * + * @param paint the paint to be used for rendering the text. + * @param text the character sequence to be measured + * @param start the inclusive start offset of the target region in the text + * @param end the exclusive end offset of the target region in the text + * @param textDir the text direction + * @param recycle pass existing MeasuredParagraph if you want to recycle it. + * + * @return measured text + */ + public static @NonNull MeasuredParagraph buildForStaticLayout( + @NonNull TextPaint paint, + @NonNull CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @NonNull TextDirectionHeuristic textDir, + @Nullable MeasuredParagraph recycle) { + final MeasuredParagraph mt = recycle == null ? obtain() : recycle; + mt.resetAndAnalyzeBidi(text, start, end, textDir); + if (mt.mTextLength == 0) { + // Need to build empty native measured text for StaticLayout. + // TODO: Stop creating empty measured text for empty lines. + long nativeBuilderPtr = nInitBuilder(); + try { + mt.bindNativeObject( + nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer)); + } finally { + nFreeBuilder(nativeBuilderPtr); + } + return mt; + } + + long nativeBuilderPtr = nInitBuilder(); + try { + if (mt.mSpanned == null) { + // No style change by MetricsAffectingSpan. Just measure all text. + mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr); + mt.mSpanEndCache.append(end); + } else { + // There may be a MetricsAffectingSpan. Split into span transitions and apply + // styles. + int spanEnd; + for (int spanStart = start; spanStart < end; spanStart = spanEnd) { + spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, + MetricAffectingSpan.class); + MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, + MetricAffectingSpan.class); + spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, + MetricAffectingSpan.class); + mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd, + nativeBuilderPtr); + mt.mSpanEndCache.append(spanEnd); + } + } + mt.bindNativeObject(nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer)); + } finally { + nFreeBuilder(nativeBuilderPtr); + } + + return mt; + } + + /** + * Reset internal state and analyzes text for bidirectional runs. + * + * @param text the character sequence to be measured + * @param start the inclusive start offset of the target region in the text + * @param end the exclusive end offset of the target region in the text + * @param textDir the text direction + */ + private void resetAndAnalyzeBidi(@NonNull CharSequence text, + @IntRange(from = 0) int start, // inclusive + @IntRange(from = 0) int end, // exclusive + @NonNull TextDirectionHeuristic textDir) { + reset(); + mSpanned = text instanceof Spanned ? (Spanned) text : null; + mTextStart = start; + mTextLength = end - start; + + if (mCopiedBuffer == null || mCopiedBuffer.length != mTextLength) { + mCopiedBuffer = new char[mTextLength]; + } + TextUtils.getChars(text, start, end, mCopiedBuffer, 0); + + // Replace characters associated with ReplacementSpan to U+FFFC. + if (mSpanned != null) { + ReplacementSpan[] spans = mSpanned.getSpans(start, end, ReplacementSpan.class); + + for (int i = 0; i < spans.length; i++) { + int startInPara = mSpanned.getSpanStart(spans[i]) - start; + int endInPara = mSpanned.getSpanEnd(spans[i]) - start; + // The span interval may be larger and must be restricted to [start, end) + if (startInPara < 0) startInPara = 0; + if (endInPara > mTextLength) endInPara = mTextLength; + Arrays.fill(mCopiedBuffer, startInPara, endInPara, OBJECT_REPLACEMENT_CHARACTER); + } + } + + if ((textDir == TextDirectionHeuristics.LTR + || textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR + || textDir == TextDirectionHeuristics.ANYRTL_LTR) + && TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) { + mLevels.clear(); + mParaDir = Layout.DIR_LEFT_TO_RIGHT; + mLtrWithoutBidi = true; + } else { + final int bidiRequest; + if (textDir == TextDirectionHeuristics.LTR) { + bidiRequest = Layout.DIR_REQUEST_LTR; + } else if (textDir == TextDirectionHeuristics.RTL) { + bidiRequest = Layout.DIR_REQUEST_RTL; + } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) { + bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR; + } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) { + bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL; + } else { + final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength); + bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR; + } + mLevels.resize(mTextLength); + mParaDir = AndroidBidi.bidi(bidiRequest, mCopiedBuffer, mLevels.getRawArray()); + mLtrWithoutBidi = false; + } + } + + private void applyReplacementRun(@NonNull ReplacementSpan replacement, + @IntRange(from = 0) int start, // inclusive, in copied buffer + @IntRange(from = 0) int end, // exclusive, in copied buffer + /* Maybe Zero */ long nativeBuilderPtr) { + // Use original text. Shouldn't matter. + // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for + // backward compatibility? or Should we initialize them for getFontMetricsInt? + final float width = replacement.getSize( + mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm); + if (nativeBuilderPtr == 0) { + // Assigns all width to the first character. This is the same behavior as minikin. + mWidths.set(start, width); + if (end > start + 1) { + Arrays.fill(mWidths.getRawArray(), start + 1, end, 0.0f); + } + mWholeWidth += width; + } else { + nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, + width); + } + } + + private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer + @IntRange(from = 0) int end, // exclusive, in copied buffer + /* Maybe Zero */ long nativeBuilderPtr) { + if (nativeBuilderPtr != 0) { + mCachedPaint.getFontMetricsInt(mCachedFm); + } + + if (mLtrWithoutBidi) { + // If the whole text is LTR direction, just apply whole region. + if (nativeBuilderPtr == 0) { + mWholeWidth += mCachedPaint.getTextRunAdvances( + mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */, + mWidths.getRawArray(), start); + } else { + nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, + false /* isRtl */); + } + } else { + // If there is multiple bidi levels, split into individual bidi level and apply style. + byte level = mLevels.get(start); + // Note that the empty text or empty range won't reach this method. + // Safe to search from start + 1. + for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) { + if (levelEnd == end || mLevels.get(levelEnd) != level) { // transition point + final boolean isRtl = (level & 0x1) != 0; + if (nativeBuilderPtr == 0) { + final int levelLength = levelEnd - levelStart; + mWholeWidth += mCachedPaint.getTextRunAdvances( + mCopiedBuffer, levelStart, levelLength, levelStart, levelLength, + isRtl, mWidths.getRawArray(), levelStart); + } else { + nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart, + levelEnd, isRtl); + } + if (levelEnd == end) { + break; + } + levelStart = levelEnd; + level = mLevels.get(levelEnd); + } + } + } + } + + private void applyMetricsAffectingSpan( + @NonNull TextPaint paint, + @Nullable MetricAffectingSpan[] spans, + @IntRange(from = 0) int start, // inclusive, in original text buffer + @IntRange(from = 0) int end, // exclusive, in original text buffer + /* Maybe Zero */ long nativeBuilderPtr) { + mCachedPaint.set(paint); + // XXX paint should not have a baseline shift, but... + mCachedPaint.baselineShift = 0; + + final boolean needFontMetrics = nativeBuilderPtr != 0; + + if (needFontMetrics && mCachedFm == null) { + mCachedFm = new Paint.FontMetricsInt(); + } + + ReplacementSpan replacement = null; + if (spans != null) { + for (int i = 0; i < spans.length; i++) { + MetricAffectingSpan span = spans[i]; + if (span instanceof ReplacementSpan) { + // The last ReplacementSpan is effective for backward compatibility reasons. + replacement = (ReplacementSpan) span; + } else { + // TODO: No need to call updateMeasureState for ReplacementSpan as well? + span.updateMeasureState(mCachedPaint); + } + } + } + + final int startInCopiedBuffer = start - mTextStart; + final int endInCopiedBuffer = end - mTextStart; + + if (replacement != null) { + applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, + nativeBuilderPtr); + } else { + applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr); + } + + if (needFontMetrics) { + if (mCachedPaint.baselineShift < 0) { + mCachedFm.ascent += mCachedPaint.baselineShift; + mCachedFm.top += mCachedPaint.baselineShift; + } else { + mCachedFm.descent += mCachedPaint.baselineShift; + mCachedFm.bottom += mCachedPaint.baselineShift; + } + + mFontMetrics.append(mCachedFm.top); + mFontMetrics.append(mCachedFm.bottom); + mFontMetrics.append(mCachedFm.ascent); + mFontMetrics.append(mCachedFm.descent); + } + } + + /** + * Returns the maximum index that the accumulated width not exceeds the width. + * + * If forward=false is passed, returns the minimum index from the end instead. + * + * This only works if the MeasuredParagraph is computed with computeForMeasurement. + * Undefined behavior in other case. + */ + @IntRange(from = 0) int breakText(int limit, boolean forwards, float width) { + float[] w = mWidths.getRawArray(); + if (forwards) { + int i = 0; + while (i < limit) { + width -= w[i]; + if (width < 0.0f) break; + i++; + } + while (i > 0 && mCopiedBuffer[i - 1] == ' ') i--; + return i; + } else { + int i = limit - 1; + while (i >= 0) { + width -= w[i]; + if (width < 0.0f) break; + i--; + } + while (i < limit - 1 && (mCopiedBuffer[i + 1] == ' ' || w[i + 1] == 0.0f)) { + i++; + } + return limit - i - 1; + } + } + + /** + * Returns the length of the substring. + * + * This only works if the MeasuredParagraph is computed with computeForMeasurement. + * Undefined behavior in other case. + */ + @FloatRange(from = 0.0f) float measure(int start, int limit) { + float width = 0; + float[] w = mWidths.getRawArray(); + for (int i = start; i < limit; ++i) { + width += w[i]; + } + return width; + } + + private static native /* Non Zero */ long nInitBuilder(); + + /** + * Apply style to make native measured text. + * + * @param nativeBuilderPtr The native MeasuredParagraph builder pointer. + * @param paintPtr The native paint pointer to be applied. + * @param start The start offset in the copied buffer. + * @param end The end offset in the copied buffer. + * @param isRtl True if the text is RTL. + */ + private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr, + /* Non Zero */ long paintPtr, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + boolean isRtl); + + /** + * Apply ReplacementRun to make native measured text. + * + * @param nativeBuilderPtr The native MeasuredParagraph builder pointer. + * @param paintPtr The native paint pointer to be applied. + * @param start The start offset in the copied buffer. + * @param end The end offset in the copied buffer. + * @param width The width of the replacement. + */ + private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr, + /* Non Zero */ long paintPtr, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @FloatRange(from = 0) float width); + + private static native long nBuildNativeMeasuredParagraph(/* Non Zero */ long nativeBuilderPtr, + @NonNull char[] text); + + private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr); + + @CriticalNative + private static native /* Non Zero */ long nGetReleaseFunc(); +} diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java index 14d6f9e8a9ef..2c30360b81bc 100644 --- a/core/java/android/text/MeasuredText.java +++ b/core/java/android/text/MeasuredText.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * 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. @@ -16,661 +16,255 @@ package android.text; -import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; -import android.annotation.Nullable; -import android.graphics.Paint; -import android.text.AutoGrowArray.ByteArray; -import android.text.AutoGrowArray.FloatArray; -import android.text.AutoGrowArray.IntArray; -import android.text.Layout.Directions; -import android.text.style.MetricAffectingSpan; -import android.text.style.ReplacementSpan; -import android.util.Pools.SynchronizedPool; +import android.util.IntArray; -import dalvik.annotation.optimization.CriticalNative; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.Preconditions; -import libcore.util.NativeAllocationRegistry; - -import java.util.Arrays; +import java.util.ArrayList; /** - * MeasuredText provides text information for rendering purpose. - * - * The first motivation of this class is identify the text directions and retrieving individual - * character widths. However retrieving character widths is slower than identifying text directions. - * Thus, this class provides several builder methods for specific purposes. - * - * - buildForBidi: - * Compute only text directions. - * - buildForMeasurement: - * Compute text direction and all character widths. - * - buildForStaticLayout: - * This is bit special. StaticLayout also needs to know text direction and character widths for - * line breaking, but all things are done in native code. Similarly, text measurement is done - * in native code. So instead of storing result to Java array, this keeps the result in native - * code since there is no good reason to move the results to Java layer. - * - * In addition to the character widths, some additional information is computed for each purposes, - * e.g. whole text length for measurement or font metrics for static layout. - * - * MeasuredText is NOT a thread safe object. - * @hide + * A text which has already been measured. */ -public class MeasuredText { - private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC'; - - private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( - MeasuredText.class.getClassLoader(), nGetReleaseFunc(), 1024); - - private MeasuredText() {} // Use build static functions instead. - - private static final SynchronizedPool<MeasuredText> sPool = new SynchronizedPool<>(1); - - private static @NonNull MeasuredText obtain() { // Use build static functions instead. - final MeasuredText mt = sPool.acquire(); - return mt != null ? mt : new MeasuredText(); - } - - /** - * Recycle the MeasuredText. - * - * Do not call any methods after you call this method. - */ - public void recycle() { - release(); - sPool.release(this); - } +public class MeasuredText implements Spanned { + private static final char LINE_FEED = '\n'; - // The casted original text. - // - // This may be null if the passed text is not a Spanned. - private @Nullable Spanned mSpanned; - - // The start offset of the target range in the original text (mSpanned); - private @IntRange(from = 0) int mTextStart; + // The original text. + private final @NonNull CharSequence mText; - // The length of the target range in the original text. - private @IntRange(from = 0) int mTextLength; - - // The copied character buffer for measuring text. - // - // The length of this array is mTextLength. - private @Nullable char[] mCopiedBuffer; + // The inclusive start offset of the measuring target. + private final @IntRange(from = 0) int mStart; - // The whole paragraph direction. - private @Layout.Direction int mParaDir; + // The exclusive end offset of the measuring target. + private final @IntRange(from = 0) int mEnd; - // True if the text is LTR direction and doesn't contain any bidi characters. - private boolean mLtrWithoutBidi; + // The TextPaint used for measurement. + private final @NonNull TextPaint mPaint; - // The bidi level for individual characters. - // - // This is empty if mLtrWithoutBidi is true. - private @NonNull ByteArray mLevels = new ByteArray(); - - // The whole width of the text. - // See getWholeWidth comments. - private @FloatRange(from = 0.0f) float mWholeWidth; - - // Individual characters' widths. - // See getWidths comments. - private @Nullable FloatArray mWidths = new FloatArray(); - - // The span end positions. - // See getSpanEndCache comments. - private @Nullable IntArray mSpanEndCache = new IntArray(4); - - // The font metrics. - // See getFontMetrics comments. - private @Nullable IntArray mFontMetrics = new IntArray(4 * 4); - - // The native MeasuredText. - // See getNativePtr comments. - // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead. - private /* Maybe Zero */ long mNativePtr = 0; - private @Nullable Runnable mNativeObjectCleaner; - - // Associate the native object to this Java object. - private void bindNativeObject(/* Non Zero*/ long nativePtr) { - mNativePtr = nativePtr; - mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr); - } + // The requested text direction. + private final @NonNull TextDirectionHeuristic mTextDir; - // Decouple the native object from this Java object and release the native object. - private void unbindNativeObject() { - if (mNativePtr != 0) { - mNativeObjectCleaner.run(); - mNativePtr = 0; - } - } + // The measured paragraph texts. + private final @NonNull MeasuredParagraph[] mMeasuredParagraphs; - // Following two objects are for avoiding object allocation. - private @NonNull TextPaint mCachedPaint = new TextPaint(); - private @Nullable Paint.FontMetricsInt mCachedFm; - - /** - * Releases internal buffers. - */ - public void release() { - reset(); - mLevels.clearWithReleasingLargeArray(); - mWidths.clearWithReleasingLargeArray(); - mFontMetrics.clearWithReleasingLargeArray(); - mSpanEndCache.clearWithReleasingLargeArray(); - } - - /** - * Resets the internal state for starting new text. - */ - private void reset() { - mSpanned = null; - mCopiedBuffer = null; - mWholeWidth = 0; - mLevels.clear(); - mWidths.clear(); - mFontMetrics.clear(); - mSpanEndCache.clear(); - unbindNativeObject(); - } + // The sorted paragraph end offsets. + private final @NonNull int[] mParagraphBreakPoints; /** - * Returns the characters to be measured. + * Build MeasuredText from the text. * - * This is always available. + * @param text The text to be measured. + * @param paint The paint to be used for drawing. + * @param textDir The text direction. + * @return The measured text. */ - public @NonNull char[] getChars() { - return mCopiedBuffer; + public static @NonNull MeasuredText build(@NonNull CharSequence text, + @NonNull TextPaint paint, + @NonNull TextDirectionHeuristic textDir) { + return MeasuredText.build(text, paint, textDir, 0, text.length()); } /** - * Returns the paragraph direction. + * Build MeasuredText from the specific range of the text.. * - * This is always available. + * @param text The text to be measured. + * @param paint The paint to be used for drawing. + * @param textDir The text direction. + * @param start The inclusive start offset of the text. + * @param end The exclusive start offset of the text. + * @return The measured text. */ - public @Layout.Direction int getParagraphDir() { - return mParaDir; - } + public static @NonNull MeasuredText build(@NonNull CharSequence text, + @NonNull TextPaint paint, + @NonNull TextDirectionHeuristic textDir, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end) { + Preconditions.checkNotNull(text); + Preconditions.checkNotNull(paint); + Preconditions.checkNotNull(textDir); + Preconditions.checkArgumentInRange(start, 0, text.length(), "start"); + Preconditions.checkArgumentInRange(end, 0, text.length(), "end"); + + final IntArray paragraphEnds = new IntArray(); + final ArrayList<MeasuredParagraph> measuredTexts = new ArrayList<>(); + + int paraEnd = 0; + for (int paraStart = start; paraStart < end; paraStart = paraEnd) { + paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end); + if (paraEnd < 0) { + // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph end. + paraEnd = end; + } else { + paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph. + } - /** - * Returns the directions. - * - * This is always available. - */ - public Directions getDirections(@IntRange(from = 0) int start, // inclusive - @IntRange(from = 0) int end) { // exclusive - if (mLtrWithoutBidi) { - return Layout.DIRS_ALL_LEFT_TO_RIGHT; + paragraphEnds.add(paraEnd); + measuredTexts.add(MeasuredParagraph.buildForStaticLayout( + paint, text, paraStart, paraEnd, textDir, null /* no recycle */)); } - final int length = end - start; - return AndroidBidi.directions(mParaDir, mLevels.getRawArray(), start, mCopiedBuffer, start, - length); + return new MeasuredText(text, start, end, paint, textDir, + measuredTexts.toArray(new MeasuredParagraph[measuredTexts.size()]), + paragraphEnds.toArray()); } - /** - * Returns the whole text width. - * - * This is available only if the MeasureText is computed with computeForMeasurement. - * Returns 0 in other cases. - */ - public @FloatRange(from = 0.0f) float getWholeWidth() { - return mWholeWidth; + // Use MeasuredText.build instead. + private MeasuredText(@NonNull CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @NonNull TextPaint paint, + @NonNull TextDirectionHeuristic textDir, + @NonNull MeasuredParagraph[] measuredTexts, + @NonNull int[] paragraphBreakPoints) { + mText = text; + mStart = start; + mEnd = end; + mPaint = paint; + mMeasuredParagraphs = measuredTexts; + mParagraphBreakPoints = paragraphBreakPoints; + mTextDir = textDir; } /** - * Returns the individual character's width. - * - * This is available only if the MeasureText is computed with computeForMeasurement. - * Returns empty array in other cases. + * Return the underlying text. */ - public @NonNull FloatArray getWidths() { - return mWidths; + public @NonNull CharSequence getText() { + return mText; } /** - * Returns the MetricsAffectingSpan end indices. - * - * If the input text is not a spanned string, this has one value that is the length of the text. - * - * This is available only if the MeasureText is computed with computeForStaticLayout. - * Returns empty array in other cases. + * Returns the inclusive start offset of measured region. */ - public @NonNull IntArray getSpanEndCache() { - return mSpanEndCache; + public @IntRange(from = 0) int getStart() { + return mStart; } /** - * Returns the int array which holds FontMetrics. - * - * This array holds the repeat of top, bottom, ascent, descent of font metrics value. - * - * This is available only if the MeasureText is computed with computeForStaticLayout. - * Returns empty array in other cases. + * Returns the exclusive end offset of measured region. */ - public @NonNull IntArray getFontMetrics() { - return mFontMetrics; + public @IntRange(from = 0) int getEnd() { + return mEnd; } /** - * Returns the native ptr of the MeasuredText. - * - * This is available only if the MeasureText is computed with computeForStaticLayout. - * Returns 0 in other cases. + * Returns the text direction associated with char sequence. */ - public /* Maybe Zero */ long getNativePtr() { - return mNativePtr; + public @NonNull TextDirectionHeuristic getTextDir() { + return mTextDir; } /** - * Generates new MeasuredText for Bidi computation. - * - * If recycle is null, this returns new instance. If recycle is not null, this fills computed - * result to recycle and returns recycle. - * - * @param text the character sequence to be measured - * @param start the inclusive start offset of the target region in the text - * @param end the exclusive end offset of the target region in the text - * @param textDir the text direction - * @param recycle pass existing MeasuredText if you want to recycle it. - * - * @return measured text + * Returns the paint used to measure this text. */ - public static @NonNull MeasuredText buildForBidi(@NonNull CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @NonNull TextDirectionHeuristic textDir, - @Nullable MeasuredText recycle) { - final MeasuredText mt = recycle == null ? obtain() : recycle; - mt.resetAndAnalyzeBidi(text, start, end, textDir); - return mt; + public @NonNull TextPaint getPaint() { + return mPaint; } /** - * Generates new MeasuredText for measuring texts. - * - * If recycle is null, this returns new instance. If recycle is not null, this fills computed - * result to recycle and returns recycle. - * - * @param paint the paint to be used for rendering the text. - * @param text the character sequence to be measured - * @param start the inclusive start offset of the target region in the text - * @param end the exclusive end offset of the target region in the text - * @param textDir the text direction - * @param recycle pass existing MeasuredText if you want to recycle it. - * - * @return measured text + * Returns the length of the paragraph of this text. */ - public static @NonNull MeasuredText buildForMeasurement(@NonNull TextPaint paint, - @NonNull CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @NonNull TextDirectionHeuristic textDir, - @Nullable MeasuredText recycle) { - final MeasuredText mt = recycle == null ? obtain() : recycle; - mt.resetAndAnalyzeBidi(text, start, end, textDir); - - mt.mWidths.resize(mt.mTextLength); - if (mt.mTextLength == 0) { - return mt; - } - - if (mt.mSpanned == null) { - // No style change by MetricsAffectingSpan. Just measure all text. - mt.applyMetricsAffectingSpan( - paint, null /* spans */, start, end, 0 /* native static layout ptr */); - } else { - // There may be a MetricsAffectingSpan. Split into span transitions and apply styles. - int spanEnd; - for (int spanStart = start; spanStart < end; spanStart = spanEnd) { - spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class); - MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, - MetricAffectingSpan.class); - spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); - mt.applyMetricsAffectingSpan( - paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */); - } - } - return mt; + public @IntRange(from = 0) int getParagraphCount() { + return mParagraphBreakPoints.length; } /** - * Generates new MeasuredText for StaticLayout. - * - * If recycle is null, this returns new instance. If recycle is not null, this fills computed - * result to recycle and returns recycle. - * - * @param paint the paint to be used for rendering the text. - * @param text the character sequence to be measured - * @param start the inclusive start offset of the target region in the text - * @param end the exclusive end offset of the target region in the text - * @param textDir the text direction - * @param recycle pass existing MeasuredText if you want to recycle it. - * - * @return measured text + * Returns the paragraph start offset of the text. */ - public static @NonNull MeasuredText buildForStaticLayout( - @NonNull TextPaint paint, - @NonNull CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @NonNull TextDirectionHeuristic textDir, - @Nullable MeasuredText recycle) { - final MeasuredText mt = recycle == null ? obtain() : recycle; - mt.resetAndAnalyzeBidi(text, start, end, textDir); - if (mt.mTextLength == 0) { - // Need to build empty native measured text for StaticLayout. - // TODO: Stop creating empty measured text for empty lines. - long nativeBuilderPtr = nInitBuilder(); - try { - mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer)); - } finally { - nFreeBuilder(nativeBuilderPtr); - } - return mt; - } - - long nativeBuilderPtr = nInitBuilder(); - try { - if (mt.mSpanned == null) { - // No style change by MetricsAffectingSpan. Just measure all text. - mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr); - mt.mSpanEndCache.append(end); - } else { - // There may be a MetricsAffectingSpan. Split into span transitions and apply - // styles. - int spanEnd; - for (int spanStart = start; spanStart < end; spanStart = spanEnd) { - spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, - MetricAffectingSpan.class); - MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, - MetricAffectingSpan.class); - spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, - MetricAffectingSpan.class); - mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd, - nativeBuilderPtr); - mt.mSpanEndCache.append(spanEnd); - } - } - mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer)); - } finally { - nFreeBuilder(nativeBuilderPtr); - } - - return mt; + public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) { + Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); + return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1]; } /** - * Reset internal state and analyzes text for bidirectional runs. - * - * @param text the character sequence to be measured - * @param start the inclusive start offset of the target region in the text - * @param end the exclusive end offset of the target region in the text - * @param textDir the text direction + * Returns the paragraph end offset of the text. */ - private void resetAndAnalyzeBidi(@NonNull CharSequence text, - @IntRange(from = 0) int start, // inclusive - @IntRange(from = 0) int end, // exclusive - @NonNull TextDirectionHeuristic textDir) { - reset(); - mSpanned = text instanceof Spanned ? (Spanned) text : null; - mTextStart = start; - mTextLength = end - start; - - if (mCopiedBuffer == null || mCopiedBuffer.length != mTextLength) { - mCopiedBuffer = new char[mTextLength]; - } - TextUtils.getChars(text, start, end, mCopiedBuffer, 0); - - // Replace characters associated with ReplacementSpan to U+FFFC. - if (mSpanned != null) { - ReplacementSpan[] spans = mSpanned.getSpans(start, end, ReplacementSpan.class); - - for (int i = 0; i < spans.length; i++) { - int startInPara = mSpanned.getSpanStart(spans[i]) - start; - int endInPara = mSpanned.getSpanEnd(spans[i]) - start; - // The span interval may be larger and must be restricted to [start, end) - if (startInPara < 0) startInPara = 0; - if (endInPara > mTextLength) endInPara = mTextLength; - Arrays.fill(mCopiedBuffer, startInPara, endInPara, OBJECT_REPLACEMENT_CHARACTER); - } - } - - if ((textDir == TextDirectionHeuristics.LTR || - textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR || - textDir == TextDirectionHeuristics.ANYRTL_LTR) && - TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) { - mLevels.clear(); - mParaDir = Layout.DIR_LEFT_TO_RIGHT; - mLtrWithoutBidi = true; - } else { - final int bidiRequest; - if (textDir == TextDirectionHeuristics.LTR) { - bidiRequest = Layout.DIR_REQUEST_LTR; - } else if (textDir == TextDirectionHeuristics.RTL) { - bidiRequest = Layout.DIR_REQUEST_RTL; - } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) { - bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR; - } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) { - bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL; - } else { - final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength); - bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR; - } - mLevels.resize(mTextLength); - mParaDir = AndroidBidi.bidi(bidiRequest, mCopiedBuffer, mLevels.getRawArray()); - mLtrWithoutBidi = false; - } + public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) { + Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); + return mParagraphBreakPoints[paraIndex]; } - private void applyReplacementRun(@NonNull ReplacementSpan replacement, - @IntRange(from = 0) int start, // inclusive, in copied buffer - @IntRange(from = 0) int end, // exclusive, in copied buffer - /* Maybe Zero */ long nativeBuilderPtr) { - // Use original text. Shouldn't matter. - // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for - // backward compatibility? or Should we initialize them for getFontMetricsInt? - final float width = replacement.getSize( - mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm); - if (nativeBuilderPtr == 0) { - // Assigns all width to the first character. This is the same behavior as minikin. - mWidths.set(start, width); - if (end > start + 1) { - Arrays.fill(mWidths.getRawArray(), start + 1, end, 0.0f); - } - mWholeWidth += width; - } else { - nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, - width); - } + /** @hide */ + public @NonNull MeasuredParagraph getMeasuredParagraph(@IntRange(from = 0) int paraIndex) { + return mMeasuredParagraphs[paraIndex]; } - private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer - @IntRange(from = 0) int end, // exclusive, in copied buffer - /* Maybe Zero */ long nativeBuilderPtr) { - if (nativeBuilderPtr != 0) { - mCachedPaint.getFontMetricsInt(mCachedFm); - } + /////////////////////////////////////////////////////////////////////////////////////////////// + // Spanned overrides + // + // Just proxy for underlying mText if appropriate. - if (mLtrWithoutBidi) { - // If the whole text is LTR direction, just apply whole region. - if (nativeBuilderPtr == 0) { - mWholeWidth += mCachedPaint.getTextRunAdvances( - mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */, - mWidths.getRawArray(), start); - } else { - nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, - false /* isRtl */); - } + @Override + public <T> T[] getSpans(int start, int end, Class<T> type) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpans(start, end, type); } else { - // If there is multiple bidi levels, split into individual bidi level and apply style. - byte level = mLevels.get(start); - // Note that the empty text or empty range won't reach this method. - // Safe to search from start + 1. - for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) { - if (levelEnd == end || mLevels.get(levelEnd) != level) { // transition point - final boolean isRtl = (level & 0x1) != 0; - if (nativeBuilderPtr == 0) { - final int levelLength = levelEnd - levelStart; - mWholeWidth += mCachedPaint.getTextRunAdvances( - mCopiedBuffer, levelStart, levelLength, levelStart, levelLength, - isRtl, mWidths.getRawArray(), levelStart); - } else { - nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart, - levelEnd, isRtl); - } - if (levelEnd == end) { - break; - } - levelStart = levelEnd; - level = mLevels.get(levelEnd); - } - } + return ArrayUtils.emptyArray(type); } } - private void applyMetricsAffectingSpan( - @NonNull TextPaint paint, - @Nullable MetricAffectingSpan[] spans, - @IntRange(from = 0) int start, // inclusive, in original text buffer - @IntRange(from = 0) int end, // exclusive, in original text buffer - /* Maybe Zero */ long nativeBuilderPtr) { - mCachedPaint.set(paint); - // XXX paint should not have a baseline shift, but... - mCachedPaint.baselineShift = 0; - - final boolean needFontMetrics = nativeBuilderPtr != 0; - - if (needFontMetrics && mCachedFm == null) { - mCachedFm = new Paint.FontMetricsInt(); - } - - ReplacementSpan replacement = null; - if (spans != null) { - for (int i = 0; i < spans.length; i++) { - MetricAffectingSpan span = spans[i]; - if (span instanceof ReplacementSpan) { - // The last ReplacementSpan is effective for backward compatibility reasons. - replacement = (ReplacementSpan) span; - } else { - // TODO: No need to call updateMeasureState for ReplacementSpan as well? - span.updateMeasureState(mCachedPaint); - } - } - } - - final int startInCopiedBuffer = start - mTextStart; - final int endInCopiedBuffer = end - mTextStart; - - if (replacement != null) { - applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, - nativeBuilderPtr); + @Override + public int getSpanStart(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanStart(tag); } else { - applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr); + return -1; } + } - if (needFontMetrics) { - if (mCachedPaint.baselineShift < 0) { - mCachedFm.ascent += mCachedPaint.baselineShift; - mCachedFm.top += mCachedPaint.baselineShift; - } else { - mCachedFm.descent += mCachedPaint.baselineShift; - mCachedFm.bottom += mCachedPaint.baselineShift; - } - - mFontMetrics.append(mCachedFm.top); - mFontMetrics.append(mCachedFm.bottom); - mFontMetrics.append(mCachedFm.ascent); - mFontMetrics.append(mCachedFm.descent); + @Override + public int getSpanEnd(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanEnd(tag); + } else { + return -1; } } - /** - * Returns the maximum index that the accumulated width not exceeds the width. - * - * If forward=false is passed, returns the minimum index from the end instead. - * - * This only works if the MeasuredText is computed with computeForMeasurement. - * Undefined behavior in other case. - */ - @IntRange(from = 0) int breakText(int limit, boolean forwards, float width) { - float[] w = mWidths.getRawArray(); - if (forwards) { - int i = 0; - while (i < limit) { - width -= w[i]; - if (width < 0.0f) break; - i++; - } - while (i > 0 && mCopiedBuffer[i - 1] == ' ') i--; - return i; + @Override + public int getSpanFlags(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanFlags(tag); } else { - int i = limit - 1; - while (i >= 0) { - width -= w[i]; - if (width < 0.0f) break; - i--; - } - while (i < limit - 1 && (mCopiedBuffer[i + 1] == ' ' || w[i + 1] == 0.0f)) { - i++; - } - return limit - i - 1; + return 0; } } - /** - * Returns the length of the substring. - * - * This only works if the MeasuredText is computed with computeForMeasurement. - * Undefined behavior in other case. - */ - @FloatRange(from = 0.0f) float measure(int start, int limit) { - float width = 0; - float[] w = mWidths.getRawArray(); - for (int i = start; i < limit; ++i) { - width += w[i]; + @Override + public int nextSpanTransition(int start, int limit, Class type) { + if (mText instanceof Spanned) { + return ((Spanned) mText).nextSpanTransition(start, limit, type); + } else { + return mText.length(); } - return width; } - private static native /* Non Zero */ long nInitBuilder(); - - /** - * Apply style to make native measured text. - * - * @param nativeBuilderPtr The native MeasuredText builder pointer. - * @param paintPtr The native paint pointer to be applied. - * @param start The start offset in the copied buffer. - * @param end The end offset in the copied buffer. - * @param isRtl True if the text is RTL. - */ - private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr, - /* Non Zero */ long paintPtr, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - boolean isRtl); + /////////////////////////////////////////////////////////////////////////////////////////////// + // CharSequence overrides. + // + // Just proxy for underlying mText. - /** - * Apply ReplacementRun to make native measured text. - * - * @param nativeBuilderPtr The native MeasuredText builder pointer. - * @param paintPtr The native paint pointer to be applied. - * @param start The start offset in the copied buffer. - * @param end The end offset in the copied buffer. - * @param width The width of the replacement. - */ - private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr, - /* Non Zero */ long paintPtr, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @FloatRange(from = 0) float width); + @Override + public int length() { + return mText.length(); + } - private static native long nBuildNativeMeasuredText(/* Non Zero */ long nativeBuilderPtr, - @NonNull char[] text); + @Override + public char charAt(int index) { + // TODO: Should this be index + mStart ? + return mText.charAt(index); + } - private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr); + @Override + public CharSequence subSequence(int start, int end) { + // TODO: return MeasuredText. + // TODO: Should this be index + mStart, end + mStart ? + return mText.subSequence(start, end); + } - @CriticalNative - private static native /* Non Zero */ long nGetReleaseFunc(); + @Override + public String toString() { + return mText.toString(); + } } diff --git a/core/java/android/text/PremeasuredText.java b/core/java/android/text/PremeasuredText.java deleted file mode 100644 index 465314dd21ac..000000000000 --- a/core/java/android/text/PremeasuredText.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * 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.text; - -import android.annotation.IntRange; -import android.annotation.NonNull; -import android.util.IntArray; - -import com.android.internal.util.ArrayUtils; -import com.android.internal.util.Preconditions; - -import java.util.ArrayList; - -/** - * A text which has already been measured. - * - * TODO: Rename to better name? e.g. MeasuredText, FrozenText etc. - */ -public class PremeasuredText implements Spanned { - private static final char LINE_FEED = '\n'; - - // The original text. - private final @NonNull CharSequence mText; - - // The inclusive start offset of the measuring target. - private final @IntRange(from = 0) int mStart; - - // The exclusive end offset of the measuring target. - private final @IntRange(from = 0) int mEnd; - - // The TextPaint used for measurement. - private final @NonNull TextPaint mPaint; - - // The requested text direction. - private final @NonNull TextDirectionHeuristic mTextDir; - - // The measured paragraph texts. - private final @NonNull MeasuredText[] mMeasuredTexts; - - // The sorted paragraph end offsets. - private final @NonNull int[] mParagraphBreakPoints; - - /** - * Build PremeasuredText from the text. - * - * @param text The text to be measured. - * @param paint The paint to be used for drawing. - * @param textDir The text direction. - * @return The measured text. - */ - public static @NonNull PremeasuredText build(@NonNull CharSequence text, - @NonNull TextPaint paint, - @NonNull TextDirectionHeuristic textDir) { - return PremeasuredText.build(text, paint, textDir, 0, text.length()); - } - - /** - * Build PremeasuredText from the specific range of the text.. - * - * @param text The text to be measured. - * @param paint The paint to be used for drawing. - * @param textDir The text direction. - * @param start The inclusive start offset of the text. - * @param end The exclusive start offset of the text. - * @return The measured text. - */ - public static @NonNull PremeasuredText build(@NonNull CharSequence text, - @NonNull TextPaint paint, - @NonNull TextDirectionHeuristic textDir, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end) { - Preconditions.checkNotNull(text); - Preconditions.checkNotNull(paint); - Preconditions.checkNotNull(textDir); - Preconditions.checkArgumentInRange(start, 0, text.length(), "start"); - Preconditions.checkArgumentInRange(end, 0, text.length(), "end"); - - final IntArray paragraphEnds = new IntArray(); - final ArrayList<MeasuredText> measuredTexts = new ArrayList<>(); - - int paraEnd = 0; - for (int paraStart = start; paraStart < end; paraStart = paraEnd) { - paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end); - if (paraEnd < 0) { - // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph end. - paraEnd = end; - } else { - paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph. - } - - paragraphEnds.add(paraEnd); - measuredTexts.add(MeasuredText.buildForStaticLayout( - paint, text, paraStart, paraEnd, textDir, null /* no recycle */)); - } - - return new PremeasuredText(text, start, end, paint, textDir, - measuredTexts.toArray(new MeasuredText[measuredTexts.size()]), - paragraphEnds.toArray()); - } - - // Use PremeasuredText.build instead. - private PremeasuredText(@NonNull CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @NonNull TextPaint paint, - @NonNull TextDirectionHeuristic textDir, - @NonNull MeasuredText[] measuredTexts, - @NonNull int[] paragraphBreakPoints) { - mText = text; - mStart = start; - mEnd = end; - mPaint = paint; - mMeasuredTexts = measuredTexts; - mParagraphBreakPoints = paragraphBreakPoints; - mTextDir = textDir; - } - - /** - * Return the underlying text. - */ - public @NonNull CharSequence getText() { - return mText; - } - - /** - * Returns the inclusive start offset of measured region. - */ - public @IntRange(from = 0) int getStart() { - return mStart; - } - - /** - * Returns the exclusive end offset of measured region. - */ - public @IntRange(from = 0) int getEnd() { - return mEnd; - } - - /** - * Returns the text direction associated with char sequence. - */ - public @NonNull TextDirectionHeuristic getTextDir() { - return mTextDir; - } - - /** - * Returns the paint used to measure this text. - */ - public @NonNull TextPaint getPaint() { - return mPaint; - } - - /** - * Returns the length of the paragraph of this text. - */ - public @IntRange(from = 0) int getParagraphCount() { - return mParagraphBreakPoints.length; - } - - /** - * Returns the paragraph start offset of the text. - */ - public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) { - Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); - return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1]; - } - - /** - * Returns the paragraph end offset of the text. - */ - public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) { - Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); - return mParagraphBreakPoints[paraIndex]; - } - - /** @hide */ - public @NonNull MeasuredText getMeasuredText(@IntRange(from = 0) int paraIndex) { - return mMeasuredTexts[paraIndex]; - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // Spanned overrides - // - // Just proxy for underlying mText if appropriate. - - @Override - public <T> T[] getSpans(int start, int end, Class<T> type) { - if (mText instanceof Spanned) { - return ((Spanned) mText).getSpans(start, end, type); - } else { - return ArrayUtils.emptyArray(type); - } - } - - @Override - public int getSpanStart(Object tag) { - if (mText instanceof Spanned) { - return ((Spanned) mText).getSpanStart(tag); - } else { - return -1; - } - } - - @Override - public int getSpanEnd(Object tag) { - if (mText instanceof Spanned) { - return ((Spanned) mText).getSpanEnd(tag); - } else { - return -1; - } - } - - @Override - public int getSpanFlags(Object tag) { - if (mText instanceof Spanned) { - return ((Spanned) mText).getSpanFlags(tag); - } else { - return 0; - } - } - - @Override - public int nextSpanTransition(int start, int limit, Class type) { - if (mText instanceof Spanned) { - return ((Spanned) mText).nextSpanTransition(start, limit, type); - } else { - return mText.length(); - } - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // CharSequence overrides. - // - // Just proxy for underlying mText. - - @Override - public int length() { - return mText.length(); - } - - @Override - public char charAt(int index) { - // TODO: Should this be index + mStart ? - return mText.charAt(index); - } - - @Override - public CharSequence subSequence(int start, int end) { - // TODO: return PremeasuredText. - // TODO: Should this be index + mStart, end + mStart ? - return mText.subSequence(start, end); - } - - @Override - public String toString() { - return mText.toString(); - } -} diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index d69b1190140f..36bec863e7ac 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -55,7 +55,8 @@ public class StaticLayout extends Layout { * First, call nInit to setup native line breaker object. Then, for each paragraph, do the * following: * - * - Create MeasuredText by MeasuredText.buildForStaticLayout which measures in native. + * - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in + * native. * - Run nComputeLineBreaks() to obtain line breaks for the paragraph. * * After all paragraphs, call finish() to release expensive buffers. @@ -650,34 +651,34 @@ public class StaticLayout extends Layout { b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE, indents, mLeftPaddings, mRightPaddings); - PremeasuredText premeasured = null; + MeasuredText measured = null; final Spanned spanned; - if (source instanceof PremeasuredText) { - premeasured = (PremeasuredText) source; + if (source instanceof MeasuredText) { + measured = (MeasuredText) source; - final CharSequence original = premeasured.getText(); + final CharSequence original = measured.getText(); spanned = (original instanceof Spanned) ? (Spanned) original : null; - if (bufStart != premeasured.getStart() || bufEnd != premeasured.getEnd()) { + if (bufStart != measured.getStart() || bufEnd != measured.getEnd()) { // The buffer position has changed. Re-measure here. - premeasured = PremeasuredText.build(original, paint, textDir, bufStart, bufEnd); + measured = MeasuredText.build(original, paint, textDir, bufStart, bufEnd); } else { - // We can use premeasured information. + // We can use measured information. - // Overwrite with the one when premeasured. + // Overwrite with the one when emeasured. // TODO: Give an option for developer not to overwrite and measure again here? - textDir = premeasured.getTextDir(); - paint = premeasured.getPaint(); + textDir = measured.getTextDir(); + paint = measured.getPaint(); } } else { - premeasured = PremeasuredText.build(source, paint, textDir, bufStart, bufEnd); + measured = MeasuredText.build(source, paint, textDir, bufStart, bufEnd); spanned = (source instanceof Spanned) ? (Spanned) source : null; } try { - for (int paraIndex = 0; paraIndex < premeasured.getParagraphCount(); paraIndex++) { - final int paraStart = premeasured.getParagraphStart(paraIndex); - final int paraEnd = premeasured.getParagraphEnd(paraIndex); + for (int paraIndex = 0; paraIndex < measured.getParagraphCount(); paraIndex++) { + final int paraStart = measured.getParagraphStart(paraIndex); + final int paraEnd = measured.getParagraphEnd(paraIndex); int firstWidthLineCount = 1; int firstWidth = outerWidth; @@ -743,10 +744,10 @@ public class StaticLayout extends Layout { } } - final MeasuredText measured = premeasured.getMeasuredText(paraIndex); - final char[] chs = measured.getChars(); - final int[] spanEndCache = measured.getSpanEndCache().getRawArray(); - final int[] fmCache = measured.getFontMetrics().getRawArray(); + final MeasuredParagraph measuredPara = measured.getMeasuredParagraph(paraIndex); + final char[] chs = measuredPara.getChars(); + final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray(); + final int[] fmCache = measuredPara.getFontMetrics().getRawArray(); // TODO: Stop keeping duplicated width copy in native and Java. widths.resize(chs.length); @@ -759,7 +760,7 @@ public class StaticLayout extends Layout { // Inputs chs, - measured.getNativePtr(), + measuredPara.getNativePtr(), paraEnd - paraStart, firstWidth, firstWidthLineCount, @@ -863,7 +864,7 @@ public class StaticLayout extends Layout { v = out(source, here, endPos, ascent, descent, fmTop, fmBottom, v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, - flags[breakIndex], needMultiply, measured, bufEnd, + flags[breakIndex], needMultiply, measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(), paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex], paint, moreChars); @@ -894,8 +895,8 @@ public class StaticLayout extends Layout { if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) && mLineCount < mMaximumVisibleLineCount) { - final MeasuredText measured = - MeasuredText.buildForBidi(source, bufEnd, bufEnd, textDir, null); + final MeasuredParagraph measuredPara = + MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null); paint.getFontMetricsInt(fm); v = out(source, bufEnd, bufEnd, fm.ascent, fm.descent, @@ -903,7 +904,7 @@ public class StaticLayout extends Layout { v, spacingmult, spacingadd, null, null, fm, 0, - needMultiply, measured, bufEnd, + needMultiply, measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, null, null, bufStart, ellipsize, ellipsizedWidth, 0, paint, false); @@ -918,7 +919,7 @@ public class StaticLayout extends Layout { private int out(final CharSequence text, final int start, final int end, int above, int below, int top, int bottom, int v, final float spacingmult, final float spacingadd, final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm, - final int flags, final boolean needMultiply, @NonNull final MeasuredText measured, + final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured, final int bufEnd, final boolean includePad, final boolean trackPad, final boolean addLastLineLineSpacing, final char[] chs, final float[] widths, final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth, diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 9c9fbf23832f..409e51438d20 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -1250,10 +1250,10 @@ public class TextUtils { @NonNull String ellipsis) { final int len = text.length(); - MeasuredText mt = null; - MeasuredText resultMt = null; + MeasuredParagraph mt = null; + MeasuredParagraph resultMt = null; try { - mt = MeasuredText.buildForMeasurement(paint, text, 0, text.length(), textDir, mt); + mt = MeasuredParagraph.buildForMeasurement(paint, text, 0, text.length(), textDir, mt); float width = mt.getWholeWidth(); if (width <= avail) { @@ -1332,7 +1332,7 @@ public class TextUtils { if (remaining == 0) { // All text is gone. textFits = true; } else { - resultMt = MeasuredText.buildForMeasurement( + resultMt = MeasuredParagraph.buildForMeasurement( paint, result, 0, result.length(), textDir, resultMt); width = resultMt.getWholeWidth(); if (width <= avail) { @@ -1479,11 +1479,11 @@ public class TextUtils { public static CharSequence commaEllipsize(CharSequence text, TextPaint p, float avail, String oneMore, String more, TextDirectionHeuristic textDir) { - MeasuredText mt = null; - MeasuredText tempMt = null; + MeasuredParagraph mt = null; + MeasuredParagraph tempMt = null; try { int len = text.length(); - mt = MeasuredText.buildForMeasurement(p, text, 0, len, textDir, mt); + mt = MeasuredParagraph.buildForMeasurement(p, text, 0, len, textDir, mt); final float width = mt.getWholeWidth(); if (width <= avail) { return text; @@ -1523,7 +1523,7 @@ public class TextUtils { } // XXX this is probably ok, but need to look at it more - tempMt = MeasuredText.buildForMeasurement( + tempMt = MeasuredParagraph.buildForMeasurement( p, format, 0, format.length(), textDir, tempMt); float moreWid = tempMt.getWholeWidth(); diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 62f971724673..e94f91a12905 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -38,13 +38,13 @@ public class FeatureFlagUtils { static { DEFAULT_FLAGS = new HashMap<>(); DEFAULT_FLAGS.put("device_info_v2", "true"); - DEFAULT_FLAGS.put("settings_search_v2", "true"); DEFAULT_FLAGS.put("settings_app_info_v2", "true"); DEFAULT_FLAGS.put("settings_connected_device_v2", "true"); DEFAULT_FLAGS.put("settings_battery_v2", "false"); DEFAULT_FLAGS.put("settings_battery_display_app_list", "false"); DEFAULT_FLAGS.put("settings_security_settings_v2", "true"); DEFAULT_FLAGS.put("settings_zone_picker_v2", "false"); + DEFAULT_FLAGS.put("settings_suggestion_ui_v2", "false"); } /** diff --git a/core/java/android/util/StatsManager.java b/core/java/android/util/StatsManager.java index c25b272c11b9..e0d085cfaa66 100644 --- a/core/java/android/util/StatsManager.java +++ b/core/java/android/util/StatsManager.java @@ -42,6 +42,16 @@ public final class StatsManager { } /** + * Temporary to prevent build failures. Will be deleted. + */ + @RequiresPermission(Manifest.permission.DUMP) + public boolean addConfiguration(String configKey, byte[] config, String pkg, String cls) { + // To prevent breakages of dependencies on old API. + + return false; + } + + /** * Clients can send a configuration and simultaneously registers the name of a broadcast * receiver that listens for when it should request data. * @@ -70,6 +80,15 @@ public final class StatsManager { } /** + * Temporary to prevent build failures. Will be deleted. + */ + @RequiresPermission(Manifest.permission.DUMP) + public boolean removeConfiguration(String configKey) { + // To prevent breakages of old dependencies. + return false; + } + + /** * Remove a configuration from logging. * * @param configKey Configuration key to remove. @@ -93,6 +112,16 @@ public final class StatsManager { } /** + * Temporary to prevent build failures. Will be deleted. + */ + @RequiresPermission(Manifest.permission.DUMP) + public byte[] getData(String configKey) { + // TODO: remove this and all other methods with String-based config keys. + // To prevent build breakages of dependencies. + return null; + } + + /** * Clients can request data with a binder call. This getter is destructive and also clears * the retrieved metrics from statsd memory. * diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index cc4a0b60dd0a..84ae20b92f3c 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -18,30 +18,18 @@ package android.util; import android.os.SystemClock; +import libcore.util.TimeZoneFinder; +import libcore.util.ZoneInfoDB; + import java.io.PrintWriter; import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Calendar; -import java.util.Collection; -import java.util.Collections; import java.util.Date; -import java.util.List; -import libcore.util.TimeZoneFinder; -import libcore.util.ZoneInfoDB; - /** * A class containing utility methods related to time zones. */ public class TimeUtils { /** @hide */ public TimeUtils() {} - private static final boolean DBG = false; - private static final String TAG = "TimeUtils"; - - /** Cached results of getTimeZonesWithUniqueOffsets */ - private static final Object sLastUniqueLockObj = new Object(); - private static List<String> sLastUniqueZoneOffsets = null; - private static String sLastUniqueCountry = null; - /** {@hide} */ private static SimpleDateFormat sLoggingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @@ -76,86 +64,6 @@ public class TimeUtils { } /** - * Returns an immutable list of unique time zone IDs for the country. - * - * @param country to find - * @return unmodifiable list of unique time zones, maybe empty but never null. - * @hide - */ - public static List<String> getTimeZoneIdsWithUniqueOffsets(String country) { - synchronized(sLastUniqueLockObj) { - if ((country != null) && country.equals(sLastUniqueCountry)) { - if (DBG) { - Log.d(TAG, "getTimeZonesWithUniqueOffsets(" + - country + "): return cached version"); - } - return sLastUniqueZoneOffsets; - } - } - - Collection<android.icu.util.TimeZone> zones = getIcuTimeZones(country); - ArrayList<android.icu.util.TimeZone> uniqueTimeZones = new ArrayList<>(); - for (android.icu.util.TimeZone zone : zones) { - // See if we already have this offset, - // Using slow but space efficient and these are small. - boolean found = false; - for (int i = 0; i < uniqueTimeZones.size(); i++) { - if (uniqueTimeZones.get(i).getRawOffset() == zone.getRawOffset()) { - found = true; - break; - } - } - if (!found) { - if (DBG) { - Log.d(TAG, "getTimeZonesWithUniqueOffsets: add unique offset=" + - zone.getRawOffset() + " zone.getID=" + zone.getID()); - } - uniqueTimeZones.add(zone); - } - } - - synchronized(sLastUniqueLockObj) { - // Cache the last result - sLastUniqueZoneOffsets = extractZoneIds(uniqueTimeZones); - sLastUniqueCountry = country; - - return sLastUniqueZoneOffsets; - } - } - - private static List<String> extractZoneIds(List<android.icu.util.TimeZone> timeZones) { - List<String> ids = new ArrayList<>(timeZones.size()); - for (android.icu.util.TimeZone timeZone : timeZones) { - ids.add(timeZone.getID()); - } - return Collections.unmodifiableList(ids); - } - - /** - * Returns an immutable list of frozen ICU time zones for the country. - * - * @param countryIso is a two character country code. - * @return TimeZone list, maybe empty but never null. - * @hide - */ - private static List<android.icu.util.TimeZone> getIcuTimeZones(String countryIso) { - if (countryIso == null) { - if (DBG) Log.d(TAG, "getIcuTimeZones(null): return empty list"); - return Collections.emptyList(); - } - List<android.icu.util.TimeZone> timeZones = - TimeZoneFinder.getInstance().lookupTimeZonesByCountry(countryIso); - if (timeZones == null) { - if (DBG) { - Log.d(TAG, "getIcuTimeZones(" + countryIso - + "): returned null, converting to empty list"); - } - return Collections.emptyList(); - } - return timeZones; - } - - /** * Returns a String indicating the version of the time zone database currently * in use. The format of the string is dependent on the underlying time zone * database implementation, but will typically contain the year in which the database diff --git a/core/java/android/view/IRemoteAnimationFinishedCallback.aidl b/core/java/android/view/IRemoteAnimationFinishedCallback.aidl new file mode 100644 index 000000000000..ae58b226ec03 --- /dev/null +++ b/core/java/android/view/IRemoteAnimationFinishedCallback.aidl @@ -0,0 +1,27 @@ +/* + * 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 android.view; + +/** + * Interface to be invoked by the controlling process when a remote animation has finished. + * + * @see IRemoteAnimationRunner + * {@hide} + */ +interface IRemoteAnimationFinishedCallback { + void onAnimationFinished(); +} diff --git a/core/java/android/view/IRemoteAnimationRunner.aidl b/core/java/android/view/IRemoteAnimationRunner.aidl new file mode 100644 index 000000000000..1350ebf10a4f --- /dev/null +++ b/core/java/android/view/IRemoteAnimationRunner.aidl @@ -0,0 +1,44 @@ +/* + * 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 android.view; + +import android.view.RemoteAnimationTarget; +import android.view.IRemoteAnimationFinishedCallback; + +/** + * Interface that is used to callback from window manager to the process that runs a remote + * animation to start or cancel it. + * + * {@hide} + */ +oneway interface IRemoteAnimationRunner { + + /** + * Called when the process needs to start the remote animation. + * + * @param apps The list of apps to animate. + * @param finishedCallback The callback to invoke when the animation is finished. + */ + void onAnimationStart(in RemoteAnimationTarget[] apps, + in IRemoteAnimationFinishedCallback finishedCallback); + + /** + * Called when the animation was cancelled. From this point on, any updates onto the leashes + * won't have any effect anymore. + */ + void onAnimationCancelled(); +} diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 8c7032207c34..4adcb8f15be7 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -38,6 +38,7 @@ import android.view.IAppTransitionAnimationSpecsFuture; import android.view.IDockedStackListener; import android.view.IOnKeyguardExitResult; import android.view.IPinnedStackListener; +import android.view.RemoteAnimationAdapter; import android.view.IRotationWatcher; import android.view.IWallpaperVisibilityListener; import android.view.IWindowSession; @@ -124,6 +125,7 @@ interface IWindowManager void overridePendingAppTransitionMultiThumbFuture( IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback startedCallback, boolean scaleUp); + void overridePendingAppTransitionRemote(in RemoteAnimationAdapter remoteAnimationAdapter); void executeAppTransition(); /** Used by system ui to report that recents has shown itself. */ @@ -294,6 +296,11 @@ interface IWindowManager boolean hasNavigationBar(); /** + * Get the position of the nav bar + */ + int getNavBarPosition(); + + /** * Lock the device immediately with the specified options (can be null). */ void lockNow(in Bundle options); diff --git a/core/java/android/view/RemoteAnimationAdapter.aidl b/core/java/android/view/RemoteAnimationAdapter.aidl new file mode 100644 index 000000000000..855bc740e75c --- /dev/null +++ b/core/java/android/view/RemoteAnimationAdapter.aidl @@ -0,0 +1,19 @@ +/* + * 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 android.view; + +parcelable RemoteAnimationAdapter; diff --git a/core/java/android/view/RemoteAnimationAdapter.java b/core/java/android/view/RemoteAnimationAdapter.java new file mode 100644 index 000000000000..d597e597b119 --- /dev/null +++ b/core/java/android/view/RemoteAnimationAdapter.java @@ -0,0 +1,108 @@ +/* + * 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 android.view; + +import android.app.ActivityOptions; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Object that describes how to run a remote animation. + * <p> + * A remote animation lets another app control the entire app transition. It does so by + * <ul> + * <li>using {@link ActivityOptions#makeRemoteAnimation}</li> + * <li>using {@link IWindowManager#overridePendingAppTransitionRemote}</li> + * </ul> + * to register a {@link RemoteAnimationAdapter} that describes how the animation should be run: + * Along some meta-data, this object contains a callback that gets invoked from window manager when + * the transition is ready to be started. + * <p> + * Window manager supplies a list of {@link RemoteAnimationTarget}s into the callback. Each target + * contains information about the activity that is animating as well as + * {@link RemoteAnimationTarget#leash}. The controlling app can modify the leash like any other + * {@link SurfaceControl}, including the possibility to synchronize updating the leash's surface + * properties with a frame to be drawn using + * {@link SurfaceControl.Transaction#deferTransactionUntil}. + * <p> + * When the animation is done, the controlling app can invoke + * {@link IRemoteAnimationFinishedCallback} that gets supplied into + * {@link IRemoteAnimationRunner#onStartAnimation} + * + * @hide + */ +public class RemoteAnimationAdapter implements Parcelable { + + private final IRemoteAnimationRunner mRunner; + private final long mDuration; + private final long mStatusBarTransitionDelay; + + /** + * @param runner The interface that gets notified when we actually need to start the animation. + * @param duration The duration of the animation. + * @param statusBarTransitionDelay The desired delay for all visual animations in the + * status bar caused by this app animation in millis. + */ + public RemoteAnimationAdapter(IRemoteAnimationRunner runner, long duration, + long statusBarTransitionDelay) { + mRunner = runner; + mDuration = duration; + mStatusBarTransitionDelay = statusBarTransitionDelay; + } + + public RemoteAnimationAdapter(Parcel in) { + mRunner = IRemoteAnimationRunner.Stub.asInterface(in.readStrongBinder()); + mDuration = in.readLong(); + mStatusBarTransitionDelay = in.readLong(); + } + + public IRemoteAnimationRunner getRunner() { + return mRunner; + } + + public long getDuration() { + return mDuration; + } + + public long getStatusBarTransitionDelay() { + return mStatusBarTransitionDelay; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeStrongInterface(mRunner); + dest.writeLong(mDuration); + dest.writeLong(mStatusBarTransitionDelay); + } + + public static final Creator<RemoteAnimationAdapter> CREATOR + = new Creator<RemoteAnimationAdapter>() { + public RemoteAnimationAdapter createFromParcel(Parcel in) { + return new RemoteAnimationAdapter(in); + } + + public RemoteAnimationAdapter[] newArray(int size) { + return new RemoteAnimationAdapter[size]; + } + }; +} diff --git a/core/java/android/view/RemoteAnimationDefinition.aidl b/core/java/android/view/RemoteAnimationDefinition.aidl new file mode 100644 index 000000000000..32ecd01ebf25 --- /dev/null +++ b/core/java/android/view/RemoteAnimationDefinition.aidl @@ -0,0 +1,19 @@ +/* + * 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 android.view; + +parcelable RemoteAnimationDefinition; diff --git a/core/java/android/view/RemoteAnimationDefinition.java b/core/java/android/view/RemoteAnimationDefinition.java new file mode 100644 index 000000000000..381f6926a1e8 --- /dev/null +++ b/core/java/android/view/RemoteAnimationDefinition.java @@ -0,0 +1,93 @@ +/* + * 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 android.view; + +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.ArrayMap; +import android.util.SparseArray; +import android.view.WindowManager.TransitionType; + +/** + * Defines which animation types should be overridden by which remote animation. + * + * @hide + */ +public class RemoteAnimationDefinition implements Parcelable { + + private final SparseArray<RemoteAnimationAdapter> mTransitionAnimationMap; + + public RemoteAnimationDefinition() { + mTransitionAnimationMap = new SparseArray<>(); + } + + /** + * Registers a remote animation for a specific transition. + * + * @param transition The transition type. Must be one of WindowManager.TRANSIT_* values. + * @param adapter The adapter that described how to run the remote animation. + */ + public void addRemoteAnimation(@TransitionType int transition, RemoteAnimationAdapter adapter) { + mTransitionAnimationMap.put(transition, adapter); + } + + /** + * Checks whether a remote animation for specific transition is defined. + * + * @param transition The transition type. Must be one of WindowManager.TRANSIT_* values. + * @return Whether this definition has defined a remote animation for the specified transition. + */ + public boolean hasTransition(@TransitionType int transition) { + return mTransitionAnimationMap.get(transition) != null; + } + + /** + * Retrieves the remote animation for a specific transition. + * + * @param transition The transition type. Must be one of WindowManager.TRANSIT_* values. + * @return The remote animation adapter for the specified transition. + */ + public @Nullable RemoteAnimationAdapter getAdapter(@TransitionType int transition) { + return mTransitionAnimationMap.get(transition); + } + + public RemoteAnimationDefinition(Parcel in) { + mTransitionAnimationMap = in.readSparseArray(null /* loader */); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeSparseArray((SparseArray) mTransitionAnimationMap); + } + + public static final Creator<RemoteAnimationDefinition> CREATOR = + new Creator<RemoteAnimationDefinition>() { + public RemoteAnimationDefinition createFromParcel(Parcel in) { + return new RemoteAnimationDefinition(in); + } + + public RemoteAnimationDefinition[] newArray(int size) { + return new RemoteAnimationDefinition[size]; + } + }; +} diff --git a/core/java/android/view/RemoteAnimationTarget.aidl b/core/java/android/view/RemoteAnimationTarget.aidl new file mode 100644 index 000000000000..769bf5e16673 --- /dev/null +++ b/core/java/android/view/RemoteAnimationTarget.aidl @@ -0,0 +1,19 @@ +/* + * 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 android.view; + +parcelable RemoteAnimationTarget; diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java new file mode 100644 index 000000000000..f39e618e169d --- /dev/null +++ b/core/java/android/view/RemoteAnimationTarget.java @@ -0,0 +1,151 @@ +/* + * 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 android.view; + +import android.annotation.IntDef; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Describes an activity to be animated as part of a remote animation. + * + * @hide + */ +public class RemoteAnimationTarget implements Parcelable { + + /** + * The app is in the set of opening apps of this transition. + */ + public static final int MODE_OPENING = 0; + + /** + * The app is in the set of closing apps of this transition. + */ + public static final int MODE_CLOSING = 1; + + @IntDef(prefix = { "MODE_" }, value = { + MODE_OPENING, + MODE_CLOSING + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Mode {} + + /** + * The {@link Mode} to describe whether this app is opening or closing. + */ + public final @Mode int mode; + + /** + * The id of the task this app belongs to. + */ + public final int taskId; + + /** + * The {@link SurfaceControl} object to actually control the transform of the app. + */ + public final SurfaceControl leash; + + /** + * Whether the app is translucent and may reveal apps behind. + */ + public final boolean isTranslucent; + + /** + * The clip rect window manager applies when clipping the app's main surface in screen space + * coordinates. This is just a hint to the animation runner: If running a clip-rect animation, + * anything that extends beyond these bounds will not have any effect. This implies that any + * clip-rect animation should likely stop at these bounds. + */ + public final Rect clipRect; + + /** + * The index of the element in the tree in prefix order. This should be used for z-layering + * to preserve original z-layer order in the hierarchy tree assuming no "boosting" needs to + * happen. + */ + public final int prefixOrderIndex; + + /** + * The source position of the app, in screen spaces coordinates. If the position of the leash + * is modified from the controlling app, any animation transform needs to be offset by this + * amount. + */ + public final Point position; + + /** + * The bounds of the source container the app lives in, in screen space coordinates. If the crop + * of the leash is modified from the controlling app, it needs to take the source container + * bounds into account when calculating the crop. + */ + public final Rect sourceContainerBounds; + + public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent, + Rect clipRect, int prefixOrderIndex, Point position, Rect sourceContainerBounds) { + this.mode = mode; + this.taskId = taskId; + this.leash = leash; + this.isTranslucent = isTranslucent; + this.clipRect = new Rect(clipRect); + this.prefixOrderIndex = prefixOrderIndex; + this.position = new Point(position); + this.sourceContainerBounds = new Rect(sourceContainerBounds); + } + + public RemoteAnimationTarget(Parcel in) { + taskId = in.readInt(); + mode = in.readInt(); + leash = in.readParcelable(null); + isTranslucent = in.readBoolean(); + clipRect = in.readParcelable(null); + prefixOrderIndex = in.readInt(); + position = in.readParcelable(null); + sourceContainerBounds = in.readParcelable(null); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(taskId); + dest.writeInt(mode); + dest.writeParcelable(leash, 0 /* flags */); + dest.writeBoolean(isTranslucent); + dest.writeParcelable(clipRect, 0 /* flags */); + dest.writeInt(prefixOrderIndex); + dest.writeParcelable(position, 0 /* flags */); + dest.writeParcelable(sourceContainerBounds, 0 /* flags */); + } + + public static final Creator<RemoteAnimationTarget> CREATOR + = new Creator<RemoteAnimationTarget>() { + public RemoteAnimationTarget createFromParcel(Parcel in) { + return new RemoteAnimationTarget(in); + } + + public RemoteAnimationTarget[] newArray(int size) { + return new RemoteAnimationTarget[size]; + } + }; +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index ad71b58514e3..f461e45372d3 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3226,6 +3226,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private static final int PFLAG3_SCREEN_READER_FOCUSABLE = 0x10000000; + /** + * The last aggregated visibility. Used to detect when it truly changes. + */ + private static final int PFLAG3_AGGREGATED_VISIBLE = 0x20000000; + /* End of masks for mPrivateFlags3 */ /** @@ -3387,6 +3392,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * decorations when they are shown. You can perform layout of your inner * UI elements to account for non-fullscreen system UI through the * {@link #fitSystemWindows(Rect)} method. + * + * <p>Note: on displays that have a {@link DisplayCutout}, the window may still be placed + * differently than if {@link #SYSTEM_UI_FLAG_FULLSCREEN} was set, if the + * window's {@link WindowManager.LayoutParams#layoutInDisplayCutoutMode + * layoutInDisplayCutoutMode} is + * {@link WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT + * LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT}. To avoid this, use either of the other modes. + * + * @see WindowManager.LayoutParams#layoutInDisplayCutoutMode + * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT + * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER */ public static final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 0x00000400; @@ -4193,6 +4210,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private static boolean sCanFocusZeroSized; + /** + * Always assign focus if a focusable View is available. + */ + private static boolean sAlwaysAssignFocus; + private String mTransitionName; static class TintInfo { @@ -4810,6 +4832,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, sCanFocusZeroSized = targetSdkVersion < Build.VERSION_CODES.P; + sAlwaysAssignFocus = targetSdkVersion < Build.VERSION_CODES.P; + sCompatibilityDone = true; } } @@ -7000,8 +7024,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Called when this view wants to give up focus. If focus is cleared * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} is called. * <p> - * <strong>Note:</strong> When a View clears focus the framework is trying - * to give focus to the first focusable View from the top. Hence, if this + * <strong>Note:</strong> When not in touch-mode, the framework will try to give focus + * to the first focusable View from the top after focus is cleared. Hence, if this * View is the first from the top that can take focus, then all callbacks * related to clearing focus will be invoked after which the framework will * give focus to this view. @@ -7012,7 +7036,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, System.out.println(this + " clearFocus()"); } - clearFocusInternal(null, true, true); + final boolean refocus = sAlwaysAssignFocus || !isInTouchMode(); + clearFocusInternal(null, true, refocus); } /** @@ -7207,20 +7232,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback, notifyEnterOrExitForAutoFillIfNeeded(gainFocus); } - private void notifyEnterOrExitForAutoFillIfNeeded(boolean enter) { - if (isAutofillable() && isAttachedToWindow()) { + /** @hide */ + public void notifyEnterOrExitForAutoFillIfNeeded(boolean enter) { + if (canNotifyAutofillEnterExitEvent()) { AutofillManager afm = getAutofillManager(); if (afm != null) { - if (enter && hasWindowFocus() && isFocused()) { + if (enter && isFocused()) { // We have not been laid out yet, hence cannot evaluate // whether this view is visible to the user, we will do // the evaluation once layout is complete. if (!isLaidOut()) { mPrivateFlags3 |= PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT; } else if (isVisibleToUser()) { + // TODO This is a potential problem that View gets focus before it's visible + // to User. Ideally View should handle the event when isVisibleToUser() + // becomes true where it should issue notifyViewEntered(). afm.notifyViewEntered(this); } - } else if (!hasWindowFocus() || !isFocused()) { + } else if (!isFocused()) { afm.notifyViewExited(this); } } @@ -7354,7 +7383,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ public void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) { - if (!isShown()) { + // Panes disappearing are relevant even if though the view is no longer visible. + boolean isWindowStateChanged = + (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + boolean isWindowDisappearedEvent = isWindowStateChanged && ((event.getContentChangeTypes() + & AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED) != 0); + if (!isShown() && !isWindowDisappearedEvent) { return; } onInitializeAccessibilityEvent(event); @@ -7462,6 +7496,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { + if ((event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) + && !TextUtils.isEmpty(getAccessibilityPaneTitle())) { + event.getText().add(getAccessibilityPaneTitle()); + } } /** @@ -8283,6 +8321,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, && getAutofillViewId() > LAST_APP_AUTOFILL_ID; } + /** @hide */ + public boolean canNotifyAutofillEnterExitEvent() { + return isAutofillable() && isAttachedToWindow(); + } + private void populateVirtualStructure(ViewStructure structure, AccessibilityNodeProvider provider, AccessibilityNodeInfo info) { structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()), @@ -11587,6 +11630,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) { return; } + // Changes to views with a pane title count as window state changes, as the pane title + // marks them as significant parts of the UI. + if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) { + final AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setEventType(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + event.setContentChangeTypes(changeType); + onPopulateAccessibilityEvent(event); + if (mParent != null) { + try { + mParent.requestSendAccessibilityEvent(this, event); + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + } + } + } + if (mParent != null) { try { mParent.notifySubtreeAccessibilityStateChanged(this, source, changeType); @@ -12400,8 +12460,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, imm.focusIn(this); } - notifyEnterOrExitForAutoFillIfNeeded(hasWindowFocus); - refreshDrawableState(); } @@ -12520,6 +12578,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ @CallSuper public void onVisibilityAggregated(boolean isVisible) { + // Update our internal visibility tracking so we can detect changes + boolean oldVisible = (mPrivateFlags3 & PFLAG3_AGGREGATED_VISIBLE) != 0; + mPrivateFlags3 = isVisible ? (mPrivateFlags3 | PFLAG3_AGGREGATED_VISIBLE) + : (mPrivateFlags3 & ~PFLAG3_AGGREGATED_VISIBLE); if (isVisible && mAttachInfo != null) { initialAwakenScrollBars(); } @@ -12560,6 +12622,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } } + if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) { + if (isVisible != oldVisible) { + notifyAccessibilityStateChanged(isVisible + ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED + : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED); + } + } } /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index f81a4c33271a..30f584c570ca 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -20,7 +20,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER; import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM; -import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL; @@ -530,7 +530,7 @@ public final class ViewRootImpl implements ViewParent, mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); if (!sCompatibilityDone) { - sAlwaysAssignFocus = true; + sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P; sCompatibilityDone = true; } @@ -1596,9 +1596,9 @@ public final class ViewRootImpl implements ViewParent, void dispatchApplyInsets(View host) { WindowInsets insets = getWindowInsets(true /* forceConstruct */); - final boolean layoutInCutout = - (mWindowAttributes.flags2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0; - if (!layoutInCutout) { + final boolean dispatchCutout = (mWindowAttributes.layoutInDisplayCutoutMode + == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS); + if (!dispatchCutout) { // Window is either not laid out in cutout or the status bar inset takes care of // clearing the cutout, so we don't need to dispatch the cutout to the hierarchy. insets = insets.consumeDisplayCutout(); @@ -2337,7 +2337,7 @@ public final class ViewRootImpl implements ViewParent, } if (mFirst) { - if (sAlwaysAssignFocus) { + if (sAlwaysAssignFocus || !isInTouchMode()) { // handle first focus request if (DEBUG_INPUT_RESIZE) { Log.v(mTag, "First: mView.hasFocus()=" + mView.hasFocus()); @@ -3608,7 +3608,7 @@ public final class ViewRootImpl implements ViewParent, checkThread(); if (mView != null) { if (!mView.hasFocus()) { - if (sAlwaysAssignFocus) { + if (sAlwaysAssignFocus || !isInTouchMode()) { v.requestFocus(); } } else { @@ -4211,10 +4211,7 @@ public final class ViewRootImpl implements ViewParent, // find the best view to give focus to in this brave new non-touch-mode // world - final View focused = focusSearch(null, View.FOCUS_DOWN); - if (focused != null) { - return focused.requestFocus(View.FOCUS_DOWN); - } + return mView.restoreDefaultFocus(); } return false; } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index a65aba152c83..3bb3a4c17b8f 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -21,7 +21,6 @@ import static android.view.WindowLayoutParamsProto.ALPHA; import static android.view.WindowLayoutParamsProto.BUTTON_BRIGHTNESS; import static android.view.WindowLayoutParamsProto.COLOR_MODE; import static android.view.WindowLayoutParamsProto.FLAGS; -import static android.view.WindowLayoutParamsProto.FLAGS_EXTRA; import static android.view.WindowLayoutParamsProto.FORMAT; import static android.view.WindowLayoutParamsProto.GRAVITY; import static android.view.WindowLayoutParamsProto.HAS_SYSTEM_UI_LISTENERS; @@ -46,7 +45,6 @@ import static android.view.WindowLayoutParamsProto.Y; import android.Manifest.permission; import android.annotation.IntDef; -import android.annotation.LongDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; @@ -107,6 +105,191 @@ public interface WindowManager extends ViewManager { final static String INPUT_CONSUMER_WALLPAPER = "wallpaper_input_consumer"; /** + * Not set up for a transition. + * @hide + */ + int TRANSIT_UNSET = -1; + + /** + * No animation for transition. + * @hide + */ + int TRANSIT_NONE = 0; + + /** + * A window in a new activity is being opened on top of an existing one in the same task. + * @hide + */ + int TRANSIT_ACTIVITY_OPEN = 6; + + /** + * The window in the top-most activity is being closed to reveal the previous activity in the + * same task. + * @hide + */ + int TRANSIT_ACTIVITY_CLOSE = 7; + + /** + * A window in a new task is being opened on top of an existing one in another activity's task. + * @hide + */ + int TRANSIT_TASK_OPEN = 8; + + /** + * A window in the top-most activity is being closed to reveal the previous activity in a + * different task. + * @hide + */ + int TRANSIT_TASK_CLOSE = 9; + + /** + * A window in an existing task is being displayed on top of an existing one in another + * activity's task. + * @hide + */ + int TRANSIT_TASK_TO_FRONT = 10; + + /** + * A window in an existing task is being put below all other tasks. + * @hide + */ + int TRANSIT_TASK_TO_BACK = 11; + + /** + * A window in a new activity that doesn't have a wallpaper is being opened on top of one that + * does, effectively closing the wallpaper. + * @hide + */ + int TRANSIT_WALLPAPER_CLOSE = 12; + + /** + * A window in a new activity that does have a wallpaper is being opened on one that didn't, + * effectively opening the wallpaper. + * @hide + */ + int TRANSIT_WALLPAPER_OPEN = 13; + + /** + * A window in a new activity is being opened on top of an existing one, and both are on top + * of the wallpaper. + * @hide + */ + int TRANSIT_WALLPAPER_INTRA_OPEN = 14; + + /** + * The window in the top-most activity is being closed to reveal the previous activity, and + * both are on top of the wallpaper. + * @hide + */ + int TRANSIT_WALLPAPER_INTRA_CLOSE = 15; + + /** + * A window in a new task is being opened behind an existing one in another activity's task. + * The new window will show briefly and then be gone. + * @hide + */ + int TRANSIT_TASK_OPEN_BEHIND = 16; + + /** + * A window in a task is being animated in-place. + * @hide + */ + int TRANSIT_TASK_IN_PLACE = 17; + + /** + * An activity is being relaunched (e.g. due to configuration change). + * @hide + */ + int TRANSIT_ACTIVITY_RELAUNCH = 18; + + /** + * A task is being docked from recents. + * @hide + */ + int TRANSIT_DOCK_TASK_FROM_RECENTS = 19; + + /** + * Keyguard is going away. + * @hide + */ + int TRANSIT_KEYGUARD_GOING_AWAY = 20; + + /** + * Keyguard is going away with showing an activity behind that requests wallpaper. + * @hide + */ + int TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER = 21; + + /** + * Keyguard is being occluded. + * @hide + */ + int TRANSIT_KEYGUARD_OCCLUDE = 22; + + /** + * Keyguard is being unoccluded. + * @hide + */ + int TRANSIT_KEYGUARD_UNOCCLUDE = 23; + + /** + * @hide + */ + @IntDef(prefix = { "TRANSIT_" }, value = { + TRANSIT_UNSET, + TRANSIT_NONE, + TRANSIT_ACTIVITY_OPEN, + TRANSIT_ACTIVITY_CLOSE, + TRANSIT_TASK_OPEN, + TRANSIT_TASK_CLOSE, + TRANSIT_TASK_TO_FRONT, + TRANSIT_TASK_TO_BACK, + TRANSIT_WALLPAPER_CLOSE, + TRANSIT_WALLPAPER_OPEN, + TRANSIT_WALLPAPER_INTRA_OPEN, + TRANSIT_WALLPAPER_INTRA_CLOSE, + TRANSIT_TASK_OPEN_BEHIND, + TRANSIT_TASK_IN_PLACE, + TRANSIT_ACTIVITY_RELAUNCH, + TRANSIT_DOCK_TASK_FROM_RECENTS, + TRANSIT_KEYGUARD_GOING_AWAY, + TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER, + TRANSIT_KEYGUARD_OCCLUDE, + TRANSIT_KEYGUARD_UNOCCLUDE + }) + @Retention(RetentionPolicy.SOURCE) + @interface TransitionType {} + + /** + * Transition flag: Keyguard is going away, but keeping the notification shade open + * @hide + */ + int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE = 0x1; + + /** + * Transition flag: Keyguard is going away, but doesn't want an animation for it + * @hide + */ + int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION = 0x2; + + /** + * Transition flag: Keyguard is going away while it was showing the system wallpaper. + * @hide + */ + int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER = 0x4; + + /** + * @hide + */ + @IntDef(flag = true, prefix = { "TRANSIT_FLAG_" }, value = { + TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE, + TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION, + TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER, + }) + @Retention(RetentionPolicy.SOURCE) + @interface TransitionFlags {} + + /** * Exception that is thrown when trying to add view whose * {@link LayoutParams} {@link LayoutParams#token} * is invalid. @@ -889,7 +1072,12 @@ public interface WindowManager extends ViewManager { * decorations around the border (such as the status bar). The * window must correctly position its contents to take the screen * decoration into account. This flag is normally set for you - * by Window as described in {@link Window#setFlags}. */ + * by Window as described in {@link Window#setFlags}. + * + * <p>Note: on displays that have a {@link DisplayCutout}, the window may be placed + * such that it avoids the {@link DisplayCutout} area if necessary according to the + * {@link #layoutInDisplayCutoutMode}. + */ public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100; /** Window flag: allow window to extend outside of the screen. */ @@ -1295,33 +1483,6 @@ public interface WindowManager extends ViewManager { }, formatToHexString = true) public int flags; - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @LongDef( - flag = true, - value = { - LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA, - }) - @interface Flags2 {} - - /** - * Window flag: allow placing the window within the area that overlaps with the - * display cutout. - * - * <p> - * The window must correctly position its contents to take the display cutout into account. - * - * @see DisplayCutout - */ - public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 0x00000001; - - /** - * Various behavioral options/flags. Default is none. - * - * @see #FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA - */ - @Flags2 public long flags2; - /** * If the window has requested hardware acceleration, but this is not * allowed in the process it is in, then still render it as if it is @@ -2050,6 +2211,77 @@ public interface WindowManager extends ViewManager { */ public boolean hasSystemUiListeners; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + flag = true, + value = {LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT, + LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS, + LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER}) + @interface LayoutInDisplayCutoutMode {} + + /** + * Controls how the window is laid out if there is a {@link DisplayCutout}. + * + * <p> + * Defaults to {@link #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT}. + * + * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT + * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER + * @see DisplayCutout + */ + @LayoutInDisplayCutoutMode + public int layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; + + /** + * The window is allowed to extend into the {@link DisplayCutout} area, only if the + * {@link DisplayCutout} is fully contained within the status bar. Otherwise, the window is + * laid out such that it does not overlap with the {@link DisplayCutout} area. + * + * <p> + * In practice, this means that if the window did not set FLAG_FULLSCREEN or + * SYSTEM_UI_FLAG_FULLSCREEN, it can extend into the cutout area in portrait. + * Otherwise (i.e. fullscreen or landscape) it is laid out such that it does overlap the + * cutout area. + * + * <p> + * The usual precautions for not overlapping with the status bar are sufficient for ensuring + * that no important content overlaps with the DisplayCutout. + * + * @see DisplayCutout + * @see WindowInsets + */ + public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0; + + /** + * The window is always allowed to extend into the {@link DisplayCutout} area, + * even if fullscreen or in landscape. + * + * <p> + * The window must make sure that no important content overlaps with the + * {@link DisplayCutout}. + * + * @see DisplayCutout + * @see WindowInsets#getDisplayCutout() + */ + public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 1; + + /** + * The window is never allowed to overlap with the DisplayCutout area. + * + * <p> + * This should be used with windows that transiently set SYSTEM_UI_FLAG_FULLSCREEN to + * avoid a relayout of the window when the flag is set or cleared. + * + * @see DisplayCutout + * @see View#SYSTEM_UI_FLAG_FULLSCREEN SYSTEM_UI_FLAG_FULLSCREEN + * @see View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + */ + public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER = 2; + + /** * When this window has focus, disable touch pad pointer gesture processing. * The window will receive raw position updates from the touch pad instead @@ -2273,9 +2505,9 @@ public interface WindowManager extends ViewManager { out.writeInt(y); out.writeInt(type); out.writeInt(flags); - out.writeLong(flags2); out.writeInt(privateFlags); out.writeInt(softInputMode); + out.writeInt(layoutInDisplayCutoutMode); out.writeInt(gravity); out.writeFloat(horizontalMargin); out.writeFloat(verticalMargin); @@ -2329,9 +2561,9 @@ public interface WindowManager extends ViewManager { y = in.readInt(); type = in.readInt(); flags = in.readInt(); - flags2 = in.readLong(); privateFlags = in.readInt(); softInputMode = in.readInt(); + layoutInDisplayCutoutMode = in.readInt(); gravity = in.readInt(); horizontalMargin = in.readFloat(); verticalMargin = in.readFloat(); @@ -2462,10 +2694,6 @@ public interface WindowManager extends ViewManager { flags = o.flags; changes |= FLAGS_CHANGED; } - if (flags2 != o.flags2) { - flags2 = o.flags2; - changes |= FLAGS_CHANGED; - } if (privateFlags != o.privateFlags) { privateFlags = o.privateFlags; changes |= PRIVATE_FLAGS_CHANGED; @@ -2474,6 +2702,10 @@ public interface WindowManager extends ViewManager { softInputMode = o.softInputMode; changes |= SOFT_INPUT_MODE_CHANGED; } + if (layoutInDisplayCutoutMode != o.layoutInDisplayCutoutMode) { + layoutInDisplayCutoutMode = o.layoutInDisplayCutoutMode; + changes |= LAYOUT_CHANGED; + } if (gravity != o.gravity) { gravity = o.gravity; changes |= LAYOUT_CHANGED; @@ -2651,6 +2883,10 @@ public interface WindowManager extends ViewManager { sb.append(softInputModeToString(softInputMode)); sb.append('}'); } + if (layoutInDisplayCutoutMode != 0) { + sb.append(" layoutInDisplayCutoutMode="); + sb.append(layoutInDisplayCutoutModeToString(layoutInDisplayCutoutMode)); + } sb.append(" ty="); sb.append(ViewDebug.intToString(LayoutParams.class, "type", type)); if (format != PixelFormat.OPAQUE) { @@ -2719,11 +2955,6 @@ public interface WindowManager extends ViewManager { sb.append(System.lineSeparator()); sb.append(prefix).append(" fl=").append( ViewDebug.flagsToString(LayoutParams.class, "flags", flags)); - if (flags2 != 0) { - sb.append(System.lineSeparator()); - // TODO(roosa): add a long overload for ViewDebug.flagsToString. - sb.append(prefix).append(" fl2=0x").append(Long.toHexString(flags2)); - } if (privateFlags != 0) { sb.append(System.lineSeparator()); sb.append(prefix).append(" pfl=").append(ViewDebug.flagsToString( @@ -2771,7 +3002,6 @@ public interface WindowManager extends ViewManager { proto.write(NEEDS_MENU_KEY, needsMenuKey); proto.write(COLOR_MODE, mColorMode); proto.write(FLAGS, flags); - proto.write(FLAGS_EXTRA, flags2); proto.write(PRIVATE_FLAGS, privateFlags); proto.write(SYSTEM_UI_VISIBILITY_FLAGS, systemUiVisibility); proto.write(SUBTREE_SYSTEM_UI_VISIBILITY_FLAGS, subtreeSystemUiVisibility); @@ -2849,6 +3079,20 @@ public interface WindowManager extends ViewManager { && height == WindowManager.LayoutParams.MATCH_PARENT; } + private static String layoutInDisplayCutoutModeToString( + @LayoutInDisplayCutoutMode int mode) { + switch (mode) { + case LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT: + return "default"; + case LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS: + return "always"; + case LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER: + return "never"; + default: + return "unknown(" + mode + ")"; + } + } + private static String softInputModeToString(@SoftInputModeFlags int softInputMode) { final StringBuilder result = new StringBuilder(); final int state = softInputMode & SOFT_INPUT_MASK_STATE; diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java index 9c1c9e34cfb4..a6f36bbf4ef4 100644 --- a/core/java/android/view/WindowManagerPolicyConstants.java +++ b/core/java/android/view/WindowManagerPolicyConstants.java @@ -45,6 +45,11 @@ public interface WindowManagerPolicyConstants { int PRESENCE_INTERNAL = 1 << 0; int PRESENCE_EXTERNAL = 1 << 1; + // Navigation bar position values + int NAV_BAR_LEFT = 1 << 0; + int NAV_BAR_RIGHT = 1 << 1; + int NAV_BAR_BOTTOM = 1 << 2; + /** * Sticky broadcast of the current HDMI plugged state. */ diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index aa61926dcedc..e0f74a7d6dd6 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -192,9 +192,11 @@ import java.util.List; * <b>TRANSITION TYPES</b></br> * </p> * <p> - * <b>Window state changed</b> - represents the event of opening a - * {@link android.widget.PopupWindow}, {@link android.view.Menu}, - * {@link android.app.Dialog}, etc.</br> + * <b>Window state changed</b> - represents the event of a change to a section of + * the user interface that is visually distinct. Should be sent from either the + * root view of a window or from a view that is marked as a pane + * {@link android.view.View#setAccessibilityPaneTitle(CharSequence)}. Not that changes + * to true windows are represented by {@link #TYPE_WINDOWS_CHANGED}.</br> * <em>Type:</em> {@link #TYPE_WINDOW_STATE_CHANGED}</br> * <em>Properties:</em></br> * <ul> @@ -203,7 +205,7 @@ import java.util.List; * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the source's sub-tree.</li> + * <li>{@link #getText()} - The text of the source's sub-tree, including the pane titles.</li> * </ul> * </p> * <p> @@ -436,8 +438,10 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final int TYPE_VIEW_TEXT_CHANGED = 0x00000010; /** - * Represents the event of opening a {@link android.widget.PopupWindow}, - * {@link android.view.Menu}, {@link android.app.Dialog}, etc. + * Represents the event of a change to a visually distinct section of the user interface. + * These events should only be dispatched from {@link android.view.View}s that have + * accessibility pane titles, and replaces {@link #TYPE_WINDOW_CONTENT_CHANGED} for those + * sources. Details about the change are available from {@link #getContentChangeTypes()}. */ public static final int TYPE_WINDOW_STATE_CHANGED = 0x00000020; @@ -565,12 +569,30 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004; /** - * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: + * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event: * The node's pane title changed. */ public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 0x00000008; /** + * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event: + * The node has a pane title, and either just appeared or just was assigned a title when it + * had none before. + */ + public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 0x00000010; + + /** + * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event: + * Can mean one of two slightly different things. The primary meaning is that the node has + * a pane title, and was removed from the node hierarchy. It will also be sent if the pane + * title is set to {@code null} after it contained a title. + * No source will be returned if the node is no longer on the screen. To make the change more + * clear for the user, the first entry in {@link #getText()} will return the value that would + * have been returned by {@code getSource().getPaneTitle()}. + */ + public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 0x00000020; + + /** * Change type for {@link #TYPE_WINDOWS_CHANGED} event: * The window was added. */ diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 78b41c6f4c7b..de9b0d7dfa4a 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -341,6 +341,10 @@ public final class AutofillManager { @GuardedBy("mLock") @Nullable private AutofillId mSaveTriggerId; + /** set to true when onInvisibleForAutofill is called, used by onAuthenticationResult */ + @GuardedBy("mLock") + private boolean mOnInvisibleCalled; + /** If set, session is commited when the activity is finished; otherwise session is canceled. */ @GuardedBy("mLock") private boolean mSaveOnFinish; @@ -397,6 +401,11 @@ public final class AutofillManager { boolean isVisibleForAutofill(); /** + * Client might disable enter/exit event e.g. when activity is paused. + */ + boolean isDisablingEnterExitEventForAutofill(); + + /** * Finds views by traversing the hierarchies of the client. * * @param viewIds The autofill ids of the views to find @@ -499,6 +508,19 @@ public final class AutofillManager { } /** + * Called once the client becomes invisible. + * + * @see AutofillClient#isVisibleForAutofill() + * + * {@hide} + */ + public void onInvisibleForAutofill() { + synchronized (mLock) { + mOnInvisibleCalled = true; + } + } + + /** * Save state before activity lifecycle * * @param outState Place to store the state @@ -623,21 +645,45 @@ public final class AutofillManager { return false; } + private boolean isClientVisibleForAutofillLocked() { + final AutofillClient client = getClient(); + return client != null && client.isVisibleForAutofill(); + } + + private boolean isClientDisablingEnterExitEvent() { + final AutofillClient client = getClient(); + return client != null && client.isDisablingEnterExitEventForAutofill(); + } + private void notifyViewEntered(@NonNull View view, int flags) { if (!hasAutofillFeature()) { return; } - AutofillCallback callback = null; + AutofillCallback callback; synchronized (mLock) { - if (shouldIgnoreViewEnteredLocked(view, flags)) return; + callback = notifyViewEnteredLocked(view, flags); + } - ensureServiceClientAddedIfNeededLocked(); + if (callback != null) { + mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE); + } + } - if (!mEnabled) { - if (mCallback != null) { - callback = mCallback; - } - } else { + /** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */ + private AutofillCallback notifyViewEnteredLocked(@NonNull View view, int flags) { + if (shouldIgnoreViewEnteredLocked(view, flags)) return null; + + AutofillCallback callback = null; + + ensureServiceClientAddedIfNeededLocked(); + + if (!mEnabled) { + if (mCallback != null) { + callback = mCallback; + } + } else { + // don't notify entered when Activity is already in background + if (!isClientDisablingEnterExitEvent()) { final AutofillId id = getAutofillId(view); final AutofillValue value = view.getAutofillValue(); @@ -650,10 +696,7 @@ public final class AutofillManager { } } } - - if (callback != null) { - mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE); - } + return callback; } /** @@ -666,9 +709,16 @@ public final class AutofillManager { return; } synchronized (mLock) { - ensureServiceClientAddedIfNeededLocked(); + notifyViewExitedLocked(view); + } + } - if (mEnabled && isActiveLocked()) { + void notifyViewExitedLocked(@NonNull View view) { + ensureServiceClientAddedIfNeededLocked(); + + if (mEnabled && isActiveLocked()) { + // dont notify exited when Activity is already in background + if (!isClientDisablingEnterExitEvent()) { final AutofillId id = getAutofillId(view); // Update focus on existing session. @@ -719,7 +769,7 @@ public final class AutofillManager { } } if (mTrackedViews != null) { - mTrackedViews.notifyViewVisibilityChanged(id, isVisible); + mTrackedViews.notifyViewVisibilityChangedLocked(id, isVisible); } } } @@ -752,17 +802,32 @@ public final class AutofillManager { if (!hasAutofillFeature()) { return; } - AutofillCallback callback = null; + AutofillCallback callback; synchronized (mLock) { - if (shouldIgnoreViewEnteredLocked(view, flags)) return; + callback = notifyViewEnteredLocked(view, virtualId, bounds, flags); + } - ensureServiceClientAddedIfNeededLocked(); + if (callback != null) { + callback.onAutofillEvent(view, virtualId, + AutofillCallback.EVENT_INPUT_UNAVAILABLE); + } + } - if (!mEnabled) { - if (mCallback != null) { - callback = mCallback; - } - } else { + /** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */ + private AutofillCallback notifyViewEnteredLocked(View view, int virtualId, Rect bounds, + int flags) { + AutofillCallback callback = null; + if (shouldIgnoreViewEnteredLocked(view, flags)) return callback; + + ensureServiceClientAddedIfNeededLocked(); + + if (!mEnabled) { + if (mCallback != null) { + callback = mCallback; + } + } else { + // don't notify entered when Activity is already in background + if (!isClientDisablingEnterExitEvent()) { final AutofillId id = getAutofillId(view, virtualId); if (!isActiveLocked()) { @@ -774,11 +839,7 @@ public final class AutofillManager { } } } - - if (callback != null) { - callback.onAutofillEvent(view, virtualId, - AutofillCallback.EVENT_INPUT_UNAVAILABLE); - } + return callback; } /** @@ -792,9 +853,16 @@ public final class AutofillManager { return; } synchronized (mLock) { - ensureServiceClientAddedIfNeededLocked(); + notifyViewExitedLocked(view, virtualId); + } + } - if (mEnabled && isActiveLocked()) { + private void notifyViewExitedLocked(@NonNull View view, int virtualId) { + ensureServiceClientAddedIfNeededLocked(); + + if (mEnabled && isActiveLocked()) { + // don't notify exited when Activity is already in background + if (!isClientDisablingEnterExitEvent()) { final AutofillId id = getAutofillId(view, virtualId); // Update focus on existing session. @@ -1155,7 +1223,7 @@ public final class AutofillManager { } /** @hide */ - public void onAuthenticationResult(int authenticationId, Intent data) { + public void onAuthenticationResult(int authenticationId, Intent data, View focusView) { if (!hasAutofillFeature()) { return; } @@ -1167,9 +1235,24 @@ public final class AutofillManager { if (sDebug) Log.d(TAG, "onAuthenticationResult(): d=" + data); synchronized (mLock) { - if (!isActiveLocked() || data == null) { + if (!isActiveLocked()) { return; } + // If authenticate activity closes itself during onCreate(), there is no onStop/onStart + // of app activity. We enforce enter event to re-show fill ui in such case. + // CTS example: + // LoginActivityTest#testDatasetAuthTwoFieldsUserCancelsFirstAttempt + // LoginActivityTest#testFillResponseAuthBothFieldsUserCancelsFirstAttempt + if (!mOnInvisibleCalled && focusView != null + && focusView.canNotifyAutofillEnterExitEvent()) { + notifyViewExitedLocked(focusView); + notifyViewEnteredLocked(focusView, 0); + } + if (data == null) { + // data is set to null when result is not RESULT_OK + return; + } + final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT); final Bundle responseData = new Bundle(); responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result); @@ -1402,6 +1485,9 @@ public final class AutofillManager { if (sessionId == mSessionId) { final AutofillClient client = getClient(); if (client != null) { + // clear mOnInvisibleCalled and we will see if receive onInvisibleForAutofill() + // before onAuthenticationResult() + mOnInvisibleCalled = false; client.autofillCallbackAuthenticate(authenticationId, intent, fillInIntent); } } @@ -1767,6 +1853,7 @@ public final class AutofillManager { pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled); pw.print(pfx); pw.print("hasService: "); pw.println(mService != null); pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null); + pw.print(pfx); pw.print("onInvisibleCalled "); pw.println(mOnInvisibleCalled); pw.print(pfx); pw.print("last autofilled data: "); pw.println(mLastAutofilledData); pw.print(pfx); pw.print("tracked views: "); if (mTrackedViews == null) { @@ -1937,15 +2024,13 @@ public final class AutofillManager { * @param id the id of the view/virtual view whose visibility changed. * @param isVisible visible if the view is visible in the view hierarchy. */ - void notifyViewVisibilityChanged(@NonNull AutofillId id, boolean isVisible) { - AutofillClient client = getClient(); - + void notifyViewVisibilityChangedLocked(@NonNull AutofillId id, boolean isVisible) { if (sDebug) { Log.d(TAG, "notifyViewVisibilityChanged(): id=" + id + " isVisible=" + isVisible); } - if (client != null && client.isVisibleForAutofill()) { + if (isClientVisibleForAutofillLocked()) { if (isVisible) { if (isInSet(mInvisibleTrackedIds, id)) { mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id); diff --git a/core/java/android/view/autofill/AutofillPopupWindow.java b/core/java/android/view/autofill/AutofillPopupWindow.java index 5cba21e3cc07..e80fdd93542c 100644 --- a/core/java/android/view/autofill/AutofillPopupWindow.java +++ b/core/java/android/view/autofill/AutofillPopupWindow.java @@ -78,8 +78,10 @@ public class AutofillPopupWindow extends PopupWindow { public AutofillPopupWindow(@NonNull IAutofillWindowPresenter presenter) { mWindowPresenter = new WindowPresenter(presenter); + setTouchModal(false); setOutsideTouchable(true); - setInputMethodMode(INPUT_METHOD_NEEDED); + setInputMethodMode(INPUT_METHOD_NOT_NEEDED); + setFocusable(true); } @Override diff --git a/core/java/android/view/inputmethod/ExtractedText.java b/core/java/android/view/inputmethod/ExtractedText.java index 003f221d08b2..1eb300eafb66 100644 --- a/core/java/android/view/inputmethod/ExtractedText.java +++ b/core/java/android/view/inputmethod/ExtractedText.java @@ -29,6 +29,8 @@ import android.text.TextUtils; public class ExtractedText implements Parcelable { /** * The text that has been extracted. + * + * @see android.widget.TextView#getText() */ public CharSequence text; @@ -88,6 +90,8 @@ public class ExtractedText implements Parcelable { /** * The hint that has been extracted. + * + * @see android.widget.TextView#getHint() */ public CharSequence hint; diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index 57f9895f45fa..e5545405728d 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -1,17 +1,17 @@ /* - * Copyright (C) 2007-2008 The Android Open Source Project + * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at + * 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 + * 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. + * 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.view.inputmethod; @@ -131,13 +131,13 @@ public interface InputConnection { * spans. <strong>Editor authors</strong>: you should strive to * send text with styles if possible, but it is not required. */ - static final int GET_TEXT_WITH_STYLES = 0x0001; + int GET_TEXT_WITH_STYLES = 0x0001; /** * Flag for use with {@link #getExtractedText} to indicate you * would like to receive updates when the extracted text changes. */ - public static final int GET_EXTRACTED_TEXT_MONITOR = 0x0001; + int GET_EXTRACTED_TEXT_MONITOR = 0x0001; /** * Get <var>n</var> characters of text before the current cursor @@ -176,7 +176,7 @@ public interface InputConnection { * @return the text before the cursor position; the length of the * returned text might be less than <var>n</var>. */ - public CharSequence getTextBeforeCursor(int n, int flags); + CharSequence getTextBeforeCursor(int n, int flags); /** * Get <var>n</var> characters of text after the current cursor @@ -215,7 +215,7 @@ public interface InputConnection { * @return the text after the cursor position; the length of the * returned text might be less than <var>n</var>. */ - public CharSequence getTextAfterCursor(int n, int flags); + CharSequence getTextAfterCursor(int n, int flags); /** * Gets the selected text, if any. @@ -249,7 +249,7 @@ public interface InputConnection { * later, returns false when the target application does not implement * this method. */ - public CharSequence getSelectedText(int flags); + CharSequence getSelectedText(int flags); /** * Retrieve the current capitalization mode in effect at the @@ -279,7 +279,7 @@ public interface InputConnection { * @return the caps mode flags that are in effect at the current * cursor position. See TYPE_TEXT_FLAG_CAPS_* in {@link android.text.InputType}. */ - public int getCursorCapsMode(int reqModes); + int getCursorCapsMode(int reqModes); /** * Retrieve the current text in the input connection's editor, and @@ -314,8 +314,7 @@ public interface InputConnection { * longer valid of the editor can't comply with the request for * some reason. */ - public ExtractedText getExtractedText(ExtractedTextRequest request, - int flags); + ExtractedText getExtractedText(ExtractedTextRequest request, int flags); /** * Delete <var>beforeLength</var> characters of text before the @@ -342,8 +341,8 @@ public interface InputConnection { * delete more characters than are in the editor, as that may have * ill effects on the application. Calling this method will cause * the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on your service after the batch input is over.</p> + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on your service after the batch input is over.</p> * * <p><strong>Editor authors:</strong> please be careful of race * conditions in implementing this call. An IME can make a change @@ -369,7 +368,7 @@ public interface InputConnection { * that range. * @return true on success, false if the input connection is no longer valid. */ - public boolean deleteSurroundingText(int beforeLength, int afterLength); + boolean deleteSurroundingText(int beforeLength, int afterLength); /** * A variant of {@link #deleteSurroundingText(int, int)}. Major differences are: @@ -397,7 +396,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer valid. Returns * {@code false} when the target application does not implement this method. */ - public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength); + boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength); /** * Replace the currently composing text with the given text, and @@ -416,8 +415,8 @@ public interface InputConnection { * <p>This is usually called by IMEs to add or remove or change * characters in the composing span. Calling this method will * cause the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over.</p> + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over.</p> * * <p><strong>Editor authors:</strong> please keep in mind the * text may be very similar or completely different than what was @@ -455,7 +454,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean setComposingText(CharSequence text, int newCursorPosition); + boolean setComposingText(CharSequence text, int newCursorPosition); /** * Mark a certain region of text as composing text. If there was a @@ -474,8 +473,8 @@ public interface InputConnection { * <p>Since this does not change the contents of the text, editors should not call * {@link InputMethodManager#updateSelection(View, int, int, int, int)} and * IMEs should not receive - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}. - * </p> + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)}.</p> * * <p>This has no impact on the cursor/selection position. It may * result in the cursor being anywhere inside or outside the @@ -488,7 +487,7 @@ public interface InputConnection { * valid. In {@link android.os.Build.VERSION_CODES#N} and later, false is returned when the * target application does not implement this method. */ - public boolean setComposingRegion(int start, int end); + boolean setComposingRegion(int start, int end); /** * Have the text editor finish whatever composing text is @@ -507,7 +506,7 @@ public interface InputConnection { * @return true on success, false if the input connection * is no longer valid. */ - public boolean finishComposingText(); + boolean finishComposingText(); /** * Commit text to the text box and set the new cursor position. @@ -522,8 +521,8 @@ public interface InputConnection { * then {@link #finishComposingText()}.</p> * * <p>Calling this method will cause the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over. + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over. * <strong>Editor authors</strong>, for this to happen you need to * make the changes known to the input method by calling * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, @@ -543,7 +542,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean commitText(CharSequence text, int newCursorPosition); + boolean commitText(CharSequence text, int newCursorPosition); /** * Commit a completion the user has selected from the possible ones @@ -569,8 +568,8 @@ public interface InputConnection { * * <p>Calling this method (with a valid {@link CompletionInfo} object) * will cause the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over. + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over. * <strong>Editor authors</strong>, for this to happen you need to * make the changes known to the input method by calling * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, @@ -581,15 +580,15 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean commitCompletion(CompletionInfo text); + boolean commitCompletion(CompletionInfo text); /** * Commit a correction automatically performed on the raw user's input. A * typical example would be to correct typos using a dictionary. * * <p>Calling this method will cause the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over. + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over. * <strong>Editor authors</strong>, for this to happen you need to * make the changes known to the input method by calling * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, @@ -601,7 +600,7 @@ public interface InputConnection { * In {@link android.os.Build.VERSION_CODES#N} and later, returns false * when the target application does not implement this method. */ - public boolean commitCorrection(CorrectionInfo correctionInfo); + boolean commitCorrection(CorrectionInfo correctionInfo); /** * Set the selection of the text editor. To set the cursor @@ -609,8 +608,8 @@ public interface InputConnection { * * <p>Since this moves the cursor, calling this method will cause * the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over. + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over. * <strong>Editor authors</strong>, for this to happen you need to * make the changes known to the input method by calling * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, @@ -628,7 +627,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean setSelection(int start, int end); + boolean setSelection(int start, int end); /** * Have the editor perform an action it has said it can do. @@ -642,7 +641,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean performEditorAction(int editorAction); + boolean performEditorAction(int editorAction); /** * Perform a context menu action on the field. The given id may be one of: @@ -652,7 +651,7 @@ public interface InputConnection { * {@link android.R.id#paste}, {@link android.R.id#copyUrl}, * or {@link android.R.id#switchInputMethod} */ - public boolean performContextMenuAction(int id); + boolean performContextMenuAction(int id); /** * Tell the editor that you are starting a batch of editor @@ -662,8 +661,8 @@ public interface InputConnection { * * <p><strong>IME authors:</strong> use this to avoid getting * calls to - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * corresponding to intermediate state. Also, use this to avoid + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} corresponding to intermediate state. Also, use this to avoid * flickers that may arise from displaying intermediate state. Be * sure to call {@link #endBatchEdit} for each call to this, or * you may block updates in the editor.</p> @@ -678,7 +677,7 @@ public interface InputConnection { * this method starts a batch edit, that means it will always return true * unless the input connection is no longer valid. */ - public boolean beginBatchEdit(); + boolean beginBatchEdit(); /** * Tell the editor that you are done with a batch edit previously @@ -696,7 +695,7 @@ public interface InputConnection { * the latest one (in other words, if the nesting count is > 0), false * otherwise or if the input connection is no longer valid. */ - public boolean endBatchEdit(); + boolean endBatchEdit(); /** * Send a key event to the process that is currently attached @@ -734,7 +733,7 @@ public interface InputConnection { * @see KeyCharacterMap#PREDICTIVE * @see KeyCharacterMap#ALPHA */ - public boolean sendKeyEvent(KeyEvent event); + boolean sendKeyEvent(KeyEvent event); /** * Clear the given meta key pressed states in the given input @@ -749,7 +748,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean clearMetaKeyStates(int states); + boolean clearMetaKeyStates(int states); /** * Called back when the connected IME switches between fullscreen and normal modes. @@ -766,7 +765,7 @@ public interface InputConnection { * devices. * @see InputMethodManager#isFullscreenMode() */ - public boolean reportFullscreenMode(boolean enabled); + boolean reportFullscreenMode(boolean enabled); /** * API to send private commands from an input method to its @@ -786,7 +785,7 @@ public interface InputConnection { * associated editor understood it), false if the input connection is no longer * valid. */ - public boolean performPrivateCommand(String action, Bundle data); + boolean performPrivateCommand(String action, Bundle data); /** * The editor is requested to call @@ -794,7 +793,7 @@ public interface InputConnection { * once, as soon as possible, regardless of cursor/anchor position changes. This flag can be * used together with {@link #CURSOR_UPDATE_MONITOR}. */ - public static final int CURSOR_UPDATE_IMMEDIATE = 1 << 0; + int CURSOR_UPDATE_IMMEDIATE = 1 << 0; /** * The editor is requested to call @@ -805,7 +804,7 @@ public interface InputConnection { * This flag can be used together with {@link #CURSOR_UPDATE_IMMEDIATE}. * </p> */ - public static final int CURSOR_UPDATE_MONITOR = 1 << 1; + int CURSOR_UPDATE_MONITOR = 1 << 1; /** * Called by the input method to ask the editor for calling back @@ -821,7 +820,7 @@ public interface InputConnection { * In {@link android.os.Build.VERSION_CODES#N} and later, returns {@code false} also when the * target application does not implement this method. */ - public boolean requestCursorUpdates(int cursorUpdateMode); + boolean requestCursorUpdates(int cursorUpdateMode); /** * Called by the {@link InputMethodManager} to enable application developers to specify a @@ -832,7 +831,7 @@ public interface InputConnection { * * @return {@code null} to use the default {@link Handler}. */ - public Handler getHandler(); + Handler getHandler(); /** * Called by the system up to only once to notify that the system is about to invalidate @@ -846,7 +845,7 @@ public interface InputConnection { * * <p>Note: This does nothing when called from input methods.</p> */ - public void closeConnection(); + void closeConnection(); /** * When this flag is used, the editor will be able to request read access to the content URI @@ -863,7 +862,7 @@ public interface InputConnection { * client is able to request a temporary read-only access even after the current IME is switched * to any other IME as long as the client keeps {@link InputContentInfo} object.</p> **/ - public static int INPUT_CONTENT_GRANT_READ_URI_PERMISSION = + int INPUT_CONTENT_GRANT_READ_URI_PERMISSION = android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; // 0x00000001 /** @@ -897,6 +896,6 @@ public interface InputConnection { * @return {@code true} if this request is accepted by the application, whether the request * is already handled or still being handled in background, {@code false} otherwise. */ - public boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags, + boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags, @Nullable Bundle opts); } diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java index 317730ca092c..f671e22b4922 100644 --- a/core/java/android/view/inputmethod/InputConnectionWrapper.java +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -1,17 +1,17 @@ /* - * Copyright (C) 2007-2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. + * 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.view.inputmethod; @@ -74,6 +74,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public CharSequence getTextBeforeCursor(int n, int flags) { return mTarget.getTextBeforeCursor(n, flags); } @@ -82,6 +83,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public CharSequence getTextAfterCursor(int n, int flags) { return mTarget.getTextAfterCursor(n, flags); } @@ -90,6 +92,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public CharSequence getSelectedText(int flags) { return mTarget.getSelectedText(flags); } @@ -98,6 +101,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public int getCursorCapsMode(int reqModes) { return mTarget.getCursorCapsMode(reqModes); } @@ -106,6 +110,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { return mTarget.getExtractedText(request, flags); } @@ -114,6 +119,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) { return mTarget.deleteSurroundingTextInCodePoints(beforeLength, afterLength); } @@ -122,6 +128,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean deleteSurroundingText(int beforeLength, int afterLength) { return mTarget.deleteSurroundingText(beforeLength, afterLength); } @@ -130,6 +137,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean setComposingText(CharSequence text, int newCursorPosition) { return mTarget.setComposingText(text, newCursorPosition); } @@ -138,6 +146,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean setComposingRegion(int start, int end) { return mTarget.setComposingRegion(start, end); } @@ -146,6 +155,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean finishComposingText() { return mTarget.finishComposingText(); } @@ -154,6 +164,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean commitText(CharSequence text, int newCursorPosition) { return mTarget.commitText(text, newCursorPosition); } @@ -162,6 +173,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean commitCompletion(CompletionInfo text) { return mTarget.commitCompletion(text); } @@ -170,6 +182,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean commitCorrection(CorrectionInfo correctionInfo) { return mTarget.commitCorrection(correctionInfo); } @@ -178,6 +191,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean setSelection(int start, int end) { return mTarget.setSelection(start, end); } @@ -186,6 +200,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean performEditorAction(int editorAction) { return mTarget.performEditorAction(editorAction); } @@ -194,6 +209,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean performContextMenuAction(int id) { return mTarget.performContextMenuAction(id); } @@ -202,6 +218,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean beginBatchEdit() { return mTarget.beginBatchEdit(); } @@ -210,6 +227,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean endBatchEdit() { return mTarget.endBatchEdit(); } @@ -218,6 +236,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean sendKeyEvent(KeyEvent event) { return mTarget.sendKeyEvent(event); } @@ -226,6 +245,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean clearMetaKeyStates(int states) { return mTarget.clearMetaKeyStates(states); } @@ -234,6 +254,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean reportFullscreenMode(boolean enabled) { return mTarget.reportFullscreenMode(enabled); } @@ -242,6 +263,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean performPrivateCommand(String action, Bundle data) { return mTarget.performPrivateCommand(action, data); } @@ -250,6 +272,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean requestCursorUpdates(int cursorUpdateMode) { return mTarget.requestCursorUpdates(cursorUpdateMode); } @@ -258,6 +281,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public Handler getHandler() { return mTarget.getHandler(); } @@ -266,6 +290,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public void closeConnection() { mTarget.closeConnection(); } @@ -274,6 +299,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { return mTarget.commitContent(inputContentInfo, flags, opts); } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 1e291209a373..7db5c3207296 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -1082,15 +1082,15 @@ public final class InputMethodManager { } /** - * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft - * input window should only be hidden if it was not explicitly shown + * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestHideSelf(int)} + * to indicate that the soft input window should only be hidden if it was not explicitly shown * by the user. */ public static final int HIDE_IMPLICIT_ONLY = 0x0001; /** - * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft - * input window should normally be hidden, unless it was originally + * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestShowSelf(int)} + * to indicate that the soft input window should normally be hidden, unless it was originally * shown with {@link #SHOW_FORCED}. */ public static final int HIDE_NOT_ALWAYS = 0x0002; @@ -1869,9 +1869,9 @@ public final class InputMethodManager { * @param flags Provides additional operating flags. Currently may be * 0 or have the {@link #HIDE_IMPLICIT_ONLY}, * {@link #HIDE_NOT_ALWAYS} bit set. - * @deprecated Use {@link InputMethodService#hideSoftInputFromInputMethod(int)} - * instead. This method was intended for IME developers who should be accessing APIs through - * the service. APIs in this class are intended for app developers interacting with the IME. + * @deprecated Use {@link InputMethodService#requestHideSelf(int)} instead. This method was + * intended for IME developers who should be accessing APIs through the service. APIs in this + * class are intended for app developers interacting with the IME. */ @Deprecated public void hideSoftInputFromInputMethod(IBinder token, int flags) { @@ -1901,9 +1901,9 @@ public final class InputMethodManager { * @param flags Provides additional operating flags. Currently may be * 0 or have the {@link #SHOW_IMPLICIT} or * {@link #SHOW_FORCED} bit set. - * @deprecated Use {@link InputMethodService#showSoftInputFromInputMethod(int)} - * instead. This method was intended for IME developers who should be accessing APIs through - * the service. APIs in this class are intended for app developers interacting with the IME. + * @deprecated Use {@link InputMethodService#requestShowSelf(int)} instead. This method was + * intended for IME developers who should be accessing APIs through the service. APIs in this + * class are intended for app developers interacting with the IME. */ @Deprecated public void showSoftInputFromInputMethod(IBinder token, int flags) { diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index b3522ec94c0c..e9fe481112a2 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -27,7 +27,6 @@ import android.content.pm.PackageManager; import android.content.pm.Signature; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.StrictMode; import android.os.Trace; import android.util.AndroidRuntimeException; import android.util.ArraySet; @@ -251,7 +250,6 @@ public final class WebViewFactory { "WebView.disableWebView() was called: WebView is disabled"); } - StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()"); try { Class<WebViewFactoryProvider> providerClass = getProviderClass(); @@ -279,7 +277,6 @@ public final class WebViewFactory { } } finally { Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); - StrictMode.setThreadPolicy(oldPolicy); } } } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index b5ac33070f1f..247c806e928e 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -4627,7 +4627,7 @@ public class Editor { return 0; } - protected final void showMagnifier() { + protected final void showMagnifier(@NonNull final MotionEvent event) { if (mMagnifier == null) { return; } @@ -4653,9 +4653,10 @@ public class Editor { final Layout layout = mTextView.getLayout(); final int lineNumber = layout.getLineForOffset(offset); - // Horizontally snap to character offset. - final float xPosInView = getHorizontal(mTextView.getLayout(), offset) - + mTextView.getTotalPaddingLeft() - mTextView.getScrollX(); + // Horizontally move the magnifier smoothly. + final int[] textViewLocationOnScreen = new int[2]; + mTextView.getLocationOnScreen(textViewLocationOnScreen); + final float xPosInView = event.getRawX() - textViewLocationOnScreen[0]; // Vertically snap to middle of current line. final float yPosInView = (mTextView.getLayout().getLineTop(lineNumber) + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f @@ -4850,11 +4851,11 @@ public class Editor { case MotionEvent.ACTION_DOWN: mDownPositionX = ev.getRawX(); mDownPositionY = ev.getRawY(); - showMagnifier(); + showMagnifier(ev); break; case MotionEvent.ACTION_MOVE: - showMagnifier(); + showMagnifier(ev); break; case MotionEvent.ACTION_UP: @@ -5208,11 +5209,11 @@ public class Editor { // re-engages the handle. mTouchWordDelta = 0.0f; mPrevX = UNSET_X_VALUE; - showMagnifier(); + showMagnifier(event); break; case MotionEvent.ACTION_MOVE: - showMagnifier(); + showMagnifier(event); break; case MotionEvent.ACTION_UP: diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index 26dfcc2d668a..310b1708cb13 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -32,6 +32,7 @@ import android.view.PixelCopy; import android.view.Surface; import android.view.SurfaceView; import android.view.View; +import android.view.ViewParent; import com.android.internal.util.Preconditions; @@ -44,6 +45,8 @@ public final class Magnifier { private static final int NONEXISTENT_PREVIOUS_CONFIG_VALUE = -1; // The view to which this magnifier is attached. private final View mView; + // The coordinates of the view in the surface. + private final int[] mViewCoordinatesInSurface; // The window containing the magnifier. private final PopupWindow mWindow; // The center coordinates of the window containing the magnifier. @@ -87,6 +90,8 @@ public final class Magnifier { com.android.internal.R.dimen.magnifier_height); mZoomScale = context.getResources().getFloat( com.android.internal.R.dimen.magnifier_zoom_scale); + // The view's surface coordinates will not be updated until the magnifier is first shown. + mViewCoordinatesInSurface = new int[2]; mWindow = new PopupWindow(context); mWindow.setContentView(content); @@ -120,9 +125,34 @@ public final class Magnifier { configureCoordinates(xPosInView, yPosInView); // Clamp startX value to avoid distorting the rendering of the magnifier content. - final int startX = Math.max(0, Math.min( + // For this, we compute: + // - zeroScrollXInSurface: this is the start x of mView, where this is not masked by a + // potential scrolling container. For example, if mView is a + // TextView contained in a HorizontalScrollView, + // mViewCoordinatesInSurface will reflect the surface position of + // the first text character, rather than the position of the first + // visible one. Therefore, we need to add back the amount of + // scrolling from the parent containers. + // - actualWidth: similarly, the width of a View will be larger than its actually visible + // width when it is contained in a scrolling container. We need to use + // the minimum width of a scrolling container which contains this view. + int zeroScrollXInSurface = mViewCoordinatesInSurface[0]; + int actualWidth = mView.getWidth(); + ViewParent viewParent = mView.getParent(); + while (viewParent instanceof View) { + final View container = (View) viewParent; + if (container.canScrollHorizontally(-1 /* left scroll */) + || container.canScrollHorizontally(1 /* right scroll */)) { + zeroScrollXInSurface += container.getScrollX(); + actualWidth = Math.min(actualWidth, container.getWidth() + - container.getPaddingLeft() - container.getPaddingRight()); + } + viewParent = viewParent.getParent(); + } + + final int startX = Math.max(zeroScrollXInSurface, Math.min( mCenterZoomCoords.x - mBitmap.getWidth() / 2, - mView.getWidth() - mBitmap.getWidth())); + zeroScrollXInSurface + actualWidth - mBitmap.getWidth())); final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2; if (xPosInView != mPrevPosInView.x || yPosInView != mPrevPosInView.y) { @@ -169,10 +199,9 @@ public final class Magnifier { posX = xPosInView; posY = yPosInView; } else { - final int[] coordinatesInSurface = new int[2]; - mView.getLocationInSurface(coordinatesInSurface); - posX = xPosInView + coordinatesInSurface[0]; - posY = yPosInView + coordinatesInSurface[1]; + mView.getLocationInSurface(mViewCoordinatesInSurface); + posX = xPosInView + mViewCoordinatesInSurface[0]; + posY = yPosInView + mViewCoordinatesInSurface[1]; } mCenterZoomCoords.x = Math.round(posX); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 4170bd112450..dac6c0269539 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -80,8 +80,8 @@ import android.text.GraphicsOperations; import android.text.InputFilter; import android.text.InputType; import android.text.Layout; +import android.text.MeasuredText; import android.text.ParcelableSpan; -import android.text.PremeasuredText; import android.text.Selection; import android.text.SpanWatcher; import android.text.Spannable; @@ -3887,6 +3887,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * * @param elegant set the paint's elegant metrics flag. * + * @see #isElegantTextHeight() * @see Paint#isElegantTextHeight() * * @attr ref android.R.styleable#TextView_elegantTextHeight @@ -5546,7 +5547,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (imm != null) imm.restartInput(this); } else if (type == BufferType.SPANNABLE || mMovement != null) { text = mSpannableFactory.newSpannable(text); - } else if (!(text instanceof PremeasuredText || text instanceof CharWrapper)) { + } else if (!(text instanceof MeasuredText || text instanceof CharWrapper)) { text = TextUtils.stringOrSpannedString(text); } @@ -9278,8 +9279,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * - * Checks whether the transformation method applied to this TextView is set to ALL CAPS. This - * settings is internally ignored if this field is editable or selectable. + * Checks whether the transformation method applied to this TextView is set to ALL CAPS. * @return Whether the current transformation method is for ALL CAPS. * * @see #setAllCaps(boolean) diff --git a/core/java/android/widget/VideoView2.java b/core/java/android/widget/VideoView2.java new file mode 100644 index 000000000000..310a7bbdcabf --- /dev/null +++ b/core/java/android/widget/VideoView2.java @@ -0,0 +1,363 @@ +/* + * Copyright 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 android.widget; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.Canvas; +import android.media.AudioAttributes; +import android.media.MediaPlayer; +import android.media.update.ApiLoader; +import android.media.update.VideoView2Provider; +import android.media.update.ViewProvider; +import android.net.Uri; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.MotionEvent; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Map; + +/** + * TODO PUBLIC API + * @hide + */ +public class VideoView2 extends FrameLayout { + @IntDef({ + VIEW_TYPE_TEXTUREVIEW, + VIEW_TYPE_SURFACEVIEW + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ViewType {} + public static final int VIEW_TYPE_SURFACEVIEW = 1; + public static final int VIEW_TYPE_TEXTUREVIEW = 2; + + private final VideoView2Provider mProvider; + + /** + * @hide + */ + public VideoView2(@NonNull Context context) { + this(context, null); + } + + /** + * @hide + */ + public VideoView2(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * @hide + */ + public VideoView2(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + /** + * @hide + */ + public VideoView2( + @NonNull Context context, @Nullable AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + mProvider = ApiLoader.getProvider(context).createVideoView2(this, new SuperProvider()); + } + + /** + * @hide + */ + public VideoView2Provider getProvider() { + return mProvider; + } + + /** + * @hide + */ + public void start() { + mProvider.start_impl(); + } + + /** + * @hide + */ + public void pause() { + mProvider.pause_impl(); + } + + /** + * @hide + */ + public int getDuration() { + return mProvider.getDuration_impl(); + } + + /** + * @hide + */ + public int getCurrentPosition() { + return mProvider.getCurrentPosition_impl(); + } + + /** + * @hide + */ + public void seekTo(int msec) { + mProvider.seekTo_impl(msec); + } + + /** + * @hide + */ + public boolean isPlaying() { + return mProvider.isPlaying_impl(); + } + + /** + * @hide + */ + public int getBufferPercentage() { + return mProvider.getBufferPercentage_impl(); + } + + /** + * @hide + */ + public int getAudioSessionId() { + return mProvider.getAudioSessionId_impl(); + } + + /** + * @hide + */ + public void showSubtitle() { + mProvider.showSubtitle_impl(); + } + + /** + * @hide + */ + public void hideSubtitle() { + mProvider.hideSubtitle_impl(); + } + + /** + * @hide + */ + public void setAudioFocusRequest(int focusGain) { + mProvider.setAudioFocusRequest_impl(focusGain); + } + + /** + * @hide + */ + public void setAudioAttributes(@NonNull AudioAttributes attributes) { + mProvider.setAudioAttributes_impl(attributes); + } + + /** + * @hide + */ + public void setVideoPath(String path) { + mProvider.setVideoPath_impl(path); + } + + /** + * @hide + */ + public void setVideoURI(Uri uri) { + mProvider.setVideoURI_impl(uri); + } + + /** + * @hide + */ + public void setVideoURI(Uri uri, Map<String, String> headers) { + mProvider.setVideoURI_impl(uri, headers); + } + + /** + * @hide + */ + public void setMediaController2(MediaController2 controllerView) { + mProvider.setMediaController2_impl(controllerView); + } + + /** + * @hide + */ + public void setViewType(@ViewType int viewType) { + mProvider.setViewType_impl(viewType); + } + + /** + * @hide + */ + @ViewType + public int getViewType() { + return mProvider.getViewType_impl(); + } + + /** + * @hide + */ + public void stopPlayback() { + mProvider.stopPlayback_impl(); + } + + /** + * @hide + */ + public void setOnPreparedListener(MediaPlayer.OnPreparedListener l) { + mProvider.setOnPreparedListener_impl(l); + } + + /** + * @hide + */ + public void setOnCompletionListener(MediaPlayer.OnCompletionListener l) { + mProvider.setOnCompletionListener_impl(l); + } + + /** + * @hide + */ + public void setOnErrorListener(MediaPlayer.OnErrorListener l) { + mProvider.setOnErrorListener_impl(l); + } + + /** + * @hide + */ + public void setOnInfoListener(MediaPlayer.OnInfoListener l) { + mProvider.setOnInfoListener_impl(l); + } + + /** + * @hide + */ + public void setOnViewTypeChangedListener(OnViewTypeChangedListener l) { + mProvider.setOnViewTypeChangedListener_impl(l); + } + + /** + * @hide + */ + public interface OnViewTypeChangedListener { + /** + * @hide + */ + void onViewTypeChanged(@ViewType int viewType); + } + + @Override + public CharSequence getAccessibilityClassName() { + return mProvider.getAccessibilityClassName_impl(); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + return mProvider.onTouchEvent_impl(ev); + } + + @Override + public boolean onTrackballEvent(MotionEvent ev) { + return mProvider.onTrackballEvent_impl(ev); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return mProvider.onKeyDown_impl(keyCode, event); + } + + @Override + public void onFinishInflate() { + mProvider.onFinishInflate_impl(); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + return mProvider.dispatchKeyEvent_impl(event); + } + + @Override + public void setEnabled(boolean enabled) { + mProvider.setEnabled_impl(enabled); + } + + private class SuperProvider implements ViewProvider { + @Override + public void onAttachedToWindow_impl() { + VideoView2.super.onAttachedToWindow(); + } + + @Override + public void onDetachedFromWindow_impl() { + VideoView2.super.onDetachedFromWindow(); + } + + @Override + public void onLayout_impl(boolean changed, int left, int top, int right, int bottom) { + VideoView2.super.onLayout(changed, left, top, right, bottom); + } + + @Override + public void draw_impl(Canvas canvas) { + VideoView2.super.draw(canvas); + } + + @Override + public CharSequence getAccessibilityClassName_impl() { + return VideoView2.super.getAccessibilityClassName(); + } + + @Override + public boolean onTouchEvent_impl(MotionEvent ev) { + return VideoView2.super.onTouchEvent(ev); + } + + @Override + public boolean onTrackballEvent_impl(MotionEvent ev) { + return VideoView2.super.onTrackballEvent(ev); + } + + @Override + public boolean onKeyDown_impl(int keyCode, KeyEvent event) { + return VideoView2.super.onKeyDown(keyCode, event); + } + + @Override + public void onFinishInflate_impl() { + VideoView2.super.onFinishInflate(); + } + + @Override + public boolean dispatchKeyEvent_impl(KeyEvent event) { + return VideoView2.super.dispatchKeyEvent(event); + } + + @Override + public void setEnabled_impl(boolean enabled) { + VideoView2.super.setEnabled(enabled); + } + } +} diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 439e5df7d352..ac5dbc4e175a 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -4062,8 +4062,6 @@ public class BatteryStatsImpl extends BatteryStats { public void noteWakupAlarmLocked(String packageName, int uid, WorkSource workSource, String tag) { - final int[] uids = new int[1]; - final String[] tags = new String[1]; if (workSource != null) { for (int i = 0; i < workSource.size(); ++i) { uid = workSource.get(i); @@ -4074,9 +4072,8 @@ public class BatteryStatsImpl extends BatteryStats { workSourceName != null ? workSourceName : packageName); pkg.noteWakeupAlarmLocked(tag); } - uids[0] = workSource.get(i); - tags[0] = workSource.getName(i); - StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uids, tags, tag); + StatsLog.write_non_chained(StatsLog.WAKEUP_ALARM_OCCURRED, workSource.get(i), + workSource.getName(i), tag); } ArrayList<WorkChain> workChains = workSource.getWorkChains(); @@ -4097,9 +4094,7 @@ public class BatteryStatsImpl extends BatteryStats { BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid, packageName); pkg.noteWakeupAlarmLocked(tag); } - uids[0] = uid; - tags[0] = null; - StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uids, tags, tag); + StatsLog.write_non_chained(StatsLog.WAKEUP_ALARM_OCCURRED, uid, null, tag); } } @@ -4224,9 +4219,8 @@ public class BatteryStatsImpl extends BatteryStats { StatsLog.write( StatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), type, name, 1); } else { - final int[] uids = new int[] { uid }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags, type, name, 1); + StatsLog.write_non_chained(StatsLog.WAKELOCK_STATE_CHANGED, uid, null, type, name, + 1); } } } @@ -4268,9 +4262,8 @@ public class BatteryStatsImpl extends BatteryStats { StatsLog.write( StatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), type, name, 0); } else { - final int[] uids = new int[] { uid }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags, type, name, 0); + StatsLog.write_non_chained(StatsLog.WAKELOCK_STATE_CHANGED, uid, null, type, name, + 0); } } } @@ -4364,10 +4357,8 @@ public class BatteryStatsImpl extends BatteryStats { } public void noteLongPartialWakelockStart(String name, String historyName, int uid) { - final int[] uids = new int[] { uid }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, - uids, tags, name, historyName, 1); + StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, + uid, null, name, historyName, 1); uid = mapUid(uid); noteLongPartialWakeLockStartInternal(name, historyName, uid); @@ -4376,15 +4367,11 @@ public class BatteryStatsImpl extends BatteryStats { public void noteLongPartialWakelockStartFromSource(String name, String historyName, WorkSource workSource) { final int N = workSource.size(); - final int[] uids = new int[1]; - final String[] tags = new String[1]; for (int i = 0; i < N; ++i) { final int uid = mapUid(workSource.get(i)); noteLongPartialWakeLockStartInternal(name, historyName, uid); - uids[0] = workSource.get(i); - tags[0] = workSource.getName(i); - StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uids, tags, name, - historyName, 1); + StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, + workSource.get(i), workSource.getName(i), name, historyName, 1); } final ArrayList<WorkChain> workChains = workSource.getWorkChains(); @@ -4415,10 +4402,8 @@ public class BatteryStatsImpl extends BatteryStats { } public void noteLongPartialWakelockFinish(String name, String historyName, int uid) { - int[] uids = new int[] { uid }; - String[] tags = new String[] { null }; - StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, - uids, tags, name, historyName, 0); + StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, + uid, null, name, historyName, 0); uid = mapUid(uid); noteLongPartialWakeLockFinishInternal(name, historyName, uid); @@ -4427,15 +4412,11 @@ public class BatteryStatsImpl extends BatteryStats { public void noteLongPartialWakelockFinishFromSource(String name, String historyName, WorkSource workSource) { final int N = workSource.size(); - final int[] uids = new int[1]; - final String[] tags = new String[1]; for (int i = 0; i < N; ++i) { final int uid = mapUid(workSource.get(i)); noteLongPartialWakeLockFinishInternal(name, historyName, uid); - uids[0] = workSource.get(i); - tags[0] = workSource.getName(i); - StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, - uids, tags, name, historyName, 0); + StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, + workSource.get(i), workSource.getName(i), name, historyName, 0); } final ArrayList<WorkChain> workChains = workSource.getWorkChains(); @@ -5420,11 +5401,10 @@ public class BatteryStatsImpl extends BatteryStats { workChain.getUids(), workChain.getTags(), 1); } } else { - final int[] uids = new int[] {uid}; - final String[] tags = new String[] {null}; - StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, uids, tags, 1); + StatsLog.write_non_chained(StatsLog.BLE_SCAN_STATE_CHANGED, uid, null, 1); if (isUnoptimized) { - StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uids, tags, 1); + StatsLog.write_non_chained(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uid, null, + 1); } } @@ -5470,11 +5450,10 @@ public class BatteryStatsImpl extends BatteryStats { workChain.getUids(), workChain.getTags(), 0); } } else { - final int[] uids = new int[] { uid }; - final String[] tags = new String[] {null}; - StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.BLE_SCAN_STATE_CHANGED, uid, null, 0); if (isUnoptimized) { - StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uid, null, + 0); } } @@ -5547,14 +5526,11 @@ public class BatteryStatsImpl extends BatteryStats { public void noteBluetoothScanResultsFromSourceLocked(WorkSource ws, int numNewResults) { final int N = ws.size(); - final int[] uids = new int[1]; - final String[] tags = new String[1]; for (int i = 0; i < N; i++) { int uid = mapUid(ws.get(i)); getUidStatsLocked(uid).noteBluetoothScanResultsLocked(numNewResults); - uids[0] = ws.get(i); - tags[0] = ws.getName(i); - StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, uids, tags, numNewResults); + StatsLog.write_non_chained(StatsLog.BLE_SCAN_RESULT_RECEIVED, ws.get(i), ws.getName(i), + numNewResults); } final List<WorkChain> workChains = ws.getWorkChains(); @@ -5879,14 +5855,10 @@ public class BatteryStatsImpl extends BatteryStats { public void noteFullWifiLockAcquiredFromSourceLocked(WorkSource ws) { int N = ws.size(); - final int[] uids = new int[1]; - final String[] tags = new String[1]; for (int i=0; i<N; i++) { final int uid = mapUid(ws.get(i)); noteFullWifiLockAcquiredLocked(uid); - uids[0] = ws.get(i); - tags[0] = ws.getName(i); - StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, uids, tags, 1); + StatsLog.write_non_chained(StatsLog.WIFI_LOCK_STATE_CHANGED, ws.get(i), ws.getName(i), 1); } final List<WorkChain> workChains = ws.getWorkChains(); @@ -5903,14 +5875,10 @@ public class BatteryStatsImpl extends BatteryStats { public void noteFullWifiLockReleasedFromSourceLocked(WorkSource ws) { int N = ws.size(); - final int[] uids = new int[1]; - final String[] tags = new String[1]; for (int i=0; i<N; i++) { final int uid = mapUid(ws.get(i)); noteFullWifiLockReleasedLocked(uid); - uids[0] = ws.get(i); - tags[0] = ws.getName(i); - StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.WIFI_LOCK_STATE_CHANGED, ws.get(i), ws.getName(i), 0); } final List<WorkChain> workChains = ws.getWorkChains(); @@ -5927,14 +5895,11 @@ public class BatteryStatsImpl extends BatteryStats { public void noteWifiScanStartedFromSourceLocked(WorkSource ws) { int N = ws.size(); - final int[] uids = new int[1]; - final String[] tags = new String[1]; for (int i=0; i<N; i++) { final int uid = mapUid(ws.get(i)); noteWifiScanStartedLocked(uid); - uids[0] = ws.get(i); - tags[0] = ws.getName(i); - StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, uids, tags, 1); + StatsLog.write_non_chained(StatsLog.WIFI_SCAN_STATE_CHANGED, ws.get(i), ws.getName(i), + 1); } final List<WorkChain> workChains = ws.getWorkChains(); @@ -5951,14 +5916,11 @@ public class BatteryStatsImpl extends BatteryStats { public void noteWifiScanStoppedFromSourceLocked(WorkSource ws) { int N = ws.size(); - final int[] uids = new int[1]; - final String[] tags = new String[1]; for (int i=0; i<N; i++) { final int uid = mapUid(ws.get(i)); noteWifiScanStoppedLocked(uid); - uids[0] = ws.get(i); - tags[0] = ws.getName(i); - StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.WIFI_SCAN_STATE_CHANGED, ws.get(i), ws.getName(i), + 0); } final List<WorkChain> workChains = ws.getWorkChains(); @@ -6934,18 +6896,14 @@ public class BatteryStatsImpl extends BatteryStats { public void noteAudioTurnedOnLocked(long elapsedRealtimeMs) { createAudioTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, uids, tags, 1); + StatsLog.write_non_chained(StatsLog.AUDIO_STATE_CHANGED, getUid(), null, 1); } public void noteAudioTurnedOffLocked(long elapsedRealtimeMs) { if (mAudioTurnedOnTimer != null) { mAudioTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs); if (!mAudioTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.AUDIO_STATE_CHANGED, getUid(), null, 0); } } } @@ -6953,9 +6911,7 @@ public class BatteryStatsImpl extends BatteryStats { public void noteResetAudioLocked(long elapsedRealtimeMs) { if (mAudioTurnedOnTimer != null) { mAudioTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.AUDIO_STATE_CHANGED, getUid(), null, 0); } } @@ -6969,18 +6925,15 @@ public class BatteryStatsImpl extends BatteryStats { public void noteVideoTurnedOnLocked(long elapsedRealtimeMs) { createVideoTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, uids, tags, 1); + StatsLog.write_non_chained(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), null, 1); } public void noteVideoTurnedOffLocked(long elapsedRealtimeMs) { if (mVideoTurnedOnTimer != null) { mVideoTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs); if (!mVideoTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), + null, 0); } } } @@ -6988,9 +6941,8 @@ public class BatteryStatsImpl extends BatteryStats { public void noteResetVideoLocked(long elapsedRealtimeMs) { if (mVideoTurnedOnTimer != null) { mVideoTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), null, + 0); } } @@ -7004,18 +6956,15 @@ public class BatteryStatsImpl extends BatteryStats { public void noteFlashlightTurnedOnLocked(long elapsedRealtimeMs) { createFlashlightTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, uids, tags, 1); + StatsLog.write_non_chained(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), null,1); } public void noteFlashlightTurnedOffLocked(long elapsedRealtimeMs) { if (mFlashlightTurnedOnTimer != null) { mFlashlightTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs); if (!mFlashlightTurnedOnTimer.isRunningLocked()) { - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), null, + 0); } } } @@ -7023,9 +6972,7 @@ public class BatteryStatsImpl extends BatteryStats { public void noteResetFlashlightLocked(long elapsedRealtimeMs) { if (mFlashlightTurnedOnTimer != null) { mFlashlightTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), null, 0); } } @@ -7039,18 +6986,14 @@ public class BatteryStatsImpl extends BatteryStats { public void noteCameraTurnedOnLocked(long elapsedRealtimeMs) { createCameraTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, uids, tags, 1); + StatsLog.write_non_chained(StatsLog.CAMERA_STATE_CHANGED, getUid(), null, 1); } public void noteCameraTurnedOffLocked(long elapsedRealtimeMs) { if (mCameraTurnedOnTimer != null) { mCameraTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs); if (!mCameraTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.CAMERA_STATE_CHANGED, getUid(), null, 0); } } } @@ -7058,9 +7001,7 @@ public class BatteryStatsImpl extends BatteryStats { public void noteResetCameraLocked(long elapsedRealtimeMs) { if (mCameraTurnedOnTimer != null) { mCameraTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.CAMERA_STATE_CHANGED, getUid(), null, 0); } } @@ -9622,9 +9563,7 @@ public class BatteryStatsImpl extends BatteryStats { DualTimer t = mSyncStats.startObject(name); if (t != null) { t.startRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.SYNC_STATE_CHANGED, uids, tags, name, 1); + StatsLog.write_non_chained(StatsLog.SYNC_STATE_CHANGED, getUid(), null, name, 1); } } @@ -9633,9 +9572,7 @@ public class BatteryStatsImpl extends BatteryStats { if (t != null) { t.stopRunningLocked(elapsedRealtimeMs); if (!t.isRunningLocked()) { // only tell statsd if truly stopped - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.SYNC_STATE_CHANGED, uids, tags, name, 0); + StatsLog.write_non_chained(StatsLog.SYNC_STATE_CHANGED, getUid(), null, name, 0); } } } @@ -9644,9 +9581,8 @@ public class BatteryStatsImpl extends BatteryStats { DualTimer t = mJobStats.startObject(name); if (t != null) { t.startRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, uids, tags, name, 1); + StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_STATE_CHANGED, getUid(), null, + name, 1); } } @@ -9655,9 +9591,8 @@ public class BatteryStatsImpl extends BatteryStats { if (t != null) { t.stopRunningLocked(elapsedRealtimeMs); if (!t.isRunningLocked()) { // only tell statsd if truly stopped - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, uids, tags, name, 0); + StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_STATE_CHANGED, getUid(), null, + name, 0); } } if (mBsi.mOnBatteryTimeBase.isRunning()) { @@ -9768,12 +9703,11 @@ public class BatteryStatsImpl extends BatteryStats { public void noteStartSensor(int sensor, long elapsedRealtimeMs) { DualTimer t = getSensorTimerLocked(sensor, /* create= */ true); t.startRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; if (sensor == Sensor.GPS) { - StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, uids, tags, 1); + StatsLog.write_non_chained(StatsLog.GPS_SCAN_STATE_CHANGED, getUid(), null, 1); } else { - StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, uids, tags, sensor, 1); + StatsLog.write_non_chained(StatsLog.SENSOR_STATE_CHANGED, getUid(), null, sensor, + 1); } } @@ -9783,13 +9717,12 @@ public class BatteryStatsImpl extends BatteryStats { if (t != null) { t.stopRunningLocked(elapsedRealtimeMs); if (!t.isRunningLocked()) { // only tell statsd if truly stopped - // TODO(statsd): Possibly use a worksource instead of a uid. - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; if (sensor == Sensor.GPS) { - StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.GPS_SCAN_STATE_CHANGED, getUid(), null, + 0); } else { - StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, uids, tags, sensor, 0); + StatsLog.write_non_chained(StatsLog.SENSOR_STATE_CHANGED, getUid(), null, + sensor, 0); } } } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index c5fe4cb0177b..f814ba9e484d 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -572,10 +572,12 @@ public class ZygoteInit { final String seInfo = null; final String classLoaderContext = getSystemServerClassLoaderContext(classPathForElement); + final int targetSdkVersion = 0; // SystemServer targets the system's SDK version try { installd.dexopt(classPathElement, Process.SYSTEM_UID, packageName, instructionSet, dexoptNeeded, outputPath, dexFlags, compilerFilter, - uuid, classLoaderContext, seInfo, false /* downgrade */); + uuid, classLoaderContext, seInfo, false /* downgrade */, + targetSdkVersion); } catch (RemoteException | ServiceSpecificException e) { // Ignore (but log), we need this on the classpath for fallback mode. Log.w(TAG, "Failed compiling classpath element for system server: " diff --git a/core/java/com/android/internal/policy/KeyguardDismissCallback.java b/core/java/com/android/internal/policy/KeyguardDismissCallback.java new file mode 100644 index 000000000000..38337ec6f274 --- /dev/null +++ b/core/java/com/android/internal/policy/KeyguardDismissCallback.java @@ -0,0 +1,41 @@ +/* + * 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.internal.policy; + +import android.os.RemoteException; +import com.android.internal.policy.IKeyguardDismissCallback; + +/** + * @hide + */ +public class KeyguardDismissCallback extends IKeyguardDismissCallback.Stub { + + @Override + public void onDismissError() throws RemoteException { + // To be overidden + } + + @Override + public void onDismissSucceeded() throws RemoteException { + // To be overidden + } + + @Override + public void onDismissCancelled() throws RemoteException { + // To be overidden + } +} diff --git a/core/java/com/android/internal/print/DualDumpOutputStream.java b/core/java/com/android/internal/print/DualDumpOutputStream.java new file mode 100644 index 000000000000..4b10ef2facbb --- /dev/null +++ b/core/java/com/android/internal/print/DualDumpOutputStream.java @@ -0,0 +1,276 @@ +/* + * 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.internal.print; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Log; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.util.IndentingPrintWriter; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.LinkedList; + +/** + * Dump either to a proto or a print writer using the same interface. + * + * <p>This mirrors the interface of {@link ProtoOutputStream}. + */ +public class DualDumpOutputStream { + private static final String LOG_TAG = DualDumpOutputStream.class.getSimpleName(); + + // When writing to a proto, the proto + private final @Nullable ProtoOutputStream mProtoStream; + + // When printing in clear text, the writer + private final @Nullable IndentingPrintWriter mIpw; + // Temporary storage of data when printing to mIpw + private final LinkedList<DumpObject> mDumpObjects = new LinkedList<>(); + + private static abstract class Dumpable { + final String name; + + private Dumpable(String name) { + this.name = name; + } + + abstract void print(IndentingPrintWriter ipw, boolean printName); + } + + private static class DumpObject extends Dumpable { + private final LinkedHashMap<String, ArrayList<Dumpable>> mSubObjects = new LinkedHashMap<>(); + + private DumpObject(String name) { + super(name); + } + + @Override + void print(IndentingPrintWriter ipw, boolean printName) { + if (printName) { + ipw.println(name + "={"); + } else { + ipw.println("{"); + } + ipw.increaseIndent(); + + for (ArrayList<Dumpable> subObject: mSubObjects.values()) { + int numDumpables = subObject.size(); + + if (numDumpables == 1) { + subObject.get(0).print(ipw, true); + } else { + ipw.println(subObject.get(0).name + "=["); + ipw.increaseIndent(); + + for (int i = 0; i < numDumpables; i++) { + subObject.get(i).print(ipw, false); + } + + ipw.decreaseIndent(); + ipw.println("]"); + } + } + + ipw.decreaseIndent(); + ipw.println("}"); + } + + /** + * Add new field / subobject to this object. + * + * <p>If a name is added twice, they will be printed as a array + * + * @param fieldName name of the field added + * @param d The dumpable to add + */ + public void add(String fieldName, Dumpable d) { + ArrayList<Dumpable> l = mSubObjects.get(fieldName); + + if (l == null) { + l = new ArrayList<>(1); + mSubObjects.put(fieldName, l); + } + + l.add(d); + } + } + + private static class DumpField extends Dumpable { + private final String mValue; + + private DumpField(String name, String value) { + super(name); + this.mValue = value; + } + + @Override + void print(IndentingPrintWriter ipw, boolean printName) { + if (printName) { + ipw.println(name + "=" + mValue); + } else { + ipw.println(mValue); + } + } + } + + + /** + * Create a new DualDumpOutputStream. Only one output should be set. + * + * @param proto If dumping to proto the {@link ProtoOutputStream} + * @param ipw If dumping to a print writer, the {@link IndentingPrintWriter} + */ + public DualDumpOutputStream(@Nullable ProtoOutputStream proto, + @Nullable IndentingPrintWriter ipw) { + if ((proto == null) == (ipw == null)) { + Log.e(LOG_TAG, "Cannot dump to clear text and proto at once. Ignoring proto"); + proto = null; + } + + mProtoStream = proto; + mIpw = ipw; + + if (!isProto()) { + // Add root object + mDumpObjects.add(new DumpObject(null)); + } + } + + public void write(@NonNull String fieldName, long fieldId, double val) { + if (mProtoStream != null) { + mProtoStream.write(fieldId, val); + } else { + mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); + } + } + + public void write(@NonNull String fieldName, long fieldId, boolean val) { + if (mProtoStream != null) { + mProtoStream.write(fieldId, val); + } else { + mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); + } + } + + public void write(@NonNull String fieldName, long fieldId, int val) { + if (mProtoStream != null) { + mProtoStream.write(fieldId, val); + } else { + mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); + } + } + + public void write(@NonNull String fieldName, long fieldId, float val) { + if (mProtoStream != null) { + mProtoStream.write(fieldId, val); + } else { + mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); + } + } + + public void write(@NonNull String fieldName, long fieldId, byte[] val) { + if (mProtoStream != null) { + mProtoStream.write(fieldId, val); + } else { + mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, Arrays.toString(val))); + } + } + + public void write(@NonNull String fieldName, long fieldId, long val) { + if (mProtoStream != null) { + mProtoStream.write(fieldId, val); + } else { + mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); + } + } + + public void write(@NonNull String fieldName, long fieldId, @Nullable String val) { + if (mProtoStream != null) { + mProtoStream.write(fieldId, val); + } else { + mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); + } + } + + public long start(@NonNull String fieldName, long fieldId) { + if (mProtoStream != null) { + return mProtoStream.start(fieldId); + } else { + DumpObject d = new DumpObject(fieldName); + mDumpObjects.getLast().add(fieldName, d); + mDumpObjects.addLast(d); + return System.identityHashCode(d); + } + } + + public void end(long token) { + if (mProtoStream != null) { + mProtoStream.end(token); + } else { + if (System.identityHashCode(mDumpObjects.getLast()) != token) { + Log.w(LOG_TAG, "Unexpected token for ending " + mDumpObjects.getLast().name + + " at " + Arrays.toString(Thread.currentThread().getStackTrace())); + } + mDumpObjects.removeLast(); + } + } + + public void flush() { + if (mProtoStream != null) { + mProtoStream.flush(); + } else { + if (mDumpObjects.size() == 1) { + mDumpObjects.getFirst().print(mIpw, false); + + // Reset root object + mDumpObjects.clear(); + mDumpObjects.add(new DumpObject(null)); + } + + mIpw.flush(); + } + } + + /** + * Add a dump from a different service into this dump. + * + * <p>Only for clear text dump. For proto dump use {@link #write(String, long, byte[])}. + * + * @param fieldName The name of the field + * @param nestedState The state of the dump + */ + public void writeNested(@NonNull String fieldName, byte[] nestedState) { + if (mIpw == null) { + Log.w(LOG_TAG, "writeNested does not work for proto logging"); + return; + } + + mDumpObjects.getLast().add(fieldName, + new DumpField(fieldName, (new String(nestedState, StandardCharsets.UTF_8)).trim())); + } + + /** + * @return {@code true} iff we are dumping to a proto + */ + public boolean isProto() { + return mProtoStream != null; + } +} diff --git a/core/java/com/android/internal/print/DumpUtils.java b/core/java/com/android/internal/print/DumpUtils.java index 28c7fc2182b2..3192d5cbbd1d 100644 --- a/core/java/com/android/internal/print/DumpUtils.java +++ b/core/java/com/android/internal/print/DumpUtils.java @@ -39,7 +39,6 @@ import android.service.print.PrinterCapabilitiesProto; import android.service.print.PrinterIdProto; import android.service.print.PrinterInfoProto; import android.service.print.ResolutionProto; -import android.util.proto.ProtoOutputStream; /** * Utilities for dumping print related proto buffer @@ -49,13 +48,14 @@ public class DumpUtils { * Write a string to a proto if the string is not {@code null}. * * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the string * @param string The string to write */ - public static void writeStringIfNotNull(@NonNull ProtoOutputStream proto, long id, - @Nullable String string) { + public static void writeStringIfNotNull(@NonNull DualDumpOutputStream proto, String idName, + long id, @Nullable String string) { if (string != null) { - proto.write(id, string); + proto.write(idName, id, string); } } @@ -63,14 +63,15 @@ public class DumpUtils { * Write a {@link ComponentName} to a proto. * * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param component The component name to write */ - public static void writeComponentName(@NonNull ProtoOutputStream proto, long id, - @NonNull ComponentName component) { - long token = proto.start(id); - proto.write(ComponentNameProto.PACKAGE_NAME, component.getPackageName()); - proto.write(ComponentNameProto.CLASS_NAME, component.getClassName()); + public static void writeComponentName(@NonNull DualDumpOutputStream proto, String idName, + long id, @NonNull ComponentName component) { + long token = proto.start(idName, id); + proto.write("package_name", ComponentNameProto.PACKAGE_NAME, component.getPackageName()); + proto.write("class_name", ComponentNameProto.CLASS_NAME, component.getClassName()); proto.end(token); } @@ -78,14 +79,16 @@ public class DumpUtils { * Write a {@link PrinterId} to a proto. * * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param printerId The printer id to write */ - public static void writePrinterId(@NonNull ProtoOutputStream proto, long id, + public static void writePrinterId(@NonNull DualDumpOutputStream proto, String idName, long id, @NonNull PrinterId printerId) { - long token = proto.start(id); - writeComponentName(proto, PrinterIdProto.SERVICE_NAME, printerId.getServiceName()); - proto.write(PrinterIdProto.LOCAL_ID, printerId.getLocalId()); + long token = proto.start(idName, id); + writeComponentName(proto, "service_name", PrinterIdProto.SERVICE_NAME, + printerId.getServiceName()); + proto.write("local_id", PrinterIdProto.LOCAL_ID, printerId.getLocalId()); proto.end(token); } @@ -93,71 +96,76 @@ public class DumpUtils { * Write a {@link PrinterCapabilitiesInfo} to a proto. * * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param cap The capabilities to write */ public static void writePrinterCapabilities(@NonNull Context context, - @NonNull ProtoOutputStream proto, long id, @NonNull PrinterCapabilitiesInfo cap) { - long token = proto.start(id); - writeMargins(proto, PrinterCapabilitiesProto.MIN_MARGINS, cap.getMinMargins()); + @NonNull DualDumpOutputStream proto, String idName, long id, + @NonNull PrinterCapabilitiesInfo cap) { + long token = proto.start(idName, id); + writeMargins(proto, "min_margins", PrinterCapabilitiesProto.MIN_MARGINS, + cap.getMinMargins()); int numMediaSizes = cap.getMediaSizes().size(); for (int i = 0; i < numMediaSizes; i++) { - writeMediaSize(context, proto, PrinterCapabilitiesProto.MEDIA_SIZES, + writeMediaSize(context, proto, "media_sizes", PrinterCapabilitiesProto.MEDIA_SIZES, cap.getMediaSizes().get(i)); } int numResolutions = cap.getResolutions().size(); for (int i = 0; i < numResolutions; i++) { - writeResolution(proto, PrinterCapabilitiesProto.RESOLUTIONS, + writeResolution(proto, "resolutions", PrinterCapabilitiesProto.RESOLUTIONS, cap.getResolutions().get(i)); } if ((cap.getColorModes() & PrintAttributes.COLOR_MODE_MONOCHROME) != 0) { - proto.write(PrinterCapabilitiesProto.COLOR_MODES, + proto.write("color_modes", PrinterCapabilitiesProto.COLOR_MODES, PrintAttributesProto.COLOR_MODE_MONOCHROME); } if ((cap.getColorModes() & PrintAttributes.COLOR_MODE_COLOR) != 0) { - proto.write(PrinterCapabilitiesProto.COLOR_MODES, + proto.write("color_modes", PrinterCapabilitiesProto.COLOR_MODES, PrintAttributesProto.COLOR_MODE_COLOR); } if ((cap.getDuplexModes() & PrintAttributes.DUPLEX_MODE_NONE) != 0) { - proto.write(PrinterCapabilitiesProto.DUPLEX_MODES, + proto.write("duplex_modes", PrinterCapabilitiesProto.DUPLEX_MODES, PrintAttributesProto.DUPLEX_MODE_NONE); } if ((cap.getDuplexModes() & PrintAttributes.DUPLEX_MODE_LONG_EDGE) != 0) { - proto.write(PrinterCapabilitiesProto.DUPLEX_MODES, + proto.write("duplex_modes", PrinterCapabilitiesProto.DUPLEX_MODES, PrintAttributesProto.DUPLEX_MODE_LONG_EDGE); } if ((cap.getDuplexModes() & PrintAttributes.DUPLEX_MODE_SHORT_EDGE) != 0) { - proto.write(PrinterCapabilitiesProto.DUPLEX_MODES, + proto.write("duplex_modes", PrinterCapabilitiesProto.DUPLEX_MODES, PrintAttributesProto.DUPLEX_MODE_SHORT_EDGE); } proto.end(token); } - /** * Write a {@link PrinterInfo} to a proto. * * @param context The context used to resolve resources * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param info The printer info to write */ - public static void writePrinterInfo(@NonNull Context context, @NonNull ProtoOutputStream proto, - long id, @NonNull PrinterInfo info) { - long token = proto.start(id); - writePrinterId(proto, PrinterInfoProto.ID, info.getId()); - proto.write(PrinterInfoProto.NAME, info.getName()); - proto.write(PrinterInfoProto.STATUS, info.getStatus()); - proto.write(PrinterInfoProto.DESCRIPTION, info.getDescription()); + public static void writePrinterInfo(@NonNull Context context, + @NonNull DualDumpOutputStream proto, String idName, long id, + @NonNull PrinterInfo info) { + long token = proto.start(idName, id); + writePrinterId(proto, "id", PrinterInfoProto.ID, info.getId()); + proto.write("name", PrinterInfoProto.NAME, info.getName()); + proto.write("status", PrinterInfoProto.STATUS, info.getStatus()); + proto.write("description", PrinterInfoProto.DESCRIPTION, info.getDescription()); PrinterCapabilitiesInfo cap = info.getCapabilities(); if (cap != null) { - writePrinterCapabilities(context, proto, PrinterInfoProto.CAPABILITIES, cap); + writePrinterCapabilities(context, proto, "capabilities", PrinterInfoProto.CAPABILITIES, + cap); } proto.end(token); @@ -168,16 +176,17 @@ public class DumpUtils { * * @param context The context used to resolve resources * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param mediaSize The media size to write */ - public static void writeMediaSize(@NonNull Context context, @NonNull ProtoOutputStream proto, - long id, @NonNull PrintAttributes.MediaSize mediaSize) { - long token = proto.start(id); - proto.write(MediaSizeProto.ID, mediaSize.getId()); - proto.write(MediaSizeProto.LABEL, mediaSize.getLabel(context.getPackageManager())); - proto.write(MediaSizeProto.HEIGHT_MILS, mediaSize.getHeightMils()); - proto.write(MediaSizeProto.WIDTH_MILS, mediaSize.getWidthMils()); + public static void writeMediaSize(@NonNull Context context, @NonNull DualDumpOutputStream proto, + String idName, long id, @NonNull PrintAttributes.MediaSize mediaSize) { + long token = proto.start(idName, id); + proto.write("id", MediaSizeProto.ID, mediaSize.getId()); + proto.write("label", MediaSizeProto.LABEL, mediaSize.getLabel(context.getPackageManager())); + proto.write("height_mils", MediaSizeProto.HEIGHT_MILS, mediaSize.getHeightMils()); + proto.write("width_mils", MediaSizeProto.WIDTH_MILS, mediaSize.getWidthMils()); proto.end(token); } @@ -185,16 +194,17 @@ public class DumpUtils { * Write a {@link PrintAttributes.Resolution} to a proto. * * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param res The resolution to write */ - public static void writeResolution(@NonNull ProtoOutputStream proto, long id, + public static void writeResolution(@NonNull DualDumpOutputStream proto, String idName, long id, @NonNull PrintAttributes.Resolution res) { - long token = proto.start(id); - proto.write(ResolutionProto.ID, res.getId()); - proto.write(ResolutionProto.LABEL, res.getLabel()); - proto.write(ResolutionProto.HORIZONTAL_DPI, res.getHorizontalDpi()); - proto.write(ResolutionProto.VERTICAL_DPI, res.getVerticalDpi()); + long token = proto.start(idName, id); + proto.write("id", ResolutionProto.ID, res.getId()); + proto.write("label", ResolutionProto.LABEL, res.getLabel()); + proto.write("horizontal_DPI", ResolutionProto.HORIZONTAL_DPI, res.getHorizontalDpi()); + proto.write("veritical_DPI", ResolutionProto.VERTICAL_DPI, res.getVerticalDpi()); proto.end(token); } @@ -202,16 +212,17 @@ public class DumpUtils { * Write a {@link PrintAttributes.Margins} to a proto. * * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param margins The margins to write */ - public static void writeMargins(@NonNull ProtoOutputStream proto, long id, + public static void writeMargins(@NonNull DualDumpOutputStream proto, String idName, long id, @NonNull PrintAttributes.Margins margins) { - long token = proto.start(id); - proto.write(MarginsProto.TOP_MILS, margins.getTopMils()); - proto.write(MarginsProto.LEFT_MILS, margins.getLeftMils()); - proto.write(MarginsProto.RIGHT_MILS, margins.getRightMils()); - proto.write(MarginsProto.BOTTOM_MILS, margins.getBottomMils()); + long token = proto.start(idName, id); + proto.write("top_mils", MarginsProto.TOP_MILS, margins.getTopMils()); + proto.write("left_mils", MarginsProto.LEFT_MILS, margins.getLeftMils()); + proto.write("right_mils", MarginsProto.RIGHT_MILS, margins.getRightMils()); + proto.write("bottom_mils", MarginsProto.BOTTOM_MILS, margins.getBottomMils()); proto.end(token); } @@ -220,32 +231,34 @@ public class DumpUtils { * * @param context The context used to resolve resources * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param attributes The attributes to write */ public static void writePrintAttributes(@NonNull Context context, - @NonNull ProtoOutputStream proto, long id, @NonNull PrintAttributes attributes) { - long token = proto.start(id); + @NonNull DualDumpOutputStream proto, String idName, long id, + @NonNull PrintAttributes attributes) { + long token = proto.start(idName, id); PrintAttributes.MediaSize mediaSize = attributes.getMediaSize(); if (mediaSize != null) { - writeMediaSize(context, proto, PrintAttributesProto.MEDIA_SIZE, mediaSize); + writeMediaSize(context, proto, "media_size", PrintAttributesProto.MEDIA_SIZE, mediaSize); } - proto.write(PrintAttributesProto.IS_PORTRAIT, attributes.isPortrait()); + proto.write("is_portrait", PrintAttributesProto.IS_PORTRAIT, attributes.isPortrait()); PrintAttributes.Resolution res = attributes.getResolution(); if (res != null) { - writeResolution(proto, PrintAttributesProto.RESOLUTION, res); + writeResolution(proto, "resolution", PrintAttributesProto.RESOLUTION, res); } PrintAttributes.Margins minMargins = attributes.getMinMargins(); if (minMargins != null) { - writeMargins(proto, PrintAttributesProto.MIN_MARGINS, minMargins); + writeMargins(proto, "min_margings", PrintAttributesProto.MIN_MARGINS, minMargins); } - proto.write(PrintAttributesProto.COLOR_MODE, attributes.getColorMode()); - proto.write(PrintAttributesProto.DUPLEX_MODE, attributes.getDuplexMode()); + proto.write("color_mode", PrintAttributesProto.COLOR_MODE, attributes.getColorMode()); + proto.write("duplex_mode", PrintAttributesProto.DUPLEX_MODE, attributes.getDuplexMode()); proto.end(token); } @@ -253,21 +266,22 @@ public class DumpUtils { * Write a {@link PrintDocumentInfo} to a proto. * * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param info The info to write */ - public static void writePrintDocumentInfo(@NonNull ProtoOutputStream proto, long id, - @NonNull PrintDocumentInfo info) { - long token = proto.start(id); - proto.write(PrintDocumentInfoProto.NAME, info.getName()); + public static void writePrintDocumentInfo(@NonNull DualDumpOutputStream proto, String idName, + long id, @NonNull PrintDocumentInfo info) { + long token = proto.start(idName, id); + proto.write("name", PrintDocumentInfoProto.NAME, info.getName()); int pageCount = info.getPageCount(); if (pageCount != PrintDocumentInfo.PAGE_COUNT_UNKNOWN) { - proto.write(PrintDocumentInfoProto.PAGE_COUNT, pageCount); + proto.write("page_count", PrintDocumentInfoProto.PAGE_COUNT, pageCount); } - proto.write(PrintDocumentInfoProto.CONTENT_TYPE, info.getContentType()); - proto.write(PrintDocumentInfoProto.DATA_SIZE, info.getDataSize()); + proto.write("content_type", PrintDocumentInfoProto.CONTENT_TYPE, info.getContentType()); + proto.write("data_size", PrintDocumentInfoProto.DATA_SIZE, info.getDataSize()); proto.end(token); } @@ -275,14 +289,15 @@ public class DumpUtils { * Write a {@link PageRange} to a proto. * * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param range The range to write */ - public static void writePageRange(@NonNull ProtoOutputStream proto, long id, + public static void writePageRange(@NonNull DualDumpOutputStream proto, String idName, long id, @NonNull PageRange range) { - long token = proto.start(id); - proto.write(PageRangeProto.START, range.getStart()); - proto.write(PageRangeProto.END, range.getEnd()); + long token = proto.start(idName, id); + proto.write("start", PageRangeProto.START, range.getStart()); + proto.write("end", PageRangeProto.END, range.getEnd()); proto.end(token); } @@ -291,64 +306,70 @@ public class DumpUtils { * * @param context The context used to resolve resources * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param printJobInfo The print job info to write */ - public static void writePrintJobInfo(@NonNull Context context, @NonNull ProtoOutputStream proto, - long id, @NonNull PrintJobInfo printJobInfo) { - long token = proto.start(id); - proto.write(PrintJobInfoProto.LABEL, printJobInfo.getLabel()); + public static void writePrintJobInfo(@NonNull Context context, + @NonNull DualDumpOutputStream proto, String idName, long id, + @NonNull PrintJobInfo printJobInfo) { + long token = proto.start(idName, id); + proto.write("label", PrintJobInfoProto.LABEL, printJobInfo.getLabel()); PrintJobId printJobId = printJobInfo.getId(); if (printJobId != null) { - proto.write(PrintJobInfoProto.PRINT_JOB_ID, printJobId.flattenToString()); + proto.write("print_job_id", PrintJobInfoProto.PRINT_JOB_ID, + printJobId.flattenToString()); } int state = printJobInfo.getState(); if (state >= PrintJobInfoProto.STATE_CREATED && state <= PrintJobInfoProto.STATE_CANCELED) { - proto.write(PrintJobInfoProto.STATE, state); + proto.write("state", PrintJobInfoProto.STATE, state); } else { - proto.write(PrintJobInfoProto.STATE, PrintJobInfoProto.STATE_UNKNOWN); + proto.write("state", PrintJobInfoProto.STATE, PrintJobInfoProto.STATE_UNKNOWN); } PrinterId printer = printJobInfo.getPrinterId(); if (printer != null) { - writePrinterId(proto, PrintJobInfoProto.PRINTER, printer); + writePrinterId(proto, "printer", PrintJobInfoProto.PRINTER, printer); } String tag = printJobInfo.getTag(); if (tag != null) { - proto.write(PrintJobInfoProto.TAG, tag); + proto.write("tag", PrintJobInfoProto.TAG, tag); } - proto.write(PrintJobInfoProto.CREATION_TIME, printJobInfo.getCreationTime()); + proto.write("creation_time", PrintJobInfoProto.CREATION_TIME, + printJobInfo.getCreationTime()); PrintAttributes attributes = printJobInfo.getAttributes(); if (attributes != null) { - writePrintAttributes(context, proto, PrintJobInfoProto.ATTRIBUTES, attributes); + writePrintAttributes(context, proto, "attributes", PrintJobInfoProto.ATTRIBUTES, + attributes); } PrintDocumentInfo docInfo = printJobInfo.getDocumentInfo(); if (docInfo != null) { - writePrintDocumentInfo(proto, PrintJobInfoProto.DOCUMENT_INFO, docInfo); + writePrintDocumentInfo(proto, "document_info", PrintJobInfoProto.DOCUMENT_INFO, + docInfo); } - proto.write(PrintJobInfoProto.IS_CANCELING, printJobInfo.isCancelling()); + proto.write("is_canceling", PrintJobInfoProto.IS_CANCELING, printJobInfo.isCancelling()); PageRange[] pages = printJobInfo.getPages(); if (pages != null) { for (int i = 0; i < pages.length; i++) { - writePageRange(proto, PrintJobInfoProto.PAGES, pages[i]); + writePageRange(proto, "pages", PrintJobInfoProto.PAGES, pages[i]); } } - proto.write(PrintJobInfoProto.HAS_ADVANCED_OPTIONS, + proto.write("has_advanced_options", PrintJobInfoProto.HAS_ADVANCED_OPTIONS, printJobInfo.getAdvancedOptions() != null); - proto.write(PrintJobInfoProto.PROGRESS, printJobInfo.getProgress()); + proto.write("progress", PrintJobInfoProto.PROGRESS, printJobInfo.getProgress()); CharSequence status = printJobInfo.getStatus(context.getPackageManager()); if (status != null) { - proto.write(PrintJobInfoProto.STATUS, status.toString()); + proto.write("status", PrintJobInfoProto.STATUS, status.toString()); } proto.end(token); diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index 31d22e0f92fd..b2bab6f7d58f 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -19,9 +19,9 @@ package com.android.internal.widget; import android.app.PendingIntent; import android.app.trust.IStrongAuthTracker; import android.os.Bundle; -import android.security.keystore.EntryRecoveryData; -import android.security.keystore.RecoveryData; -import android.security.keystore.RecoveryMetadata; +import android.security.keystore.WrappedApplicationKey; +import android.security.keystore.KeychainSnapshot; +import android.security.keystore.KeychainProtectionParameter; import com.android.internal.widget.ICheckCredentialProgressCallback; import com.android.internal.widget.VerifyCredentialResponse; @@ -64,7 +64,7 @@ interface ILockSettings { // {@code ServiceSpecificException} may be thrown to signal an error, which caller can // convert to {@code RecoveryManagerException}. void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList); - RecoveryData getRecoveryData(in byte[] account); + KeychainSnapshot getRecoveryData(in byte[] account); byte[] generateAndStoreKey(String alias); void removeKey(String alias); void setSnapshotCreatedPendingIntent(in PendingIntent intent); @@ -75,10 +75,10 @@ interface ILockSettings { void setRecoverySecretTypes(in int[] secretTypes); int[] getRecoverySecretTypes(); int[] getPendingRecoverySecretTypes(); - void recoverySecretAvailable(in RecoveryMetadata recoverySecret); + void recoverySecretAvailable(in KeychainProtectionParameter recoverySecret); byte[] startRecoverySession(in String sessionId, in byte[] verifierPublicKey, in byte[] vaultParams, in byte[] vaultChallenge, - in List<RecoveryMetadata> secrets); + in List<KeychainProtectionParameter> secrets); Map/*<String, byte[]>*/ recoverKeys(in String sessionId, in byte[] recoveryKeyBlob, - in List<EntryRecoveryData> applicationKeys); + in List<WrappedApplicationKey> applicationKeys); } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index b3f66e9652f6..96f3308ec178 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -84,7 +84,7 @@ cc_library_shared { "android_view_VelocityTracker.cpp", "android_text_AndroidCharacter.cpp", "android_text_Hyphenator.cpp", - "android_text_MeasuredText.cpp", + "android_text_MeasuredParagraph.cpp", "android_text_StaticLayout.cpp", "android_os_Debug.cpp", "android_os_GraphicsEnvironment.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 6d7fe056acdf..6569b4783e67 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -178,7 +178,7 @@ extern int register_android_net_LocalSocketImpl(JNIEnv* env); extern int register_android_net_NetworkUtils(JNIEnv* env); extern int register_android_text_AndroidCharacter(JNIEnv *env); extern int register_android_text_Hyphenator(JNIEnv *env); -extern int register_android_text_MeasuredText(JNIEnv* env); +extern int register_android_text_MeasuredParagraph(JNIEnv* env); extern int register_android_text_StaticLayout(JNIEnv *env); extern int register_android_opengl_classes(JNIEnv *env); extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env); @@ -1342,7 +1342,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_content_XmlBlock), REG_JNI(register_android_text_AndroidCharacter), REG_JNI(register_android_text_Hyphenator), - REG_JNI(register_android_text_MeasuredText), + REG_JNI(register_android_text_MeasuredParagraph), REG_JNI(register_android_text_StaticLayout), REG_JNI(register_android_view_InputDevice), REG_JNI(register_android_view_KeyCharacterMap), diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index 79aa5acac4ee..685fcaf15211 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -237,10 +237,22 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, // Create the codec. NinePatchPeeker peeker; - std::unique_ptr<SkAndroidCodec> codec = SkAndroidCodec::MakeFromStream( - std::move(stream), &peeker); - if (!codec.get()) { - return nullObjectReturn("SkAndroidCodec::MakeFromStream returned null"); + std::unique_ptr<SkAndroidCodec> codec; + { + SkCodec::Result result; + std::unique_ptr<SkCodec> c = SkCodec::MakeFromStream(std::move(stream), &result, + &peeker); + if (!c) { + SkString msg; + msg.printf("Failed to create image decoder with message '%s'", + SkCodec::ResultToString(result)); + return nullObjectReturn(msg.c_str()); + } + + codec = SkAndroidCodec::MakeFromCodec(std::move(c)); + if (!codec) { + return nullObjectReturn("SkAndroidCodec::MakeFromCodec returned null"); + } } // Do not allow ninepatch decodes to 565. In the past, decodes to 565 diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp index a0a4be4590be..249202a117e1 100644 --- a/core/jni/android/graphics/ImageDecoder.cpp +++ b/core/jni/android/graphics/ImageDecoder.cpp @@ -77,11 +77,27 @@ static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) { return nullptr; } std::unique_ptr<ImageDecoder> decoder(new ImageDecoder); - decoder->mCodec = SkAndroidCodec::MakeFromStream(std::move(stream), &decoder->mPeeker); + SkCodec::Result result; + auto codec = SkCodec::MakeFromStream(std::move(stream), &result, &decoder->mPeeker); + if (!codec) { + switch (result) { + case SkCodec::kIncompleteInput: + env->ThrowNew(gIncomplete_class, "Incomplete input"); + break; + default: + SkString msg; + msg.printf("Failed to create image decoder with message '%s'", + SkCodec::ResultToString(result)); + doThrowIOE(env, msg.c_str()); + break; + } + + return nullptr; + } + + decoder->mCodec = SkAndroidCodec::MakeFromCodec(std::move(codec)); if (!decoder->mCodec.get()) { - // FIXME: (b/71578461) Use the error message from - // SkCodec::MakeFromStream to report a more informative error message. - doThrowIOE(env, "Failed to create an SkCodec"); + doThrowIOE(env, "Could not create AndroidCodec"); return nullptr; } @@ -160,11 +176,6 @@ static jobject ImageDecoder_nCreateByteArray(JNIEnv* env, jobject /*clazz*/, jby return native_create(env, std::move(stream)); } -static bool supports_any_down_scale(const SkAndroidCodec* codec) { - return codec->getEncodedFormat() == SkEncodedImageFormat::kWEBP; -} - -// This method should never return null. Instead, it should throw an exception. static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, jobject jcallback, jobject jpostProcess, jint desiredWidth, jint desiredHeight, jobject jsubset, @@ -173,33 +184,14 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong jboolean asAlphaMask) { auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr); SkAndroidCodec* codec = decoder->mCodec.get(); - SkImageInfo decodeInfo = codec->getInfo(); - bool scale = false; - int sampleSize = 1; - if (desiredWidth != decodeInfo.width() || desiredHeight != decodeInfo.height()) { - bool match = false; - if (desiredWidth < decodeInfo.width() && desiredHeight < decodeInfo.height()) { - if (supports_any_down_scale(codec)) { - match = true; - decodeInfo = decodeInfo.makeWH(desiredWidth, desiredHeight); - } else { - int sampleX = decodeInfo.width() / desiredWidth; - int sampleY = decodeInfo.height() / desiredHeight; - sampleSize = std::min(sampleX, sampleY); - SkISize sampledSize = codec->getSampledDimensions(sampleSize); - decodeInfo = decodeInfo.makeWH(sampledSize.width(), sampledSize.height()); - if (decodeInfo.width() == desiredWidth && decodeInfo.height() == desiredHeight) { - match = true; - } - } - } - if (!match) { - scale = true; - if (requireUnpremul && kOpaque_SkAlphaType != decodeInfo.alphaType()) { - doThrowISE(env, "Cannot scale unpremultiplied pixels!"); - return nullptr; - } - } + const SkISize desiredSize = SkISize::Make(desiredWidth, desiredHeight); + SkISize decodeSize = desiredSize; + const int sampleSize = codec->computeSampleSize(&decodeSize); + const bool scale = desiredSize != decodeSize; + SkImageInfo decodeInfo = codec->getInfo().makeWH(decodeSize.width(), decodeSize.height()); + if (scale && requireUnpremul && kOpaque_SkAlphaType != decodeInfo.alphaType()) { + doThrowISE(env, "Cannot scale unpremultiplied pixels!"); + return nullptr; } switch (decodeInfo.alphaType()) { diff --git a/core/jni/android_text_MeasuredText.cpp b/core/jni/android_text_MeasuredParagraph.cpp index af9d13122201..bdae0b22987f 100644 --- a/core/jni/android_text_MeasuredText.cpp +++ b/core/jni/android_text_MeasuredParagraph.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "MeasuredText" +#define LOG_TAG "MeasuredParagraph" #include "ScopedIcuLocale.h" #include "unicode/locid.h" @@ -49,7 +49,7 @@ static inline Paint* toPaint(jlong ptr) { return reinterpret_cast<Paint*>(ptr); } -static inline minikin::MeasuredText* toMeasuredText(jlong ptr) { +static inline minikin::MeasuredText* toMeasuredParagraph(jlong ptr) { return reinterpret_cast<minikin::MeasuredText*>(ptr); } @@ -57,8 +57,8 @@ template<typename Ptr> static inline jlong toJLong(Ptr ptr) { return reinterpret_cast<jlong>(ptr); } -static void releaseMeasuredText(jlong measuredTextPtr) { - delete toMeasuredText(measuredTextPtr); +static void releaseMeasuredParagraph(jlong measuredTextPtr) { + delete toMeasuredParagraph(measuredTextPtr); } // Regular JNI @@ -84,7 +84,7 @@ static void nAddReplacementRun(JNIEnv* /* unused */, jclass /* unused */, jlong } // Regular JNI -static jlong nBuildNativeMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr, +static jlong nBuildNativeMeasuredParagraph(JNIEnv* env, jclass /* unused */, jlong builderPtr, jcharArray javaText) { ScopedCharArrayRO text(env, javaText); const minikin::U16StringPiece textBuffer(text.get(), text.size()); @@ -100,23 +100,23 @@ static void nFreeBuilder(JNIEnv* env, jclass /* unused */, jlong builderPtr) { // CriticalNative static jlong nGetReleaseFunc() { - return toJLong(&releaseMeasuredText); + return toJLong(&releaseMeasuredParagraph); } static const JNINativeMethod gMethods[] = { - // MeasuredTextBuilder native functions. + // MeasuredParagraphBuilder native functions. {"nInitBuilder", "()J", (void*) nInitBuilder}, {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun}, {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun}, - {"nBuildNativeMeasuredText", "(J[C)J", (void*) nBuildNativeMeasuredText}, + {"nBuildNativeMeasuredParagraph", "(J[C)J", (void*) nBuildNativeMeasuredParagraph}, {"nFreeBuilder", "(J)V", (void*) nFreeBuilder}, - // MeasuredText native functions. + // MeasuredParagraph native functions. {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc}, // Critical Natives }; -int register_android_text_MeasuredText(JNIEnv* env) { - return RegisterMethodsOrDie(env, "android/text/MeasuredText", gMethods, NELEM(gMethods)); +int register_android_text_MeasuredParagraph(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/text/MeasuredParagraph", gMethods, NELEM(gMethods)); } } diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp index b5c23dfa873d..682dc8739869 100644 --- a/core/jni/android_text_StaticLayout.cpp +++ b/core/jni/android_text_StaticLayout.cpp @@ -174,7 +174,7 @@ static const JNINativeMethod gMethods[] = { // Inputs "[C" // text - "J" // MeasuredText ptr. + "J" // MeasuredParagraph ptr. "I" // length "F" // firstWidth "I" // firstWidthLineCount diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto index d4bdb9b577dc..3ffb254b2333 100644 --- a/core/proto/android/providers/settings.proto +++ b/core/proto/android/providers/settings.proto @@ -389,8 +389,9 @@ message GlobalSettingsProto { optional SettingProto notification_snooze_options = 341; optional SettingProto enable_gnss_raw_meas_full_tracking = 346; optional SettingProto zram_enabled = 347; + optional SettingProto enable_smart_replies_in_notifications = 348; - // Next tag = 348; + // Next tag = 349; } message SecureSettingsProto { diff --git a/core/proto/android/server/forceappstandbytracker.proto b/core/proto/android/server/forceappstandbytracker.proto index 8753bf768321..c9f7d52ae83f 100644 --- a/core/proto/android/server/forceappstandbytracker.proto +++ b/core/proto/android/server/forceappstandbytracker.proto @@ -41,4 +41,13 @@ message ForceAppStandbyTrackerProto { // Packages that are disallowed OP_RUN_ANY_IN_BACKGROUND. repeated RunAnyInBackgroundRestrictedPackages run_any_in_background_restricted_packages = 5; + + // Whether device is a small battery device + optional bool is_small_battery_device = 6; + + // Whether force app standby for small battery device setting is enabled + optional bool force_all_apps_standby_for_small_battery = 7; + + // Whether device is charging + optional bool is_charging = 8; } diff --git a/core/proto/android/view/windowlayoutparams.proto b/core/proto/android/view/windowlayoutparams.proto index b81cd1f50f91..f079e1e1b5e0 100644 --- a/core/proto/android/view/windowlayoutparams.proto +++ b/core/proto/android/view/windowlayoutparams.proto @@ -58,7 +58,6 @@ message WindowLayoutParamsProto { optional NeedsMenuState needs_menu_key = 22; optional .android.view.DisplayProto.ColorMode color_mode = 23; optional uint32 flags = 24; - optional uint64 flags_extra = 25; optional uint32 private_flags = 26; optional uint32 system_ui_visibility_flags = 27; optional uint32 subtree_system_ui_visibility_flags = 28; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index a3e8f1ebd3ff..d2a22d0794b6 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3692,6 +3692,12 @@ <permission android:name="android.permission.MODIFY_QUIET_MODE" android:protectionLevel="signature|privileged" /> + <!-- Allows an application to control remote animations. See + {@link ActivityOptions#makeRemoteAnimation} + @hide <p>Not for use by third-party applications. --> + <permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" + android:protectionLevel="signature|privileged" /> + <application android:process="system" android:persistent="true" android:hasCode="false" diff --git a/core/res/res/layout/autofill_dataset_picker.xml b/core/res/res/layout/autofill_dataset_picker.xml index 528efca49fc3..a88836eff5a0 100644 --- a/core/res/res/layout/autofill_dataset_picker.xml +++ b/core/res/res/layout/autofill_dataset_picker.xml @@ -24,6 +24,8 @@ android:id="@+id/autofill_dataset_list" android:layout_width="fill_parent" android:layout_height="fill_parent" + android:drawSelectorOnTop="true" + android:clickable="true" android:divider="@null" android:visibility="gone"> </ListView> diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml index 353a1a57fda5..445b19b5d01c 100644 --- a/core/res/res/layout/notification_template_material_base.xml +++ b/core/res/res/layout/notification_template_material_base.xml @@ -39,6 +39,10 @@ android:layout_height="@dimen/notification_progress_bar_height" android:layout_marginTop="@dimen/notification_progress_margin_top" layout="@layout/notification_template_progress" /> + <include layout="@layout/notification_template_smart_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_content_margin_bottom" /> </LinearLayout> <include layout="@layout/notification_template_right_icon" /> </FrameLayout> diff --git a/core/res/res/layout/notification_template_material_big_base.xml b/core/res/res/layout/notification_template_material_big_base.xml index 6b1049a0cb82..d47bff6403ec 100644 --- a/core/res/res/layout/notification_template_material_big_base.xml +++ b/core/res/res/layout/notification_template_material_big_base.xml @@ -56,6 +56,12 @@ android:id="@+id/notification_material_reply_container" android:layout_width="match_parent" android:layout_height="wrap_content" /> + <include layout="@layout/notification_template_smart_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_content_margin_start" + android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginBottom="@dimen/notification_content_margin_bottom" /> </LinearLayout> <include layout="@layout/notification_material_action_list" /> </FrameLayout> diff --git a/core/res/res/layout/notification_template_material_big_picture.xml b/core/res/res/layout/notification_template_material_big_picture.xml index e94e646fa581..76c0a676b6f3 100644 --- a/core/res/res/layout/notification_template_material_big_picture.xml +++ b/core/res/res/layout/notification_template_material_big_picture.xml @@ -64,6 +64,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> + <include layout="@layout/notification_template_smart_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_content_margin_start" + android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginBottom="@dimen/notification_content_margin_bottom" /> </LinearLayout> <include layout="@layout/notification_material_action_list" /> </FrameLayout> diff --git a/core/res/res/layout/notification_template_material_big_text.xml b/core/res/res/layout/notification_template_material_big_text.xml index 3c87f92639bb..ac4c052d6141 100644 --- a/core/res/res/layout/notification_template_material_big_text.xml +++ b/core/res/res/layout/notification_template_material_big_text.xml @@ -67,6 +67,12 @@ android:id="@+id/notification_material_reply_container" android:layout_width="match_parent" android:layout_height="wrap_content" /> + <include layout="@layout/notification_template_smart_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_content_margin_start" + android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginBottom="@dimen/notification_content_margin_bottom" /> </LinearLayout> <include layout="@layout/notification_material_action_list" /> <include layout="@layout/notification_template_right_icon" /> diff --git a/core/res/res/layout/notification_template_material_inbox.xml b/core/res/res/layout/notification_template_material_inbox.xml index e4c91a458220..718cf16a1fe9 100644 --- a/core/res/res/layout/notification_template_material_inbox.xml +++ b/core/res/res/layout/notification_template_material_inbox.xml @@ -119,6 +119,12 @@ android:id="@+id/notification_material_reply_container" android:layout_width="match_parent" android:layout_height="wrap_content" /> + <include layout="@layout/notification_template_smart_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_content_margin_start" + android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginBottom="@dimen/notification_content_margin_bottom" /> </LinearLayout> <include layout="@layout/notification_material_action_list" /> <include layout="@layout/notification_template_right_icon" /> diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml index a72ad538a85c..34f5ae8133be 100644 --- a/core/res/res/layout/notification_template_material_messaging.xml +++ b/core/res/res/layout/notification_template_material_messaging.xml @@ -47,6 +47,10 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:spacing="@dimen/notification_messaging_spacing" /> + <include layout="@layout/notification_template_smart_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_content_margin_bottom" /> </LinearLayout> </LinearLayout> <include layout="@layout/notification_material_action_list" /> diff --git a/core/res/res/layout/notification_template_smart_reply_container.xml b/core/res/res/layout/notification_template_smart_reply_container.xml new file mode 100644 index 000000000000..637241eba982 --- /dev/null +++ b/core/res/res/layout/notification_template_smart_reply_container.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/smart_reply_container" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <!-- SmartReplyView will be added here. --> +</LinearLayout>
\ No newline at end of file diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml index 04131009b141..f8a77f8f1c32 100644 --- a/core/res/res/values/colors_material.xml +++ b/core/res/res/values/colors_material.xml @@ -77,9 +77,9 @@ <item name="secondary_content_alpha_material_dark" format="float" type="dimen">.7</item> <item name="secondary_content_alpha_material_light" format="float" type="dimen">0.54</item> - <item name="highlight_alpha_material_light" format="float" type="dimen">0.12</item> - <item name="highlight_alpha_material_dark" format="float" type="dimen">0.20</item> - <item name="highlight_alpha_material_colored" format="float" type="dimen">0.26</item> + <item name="highlight_alpha_material_light" format="float" type="dimen">0.16</item> + <item name="highlight_alpha_material_dark" format="float" type="dimen">0.32</item> + <item name="highlight_alpha_material_colored" format="float" type="dimen">0.48</item> <!-- Primary & accent colors --> <eat-comment /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index e3a910f5cc76..3b02a967a4b2 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3249,4 +3249,7 @@ <string name="config_fontFamilyButton">@string/font_family_button_material</string> <string translatable="false" name="config_batterySaverDeviceSpecificConfig"></string> + + <!-- Package name that should be granted Notification Assistant access --> + <string name="config_defaultAssistantAccessPackage" translatable="false">android.ext.services</string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index f4ced5821e53..638f1b25b4a5 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2550,6 +2550,7 @@ <java-symbol type="bool" name="config_mainBuiltInDisplayIsRound" /> <java-symbol type="id" name="actions_container" /> + <java-symbol type="id" name="smart_reply_container" /> <java-symbol type="id" name="remote_input_tag" /> <java-symbol type="attr" name="seekBarDialogPreferenceStyle" /> @@ -3215,4 +3216,6 @@ <java-symbol type="string" name="harmful_app_warning_uninstall" /> <java-symbol type="string" name="harmful_app_warning_launch_anyway" /> <java-symbol type="string" name="harmful_app_warning_title" /> + + <java-symbol type="string" name="config_defaultAssistantAccessPackage" /> </resources> diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 410bee025df4..d5689c7539a1 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -204,6 +204,7 @@ public class SettingsBackupTest { Settings.Global.ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE, Settings.Global.ENABLE_DISKSTATS_LOGGING, Settings.Global.ENABLE_EPHEMERAL_FEATURE, + Settings.Global.ENABLE_SMART_REPLIES_IN_NOTIFICATIONS, Settings.Global.ENHANCED_4G_MODE_ENABLED, Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES, Settings.Global.ERROR_LOGCAT_PREFIX, @@ -212,6 +213,7 @@ public class SettingsBackupTest { Settings.Global.FANCY_IME_ANIMATIONS, Settings.Global.FORCE_ALLOW_ON_EXTERNAL, Settings.Global.FORCED_APP_STANDBY_ENABLED, + Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED, Settings.Global.FSTRIM_MANDATORY_INTERVAL, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST, Settings.Global.GLOBAL_HTTP_PROXY_HOST, diff --git a/core/tests/coretests/src/android/text/MeasuredTextTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java index ddef0c651b31..5d33397e13f2 100644 --- a/core/tests/coretests/src/android/text/MeasuredTextTest.java +++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java @@ -31,7 +31,7 @@ import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidJUnit4.class) -public class MeasuredTextTest { +public class MeasuredParagraphTest { private static final TextDirectionHeuristic LTR = TextDirectionHeuristics.LTR; private static final TextDirectionHeuristic RTL = TextDirectionHeuristics.RTL; @@ -60,9 +60,9 @@ public class MeasuredTextTest { @Test public void buildForBidi() { - MeasuredText mt = null; + MeasuredParagraph mt = null; - mt = MeasuredText.buildForBidi("XXX", 0, 3, LTR, null); + mt = MeasuredParagraph.buildForBidi("XXX", 0, 3, LTR, null); assertNotNull(mt); assertNotNull(mt.getChars()); assertEquals("XXX", charsToString(mt.getChars())); @@ -75,7 +75,7 @@ public class MeasuredTextTest { assertEquals(0, mt.getNativePtr()); // Recycle it - MeasuredText mt2 = MeasuredText.buildForBidi("_VVV_", 1, 4, RTL, mt); + MeasuredParagraph mt2 = MeasuredParagraph.buildForBidi("_VVV_", 1, 4, RTL, mt); assertEquals(mt2, mt); assertNotNull(mt2.getChars()); assertEquals("VVV", charsToString(mt.getChars())); @@ -91,9 +91,9 @@ public class MeasuredTextTest { @Test public void buildForMeasurement() { - MeasuredText mt = null; + MeasuredParagraph mt = null; - mt = MeasuredText.buildForMeasurement(PAINT, "XXX", 0, 3, LTR, null); + mt = MeasuredParagraph.buildForMeasurement(PAINT, "XXX", 0, 3, LTR, null); assertNotNull(mt); assertNotNull(mt.getChars()); assertEquals("XXX", charsToString(mt.getChars())); @@ -109,7 +109,8 @@ public class MeasuredTextTest { assertEquals(0, mt.getNativePtr()); // Recycle it - MeasuredText mt2 = MeasuredText.buildForMeasurement(PAINT, "_VVV_", 1, 4, RTL, mt); + MeasuredParagraph mt2 = + MeasuredParagraph.buildForMeasurement(PAINT, "_VVV_", 1, 4, RTL, mt); assertEquals(mt2, mt); assertNotNull(mt2.getChars()); assertEquals("VVV", charsToString(mt.getChars())); @@ -129,9 +130,9 @@ public class MeasuredTextTest { @Test public void buildForStaticLayout() { - MeasuredText mt = null; + MeasuredParagraph mt = null; - mt = MeasuredText.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, null); + mt = MeasuredParagraph.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, null); assertNotNull(mt); assertNotNull(mt.getChars()); assertEquals("XXX", charsToString(mt.getChars())); @@ -145,7 +146,8 @@ public class MeasuredTextTest { assertNotEquals(0, mt.getNativePtr()); // Recycle it - MeasuredText mt2 = MeasuredText.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, mt); + MeasuredParagraph mt2 = + MeasuredParagraph.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, mt); assertEquals(mt2, mt); assertNotNull(mt2.getChars()); assertEquals("VVV", charsToString(mt.getChars())); @@ -163,6 +165,6 @@ public class MeasuredTextTest { @Test public void testFor70146381() { - MeasuredText.buildForMeasurement(PAINT, "X…", 0, 2, RTL, null); + MeasuredParagraph.buildForMeasurement(PAINT, "X…", 0, 2, RTL, null); } } diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 4732beca6394..c0958cd6cdd7 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -75,6 +75,7 @@ applications that come with the platform <privapp-permissions package="com.android.launcher3"> <permission name="android.permission.BIND_APPWIDGET"/> + <permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/> <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/> </privapp-permissions> diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index 419e2b7e4818..d13e05c22509 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -239,11 +239,12 @@ public final class ImageDecoder implements AutoCloseable { }; /** - * Supplied to onPartialImage if the provided data is incomplete. + * Used if the provided data is incomplete. * - * Will never be thrown by ImageDecoder. + * May be thrown if there is nothing to display. * - * There may be a partial image to display. + * If supplied to onPartialImage, there may be a correct partial image to + * display. */ public static class IncompleteException extends IOException {}; diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java index dea194e4ffde..4571553d4d4e 100644 --- a/graphics/java/android/graphics/drawable/RippleBackground.java +++ b/graphics/java/android/graphics/drawable/RippleBackground.java @@ -16,17 +16,12 @@ package android.graphics.drawable; -import android.animation.Animator; -import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.graphics.Canvas; -import android.graphics.CanvasProperty; import android.graphics.Paint; import android.graphics.Rect; import android.util.FloatProperty; -import android.view.DisplayListCanvas; -import android.view.RenderNodeAnimator; import android.view.animation.LinearInterpolator; /** @@ -78,8 +73,8 @@ class RippleBackground extends RippleComponent { private void onStateChanged(boolean animateChanged) { float newOpacity = 0.0f; - if (mHovered) newOpacity += 1.0f; - if (mFocused) newOpacity += 1.0f; + if (mHovered) newOpacity += .25f; + if (mFocused) newOpacity += .75f; if (mAnimator != null) { mAnimator.cancel(); mAnimator = null; diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index 734cff542c51..b883656d784a 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -264,8 +264,8 @@ public class RippleDrawable extends LayerDrawable { } setRippleActive(enabled && pressed); - setBackgroundActive(hovered, focused); + return changed; } @@ -879,22 +879,18 @@ public class RippleDrawable extends LayerDrawable { // Grab the color for the current state and cut the alpha channel in // half so that the ripple and background together yield full alpha. final int color = mState.mColor.getColorForState(getState(), Color.BLACK); - final int halfAlpha = (Color.alpha(color) / 2) << 24; final Paint p = mRipplePaint; if (mMaskColorFilter != null) { // The ripple timing depends on the paint's alpha value, so we need // to push just the alpha channel into the paint and let the filter // handle the full-alpha color. - final int fullAlphaColor = color | (0xFF << 24); - mMaskColorFilter.setColor(fullAlphaColor); - - p.setColor(halfAlpha); + mMaskColorFilter.setColor(color | 0xFF000000); + p.setColor(color & 0xFF000000); p.setColorFilter(mMaskColorFilter); p.setShader(mMaskShader); } else { - final int halfAlphaColor = (color & 0xFFFFFF) | halfAlpha; - p.setColor(halfAlphaColor); + p.setColor(color); p.setColorFilter(null); p.setShader(null); } diff --git a/media/java/android/media/update/ApiLoader.java b/media/java/android/media/update/ApiLoader.java index b57e02d559e0..07483f60c69e 100644 --- a/media/java/android/media/update/ApiLoader.java +++ b/media/java/android/media/update/ApiLoader.java @@ -49,8 +49,8 @@ public final class ApiLoader { Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY); sMediaLibrary = libContext.getClassLoader() .loadClass(UPDATE_CLASS) - .getMethod(UPDATE_METHOD, Context.class) - .invoke(null, appContext); + .getMethod(UPDATE_METHOD, Context.class, Context.class) + .invoke(null, appContext, libContext); return sMediaLibrary; } } diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java index 19f01c2bcc7f..a1e2404ca78a 100644 --- a/media/java/android/media/update/StaticProvider.java +++ b/media/java/android/media/update/StaticProvider.java @@ -18,6 +18,7 @@ package android.media.update; import android.annotation.SystemApi; import android.widget.MediaController2; +import android.widget.VideoView2; /** * Interface for connecting the public API to an updatable implementation. @@ -31,4 +32,5 @@ import android.widget.MediaController2; public interface StaticProvider { MediaController2Provider createMediaController2( MediaController2 instance, ViewProvider superProvider); + VideoView2Provider createVideoView2(VideoView2 instance, ViewProvider superProvider); } diff --git a/media/java/android/media/update/VideoView2Provider.java b/media/java/android/media/update/VideoView2Provider.java new file mode 100644 index 000000000000..6fc9bdc64e02 --- /dev/null +++ b/media/java/android/media/update/VideoView2Provider.java @@ -0,0 +1,66 @@ +/* + * Copyright 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 android.media.update; + +import android.media.AudioAttributes; +import android.media.MediaPlayer; +import android.net.Uri; +import android.widget.MediaController2; +import android.widget.VideoView2; + +import java.util.Map; + +/** + * Interface for connecting the public API to an updatable implementation. + * + * Each instance object is connected to one corresponding updatable object which implements the + * runtime behavior of that class. There should a corresponding provider method for all public + * methods. + * + * All methods behave as per their namesake in the public API. + * + * @see android.widget.VideoView2 + * + * @hide + */ +// TODO @SystemApi +public interface VideoView2Provider extends ViewProvider { + void start_impl(); + void pause_impl(); + int getDuration_impl(); + int getCurrentPosition_impl(); + void seekTo_impl(int msec); + boolean isPlaying_impl(); + int getBufferPercentage_impl(); + int getAudioSessionId_impl(); + void showSubtitle_impl(); + void hideSubtitle_impl(); + void setAudioFocusRequest_impl(int focusGain); + void setAudioAttributes_impl(AudioAttributes attributes); + void setVideoPath_impl(String path); + void setVideoURI_impl(Uri uri); + void setVideoURI_impl(Uri uri, Map<String, String> headers); + void setMediaController2_impl(MediaController2 controllerView); + void setViewType_impl(int viewType); + int getViewType_impl(); + void stopPlayback_impl(); + void setOnPreparedListener_impl(MediaPlayer.OnPreparedListener l); + void setOnCompletionListener_impl(MediaPlayer.OnCompletionListener l); + void setOnErrorListener_impl(MediaPlayer.OnErrorListener l); + void setOnInfoListener_impl(MediaPlayer.OnInfoListener l); + void setOnViewTypeChangedListener_impl(VideoView2.OnViewTypeChangedListener l); +} diff --git a/native/android/net.c b/native/android/net.c index de4b90cc1956..60296a7bd00c 100644 --- a/native/android/net.c +++ b/native/android/net.c @@ -27,7 +27,7 @@ static int getnetidfromhandle(net_handle_t handle, unsigned *netid) { static const uint32_t k32BitMask = 0xffffffff; // This value MUST be kept in sync with the corresponding value in // the android.net.Network#getNetworkHandle() implementation. - static const uint32_t kHandleMagic = 0xfacade; + static const uint32_t kHandleMagic = 0xcafed00d; // Check for minimum acceptable version of the API in the low bits. if (handle != NETWORK_UNSPECIFIED && diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java index e0a3f6cb31ca..6c7441802d63 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java +++ b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java @@ -59,7 +59,9 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.logging.MetricsLogger; import com.android.internal.os.HandlerCaller; +import com.android.internal.print.DualDumpOutputStream; import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.printspooler.R; import com.android.printspooler.util.ApprovedPrintServices; @@ -159,44 +161,11 @@ public final class PrintSpoolerService extends Service { return new PrintSpooler(); } - private void dumpLocked(PrintWriter pw, String[] args) { - String prefix = (args.length > 0) ? args[0] : ""; - String tab = " "; - - pw.append(prefix).append("print jobs:").println(); - final int printJobCount = mPrintJobs.size(); - for (int i = 0; i < printJobCount; i++) { - PrintJobInfo printJob = mPrintJobs.get(i); - pw.append(prefix).append(tab).append(printJob.toString()); - pw.println(); - } - - pw.append(prefix).append("print job files:").println(); - File[] files = getFilesDir().listFiles(); - if (files != null) { - final int fileCount = files.length; - for (int i = 0; i < fileCount; i++) { - File file = files[i]; - if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) { - pw.append(prefix).append(tab).append(file.getName()).println(); - } - } - } - - pw.append(prefix).append("approved print services:").println(); - Set<String> approvedPrintServices = (new ApprovedPrintServices(this)).getApprovedServices(); - if (approvedPrintServices != null) { - for (String approvedService : approvedPrintServices) { - pw.append(prefix).append(tab).append(approvedService).println(); - } - } - } - - private void dumpLocked(@NonNull ProtoOutputStream proto) { + private void dumpLocked(@NonNull DualDumpOutputStream dumpStream) { int numPrintJobs = mPrintJobs.size(); for (int i = 0; i < numPrintJobs; i++) { - writePrintJobInfo(this, proto, PrintSpoolerInternalStateProto.PRINT_JOBS, - mPrintJobs.get(i)); + writePrintJobInfo(this, dumpStream, "print_jobs", + PrintSpoolerInternalStateProto.PRINT_JOBS, mPrintJobs.get(i)); } File[] files = getFilesDir().listFiles(); @@ -204,7 +173,8 @@ public final class PrintSpoolerService extends Service { for (int i = 0; i < files.length; i++) { File file = files[i]; if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) { - proto.write(PrintSpoolerInternalStateProto.PRINT_JOB_FILES, file.getName()); + dumpStream.write("print_job_files", + PrintSpoolerInternalStateProto.PRINT_JOB_FILES, file.getName()); } } } @@ -214,13 +184,13 @@ public final class PrintSpoolerService extends Service { for (String approvedService : approvedPrintServices) { ComponentName componentName = ComponentName.unflattenFromString(approvedService); if (componentName != null) { - writeComponentName(proto, PrintSpoolerInternalStateProto.APPROVED_SERVICES, - componentName); + writeComponentName(dumpStream, "approved_services", + PrintSpoolerInternalStateProto.APPROVED_SERVICES, componentName); } } } - proto.flush(); + dumpStream.flush(); } @Override @@ -244,9 +214,15 @@ public final class PrintSpoolerService extends Service { try { synchronized (mLock) { if (dumpAsProto) { - dumpLocked(new ProtoOutputStream(fd)); + dumpLocked(new DualDumpOutputStream(new ProtoOutputStream(fd), null)); } else { - dumpLocked(pw, args); + try (FileOutputStream out = new FileOutputStream(fd)) { + try (PrintWriter w = new PrintWriter(out)) { + dumpLocked(new DualDumpOutputStream(null, new IndentingPrintWriter(w, + " "))); + } + } catch (IOException ignored) { + } } } } finally { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java index adb4832adcff..ae24c079e37b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java @@ -16,9 +16,9 @@ package com.android.settingslib.core.lifecycle; import static android.arch.lifecycle.Lifecycle.Event.ON_START; - import static com.google.common.truth.Truth.assertThat; +import android.arch.lifecycle.LifecycleOwner; import android.content.Context; import android.view.Menu; import android.view.MenuInflater; @@ -48,6 +48,7 @@ import org.robolectric.annotation.Config; @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class LifecycleTest { + private LifecycleOwner mLifecycleOwner; private Lifecycle mLifecycle; public static class TestDialogFragment extends ObservableDialogFragment { @@ -146,7 +147,8 @@ public class LifecycleTest { @Before public void setUp() { - mLifecycle = new Lifecycle(() -> mLifecycle); + mLifecycleOwner = () -> mLifecycle; + mLifecycle = new Lifecycle(mLifecycleOwner); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogpersistPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogpersistPreferenceControllerTest.java index 5d5733e463ea..050877d3ebfe 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogpersistPreferenceControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogpersistPreferenceControllerTest.java @@ -17,11 +17,11 @@ package com.android.settingslib.development; import static com.google.common.truth.Truth.assertThat; - import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; +import android.arch.lifecycle.LifecycleOwner; import android.os.SystemProperties; import android.support.v7.preference.ListPreference; import android.support.v7.preference.Preference; @@ -44,6 +44,7 @@ import org.robolectric.annotation.Config; shadows = SystemPropertiesTestImpl.class) public class LogpersistPreferenceControllerTest { + private LifecycleOwner mLifecycleOwner; private Lifecycle mLifecycle; @Mock @@ -57,7 +58,8 @@ public class LogpersistPreferenceControllerTest { public void setUp() { MockitoAnnotations.initMocks(this); SystemProperties.set("ro.debuggable", "1"); - mLifecycle = new Lifecycle(() -> mLifecycle); + mLifecycleOwner = () -> mLifecycle; + mLifecycle = new Lifecycle(mLifecycleOwner); mController = new AbstractLogpersistPreferenceController(RuntimeEnvironment.application, mLifecycle) { @Override diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java index 75b6c5fcefc1..88c57b50f87e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java @@ -17,13 +17,13 @@ package com.android.settingslib.widget; import static com.google.common.truth.Truth.assertThat; - import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.arch.lifecycle.LifecycleOwner; import android.support.v14.preference.PreferenceFragment; import android.support.v7.preference.PreferenceManager; import android.support.v7.preference.PreferenceScreen; @@ -49,13 +49,15 @@ public class FooterPreferenceMixinTest { @Mock private PreferenceScreen mScreen; + private LifecycleOwner mLifecycleOwner; private Lifecycle mLifecycle; private FooterPreferenceMixin mMixin; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mLifecycle = new Lifecycle(() -> mLifecycle); + mLifecycleOwner = () -> mLifecycle; + mLifecycle = new Lifecycle(mLifecycleOwner); when(mFragment.getPreferenceManager()).thenReturn(mock(PreferenceManager.class)); when(mFragment.getPreferenceManager().getContext()) .thenReturn(ShadowApplication.getInstance().getApplicationContext()); diff --git a/packages/SettingsProvider/AndroidManifest.xml b/packages/SettingsProvider/AndroidManifest.xml index 287a888a23b2..fd4d2967bf61 100644 --- a/packages/SettingsProvider/AndroidManifest.xml +++ b/packages/SettingsProvider/AndroidManifest.xml @@ -8,6 +8,7 @@ android:process="system" android:backupAgent="SettingsBackupAgent" android:killAfterRestore="false" + android:restoreAnyVersion="true" android:icon="@mipmap/ic_launcher_settings" android:defaultToDeviceProtectedStorage="true" android:directBootAware="true"> diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index 1be064564574..48a3a3084ca0 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -28,7 +28,7 @@ <string name="def_bluetooth_disabled_profiles" translatable="false">0</string> <bool name="def_auto_time">true</bool> <bool name="def_auto_time_zone">true</bool> - <bool name="def_accelerometer_rotation">true</bool> + <bool name="def_accelerometer_rotation">false</bool> <!-- Default screen brightness, from 0 to 255. 102 is 40%. --> <integer name="def_screen_brightness">102</integer> <bool name="def_screen_brightness_automatic_mode">false</bool> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index ae882275de7c..c7ba4d6ec750 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -31,10 +31,12 @@ import android.net.NetworkPolicyManager; import android.net.Uri; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; +import android.os.Build; import android.os.ParcelFileDescriptor; import android.os.UserHandle; import android.provider.Settings; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.BackupUtils; import android.util.Log; @@ -50,6 +52,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -147,6 +150,13 @@ public class SettingsBackupAgent extends BackupAgentHelper { // stored in the full-backup tarfile as well, so should not be changed. private static final String STAGE_FILE = "flattened-data"; + // List of keys that support restore to lower version of the SDK, introduced in Android P + private static final ArraySet<String> RESTORE_FROM_HIGHER_SDK_INT_SUPPORTED_KEYS = + new ArraySet<String>(Arrays.asList(new String[] { + KEY_NETWORK_POLICIES, + KEY_WIFI_NEW_CONFIG, + })); + private SettingsHelper mSettingsHelper; private WifiManager mWifiManager; @@ -209,6 +219,10 @@ public class SettingsBackupAgent extends BackupAgentHelper { public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException { + if (DEBUG) { + Log.d(TAG, "onRestore(): appVersionCode: " + appVersionCode + + "; Build.VERSION.SDK_INT: " + Build.VERSION.SDK_INT); + } // versionCode of com.android.providers.settings corresponds to SDK_INT mRestoredFromSdkInt = appVersionCode; @@ -221,6 +235,15 @@ public class SettingsBackupAgent extends BackupAgentHelper { while (data.readNextHeader()) { final String key = data.getKey(); final int size = data.getDataSize(); + + // bail out of restoring from higher SDK_INT version for unsupported keys + if (appVersionCode > Build.VERSION.SDK_INT + && !RESTORE_FROM_HIGHER_SDK_INT_SUPPORTED_KEYS.contains(key)) { + Log.w(TAG, "Not restoring unrecognized key '" + + key + "' from future version " + appVersionCode); + continue; + } + switch (key) { case KEY_SYSTEM : restoreSettings(data, Settings.System.CONTENT_URI, movedToGlobal); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 36981320ba5f..d33e0841a906 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1122,6 +1122,9 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Global.ZRAM_ENABLED, GlobalSettingsProto.ZRAM_ENABLED); + dumpSetting(s, p, + Settings.Global.ENABLE_SMART_REPLIES_IN_NOTIFICATIONS, + GlobalSettingsProto.ENABLE_SMART_REPLIES_IN_NOTIFICATIONS); } /** Dump a single {@link SettingsState.Setting} to a proto buf */ diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 1167d69a1577..175cff6b61b2 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -1584,6 +1584,11 @@ public class SettingsProvider extends ContentProvider { restriction = UserManager.DISALLOW_SAFE_BOOT; break; + case Settings.Global.AIRPLANE_MODE_ON: + if ("0".equals(value)) return false; + restriction = UserManager.DISALLOW_AIRPLANE_MODE; + break; + default: if (setting != null && setting.startsWith(Settings.Global.DATA_ROAMING)) { if ("0".equals(value)) return false; @@ -2940,7 +2945,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 150; + private static final int SETTINGS_VERSION = 151; private final int mUserId; @@ -3533,6 +3538,18 @@ public class SettingsProvider extends ContentProvider { currentVersion = 150; } + if (currentVersion == 150) { + // Version 151: Reset rotate locked setting for upgrading users + final SettingsState systemSettings = getSystemSettingsLocked(userId); + systemSettings.insertSettingLocked( + Settings.System.ACCELEROMETER_ROTATION, + getContext().getResources().getBoolean( + R.bool.def_accelerometer_rotation) ? "1" : "0", + null, true, SettingsState.SYSTEM_PACKAGE_NAME); + + currentVersion = 151; + } + // vXXX: Add new settings above this point. if (currentVersion != newVersion) { diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java index c52c0aae3556..61f7fe8dc019 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java @@ -108,6 +108,7 @@ public interface QSTile { public Supplier<Icon> iconSupplier; public int state = Tile.STATE_ACTIVE; public CharSequence label; + public CharSequence secondaryLabel; public CharSequence contentDescription; public CharSequence dualLabelContentDescription; public boolean disabledByPolicy; @@ -122,6 +123,7 @@ public interface QSTile { final boolean changed = !Objects.equals(other.icon, icon) || !Objects.equals(other.iconSupplier, iconSupplier) || !Objects.equals(other.label, label) + || !Objects.equals(other.secondaryLabel, secondaryLabel) || !Objects.equals(other.contentDescription, contentDescription) || !Objects.equals(other.dualLabelContentDescription, dualLabelContentDescription) @@ -135,6 +137,7 @@ public interface QSTile { other.icon = icon; other.iconSupplier = iconSupplier; other.label = label; + other.secondaryLabel = secondaryLabel; other.contentDescription = contentDescription; other.dualLabelContentDescription = dualLabelContentDescription; other.expandedAccessibilityClassName = expandedAccessibilityClassName; @@ -156,6 +159,7 @@ public interface QSTile { sb.append(",icon=").append(icon); sb.append(",iconSupplier=").append(iconSupplier); sb.append(",label=").append(label); + sb.append(",secondaryLabel=").append(secondaryLabel); sb.append(",contentDescription=").append(contentDescription); sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription); sb.append(",expandedAccessibilityClassName=").append(expandedAccessibilityClassName); diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java index 56a3ee3a28f7..e25930c18947 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java @@ -50,5 +50,7 @@ public interface NavBarButtonProvider extends Plugin { } void setDarkIntensity(float intensity); + + void setDelayTouchFeedback(boolean shouldDelay); } } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java index 674ed5a3a480..6131acc91ca5 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java @@ -14,6 +14,7 @@ package com.android.systemui.plugins.statusbar.phone; +import android.graphics.Canvas; import android.view.MotionEvent; import com.android.systemui.plugins.Plugin; @@ -35,6 +36,12 @@ public interface NavGesture extends Plugin { public void setBarState(boolean vertical, boolean isRtl); + public void onDraw(Canvas canvas); + + public void onDarkIntensityChange(float intensity); + + public void onLayout(boolean changed, int left, int top, int right, int bottom); + public default void destroy() { } } diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml index c97cfc4bb835..9adb5501345d 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml @@ -34,7 +34,6 @@ android:orientation="vertical"> <RelativeLayout android:id="@+id/keyguard_clock_container" - android:animateLayoutChanges="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal|top"> @@ -51,16 +50,6 @@ android:format12Hour="@string/keyguard_widget_12_hours_format" android:format24Hour="@string/keyguard_widget_24_hours_format" android:layout_marginBottom="@dimen/bottom_text_spacing_digital" /> - <com.android.systemui.ChargingView - android:id="@+id/battery_doze" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignTop="@id/clock_view" - android:layout_alignBottom="@id/clock_view" - android:layout_toEndOf="@id/clock_view" - android:visibility="invisible" - android:src="@drawable/ic_aod_charging_24dp" - android:contentDescription="@string/accessibility_ambient_display_charging" /> <View android:id="@+id/clock_separator" android:layout_width="16dp" diff --git a/packages/SystemUI/res/drawable/ic_face_unlock.xml b/packages/SystemUI/res/drawable/ic_face_unlock.xml new file mode 100644 index 000000000000..29c22759be70 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_face_unlock.xml @@ -0,0 +1,27 @@ +<!-- + ~ 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 + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path android:fillColor="?attr/wallpaperTextColor" + android:strokeColor="?attr/wallpaperTextColor" + android:strokeWidth="1" + android:pathData="M9,11.75C8.31,11.75 7.75,12.31 7.75,13C7.75,13.69 8.31,14.25 9,14.25C9.69,14.25 10.25,13.69 10.25,13C10.25,12.31 9.69,11.75 9,11.75ZM15,11.75C14.31,11.75 13.75,12.31 13.75,13C13.75,13.69 14.31,14.25 15,14.25C15.69,14.25 16.25,13.69 16.25,13C16.25,12.31 15.69,11.75 15,11.75ZM12,2C6.48,2 2,6.48 2,12C2,17.52 6.48,22 12,22C17.52,22 22,17.52 22,12C22,6.48 17.52,2 12,2ZM12,20C7.59,20 4,16.41 4,12C4,11.71 4.02,11.42 4.05,11.14C6.41,10.09 8.28,8.16 9.26,5.77C11.07,8.33 14.05,10 17.42,10C18.2,10 18.95,9.91 19.67,9.74C19.88,10.45 20,11.21 20,12C20,16.41 16.41,20 12,20Z" + /> +</vector> diff --git a/packages/SystemUI/res/drawable/qs_background_primary.xml b/packages/SystemUI/res/drawable/qs_background_primary.xml index 03bba53946da..dd74cadd0955 100644 --- a/packages/SystemUI/res/drawable/qs_background_primary.xml +++ b/packages/SystemUI/res/drawable/qs_background_primary.xml @@ -16,5 +16,6 @@ <inset xmlns:android="http://schemas.android.com/apk/res/android"> <shape> <solid android:color="@color/qs_background_dark"/> + <corners android:radius="?android:attr/dialogCornerRadius" /> </shape> </inset> diff --git a/packages/SystemUI/res/drawable/smart_reply_button_background.xml b/packages/SystemUI/res/drawable/smart_reply_button_background.xml new file mode 100644 index 000000000000..1cd1451008b4 --- /dev/null +++ b/packages/SystemUI/res/drawable/smart_reply_button_background.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ 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 + --> + +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="@color/notification_ripple_untinted_color"> + <item> + <shape android:shape="rectangle"> + <corners android:radius="@dimen/smart_reply_button_corner_radius"/> + <solid android:color="@color/smart_reply_button_background"/> + </shape> + </item> +</ripple> diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index 2fd4df4d1cc7..d0d379c286bd 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -40,6 +40,7 @@ android:gravity="center_horizontal" android:textStyle="italic" android:textColor="?attr/wallpaperTextColorSecondary" + android:textSize="16sp" android:textAppearance="?android:attr/textAppearanceSmall" android:visibility="gone" /> @@ -50,6 +51,7 @@ android:gravity="center_horizontal" android:textStyle="italic" android:textColor="?attr/wallpaperTextColorSecondary" + android:textSize="16sp" android:textAppearance="?android:attr/textAppearanceSmall" android:accessibilityLiveRegion="polite" /> diff --git a/packages/SystemUI/res/layout/smart_reply_button.xml b/packages/SystemUI/res/layout/smart_reply_button.xml new file mode 100644 index 000000000000..4ac41d5cf6c3 --- /dev/null +++ b/packages/SystemUI/res/layout/smart_reply_button.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ 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 + --> + +<Button xmlns:android="http://schemas.android.com/apk/res/android" + style="@android:style/Widget.Material.Button.Borderless.Small" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/smart_reply_button_spacing" + android:paddingVertical="@dimen/smart_reply_button_padding_vertical" + android:paddingHorizontal="@dimen/smart_reply_button_corner_radius" + android:background="@drawable/smart_reply_button_background" + android:gravity="center" + android:fontFamily="sans-serif" + android:textSize="@dimen/smart_reply_button_font_size" + android:textColor="@color/smart_reply_button_text" + android:textStyle="normal" + android:singleLine="true"/>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/smart_reply_view.xml b/packages/SystemUI/res/layout/smart_reply_view.xml new file mode 100644 index 000000000000..6d5338697161 --- /dev/null +++ b/packages/SystemUI/res/layout/smart_reply_view.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ 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 + --> + +<!-- LinearLayout --> +<com.android.systemui.statusbar.policy.SmartReplyView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/smart_reply_view" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="end"> + <!-- smart_reply_button(s) will be added here. --> +</com.android.systemui.statusbar.policy.SmartReplyView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index bef0830d5802..c5e5ee1f4af0 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -43,6 +43,8 @@ android:layout_width="@dimen/qs_panel_width" android:layout_height="match_parent" android:layout_gravity="@integer/notification_panel_layout_gravity" + android:layout_marginStart="@dimen/notification_side_paddings" + android:layout_marginEnd="@dimen/notification_side_paddings" android:clipToPadding="false" android:clipChildren="false" systemui:viewType="com.android.systemui.plugins.qs.QS" /> diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml index 7f3708788d95..4614999e3c4f 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_row.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml @@ -54,6 +54,18 @@ android:paddingStart="8dp" /> + <ImageButton + android:id="@+id/helper" + android:layout_width="48dp" + android:layout_height="@*android:dimen/notification_header_height" + android:layout_gravity="top|end" + android:layout_marginEnd="6dp" + android:src="@drawable/ic_dnd" + android:tint="#FF0000" + android:background="@drawable/ripple_drawable" + android:visibility="visible" + /> + <ViewStub android:layout="@layout/notification_children_container" android:id="@+id/child_container_stub" diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index f0d23469a49a..748c9a5ec7ed 100644 --- a/packages/SystemUI/res/layout/volume_dialog.xml +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -17,130 +17,76 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:background="@android:color/transparent" android:theme="@style/qs_theme" android:clipChildren="false" > - <RelativeLayout + <LinearLayout android:id="@+id/volume_dialog" - android:layout_width="match_parent" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:paddingTop="@dimen/volume_row_padding_bottom" - android:background="@drawable/rounded_full_bg_bottom" + android:layout_gravity="center_vertical|end" + android:minWidth="@dimen/volume_dialog_panel_width" + android:background="@android:color/transparent" + android:layout_margin="12dp" android:translationZ="8dp" + android:orientation="vertical" android:clipChildren="false" > <LinearLayout - android:id="@+id/volume_dialog_content" - android:layout_width="match_parent" + android:id="@+id/volume_dialog_rows" + android:layout_width="@dimen/volume_dialog_panel_width" android:layout_height="wrap_content" - android:layout_toStartOf="@id/expand" android:clipChildren="false" android:clipToPadding="false" - android:orientation="vertical" > - - <LinearLayout - android:id="@+id/volume_dialog_rows" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" > + android:paddingTop="12dp" + android:paddingBottom="12dp" + android:background="@drawable/rounded_bg_full" + android:orientation="horizontal" > <!-- volume rows added and removed here! :-) --> - </LinearLayout> - - </LinearLayout> + <LinearLayout - android:id="@+id/expand" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical" - android:layout_alignParentEnd="true" - android:layout_alignParentTop="true" - android:layout_marginEnd="@dimen/volume_expander_margin_end" > + android:id="@+id/footer" + android:layout_width="@dimen/volume_dialog_panel_width" + android:layout_height="@dimen/volume_dialog_panel_width" + android:clipChildren="false" + android:clipToPadding="false" + android:layout_marginTop="6dp" + android:layout_marginBottom="6dp" + android:layout_below="@id/volume_dialog_rows" + android:background="@drawable/rounded_bg_full" + android:gravity="center" + android:orientation="vertical" > + <TextView + android:id="@+id/ringer_title" + android:text="@string/ring_toggle_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:ellipsize="end" android:maxLines="1" - android:textAppearance="@style/TextAppearance.Volume.Header" /> - <com.android.keyguard.AlphaOptimizedImageButton - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/volume_expand_button" - style="@style/VolumeButtons" - android:layout_width="@dimen/volume_button_size" - android:layout_height="@dimen/volume_button_size" - android:clickable="true" - android:soundEffectsEnabled="false" - android:src="@drawable/ic_volume_expand_animation" - android:background="@drawable/ripple_drawable" - tools:ignore="RtlHardcoded" /> - </LinearLayout> - <RelativeLayout - android:id="@+id/footer" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:clipChildren="false" - android:clipToPadding="false" - android:layout_below="@id/volume_dialog_content" - android:layout_margin="10dp"> - <!-- special row for ringer mode --> - <RelativeLayout - android:id="@+id/ringer_mode" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@drawable/rounded_bg_full" - android:clipChildren="false" - android:clipToPadding="false" - android:layout_toStartOf="@id/output_chooser" - android:layout_margin="10dp"> - - <com.android.keyguard.AlphaOptimizedImageButton - android:id="@+id/ringer_icon" - style="@style/VolumeButtons" - android:background="?android:selectableItemBackgroundBorderless" - android:layout_width="@dimen/volume_button_size" - android:layout_height="@dimen/volume_button_size" - android:layout_alignParentStart="true" - android:layout_centerVertical="true" - android:soundEffectsEnabled="false" /> - - <TextView - android:id="@+id/ringer_title" - android:text="@string/ring_toggle_title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:ellipsize="end" - android:maxLines="1" - android:layout_alignParentStart="true" - android:layout_centerVertical="true" - android:layout_toEndOf="@+id/ringer_icon" - android:layout_marginStart="64dp" - android:textColor="?android:attr/colorControlNormal" - android:textAppearance="?android:attr/textAppearanceSmall" - android:paddingStart="@dimen/volume_row_header_padding_start" /> - - <TextView - android:id="@+id/ringer_status" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:ellipsize="end" - android:layout_alignParentEnd="true" - android:layout_centerVertical="true" - android:layout_marginEnd="14dp" - android:maxLines="1" - android:textColor="?android:attr/colorControlNormal" - android:textAppearance="?android:attr/textAppearanceSmall" /> + android:layout_centerVertical="true" + android:textColor="?android:attr/colorControlNormal" + android:textAppearance="?android:attr/textAppearanceSmall" /> - </RelativeLayout> <com.android.keyguard.AlphaOptimizedImageButton - android:id="@+id/output_chooser" + android:id="@+id/ringer_icon" style="@style/VolumeButtons" android:background="?android:selectableItemBackgroundBorderless" android:layout_width="@dimen/volume_button_size" android:layout_height="@dimen/volume_button_size" - android:layout_alignParentEnd="true" - android:layout_centerVertical="true" - android:src="@drawable/ic_settings_bluetooth" + android:tint="?android:attr/colorAccent" android:soundEffectsEnabled="false" /> - </RelativeLayout> - </RelativeLayout> + + <TextView + android:id="@+id/ringer_status" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="1" + android:textColor="?android:attr/colorControlNormal" + android:textAppearance="?android:attr/textAppearanceSmall" /> + + </LinearLayout> + </LinearLayout> </com.android.systemui.volume.VolumeUiLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml index bf76e78c94a2..3590b768c91b 100644 --- a/packages/SystemUI/res/layout/volume_dialog_row.xml +++ b/packages/SystemUI/res/layout/volume_dialog_row.xml @@ -15,48 +15,70 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="@dimen/volume_row_height" - android:clipChildren="false" - android:clipToPadding="false" + android:tag="row" + android:layout_height="wrap_content" + android:layout_width="@dimen/volume_dialog_panel_width" + android:clipChildren="true" + android:clipToPadding="true" android:theme="@style/qs_theme" + android:gravity="center" android:orientation="vertical" > - <TextView - android:id="@+id/volume_row_header" + <LinearLayout + android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:ellipsize="end" - android:maxLines="1" - android:textColor="?android:attr/colorControlNormal" - android:textAppearance="?android:attr/textAppearanceSmall" - android:paddingStart="@dimen/volume_row_header_padding_start" /> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="@dimen/volume_row_slider_height" - android:orientation="horizontal" - android:paddingStart="@dimen/volume_row_padding_start" > + android:gravity="center" + android:padding="10dp"> + <TextView + android:id="@+id/volume_row_header" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="1" + android:textColor="?android:attr/colorControlNormal" + android:textAppearance="?android:attr/textAppearanceSmall" /> + <TextView + android:id="@+id/volume_row_connected_device" + android:visibility="gone" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="1" + android:textAppearance="@style/TextAppearance.QS.DetailItemSecondary" /> <com.android.keyguard.AlphaOptimizedImageButton - android:id="@+id/volume_row_icon" - style="@style/VolumeButtons" - android:layout_width="@dimen/volume_button_size" - android:layout_height="@dimen/volume_button_size" - android:soundEffectsEnabled="false" /> - - <SeekBar - android:id="@+id/volume_row_slider" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_alignWithParentIfMissing="true" - android:focusable="true" - android:focusableInTouchMode="true" - android:paddingStart="@dimen/volume_row_slider_padding_start"/> + android:id="@+id/output_chooser" + style="@style/VolumeButtons" + android:background="?android:selectableItemBackgroundBorderless" + android:layout_width="@dimen/volume_button_size" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:src="@drawable/ic_volume_expand_animation" + android:soundEffectsEnabled="false" /> </LinearLayout> + <FrameLayout + android:id="@+id/volume_row_slider_frame" + android:padding="10dp" + android:layout_width="@dimen/volume_dialog_panel_width" + android:layout_height="150dp"> + <SeekBar + android:id="@+id/volume_row_slider" + android:padding="0dp" + android:layout_margin="0dp" + android:layout_width="150dp" + android:layout_height="@dimen/volume_dialog_panel_width" + android:layout_gravity="center" + android:focusable="true" + android:focusableInTouchMode="true" + android:rotation="270" /> + </FrameLayout> - <Space - android:id="@+id/spacer" - android:layout_width="match_parent" - android:layout_height="@dimen/volume_row_padding_bottom"/> + <com.android.keyguard.AlphaOptimizedImageButton + android:id="@+id/volume_row_icon" + style="@style/VolumeButtons" + android:padding="10dp" + android:layout_width="@dimen/volume_button_size" + android:layout_height="@dimen/volume_button_size" + android:soundEffectsEnabled="false" /> </LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index f244d88b8573..0f4c3b8b9977 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -143,6 +143,9 @@ <color name="remote_input_accent">#eeeeee</color> + <color name="quick_step_track_background_dark">#61000000</color> + <color name="quick_step_track_background_light">#4DFFFFFF</color> + <!-- Keyboard shortcuts colors --> <color name="ksh_application_group_color">#fff44336</color> <color name="ksh_keyword_color">#d9000000</color> @@ -153,4 +156,6 @@ <color name="zen_introduction">#ffffffff</color> + <color name="smart_reply_button_text">#ff4285f4</color><!-- blue 500 --> + <color name="smart_reply_button_background">#fff7f7f7</color> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index a510c4a6c503..7a670fd1ae5d 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -264,7 +264,7 @@ <!-- The width of the panel that holds the quick settings. --> <dimen name="qs_panel_width">@dimen/notification_panel_width</dimen> - <dimen name="volume_dialog_panel_width">315dp</dimen> + <dimen name="volume_dialog_panel_width">120dp</dimen> <!-- Gravity for the notification panel --> <integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top --> @@ -579,6 +579,7 @@ <dimen name="keyguard_affordance_icon_width">24dp</dimen> <dimen name="keyguard_indication_margin_bottom">65dp</dimen> + <dimen name="keyguard_indication_margin_bottom_ambient">30dp</dimen> <!-- The text size for battery level --> <dimen name="battery_level_text_size">12sp</dimen> @@ -870,6 +871,8 @@ <dimen name="rounded_corner_radius">0dp</dimen> <dimen name="rounded_corner_content_padding">0dp</dimen> <dimen name="nav_content_padding">0dp</dimen> + <dimen name="nav_quick_scrub_track_edge_padding">32dp</dimen> + <dimen name="nav_quick_scrub_track_thickness">2dp</dimen> <!-- Intended corner radius when drawing the mobile signal --> <dimen name="stat_sys_mobile_signal_corner_radius">0.75dp</dimen> @@ -879,4 +882,9 @@ <!-- Home button padding for sizing --> <dimen name="home_padding">15dp</dimen> + <!-- Smart reply button --> + <dimen name="smart_reply_button_corner_radius">24dip</dimen> + <dimen name="smart_reply_button_spacing">8dp</dimen> + <dimen name="smart_reply_button_padding_vertical">4dp</dimen> + <dimen name="smart_reply_button_font_size">14sp</dimen> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 6ff239ebd2ee..dde4dcfb23fe 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -61,11 +61,26 @@ <!-- When the battery is low, this is displayed to the user in a dialog. The title of the low battery alert. [CHAR LIMIT=NONE]--> <string name="battery_low_title">Battery is low</string> + <!-- When the battery is low and hybrid notifications are enabled, this is displayed to the user in a dialog. + The title of the low battery alert. [CHAR LIMIT=NONE]--> + <string name="battery_low_title_hybrid">Battery is low. Turn on Battery Saver</string> + <!-- A message that appears when the battery level is getting low in a dialog. This is - appened to the subtitle of the low battery alert. "percentage" is the percentage of battery + appended to the subtitle of the low battery alert. "percentage" is the percentage of battery remaining [CHAR LIMIT=none]--> <string name="battery_low_percent_format"><xliff:g id="percentage">%s</xliff:g> remaining</string> + <!-- A message that appears when the battery remaining estimate is low in a dialog. This is + appended to the subtitle of the low battery alert. "percentage" is the percentage of battery + remaining. "time" is the amount of time remaining before the phone runs out of battery [CHAR LIMIT=none]--> + <string name="battery_low_percent_format_hybrid"><xliff:g id="percentage">%s</xliff:g> remaining, about <xliff:g id="time">%s</xliff:g> left based on your usage</string> + + <!-- A message that appears when the battery remaining estimate is low in a dialog and insufficient + data was present to say it is customized to the user. This is appended to the subtitle of the + low battery alert. "percentage" is the percentage of battery remaining. "time" is the amount + of time remaining before the phone runs out of battery [CHAR LIMIT=none]--> + <string name="battery_low_percent_format_hybrid_short"><xliff:g id="percentage">%s</xliff:g> remaining, about <xliff:g id="time">%s</xliff:g> left</string> + <!-- Same as battery_low_percent_format, with a notice about battery saver if on. [CHAR LIMIT=none]--> <string name="battery_low_percent_format_saver_started"><xliff:g id="percentage">%s</xliff:g> remaining. Battery Saver is on.</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index 173a90a5baa7..64fa9c61280a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -22,4 +22,8 @@ import com.android.systemui.shared.recents.ISystemUiProxy; oneway interface IOverviewProxy { void onBind(in ISystemUiProxy sysUiProxy); void onMotionEvent(in MotionEvent event); + void onQuickSwitch(); + void onQuickScrubStart(); + void onQuickScrubEnd(); + void onQuickScrubProgress(float progress); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java index a5d19639580e..13f30b2c27b9 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java @@ -28,11 +28,14 @@ import android.graphics.Color; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Message; import android.os.Trace; import android.util.ArraySet; import android.util.IntProperty; import android.util.Property; import android.util.TypedValue; +import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; @@ -291,6 +294,20 @@ public class Utilities { } /** + * @return The next frame name for the specified surface. + */ + public static long getNextFrameNumber(Surface s) { + return s.getNextFrameNumber(); + } + + /** + * @return The surface for the specified view. + */ + public static Surface getSurface(View v) { + return v.getViewRootImpl().mSurface; + } + + /** * Returns a lightweight dump of a rect. */ public static String dumpRect(Rect r) { @@ -299,4 +316,12 @@ public class Utilities { } return r.left + "," + r.top + "-" + r.right + "," + r.bottom; } + + /** + * Posts a runnable on a handler at the front of the queue ignoring any sync barriers. + */ + public static void postAtFrontOfQueueAsynchronously(Handler h, Runnable r) { + Message msg = h.obtainMessage().setCallback(r); + h.sendMessageAtFrontOfQueue(msg); + } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java new file mode 100644 index 000000000000..0d8ce58d55fd --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java @@ -0,0 +1,34 @@ +/* + * 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.app.Activity; + +public class ActivityCompat { + private final Activity mWrapped; + + public ActivityCompat(Activity activity) { + mWrapped = activity; + } + + /** + * @see Activity#registerRemoteAnimations + */ + public void registerRemoteAnimations(RemoteAnimationDefinitionCompat definition) { + mWrapped.registerRemoteAnimations(definition.getWrapped()); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java index 705a21522b0a..712cca67c5d6 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java @@ -38,4 +38,9 @@ public abstract class ActivityOptionsCompat { : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT); return options; } + + public static ActivityOptions makeRemoteAnimation( + RemoteAnimationAdapterCompat remoteAnimationAdapter) { + return ActivityOptions.makeRemoteAnimation(remoteAnimationAdapter.getWrapped()); + } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java new file mode 100644 index 000000000000..625b1de74290 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java @@ -0,0 +1,71 @@ +/* + * 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.os.RemoteException; +import android.util.Log; +import android.view.IRemoteAnimationFinishedCallback; +import android.view.IRemoteAnimationRunner; +import android.view.RemoteAnimationAdapter; +import android.view.RemoteAnimationTarget; + +/** + * @see RemoteAnimationAdapter + */ +public class RemoteAnimationAdapterCompat { + + private final RemoteAnimationAdapter mWrapped; + + public RemoteAnimationAdapterCompat(RemoteAnimationRunnerCompat runner, long duration, + long statusBarTransitionDelay) { + mWrapped = new RemoteAnimationAdapter(wrapRemoteAnimationRunner(runner), duration, + statusBarTransitionDelay); + } + + RemoteAnimationAdapter getWrapped() { + return mWrapped; + } + + private static IRemoteAnimationRunner.Stub wrapRemoteAnimationRunner( + RemoteAnimationRunnerCompat remoteAnimationAdapter) { + return new IRemoteAnimationRunner.Stub() { + @Override + public void onAnimationStart(RemoteAnimationTarget[] apps, + IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { + final RemoteAnimationTargetCompat[] appsCompat = + RemoteAnimationTargetCompat.wrap(apps); + final Runnable animationFinishedCallback = new Runnable() { + @Override + public void run() { + try { + finishedCallback.onAnimationFinished(); + } catch (RemoteException e) { + Log.e("ActivityOptionsCompat", "Failed to call app controlled animation" + + " finished callback", e); + } + } + }; + remoteAnimationAdapter.onAnimationStart(appsCompat, animationFinishedCallback); + } + + @Override + public void onAnimationCancelled() throws RemoteException { + remoteAnimationAdapter.onAnimationCancelled(); + } + }; + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java new file mode 100644 index 000000000000..5fff5febec85 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java @@ -0,0 +1,35 @@ +/* + * 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.view.RemoteAnimationDefinition; + +/** + * @see RemoteAnimationDefinition + */ +public class RemoteAnimationDefinitionCompat { + + private final RemoteAnimationDefinition mWrapped = new RemoteAnimationDefinition(); + + public void addRemoteAnimation(int transition, RemoteAnimationAdapterCompat adapter) { + mWrapped.addRemoteAnimation(transition, adapter.getWrapped()); + } + + RemoteAnimationDefinition getWrapped() { + return mWrapped; + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java new file mode 100644 index 000000000000..5a85df967197 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java @@ -0,0 +1,22 @@ +/* + * 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; + +public interface RemoteAnimationRunnerCompat { + void onAnimationStart(RemoteAnimationTargetCompat[] apps, Runnable finishedCallback); + void onAnimationCancelled(); +}
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java new file mode 100644 index 000000000000..3871980a5b17 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java @@ -0,0 +1,59 @@ +/* + * 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.graphics.Point; +import android.graphics.Rect; +import android.view.RemoteAnimationTarget; + +/** + * @see RemoteAnimationTarget + */ +public class RemoteAnimationTargetCompat { + + public static final int MODE_OPENING = RemoteAnimationTarget.MODE_OPENING; + public static final int MODE_CLOSING = RemoteAnimationTarget.MODE_CLOSING; + + public final int taskId; + public final int mode; + public final SurfaceControlCompat leash; + public final boolean isTranslucent; + public final Rect clipRect; + public final int prefixOrderIndex; + public final Point position; + public final Rect sourceContainerBounds; + + public RemoteAnimationTargetCompat(RemoteAnimationTarget app) { + taskId = app.taskId; + mode = app.mode; + leash = new SurfaceControlCompat(app.leash); + isTranslucent = app.isTranslucent; + clipRect = app.clipRect; + position = app.position; + sourceContainerBounds = app.sourceContainerBounds; + prefixOrderIndex = app.prefixOrderIndex; + } + + public static RemoteAnimationTargetCompat[] wrap(RemoteAnimationTarget[] apps) { + final RemoteAnimationTargetCompat[] appsCompat = + new RemoteAnimationTargetCompat[apps.length]; + for (int i = 0; i < apps.length; i++) { + appsCompat[i] = new RemoteAnimationTargetCompat(apps[i]); + } + return appsCompat; + } +}
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceControlCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceControlCompat.java new file mode 100644 index 000000000000..cd12141c3268 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceControlCompat.java @@ -0,0 +1,27 @@ +/* + * 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.view.SurfaceControl; + +public class SurfaceControlCompat { + SurfaceControl mSurfaceControl; + + public SurfaceControlCompat(SurfaceControl surfaceControl) { + mSurfaceControl = surfaceControl; + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java new file mode 100644 index 000000000000..c82c5191b127 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java @@ -0,0 +1,108 @@ +/* + * 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.graphics.Matrix; +import android.graphics.Rect; +import android.os.IBinder; +import android.view.Surface; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; + +public class TransactionCompat { + + private final Transaction mTransaction; + + private final float[] mTmpValues = new float[9]; + + public TransactionCompat() { + mTransaction = new Transaction(); + } + + public void apply() { + mTransaction.apply(); + } + + public TransactionCompat show(SurfaceControlCompat surfaceControl) { + mTransaction.show(surfaceControl.mSurfaceControl); + return this; + } + + public TransactionCompat hide(SurfaceControlCompat surfaceControl) { + mTransaction.hide(surfaceControl.mSurfaceControl); + return this; + } + + public TransactionCompat setPosition(SurfaceControlCompat surfaceControl, float x, float y) { + mTransaction.setPosition(surfaceControl.mSurfaceControl, x, y); + return this; + } + + public TransactionCompat setSize(SurfaceControlCompat surfaceControl, int w, int h) { + mTransaction.setSize(surfaceControl.mSurfaceControl, w, h); + return this; + } + + public TransactionCompat setLayer(SurfaceControlCompat surfaceControl, int z) { + mTransaction.setLayer(surfaceControl.mSurfaceControl, z); + return this; + } + + public TransactionCompat setAlpha(SurfaceControlCompat surfaceControl, float alpha) { + mTransaction.setAlpha(surfaceControl.mSurfaceControl, alpha); + return this; + } + + public TransactionCompat setMatrix(SurfaceControlCompat surfaceControl, float dsdx, float dtdx, + float dtdy, float dsdy) { + mTransaction.setMatrix(surfaceControl.mSurfaceControl, dsdx, dtdx, dtdy, dsdy); + return this; + } + + public TransactionCompat setMatrix(SurfaceControlCompat surfaceControl, Matrix matrix) { + mTransaction.setMatrix(surfaceControl.mSurfaceControl, matrix, mTmpValues); + return this; + } + + public TransactionCompat setWindowCrop(SurfaceControlCompat surfaceControl, Rect crop) { + mTransaction.setWindowCrop(surfaceControl.mSurfaceControl, crop); + return this; + } + + public TransactionCompat setFinalCrop(SurfaceControlCompat surfaceControl, Rect crop) { + mTransaction.setFinalCrop(surfaceControl.mSurfaceControl, crop); + return this; + } + + public TransactionCompat deferTransactionUntil(SurfaceControlCompat surfaceControl, + IBinder handle, long frameNumber) { + mTransaction.deferTransactionUntil(surfaceControl.mSurfaceControl, handle, frameNumber); + return this; + } + + public TransactionCompat deferTransactionUntil(SurfaceControlCompat surfaceControl, + Surface barrier, long frameNumber) { + mTransaction.deferTransactionUntilSurface(surfaceControl.mSurfaceControl, barrier, + frameNumber); + return this; + } + + public TransactionCompat setColor(SurfaceControlCompat surfaceControl, float[] color) { + mTransaction.setColor(surfaceControl.mSurfaceControl, color); + return this; + } +}
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java index 225dbb4aafbe..68400fc977df 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java @@ -20,9 +20,10 @@ import static android.view.Display.DEFAULT_DISPLAY; import android.graphics.Rect; import android.os.Handler; -import android.os.IRemoteCallback; import android.os.RemoteException; import android.util.Log; +import android.view.RemoteAnimationAdapter; +import android.view.WindowManager; import android.view.WindowManagerGlobal; import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture; @@ -32,6 +33,31 @@ public class WindowManagerWrapper { private static final String TAG = "WindowManagerWrapper"; + public static final int TRANSIT_UNSET = WindowManager.TRANSIT_UNSET; + public static final int TRANSIT_NONE = WindowManager.TRANSIT_NONE; + public static final int TRANSIT_ACTIVITY_OPEN = WindowManager.TRANSIT_ACTIVITY_OPEN; + public static final int TRANSIT_ACTIVITY_CLOSE = WindowManager.TRANSIT_ACTIVITY_CLOSE; + public static final int TRANSIT_TASK_OPEN = WindowManager.TRANSIT_TASK_OPEN; + public static final int TRANSIT_TASK_CLOSE = WindowManager.TRANSIT_TASK_CLOSE; + public static final int TRANSIT_TASK_TO_FRONT = WindowManager.TRANSIT_TASK_TO_FRONT; + public static final int TRANSIT_TASK_TO_BACK = WindowManager.TRANSIT_TASK_TO_BACK; + public static final int TRANSIT_WALLPAPER_CLOSE = WindowManager.TRANSIT_WALLPAPER_CLOSE; + public static final int TRANSIT_WALLPAPER_OPEN = WindowManager.TRANSIT_WALLPAPER_OPEN; + public static final int TRANSIT_WALLPAPER_INTRA_OPEN = + WindowManager.TRANSIT_WALLPAPER_INTRA_OPEN; + public static final int TRANSIT_WALLPAPER_INTRA_CLOSE = + WindowManager.TRANSIT_WALLPAPER_INTRA_CLOSE; + public static final int TRANSIT_TASK_OPEN_BEHIND = WindowManager.TRANSIT_TASK_OPEN_BEHIND; + public static final int TRANSIT_TASK_IN_PLACE = WindowManager.TRANSIT_TASK_IN_PLACE; + public static final int TRANSIT_ACTIVITY_RELAUNCH = WindowManager.TRANSIT_ACTIVITY_RELAUNCH; + public static final int TRANSIT_DOCK_TASK_FROM_RECENTS = + WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS; + public static final int TRANSIT_KEYGUARD_GOING_AWAY = WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; + public static final int TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER = + WindowManager.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER; + public static final int TRANSIT_KEYGUARD_OCCLUDE = WindowManager.TRANSIT_KEYGUARD_OCCLUDE; + public static final int TRANSIT_KEYGUARD_UNOCCLUDE = WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; + private static final WindowManagerWrapper sInstance = new WindowManagerWrapper(); public static WindowManagerWrapper getInstance() { @@ -65,4 +91,14 @@ public class WindowManagerWrapper { Log.w(TAG, "Failed to override pending app transition (multi-thumbnail future): ", e); } } + + public void overridePendingAppTransitionRemote( + RemoteAnimationAdapterCompat remoteAnimationAdapter) { + try { + WindowManagerGlobal.getWindowManagerService().overridePendingAppTransitionRemote( + remoteAnimationAdapter.getWrapped()); + } catch (RemoteException e) { + Log.w(TAG, "Failed to override pending app transition (remote): ", e); + } + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index 8135c616d87e..d80a33632f3e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -151,6 +151,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe mClickActions.clear(); final int subItemsCount = subItems.size(); + final int blendedColor = getTextColor(); for (int i = 0; i < subItemsCount; i++) { SliceItem item = subItems.get(i); @@ -159,7 +160,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe KeyguardSliceButton button = mRow.findViewWithTag(itemTag); if (button == null) { button = new KeyguardSliceButton(mContext); - button.setTextColor(mTextColor); + button.setTextColor(blendedColor); button.setTag(itemTag); } else { mRow.removeView(button); @@ -258,7 +259,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe } private void updateTextColors() { - final int blendedColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount); + final int blendedColor = getTextColor(); mTitle.setTextColor(blendedColor); int childCount = mRow.getChildCount(); for (int i = 0; i < childCount; i++) { @@ -322,6 +323,10 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe } } + public int getTextColor() { + return ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount); + } + /** * Representation of an item that appears under the clock on main keyguard message. * Shows optional separator. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index 4b9a8744900d..2873afbca8e9 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -16,7 +16,6 @@ package com.android.keyguard; -import android.app.ActivityManager; import android.app.AlarmManager; import android.content.Context; import android.content.res.Configuration; @@ -40,7 +39,6 @@ import android.widget.TextClock; import android.widget.TextView; import com.android.internal.widget.LockPatternUtils; -import com.android.systemui.ChargingView; import com.google.android.collect.Sets; @@ -60,7 +58,6 @@ public class KeyguardStatusView extends GridLayout { private View mClockSeparator; private TextView mOwnerInfo; private ViewGroup mClockContainer; - private ChargingView mBatteryDoze; private KeyguardSliceView mKeyguardSlice; private Runnable mPendingMarqueeStart; private Handler mHandler; @@ -155,11 +152,9 @@ public class KeyguardStatusView extends GridLayout { mClockView.setAccessibilityDelegate(new KeyguardClockAccessibilityDelegate(mContext)); } mOwnerInfo = findViewById(R.id.owner_info); - mBatteryDoze = findViewById(R.id.battery_doze); mKeyguardSlice = findViewById(R.id.keyguard_status_area); mClockSeparator = findViewById(R.id.clock_separator); - mVisibleInDoze = Sets.newArraySet(mBatteryDoze, mClockView, mKeyguardSlice, - mClockSeparator); + mVisibleInDoze = Sets.newArraySet(mClockView, mKeyguardSlice, mClockSeparator); mTextColor = mClockView.getCurrentTextColor(); mKeyguardSlice.setListener(this::onSliceContentChanged); @@ -184,10 +179,6 @@ public class KeyguardStatusView extends GridLayout { mClockView.setTranslationY(translation); mClockView.setScaleX(clockScale); mClockView.setScaleY(clockScale); - final float batteryTranslation = - -(mClockView.getWidth() - (mClockView.getWidth() * clockScale)) / 2; - mBatteryDoze.setTranslationX(batteryTranslation); - mBatteryDoze.setTranslationY(translation); mClockSeparator.setVisibility(hasHeader ? VISIBLE : GONE); } @@ -310,7 +301,7 @@ public class KeyguardStatusView extends GridLayout { } } - public void setDark(float darkAmount) { + public void setDarkAmount(float darkAmount) { if (mDarkAmount == darkAmount) { return; } @@ -331,7 +322,6 @@ public class KeyguardStatusView extends GridLayout { final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount); updateDozeVisibleViews(); - mBatteryDoze.setDark(dark); mKeyguardSlice.setDark(darkAmount); mClockView.setTextColor(blendedTextColor); mClockSeparator.setBackgroundColor(blendedTextColor); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index ab2bce8cc094..e58ad0589c8a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1613,11 +1613,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } } - private static boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) { + private boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) { final boolean nowPluggedIn = current.isPluggedIn(); final boolean wasPluggedIn = old.isPluggedIn(); - final boolean stateChangedWhilePluggedIn = - wasPluggedIn == true && nowPluggedIn == true + final boolean stateChangedWhilePluggedIn = wasPluggedIn && nowPluggedIn && (old.status != current.status); // change in plug state is always interesting @@ -1630,6 +1629,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { return true; } + // change in battery level while keyguard visible + if (mKeyguardIsVisible && old.level != current.level) { + return true; + } + // change where battery needs charging if (!nowPluggedIn && current.isBatteryLow() && current.level != old.level) { return true; diff --git a/packages/SystemUI/src/com/android/systemui/ChargingView.java b/packages/SystemUI/src/com/android/systemui/ChargingView.java deleted file mode 100644 index 33f8b069b751..000000000000 --- a/packages/SystemUI/src/com/android/systemui/ChargingView.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui; - -import android.annotation.Nullable; -import android.content.Context; -import android.content.res.TypedArray; -import android.os.UserHandle; -import android.util.AttributeSet; -import android.widget.ImageView; - -import com.android.internal.hardware.AmbientDisplayConfiguration; -import com.android.systemui.statusbar.policy.BatteryController; -import com.android.systemui.statusbar.policy.ConfigurationController; - -/** - * A view that only shows its drawable while the phone is charging. - * - * Also reloads its drawable upon density changes. - */ -public class ChargingView extends ImageView implements - BatteryController.BatteryStateChangeCallback, - ConfigurationController.ConfigurationListener { - - private static final long CHARGING_INDICATION_DELAY_MS = 1000; - - private final AmbientDisplayConfiguration mConfig; - private final Runnable mClearSuppressCharging = this::clearSuppressCharging; - private BatteryController mBatteryController; - private int mImageResource; - private boolean mCharging; - private boolean mDark; - private boolean mSuppressCharging; - - - private void clearSuppressCharging() { - mSuppressCharging = false; - removeCallbacks(mClearSuppressCharging); - updateVisibility(); - } - - public ChargingView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - - mConfig = new AmbientDisplayConfiguration(context); - - TypedArray a = context.obtainStyledAttributes(attrs, new int[]{android.R.attr.src}); - int srcResId = a.getResourceId(0, 0); - - if (srcResId != 0) { - mImageResource = srcResId; - } - - a.recycle(); - - updateVisibility(); - } - - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - mBatteryController = Dependency.get(BatteryController.class); - mBatteryController.addCallback(this); - Dependency.get(ConfigurationController.class).addCallback(this); - } - - @Override - public void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mBatteryController.removeCallback(this); - Dependency.get(ConfigurationController.class).removeCallback(this); - removeCallbacks(mClearSuppressCharging); - } - - @Override - public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { - boolean startCharging = charging && !mCharging; - if (startCharging && deviceWillWakeUpWhenPluggedIn() && mDark) { - // We're about to wake up, and thus don't want to show the indicator just for it to be - // hidden again. - clearSuppressCharging(); - mSuppressCharging = true; - postDelayed(mClearSuppressCharging, CHARGING_INDICATION_DELAY_MS); - } - mCharging = charging; - updateVisibility(); - } - - private boolean deviceWillWakeUpWhenPluggedIn() { - boolean plugTurnsOnScreen = getResources().getBoolean( - com.android.internal.R.bool.config_unplugTurnsOnScreen); - boolean aod = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT); - return !aod && plugTurnsOnScreen; - } - - @Override - public void onDensityOrFontScaleChanged() { - setImageResource(mImageResource); - } - - public void setDark(boolean dark) { - mDark = dark; - if (!dark) { - clearSuppressCharging(); - } - updateVisibility(); - } - - private void updateVisibility() { - setVisibility(mCharging && !mSuppressCharging && mDark ? VISIBLE : INVISIBLE); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index e7e70afa20ce..7403ddc441f6 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -40,6 +40,8 @@ import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.PluginManagerImpl; import com.android.systemui.plugins.VolumeDialogController; +import com.android.systemui.power.EnhancedEstimates; +import com.android.systemui.power.EnhancedEstimatesImpl; import com.android.systemui.power.PowerNotificationWarnings; import com.android.systemui.power.PowerUI; import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; @@ -310,6 +312,8 @@ public class Dependency extends SystemUI { mProviders.put(OverviewProxyService.class, () -> new OverviewProxyService(mContext)); + mProviders.put(EnhancedEstimates.class, () -> new EnhancedEstimatesImpl()); + // Put all dependencies above here so the factory can override them if it wants. SystemUIFactory.getInstance().injectDependencies(mProviders, mContext); } diff --git a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java index f41425a1f848..5d2e4d09ff43 100644 --- a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java +++ b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java @@ -16,20 +16,14 @@ package com.android.systemui; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + import android.content.Context; -import android.database.ContentObserver; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; -import android.graphics.Point; -import android.graphics.PorterDuff; -import android.graphics.Region; -import android.os.Handler; -import android.os.Looper; -import android.os.UserHandle; -import android.provider.Settings; import android.view.DisplayCutout; import android.view.Gravity; import android.view.View; @@ -41,9 +35,6 @@ import android.view.WindowManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; -import java.util.Collections; -import java.util.List; - /** * Emulates a display cutout by drawing its shape in an overlay as supplied by * {@link DisplayCutout}. @@ -101,7 +92,7 @@ public class EmulatedDisplayCutout extends SystemUI implements ConfigurationList PixelFormat.TRANSLUCENT); lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; - lp.flags2 |= WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; + lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; lp.setTitle("EmulatedDisplayCutout"); lp.gravity = Gravity.TOP; return lp; diff --git a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java index 6f7a270799bc..c960fa122d50 100644 --- a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java +++ b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java @@ -14,6 +14,8 @@ package com.android.systemui; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + import static com.android.systemui.tuner.TunablePadding.FLAG_START; import static com.android.systemui.tuner.TunablePadding.FLAG_END; @@ -163,7 +165,7 @@ public class RoundedCorners extends SystemUI implements Tunable { | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; lp.setTitle("RoundedOverlay"); lp.gravity = Gravity.TOP; - lp.flags2 |= WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; + lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; return lp; } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java index bfe07a980ce7..0486a9dcca74 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -373,7 +373,7 @@ public class PipMenuActivity extends Activity { if (menuState == MENU_STATE_FULL) { mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim); } else { - mMenuContainerAnimator.playTogether(settingsAnim, dismissAnim); + mMenuContainerAnimator.playTogether(dismissAnim); } mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN); mMenuContainerAnimator.setDuration(MENU_FADE_DURATION); diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java new file mode 100644 index 000000000000..8f41a6036072 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java @@ -0,0 +1,8 @@ +package com.android.systemui.power; + +public interface EnhancedEstimates { + + boolean isHybridNotificationEnabled(); + + Estimate getEstimate(); +} diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java new file mode 100644 index 000000000000..d447542588c5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java @@ -0,0 +1,16 @@ +package com.android.systemui.power; + +import android.util.Log; + +public class EnhancedEstimatesImpl implements EnhancedEstimates { + + @Override + public boolean isHybridNotificationEnabled() { + return false; + } + + @Override + public Estimate getEstimate() { + return null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/power/Estimate.java b/packages/SystemUI/src/com/android/systemui/power/Estimate.java new file mode 100644 index 000000000000..12a8f0a435b4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/power/Estimate.java @@ -0,0 +1,11 @@ +package com.android.systemui.power; + +public class Estimate { + public final long estimateMillis; + public final boolean isBasedOnUsage; + + public Estimate(long estimateMillis, boolean isBasedOnUsage) { + this.estimateMillis = estimateMillis; + this.isBasedOnUsage = isBasedOnUsage; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index c29b362bda13..736286f21bfe 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -17,40 +17,40 @@ package com.android.systemui.power; import android.app.Notification; -import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnDismissListener; import android.content.Intent; import android.content.IntentFilter; +import android.icu.text.MeasureFormat; +import android.icu.text.MeasureFormat.FormatWidth; +import android.icu.util.Measure; +import android.icu.util.MeasureUnit; import android.media.AudioAttributes; -import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; -import android.os.SystemClock; import android.os.UserHandle; -import android.provider.Settings; import android.support.annotation.VisibleForTesting; +import android.text.format.DateUtils; import android.util.Slog; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.internal.notification.SystemNotificationChannels; import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.SystemUI; -import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.util.NotificationChannels; import java.io.PrintWriter; import java.text.NumberFormat; +import java.util.Locale; +import java.util.concurrent.TimeUnit; public class PowerNotificationWarnings implements PowerUI.WarningsUI { private static final String TAG = PowerUI.TAG + ".Notification"; @@ -96,8 +96,9 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { private long mScreenOffTime; private int mShowing; - private long mBucketDroppedNegativeTimeMs; + private long mWarningTriggerTimeMs; + private Estimate mEstimate; private boolean mWarning; private boolean mPlaySound; private boolean mInvalidCharger; @@ -130,14 +131,22 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { public void update(int batteryLevel, int bucket, long screenOffTime) { mBatteryLevel = batteryLevel; if (bucket >= 0) { - mBucketDroppedNegativeTimeMs = 0; + mWarningTriggerTimeMs = 0; } else if (bucket < mBucket) { - mBucketDroppedNegativeTimeMs = System.currentTimeMillis(); + mWarningTriggerTimeMs = System.currentTimeMillis(); } mBucket = bucket; mScreenOffTime = screenOffTime; } + @Override + public void updateEstimate(Estimate estimate) { + mEstimate = estimate; + if (estimate.estimateMillis <= PowerUI.THREE_HOURS_IN_MILLIS) { + mWarningTriggerTimeMs = System.currentTimeMillis(); + } + } + private void updateNotification() { if (DEBUG) Slog.d(TAG, "updateNotification mWarning=" + mWarning + " mPlaySound=" + mPlaySound + " mInvalidCharger=" + mInvalidCharger); @@ -171,25 +180,43 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_BAD_CHARGER, n, UserHandle.ALL); } - private void showWarningNotification() { - final int textRes = R.string.battery_low_percent_format; + protected void showWarningNotification() { final String percentage = NumberFormat.getPercentInstance().format((double) mBatteryLevel / 100.0); + // get standard notification copy + String title = mContext.getString(R.string.battery_low_title); + String contentText = mContext.getString(R.string.battery_low_percent_format, percentage); + + // override notification copy if hybrid notification enabled + if (mEstimate != null) { + title = mContext.getString(R.string.battery_low_title_hybrid); + contentText = mContext.getString( + mEstimate.isBasedOnUsage + ? R.string.battery_low_percent_format_hybrid + : R.string.battery_low_percent_format_hybrid_short, + percentage, + getTimeRemainingFormatted()); + } + final Notification.Builder nb = new Notification.Builder(mContext, NotificationChannels.BATTERY) .setSmallIcon(R.drawable.ic_power_low) // Bump the notification when the bucket dropped. - .setWhen(mBucketDroppedNegativeTimeMs) + .setWhen(mWarningTriggerTimeMs) .setShowWhen(false) - .setContentTitle(mContext.getString(R.string.battery_low_title)) - .setContentText(mContext.getString(textRes, percentage)) + .setContentTitle(title) + .setContentText(contentText) .setOnlyAlertOnce(true) .setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_WARNING)) - .setVisibility(Notification.VISIBILITY_PUBLIC) - .setColor(Utils.getColorAttr(mContext, android.R.attr.colorError)); + .setVisibility(Notification.VISIBILITY_PUBLIC); if (hasBatterySettings()) { nb.setContentIntent(pendingBroadcast(ACTION_SHOW_BATTERY_SETTINGS)); } + // Make the notification red if the percentage goes below a certain amount or the time + // remaining estimate is disabled + if (mEstimate == null || mBucket < 0) { + nb.setColor(Utils.getColorAttr(mContext, android.R.attr.colorError)); + } nb.addAction(0, mContext.getString(R.string.battery_saver_start_action), pendingBroadcast(ACTION_START_SAVER)); @@ -201,6 +228,23 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_POWER_LOW, n, UserHandle.ALL); } + @VisibleForTesting + String getTimeRemainingFormatted() { + final Locale currentLocale = mContext.getResources().getConfiguration().getLocales().get(0); + MeasureFormat frmt = MeasureFormat.getInstance(currentLocale, FormatWidth.NARROW); + + final long remainder = mEstimate.estimateMillis % DateUtils.HOUR_IN_MILLIS; + final long hours = TimeUnit.MILLISECONDS.toHours( + mEstimate.estimateMillis - remainder); + // round down to the nearest 15 min for now to not appear overly precise + final long minutes = TimeUnit.MILLISECONDS.toMinutes( + remainder - (remainder % TimeUnit.MINUTES.toMillis(15))); + final Measure hoursMeasure = new Measure(hours, MeasureUnit.HOUR); + final Measure minutesMeasure = new Measure(minutes, MeasureUnit.MINUTE); + + return frmt.formatMeasures(hoursMeasure, minutesMeasure); + } + private PendingIntent pendingBroadcast(String action) { return PendingIntent.getBroadcastAsUser(mContext, 0, new Intent(action), 0, UserHandle.CURRENT); diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index a351c09f68b7..c5aab601283c 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java @@ -52,6 +52,7 @@ import com.android.systemui.statusbar.phone.StatusBar; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Arrays; +import java.util.concurrent.TimeUnit; public class PowerUI extends SystemUI { static final String TAG = "PowerUI"; @@ -59,6 +60,7 @@ public class PowerUI extends SystemUI { private static final long TEMPERATURE_INTERVAL = 30 * DateUtils.SECOND_IN_MILLIS; private static final long TEMPERATURE_LOGGING_INTERVAL = DateUtils.HOUR_IN_MILLIS; private static final int MAX_RECENT_TEMPS = 125; // TEMPERATURE_LOGGING_INTERVAL plus a buffer + static final long THREE_HOURS_IN_MILLIS = DateUtils.HOUR_IN_MILLIS * 3; private final Handler mHandler = new Handler(); private final Receiver mReceiver = new Receiver(); @@ -68,9 +70,11 @@ public class PowerUI extends SystemUI { private WarningsUI mWarnings; private final Configuration mLastConfiguration = new Configuration(); private int mBatteryLevel = 100; + private long mTimeRemaining = Long.MAX_VALUE; private int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN; private int mPlugType = 0; private int mInvalidCharger = 0; + private EnhancedEstimates mEnhancedEstimates; private int mLowBatteryAlertCloseLevel; private final int[] mLowBatteryReminderLevels = new int[2]; @@ -83,8 +87,8 @@ public class PowerUI extends SystemUI { private long mNextLogTime; private IThermalService mThermalService; - // We create a method reference here so that we are guaranteed that we can remove a callback // by using the same instance (method references are not guaranteed to be the same object + // We create a method reference here so that we are guaranteed that we can remove a callback // each time they are created). private final Runnable mUpdateTempCallback = this::updateTemperatureWarning; @@ -94,6 +98,7 @@ public class PowerUI extends SystemUI { mContext.getSystemService(Context.HARDWARE_PROPERTIES_SERVICE); mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime(); mWarnings = Dependency.get(WarningsUI.class); + mEnhancedEstimates = Dependency.get(EnhancedEstimates.class); mLastConfiguration.setTo(mContext.getResources().getConfiguration()); ContentObserver obs = new ContentObserver(mHandler) { @@ -236,21 +241,9 @@ public class PowerUI extends SystemUI { return; } - boolean isPowerSaver = mPowerManager.isPowerSaveMode(); - if (!plugged - && !isPowerSaver - && (bucket < oldBucket || oldPlugged) - && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN - && bucket < 0) { - - // only play SFX when the dialog comes up or the bucket changes - final boolean playSound = bucket != oldBucket || oldPlugged; - mWarnings.showLowBatteryWarning(playSound); - } else if (isPowerSaver || plugged || (bucket > oldBucket && bucket > 0)) { - mWarnings.dismissLowBatteryWarning(); - } else { - mWarnings.updateLowBatteryWarning(); - } + // Show the correct version of low battery warning if needed + maybeShowBatteryWarning(plugged, oldPlugged, oldBucket, bucket); + } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { mScreenOffTime = SystemClock.elapsedRealtime(); } else if (Intent.ACTION_SCREEN_ON.equals(action)) { @@ -261,7 +254,65 @@ public class PowerUI extends SystemUI { Slog.w(TAG, "unknown intent: " + intent); } } - }; + } + + protected void maybeShowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket, + int bucket) { + boolean isPowerSaver = mPowerManager.isPowerSaveMode(); + // only play SFX when the dialog comes up or the bucket changes + final boolean playSound = bucket != oldBucket || oldPlugged; + long oldTimeRemaining = mTimeRemaining; + if (mEnhancedEstimates.isHybridNotificationEnabled()) { + final Estimate estimate = mEnhancedEstimates.getEstimate(); + // Turbo is not always booted once SysUI is running so we have ot make sure we actually + // get data back + if (estimate != null) { + mTimeRemaining = estimate.estimateMillis; + mWarnings.updateEstimate(estimate); + } + } + + if (shouldShowLowBatteryWarning(plugged, oldPlugged, oldBucket, bucket, oldTimeRemaining, + mTimeRemaining, + isPowerSaver, mBatteryStatus)) { + mWarnings.showLowBatteryWarning(playSound); + } else if (shouldDismissLowBatteryWarning(plugged, oldBucket, bucket, mTimeRemaining, + isPowerSaver)) { + mWarnings.dismissLowBatteryWarning(); + } else { + mWarnings.updateLowBatteryWarning(); + } + } + + @VisibleForTesting + boolean shouldShowLowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket, + int bucket, long oldTimeRemaining, long timeRemaining, + boolean isPowerSaver, int mBatteryStatus) { + return !plugged + && !isPowerSaver + && (((bucket < oldBucket || oldPlugged) && bucket < 0) + || (mEnhancedEstimates.isHybridNotificationEnabled() + && timeRemaining < THREE_HOURS_IN_MILLIS + && isHourLess(oldTimeRemaining, timeRemaining))) + && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN; + } + + private boolean isHourLess(long oldTimeRemaining, long timeRemaining) { + final long dif = oldTimeRemaining - timeRemaining; + return dif >= TimeUnit.HOURS.toMillis(1); + } + + @VisibleForTesting + boolean shouldDismissLowBatteryWarning(boolean plugged, int oldBucket, int bucket, + long timeRemaining, boolean isPowerSaver) { + final boolean hybridWouldDismiss = mEnhancedEstimates.isHybridNotificationEnabled() + && timeRemaining > THREE_HOURS_IN_MILLIS; + final boolean standardWouldDismiss = (bucket > oldBucket && bucket > 0); + return isPowerSaver + || plugged + || (standardWouldDismiss && (!mEnhancedEstimates.isHybridNotificationEnabled() + || hybridWouldDismiss)); + } private void initTemperatureWarning() { ContentResolver resolver = mContext.getContentResolver(); @@ -433,6 +484,7 @@ public class PowerUI extends SystemUI { public interface WarningsUI { void update(int batteryLevel, int bucket, long screenOffTime); + void updateEstimate(Estimate estimate); void dismissLowBatteryWarning(); void showLowBatteryWarning(boolean playSound); void dismissInvalidChargerWarning(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 33b5268e03e1..7f0acc254a7c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -17,13 +17,18 @@ package com.android.systemui.qs; import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Path; import android.graphics.Point; import android.util.AttributeSet; +import android.util.Log; import android.view.View; import android.widget.FrameLayout; +import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.qs.customize.QSCustomizer; +import com.android.systemui.statusbar.ExpandableOutlineView; /** * Wrapper view with background which contains {@link QSPanel} and {@link BaseStatusBarHeader} @@ -31,6 +36,7 @@ import com.android.systemui.qs.customize.QSCustomizer; public class QSContainerImpl extends FrameLayout { private final Point mSizePoint = new Point(); + private final Path mClipPath = new Path(); private int mHeightOverride = -1; protected View mQSPanel; @@ -40,6 +46,7 @@ public class QSContainerImpl extends FrameLayout { private QSCustomizer mQSCustomizer; private View mQSFooter; private float mFullElevation; + private float mRadius; public QSContainerImpl(Context context, AttributeSet attrs) { super(context, attrs); @@ -54,6 +61,8 @@ public class QSContainerImpl extends FrameLayout { mQSCustomizer = findViewById(R.id.qs_customize); mQSFooter = findViewById(R.id.qs_footer); mFullElevation = mQSPanel.getElevation(); + mRadius = getResources().getDimensionPixelSize( + Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius)); setClickable(true); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); @@ -93,6 +102,18 @@ public class QSContainerImpl extends FrameLayout { updateExpansion(); } + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + boolean ret; + canvas.save(); + if (child != mQSCustomizer) { + canvas.clipPath(mClipPath); + } + ret = super.drawChild(canvas, child, drawingTime); + canvas.restore(); + return ret; + } + /** * Overrides the height of this view (post-layout), so that the content is clipped to that * height and the background is set to that height. @@ -110,6 +131,10 @@ public class QSContainerImpl extends FrameLayout { mQSDetail.setBottom(getTop() + height); // Pin QS Footer to the bottom of the panel. mQSFooter.setTranslationY(height - mQSFooter.getHeight()); + + ExpandableOutlineView.getRoundedRectPath(0, 0, getWidth(), height, mRadius, + mRadius, + mClipPath); } protected int calculateContainerHeight() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java index c249e3778c0a..0f83078e0738 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java @@ -103,7 +103,7 @@ public class QSIconViewImpl extends QSIconView { if (iv instanceof SlashImageView) { ((SlashImageView) iv).setAnimationEnabled(shouldAnimate); - ((SlashImageView) iv).setState(state.slash, d); + ((SlashImageView) iv).setState(null, d); } else { iv.setImageDrawable(d); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java index 4d0e60d505f6..acd327b8eae2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java @@ -13,7 +13,12 @@ */ package com.android.systemui.qs.tileimpl; +import static com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH; + +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.content.Context; +import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; @@ -22,16 +27,21 @@ import android.os.Looper; import android.os.Message; import android.service.quicksettings.Tile; import android.text.TextUtils; +import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ImageView.ScaleType; import android.widget.Switch; +import com.android.settingslib.Utils; import com.android.systemui.R; -import com.android.systemui.plugins.qs.*; +import com.android.systemui.plugins.qs.QSIconView; +import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTile.BooleanState; public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { @@ -47,6 +57,12 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { private boolean mCollapsedView; private boolean mClicked; + private final ImageView mBg; + private final int mColorActive; + private final int mColorInactive; + private final int mColorDisabled; + private int mCircleColor; + public QSTileBaseView(Context context, QSIconView icon) { this(context, icon, false); } @@ -60,6 +76,10 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { mIconFrame.setForegroundGravity(Gravity.CENTER); int size = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size); addView(mIconFrame, new LayoutParams(size, size)); + mBg = new ImageView(getContext()); + mBg.setScaleType(ScaleType.FIT_CENTER); + mBg.setImageResource(R.drawable.ic_qs_circle); + mIconFrame.addView(mBg); mIcon = icon; FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); @@ -73,6 +93,11 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); setBackground(mTileBackground); + mColorActive = Utils.getColorAttr(context, android.R.attr.colorAccent); + mColorDisabled = Utils.getDisabled(context, + Utils.getColorAttr(context, android.R.attr.textColorTertiary)); + mColorInactive = Utils.getColorAttr(context, android.R.attr.textColorSecondary); + setPadding(0, 0, 0, 0); setClipChildren(false); setClipToPadding(false); @@ -80,6 +105,10 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { setFocusable(true); } + public View getBgCicle() { + return mBg; + } + protected Drawable newTileBackground() { final int[] attrs = new int[]{android.R.attr.selectableItemBackgroundBorderless}; final TypedArray ta = getContext().obtainStyledAttributes(attrs); @@ -150,6 +179,20 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { } protected void handleStateChanged(QSTile.State state) { + int circleColor = getCircleColor(state.state); + if (circleColor != mCircleColor) { + if (mBg.isShown()) { + ValueAnimator animator = ValueAnimator.ofArgb(mCircleColor, circleColor) + .setDuration(QS_ANIM_LENGTH); + animator.addUpdateListener(animation -> mBg.setImageTintList(ColorStateList.valueOf( + (Integer) animation.getAnimatedValue()))); + animator.start(); + } else { + QSIconViewImpl.setTint(mBg, circleColor); + } + mCircleColor = circleColor; + } + setClickable(state.state != Tile.STATE_UNAVAILABLE); mIcon.setIcon(state); setContentDescription(state.contentDescription); @@ -163,6 +206,19 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { } } + private int getCircleColor(int state) { + switch (state) { + case Tile.STATE_ACTIVE: + return mColorActive; + case Tile.STATE_INACTIVE: + case Tile.STATE_UNAVAILABLE: + return mColorDisabled; + default: + Log.e(TAG, "Invalid state " + state); + return 0; + } + } + @Override public void setClickable(boolean clickable) { super.setClickable(clickable); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 576a447447b5..7259282935a0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -373,11 +373,11 @@ public abstract class QSTileImpl<TState extends State> implements QSTile { switch (state) { case Tile.STATE_UNAVAILABLE: return Utils.getDisabled(context, - Utils.getColorAttr(context, android.R.attr.colorForeground)); + Utils.getColorAttr(context, android.R.attr.textColorSecondary)); case Tile.STATE_INACTIVE: - return Utils.getColorAttr(context, android.R.attr.textColorHint); + return Utils.getColorAttr(context, android.R.attr.textColorSecondary); case Tile.STATE_ACTIVE: - return Utils.getColorAttr(context, android.R.attr.textColorPrimary); + return Utils.getColorAttr(context, android.R.attr.colorPrimary); default: Log.e("QSTile", "Invalid state " + state); return 0; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java index 263dac0f44ec..a226f3cac83a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java @@ -18,6 +18,7 @@ import android.content.Context; import android.content.res.Configuration; import android.service.quicksettings.Tile; import android.text.SpannableStringBuilder; +import android.text.TextUtils; import android.text.style.ForegroundColorSpan; import android.view.Gravity; import android.view.LayoutInflater; @@ -28,6 +29,7 @@ import android.widget.TextView; import com.android.systemui.FontSizeUtils; import com.android.systemui.R; +import com.android.systemui.R.id; import com.android.systemui.plugins.qs.QSIconView; import com.android.systemui.plugins.qs.QSTile; @@ -36,8 +38,10 @@ import libcore.util.Objects; /** View that represents a standard quick settings tile. **/ public class QSTileView extends QSTileBaseView { + private static final boolean DUAL_TARGET_ALLOWED = false; private View mDivider; protected TextView mLabel; + private TextView mSecondLine; private ImageView mPadLock; private int mState; private ViewGroup mLabelContainer; @@ -86,6 +90,8 @@ public class QSTileView extends QSTileBaseView { mDivider = mLabelContainer.findViewById(R.id.underline); mExpandIndicator = mLabelContainer.findViewById(R.id.expand_indicator); mExpandSpace = mLabelContainer.findViewById(R.id.expand_space); + mSecondLine = mLabelContainer.findViewById(R.id.app_label); + mSecondLine.setAlpha(.6f); addView(mLabelContainer); } @@ -103,14 +109,20 @@ public class QSTileView extends QSTileBaseView { mState = state.state; mLabel.setText(state.label); } - mExpandIndicator.setVisibility(state.dualTarget ? View.VISIBLE : View.GONE); - mExpandSpace.setVisibility(state.dualTarget ? View.VISIBLE : View.GONE); - mLabelContainer.setContentDescription(state.dualTarget ? state.dualLabelContentDescription + if (!Objects.equal(mSecondLine.getText(), state.secondaryLabel)) { + mSecondLine.setText(state.secondaryLabel); + mSecondLine.setVisibility(TextUtils.isEmpty(state.secondaryLabel) ? View.GONE + : View.VISIBLE); + } + boolean dualTarget = DUAL_TARGET_ALLOWED && state.dualTarget; + mExpandIndicator.setVisibility(dualTarget ? View.VISIBLE : View.GONE); + mExpandSpace.setVisibility(dualTarget ? View.VISIBLE : View.GONE); + mLabelContainer.setContentDescription(dualTarget ? state.dualLabelContentDescription : null); - if (state.dualTarget != mLabelContainer.isClickable()) { - mLabelContainer.setClickable(state.dualTarget); - mLabelContainer.setLongClickable(state.dualTarget); - mLabelContainer.setBackground(state.dualTarget ? newTileBackground() : null); + if (dualTarget != mLabelContainer.isClickable()) { + mLabelContainer.setClickable(dualTarget); + mLabelContainer.setLongClickable(dualTarget); + mLabelContainer.setBackground(dualTarget ? newTileBackground() : null); } mLabel.setEnabled(!state.disabledByPolicy); mPadLock.setVisibility(state.disabledByPolicy ? View.VISIBLE : View.GONE); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 0e4a9fe32911..fff9f8e7e038 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -125,11 +125,11 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { state.slash = new SlashState(); } state.slash.isSlashed = !enabled; + state.label = mContext.getString(R.string.quick_settings_bluetooth_label); if (enabled) { - state.label = null; if (connected) { state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_connected); - state.label = mController.getLastDeviceName(); + state.secondaryLabel = mController.getLastDeviceName(); CachedBluetoothDevice lastDevice = mController.getLastDevice(); if (lastDevice != null) { int batteryLevel = lastDevice.getBatteryLevel(); @@ -140,25 +140,20 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { } } state.contentDescription = mContext.getString( - R.string.accessibility_bluetooth_name, state.label); + R.string.accessibility_bluetooth_name, state.secondaryLabel); } else if (state.isTransient) { state.icon = ResourceIcon.get(R.drawable.ic_bluetooth_transient_animation); state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_bluetooth_connecting); - state.label = mContext.getString(R.string.quick_settings_bluetooth_label); } else { state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on); state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_bluetooth_on) + "," + mContext.getString(R.string.accessibility_not_connected); } - if (TextUtils.isEmpty(state.label)) { - state.label = mContext.getString(R.string.quick_settings_bluetooth_label); - } state.state = Tile.STATE_ACTIVE; } else { state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on); - state.label = mContext.getString(R.string.quick_settings_bluetooth_label); state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_bluetooth_off); state.state = Tile.STATE_INACTIVE; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index d1e6dcc9e480..bf8a64cee974 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -33,6 +33,7 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; +import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.util.AttributeSet; import android.util.FloatProperty; @@ -173,6 +174,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private FalsingManager mFalsingManager; private AboveShelfChangedListener mAboveShelfChangedListener; private HeadsUpManager mHeadsUpManager; + private View mHelperButton; private boolean mJustClicked; private boolean mIconAnimationRunning; @@ -387,6 +389,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView updateLimits(); updateIconVisibilities(); updateShelfIconColor(); + + showBlockingHelper(mEntry.userSentiment == + NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE); } @VisibleForTesting @@ -1318,6 +1323,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView requestLayout(); } + public void showBlockingHelper(boolean show) { + mHelperButton.setVisibility(show ? View.VISIBLE : View.GONE); + } + @Override protected void onFinishInflate() { super.onFinishInflate(); @@ -1325,6 +1334,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded); mLayouts = new NotificationContentView[] {mPrivateLayout, mPublicLayout}; + final NotificationGutsManager gutsMan = Dependency.get(NotificationGutsManager.class); + mHelperButton = findViewById(R.id.helper); + mHelperButton.setOnClickListener(view -> { + doLongClickCallback(); + }); + for (NotificationContentView l : mLayouts) { l.setExpandClickListener(mExpandClickListener); l.setContainingNotification(this); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 85400a15dfc5..43047ed6a5c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -52,6 +52,8 @@ import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.util.wakelock.SettableWakeLock; import com.android.systemui.util.wakelock.WakeLock; +import java.text.NumberFormat; + /** * Controls the indications and error messages shown on the Keyguard */ @@ -87,6 +89,7 @@ public class KeyguardIndicationController { private boolean mPowerCharged; private int mChargingSpeed; private int mChargingWattage; + private int mBatteryLevel; private String mMessageToShowOnScreenOn; private KeyguardUpdateMonitorCallback mUpdateMonitorCallback; @@ -285,14 +288,18 @@ public class KeyguardIndicationController { // Walk down a precedence-ordered list of what indication // should be shown based on user or device state if (mDozing) { - // If we're dozing, never show a persistent indication. + mTextView.setTextColor(Color.WHITE); if (!TextUtils.isEmpty(mTransientIndication)) { // When dozing we ignore any text color and use white instead, because // colors can be hard to read in low brightness. - mTextView.setTextColor(Color.WHITE); mTextView.switchIndication(mTransientIndication); + } else if (mPowerPluggedIn) { + String indication = computePowerIndication(); + mTextView.switchIndication(indication); } else { - mTextView.switchIndication(null); + String percentage = NumberFormat.getPercentInstance() + .format(mBatteryLevel / 100f); + mTextView.switchIndication(percentage); } return; } @@ -422,6 +429,7 @@ public class KeyguardIndicationController { mPowerCharged = status.isCharged(); mChargingWattage = status.maxChargingWattage; mChargingSpeed = status.getChargingSpeed(mSlowThreshold, mFastThreshold); + mBatteryLevel = status.level; updateIndication(); if (mDozing) { if (!wasPluggedIn && mPowerPluggedIn) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index d0417b59448d..7e0dba5e129d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -86,6 +86,8 @@ public class NotificationData { public RemoteViews cachedAmbientContentView; public CharSequence remoteInputText; public List<SnoozeCriterion> snoozeCriteria; + public int userSentiment = Ranking.USER_SENTIMENT_NEUTRAL; + private int mCachedContrastColor = COLOR_INVALID; private int mCachedContrastColorIsFor = COLOR_INVALID; private InflationTask mRunningTask = null; @@ -463,6 +465,7 @@ public class NotificationData { } entry.channel = getChannel(entry.key); entry.snoozeCriteria = getSnoozeCriteria(entry.key); + entry.userSentiment = mTmpRanking.getUserSentiment(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java index 6cbbd6cd1f18..e5a311d099d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java @@ -17,11 +17,15 @@ package com.android.systemui.statusbar.car; import android.content.Context; +import android.graphics.Canvas; import android.util.AttributeSet; +import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; import com.android.systemui.R; +import com.android.systemui.plugins.statusbar.phone.NavGesture; +import com.android.systemui.statusbar.phone.NavigationBarGestureHelper; import com.android.systemui.statusbar.phone.NavigationBarView; /** @@ -72,4 +76,68 @@ class CarNavigationBarView extends NavigationBarView { // Calling setNavigationIconHints in the base class will result in a NPE as the car // navigation bar does not have a back button. } + + @Override + public void onPluginConnected(NavGesture plugin, Context context) { + // set to null version of the plugin ignoring incoming arg. + super.onPluginConnected(new NullNavGesture(), context); + } + + @Override + public void onPluginDisconnected(NavGesture plugin) { + // reinstall the null nav gesture plugin + super.onPluginConnected(new NullNavGesture(), getContext()); + } + + /** + * Null object pattern to work around expectations of the base class. + * This is a temporary solution to have the car system ui working. + * Already underway is a refactor of they car sys ui as to not use this class + * hierarchy. + */ + private static class NullNavGesture implements NavGesture { + @Override + public GestureHelper getGestureHelper() { + return new GestureHelper() { + @Override + public boolean onTouchEvent(MotionEvent event) { + return false; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + return false; + } + + @Override + public void setBarState(boolean vertical, boolean isRtl) { + } + + @Override + public void onDraw(Canvas canvas) { + } + + @Override + public void onDarkIntensityChange(float intensity) { + } + + @Override + public void onLayout(boolean changed, int left, int top, int right, int bottom) { + } + }; + } + + @Override + public int getVersion() { + return 0; + } + + @Override + public void onCreate(Context sysuiContext, Context pluginContext) { + } + + @Override + public void onDestroy() { + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java index 78ee04048644..9b123cbea8d7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java @@ -14,7 +14,6 @@ package com.android.systemui.statusbar.phone; -import android.graphics.drawable.Drawable; import android.view.View; import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface; @@ -39,6 +38,7 @@ public class ButtonDispatcher { private Integer mAlpha; private Float mDarkIntensity; private Integer mVisibility = -1; + private Boolean mDelayTouchFeedback; private KeyButtonDrawable mImageDrawable; private View mCurrentView; private boolean mVertical; @@ -71,10 +71,10 @@ public class ButtonDispatcher { if (mImageDrawable != null) { ((ButtonInterface) view).setImageDrawable(mImageDrawable); } - - if (view instanceof ButtonInterface) { - ((ButtonInterface) view).setVertical(mVertical); + if (mDelayTouchFeedback != null) { + ((ButtonInterface) view).setDelayTouchFeedback(mDelayTouchFeedback); } + ((ButtonInterface) view).setVertical(mVertical); } public int getId() { @@ -134,6 +134,14 @@ public class ButtonDispatcher { } } + public void setDelayTouchFeedback(boolean delay) { + mDelayTouchFeedback = delay; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + ((ButtonInterface) mViews.get(i)).setDelayTouchFeedback(delay); + } + } + public void setOnClickListener(View.OnClickListener clickListener) { mClickListener = clickListener; final int N = mViews.size(); @@ -166,6 +174,14 @@ public class ButtonDispatcher { } } + public void setClickable(boolean clickable) { + abortCurrentGesture(); + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setClickable(clickable); + } + } + public ArrayList<View> getViews() { return mViews; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 01b3b442f2b6..ca66e987933c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -51,6 +51,7 @@ import android.telecom.TelecomManager; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; +import android.util.MathUtils; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; @@ -166,6 +167,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private String mLeftButtonStr; private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger(); private boolean mDozing; + private int mIndicationBottomMargin; + private int mIndicationBottomMarginAmbient; + private float mDarkAmount; + private int mBurnInXOffset; public KeyguardBottomAreaView(Context context) { this(context, null); @@ -235,6 +240,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mEnterpriseDisclosure = findViewById( R.id.keyguard_indication_enterprise_disclosure); mIndicationText = findViewById(R.id.keyguard_indication_text); + mIndicationBottomMargin = getResources().getDimensionPixelSize( + R.dimen.keyguard_indication_margin_bottom); + mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize( + R.dimen.keyguard_indication_margin_bottom_ambient); updateCameraVisibility(); mUnlockMethodCache = UnlockMethodCache.getInstance(getContext()); mUnlockMethodCache.addListener(this); @@ -303,11 +312,13 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - int indicationBottomMargin = getResources().getDimensionPixelSize( + mIndicationBottomMargin = getResources().getDimensionPixelSize( R.dimen.keyguard_indication_margin_bottom); + mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize( + R.dimen.keyguard_indication_margin_bottom_ambient); MarginLayoutParams mlp = (MarginLayoutParams) mIndicationArea.getLayoutParams(); - if (mlp.bottomMargin != indicationBottomMargin) { - mlp.bottomMargin = indicationBottomMargin; + if (mlp.bottomMargin != mIndicationBottomMargin) { + mlp.bottomMargin = mIndicationBottomMargin; mIndicationArea.setLayoutParams(mlp); } @@ -543,6 +554,22 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } } + public void setDarkAmount(float darkAmount) { + if (darkAmount == mDarkAmount) { + return; + } + mDarkAmount = darkAmount; + // Let's randomize the bottom margin every time we wake up to avoid burn-in. + if (darkAmount == 0) { + mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize( + R.dimen.keyguard_indication_margin_bottom_ambient) + + (int) (Math.random() * mIndicationText.getTextSize()); + } + mIndicationArea.setAlpha(MathUtils.lerp(1f, 0.7f, darkAmount)); + mIndicationArea.setTranslationY(MathUtils.lerp(0, + mIndicationBottomMargin - mIndicationBottomMarginAmbient, darkAmount)); + } + private static boolean isSuccessfulLaunch(int result) { return result == ActivityManager.START_SUCCESS || result == ActivityManager.START_DELIVERED_TO_TOP @@ -687,11 +714,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL if (mRightAffordanceView.getVisibility() == View.VISIBLE) { startFinishDozeAnimationElement(mRightAffordanceView, delay); } - mIndicationArea.setAlpha(0f); - mIndicationArea.animate() - .alpha(1f) - .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN) - .setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION); } private void startFinishDozeAnimationElement(View element, long delay) { @@ -815,6 +837,22 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } } + public void dozeTimeTick() { + if (mDarkAmount == 1) { + // Move indication every minute to avoid burn-in + final int dozeTranslation = mIndicationBottomMargin - mIndicationBottomMarginAmbient; + mIndicationArea.setTranslationY(dozeTranslation + (float) Math.random() * 5); + } + } + + public void setBurnInXOffset(int burnInXOffset) { + if (mBurnInXOffset == burnInXOffset) { + return; + } + mBurnInXOffset = burnInXOffset; + mIndicationArea.setTranslationX(burnInXOffset); + } + private class DefaultLeftButton implements IntentButton { private IconState mIconState = new IconState(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java index 34486dbcaf43..264f5749fa73 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java @@ -250,7 +250,7 @@ public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChange } break; case STATE_FACE_UNLOCK: - iconRes = R.drawable.ic_account_circle; + iconRes = R.drawable.ic_face_unlock; break; case STATE_FINGERPRINT: // If screen is off and device asleep, use the draw on animation so the first frame diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java index 6f636aa6299d..4faa84aca099 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java @@ -19,15 +19,14 @@ package com.android.systemui.statusbar.phone; import android.app.ActivityManager; import android.content.Context; import android.content.res.Resources; +import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Rect; import android.os.RemoteException; import android.util.Log; -import android.view.GestureDetector; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; -import android.view.ViewConfiguration; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget; @@ -37,6 +36,7 @@ import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper; import com.android.systemui.shared.recents.IOverviewProxy; +import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.stackdivider.Divider; import com.android.systemui.tuner.TunerService; @@ -72,6 +72,7 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture private NavigationBarView mNavigationBarView; private boolean mIsVertical; + private final QuickScrubController mQuickScrubController; private final int mScrollTouchSlop; private final Matrix mTransformGlobalMatrix = new Matrix(); private final Matrix mTransformLocalMatrix = new Matrix(); @@ -89,6 +90,7 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture mContext = context; Resources r = context.getResources(); mScrollTouchSlop = r.getDimensionPixelSize(R.dimen.navigation_bar_min_swipe_distance); + mQuickScrubController = new QuickScrubController(context); Dependency.get(TunerService.class).addTunable(this, KEY_DOCK_WINDOW_GESTURE); } @@ -101,10 +103,12 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture mRecentsComponent = recentsComponent; mDivider = divider; mNavigationBarView = navigationBarView; + mQuickScrubController.setComponents(mNavigationBarView); } public void setBarState(boolean isVertical, boolean isRTL) { mIsVertical = isVertical; + mQuickScrubController.setBarState(isVertical, isRTL); } private boolean proxyMotionEvents(MotionEvent event) { @@ -126,7 +130,6 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture public boolean onInterceptTouchEvent(MotionEvent event) { int action = event.getAction(); - boolean result = false; switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { mTouchDownX = (int) event.getX(); @@ -137,24 +140,26 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix); break; } - case MotionEvent.ACTION_MOVE: { - int x = (int) event.getX(); - int y = (int) event.getY(); - int xDiff = Math.abs(x - mTouchDownX); - int yDiff = Math.abs(y - mTouchDownY); - boolean exceededTouchSlop = xDiff > mScrollTouchSlop && xDiff > yDiff - || yDiff > mScrollTouchSlop && yDiff > xDiff; - if (exceededTouchSlop) { - result = true; - } - break; - } - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - break; } - proxyMotionEvents(event); - return result || (mDockWindowEnabled && interceptDockWindowEvent(event)); + if (!mQuickScrubController.onInterceptTouchEvent(event)) { + proxyMotionEvents(event); + return false; + } + return (mDockWindowEnabled && interceptDockWindowEvent(event)); + } + + public void onDraw(Canvas canvas) { + if (mOverviewEventSender.getProxy() != null) { + mQuickScrubController.onDraw(canvas); + } + } + + public void onLayout(boolean changed, int left, int top, int right, int bottom) { + mQuickScrubController.onLayout(changed, left, top, right, bottom); + } + + public void onDarkIntensityChange(float intensity) { + mQuickScrubController.onDarkIntensityChange(intensity); } private boolean interceptDockWindowEvent(MotionEvent event) { @@ -294,7 +299,7 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture } public boolean onTouchEvent(MotionEvent event) { - boolean result = proxyMotionEvents(event); + boolean result = mQuickScrubController.onTouchEvent(event) || proxyMotionEvents(event); if (mDockWindowEnabled) { result |= handleDockWindowEvent(event); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java index bd6421c20980..b11367523c08 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java @@ -138,6 +138,7 @@ public final class NavigationBarTransitions extends BarTransitions { if (mAutoDim) { applyLightsOut(false, true); } + mView.onDarkIntensityChange(darkIntensity); } private final View.OnTouchListener mLightsOutListener = new View.OnTouchListener() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 006a85b7ef8c..059ce929b290 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -26,6 +26,7 @@ import android.app.ActivityManager; import android.app.StatusBarManager; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; @@ -625,6 +626,24 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav updateRotatedViews(); } + public void onDarkIntensityChange(float intensity) { + if (mGestureHelper != null) { + mGestureHelper.onDarkIntensityChange(intensity); + } + } + + @Override + protected void onDraw(Canvas canvas) { + mGestureHelper.onDraw(canvas); + super.onDraw(canvas); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + mGestureHelper.onLayout(changed, left, top, right, bottom); + } + private void updateRotatedViews() { mRotatedViews[Surface.ROTATION_0] = mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 32675d3b2aac..0cc7f5dfbae1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -478,6 +478,7 @@ public class NotificationPanelView extends PanelView implements } mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding); mNotificationStackScroller.setDarkShelfOffsetX(mClockPositionResult.clockX); + mKeyguardBottomArea.setBurnInXOffset(mClockPositionResult.clockX); requestScrollerTopPaddingUpdate(animate); } @@ -2608,7 +2609,8 @@ public class NotificationPanelView extends PanelView implements private void setDarkAmount(float amount) { mDarkAmount = amount; - mKeyguardStatusView.setDark(mDarkAmount); + mKeyguardStatusView.setDarkAmount(mDarkAmount); + mKeyguardBottomArea.setDarkAmount(mDarkAmount); positionClockAndNotifications(); } @@ -2630,8 +2632,9 @@ public class NotificationPanelView extends PanelView implements } } - public void refreshTime() { + public void dozeTimeTick() { mKeyguardStatusView.refreshTime(); + mKeyguardBottomArea.dozeTimeTick(); if (mDarkAmount > 0) { positionClockAndNotifications(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java new file mode 100644 index 000000000000..9f8a7efa04f3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java @@ -0,0 +1,370 @@ +/* + * 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.statusbar.phone; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.os.Handler; +import android.os.RemoteException; +import android.util.Log; +import android.util.Slog; +import android.view.Display; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.support.annotation.DimenRes; +import com.android.systemui.Dependency; +import com.android.systemui.OverviewProxyService; +import com.android.systemui.R; +import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper; +import com.android.systemui.shared.recents.IOverviewProxy; +import com.android.systemui.shared.recents.utilities.Utilities; + +import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM; + +/** + * Class to detect gestures on the navigation bar and implement quick scrub and switch. + */ +public class QuickScrubController extends GestureDetector.SimpleOnGestureListener implements + GestureHelper { + + private static final String TAG = "QuickScrubController"; + private static final int QUICK_SWITCH_FLING_VELOCITY = 0; + private static final int ANIM_DURATION_MS = 200; + private static final long LONG_PRESS_DELAY_MS = 150; + + /** + * For quick step, set a damping value to allow the button to stick closer its origin position + * when dragging before quick scrub is active. + */ + private static final int SWITCH_STICKINESS = 4; + + private NavigationBarView mNavigationBarView; + private GestureDetector mGestureDetector; + + private boolean mDraggingActive; + private boolean mQuickScrubActive; + private float mDownOffset; + private float mTranslation; + private int mTouchDownX; + private int mTouchDownY; + private boolean mDragPositive; + private boolean mIsVertical; + private boolean mIsRTL; + private float mMaxTrackPaintAlpha; + + private final Handler mHandler = new Handler(); + private final Interpolator mQuickScrubEndInterpolator = new DecelerateInterpolator(); + private final Rect mTrackRect = new Rect(); + private final Rect mHomeButtonRect = new Rect(); + private final Paint mTrackPaint = new Paint(); + private final int mScrollTouchSlop; + private final OverviewProxyService mOverviewEventSender; + private final Display mDisplay; + private final int mTrackThickness; + private final int mTrackPadding; + private final ValueAnimator mTrackAnimator; + private final ValueAnimator mButtonAnimator; + private final AnimatorSet mQuickScrubEndAnimator; + private final Context mContext; + + private final AnimatorUpdateListener mTrackAnimatorListener = valueAnimator -> { + mTrackPaint.setAlpha(Math.round((float) valueAnimator.getAnimatedValue() * 255)); + mNavigationBarView.invalidate(); + }; + + private final AnimatorUpdateListener mButtonTranslationListener = animator -> { + int pos = (int) animator.getAnimatedValue(); + if (!mQuickScrubActive) { + pos = mDragPositive ? Math.min((int) mTranslation, pos) : Math.max((int) mTranslation, pos); + } + final View homeView = mNavigationBarView.getHomeButton().getCurrentView(); + if (mIsVertical) { + homeView.setTranslationY(pos); + } else { + homeView.setTranslationX(pos); + } + }; + + private AnimatorListenerAdapter mQuickScrubEndListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mNavigationBarView.getHomeButton().setClickable(true); + mQuickScrubActive = false; + mTranslation = 0; + } + }; + + private Runnable mLongPressRunnable = this::startQuickScrub; + + private final GestureDetector.SimpleOnGestureListener mGestureListener = + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velX, float velY) { + if (mQuickScrubActive) { + return false; + } + float velocityX = mIsRTL ? -velX : velX; + float absVelY = Math.abs(velY); + final boolean isValidFling = velocityX > QUICK_SWITCH_FLING_VELOCITY && + mIsVertical ? (absVelY > velocityX) : (velocityX > absVelY); + if (isValidFling) { + mDraggingActive = false; + mButtonAnimator.setIntValues((int) mTranslation, 0); + mButtonAnimator.start(); + mHandler.removeCallbacks(mLongPressRunnable); + try { + final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy(); + overviewProxy.onQuickSwitch(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to send start of quick switch.", e); + } + } + return true; + } + }; + + public QuickScrubController(Context context) { + mContext = context; + mScrollTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + mDisplay = ((WindowManager) context.getSystemService( + Context.WINDOW_SERVICE)).getDefaultDisplay(); + mOverviewEventSender = Dependency.get(OverviewProxyService.class); + mGestureDetector = new GestureDetector(mContext, mGestureListener); + mTrackThickness = getDimensionPixelSize(mContext, R.dimen.nav_quick_scrub_track_thickness); + mTrackPadding = getDimensionPixelSize(mContext, R.dimen.nav_quick_scrub_track_edge_padding); + + mTrackAnimator = ObjectAnimator.ofFloat(); + mTrackAnimator.addUpdateListener(mTrackAnimatorListener); + mButtonAnimator = ObjectAnimator.ofInt(); + mButtonAnimator.addUpdateListener(mButtonTranslationListener); + mQuickScrubEndAnimator = new AnimatorSet(); + mQuickScrubEndAnimator.playTogether(mTrackAnimator, mButtonAnimator); + mQuickScrubEndAnimator.setDuration(ANIM_DURATION_MS); + mQuickScrubEndAnimator.addListener(mQuickScrubEndListener); + mQuickScrubEndAnimator.setInterpolator(mQuickScrubEndInterpolator); + } + + public void setComponents(NavigationBarView navigationBarView) { + mNavigationBarView = navigationBarView; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy(); + final ButtonDispatcher homeButton = mNavigationBarView.getHomeButton(); + if (overviewProxy == null) { + homeButton.setDelayTouchFeedback(false); + return false; + } + mGestureDetector.onTouchEvent(event); + int action = event.getAction(); + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: { + int x = (int) event.getX(); + int y = (int) event.getY(); + if (mHomeButtonRect.contains(x, y)) { + mTouchDownX = x; + mTouchDownY = y; + homeButton.setDelayTouchFeedback(true); + mHandler.postDelayed(mLongPressRunnable, LONG_PRESS_DELAY_MS); + } else { + mTouchDownX = mTouchDownY = -1; + } + break; + } + case MotionEvent.ACTION_MOVE: { + if (mTouchDownX != -1) { + int x = (int) event.getX(); + int y = (int) event.getY(); + int xDiff = Math.abs(x - mTouchDownX); + int yDiff = Math.abs(y - mTouchDownY); + boolean exceededTouchSlop; + int pos, touchDown, offset, trackSize; + if (mIsVertical) { + exceededTouchSlop = yDiff > mScrollTouchSlop && yDiff > xDiff; + pos = y; + touchDown = mTouchDownY; + offset = pos - mTrackRect.top; + trackSize = mTrackRect.height(); + } else { + exceededTouchSlop = xDiff > mScrollTouchSlop && xDiff > yDiff; + pos = x; + touchDown = mTouchDownX; + offset = pos - mTrackRect.left; + trackSize = mTrackRect.width(); + } + if (!mDragPositive) { + offset -= mIsVertical ? mTrackRect.height() : mTrackRect.width(); + } + + // Control the button movement + if (!mDraggingActive && exceededTouchSlop) { + boolean allowDrag = !mDragPositive + ? offset < 0 && pos < touchDown : offset >= 0 && pos > touchDown; + if (allowDrag) { + mDownOffset = offset; + homeButton.setClickable(false); + mDraggingActive = true; + } + } + if (mDraggingActive && (mDragPositive && offset >= 0 + || !mDragPositive && offset <= 0)) { + float scrubFraction = + Utilities.clamp(Math.abs(offset) * 1f / trackSize, 0, 1); + mTranslation = !mDragPositive + ? Utilities.clamp(offset - mDownOffset, -trackSize, 0) + : Utilities.clamp(offset - mDownOffset, 0, trackSize); + if (mQuickScrubActive) { + try { + overviewProxy.onQuickScrubProgress(scrubFraction); + } catch (RemoteException e) { + Log.e(TAG, "Failed to send progress of quick scrub.", e); + } + } else { + mTranslation /= SWITCH_STICKINESS; + } + if (mIsVertical) { + homeButton.getCurrentView().setTranslationY(mTranslation); + } else { + homeButton.getCurrentView().setTranslationX(mTranslation); + } + } + } + break; + } + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + endQuickScrub(); + break; + } + return mDraggingActive || mQuickScrubActive; + } + + @Override + public void onDraw(Canvas canvas) { + canvas.drawRect(mTrackRect, mTrackPaint); + } + + @Override + public void onLayout(boolean changed, int left, int top, int right, int bottom) { + final int width = right - left; + final int height = bottom - top; + final int x1, x2, y1, y2; + if (mIsVertical) { + x1 = (width - mTrackThickness) / 2; + x2 = x1 + mTrackThickness; + y1 = mDragPositive ? height / 2 : mTrackPadding; + y2 = y1 + height / 2 - mTrackPadding; + } else { + y1 = (height - mTrackThickness) / 2; + y2 = y1 + mTrackThickness; + x1 = mDragPositive ? width / 2 : mTrackPadding; + x2 = x1 + width / 2 - mTrackPadding; + } + mTrackRect.set(x1, y1, x2, y2); + + // Get the touch rect of the home button location + View homeView = mNavigationBarView.getHomeButton().getCurrentView(); + int[] globalHomePos = homeView.getLocationOnScreen(); + int[] globalNavBarPos = mNavigationBarView.getLocationOnScreen(); + int homeX = globalHomePos[0] - globalNavBarPos[0]; + int homeY = globalHomePos[1] - globalNavBarPos[1]; + mHomeButtonRect.set(homeX, homeY, homeX + homeView.getMeasuredWidth(), + homeY + homeView.getMeasuredHeight()); + } + + @Override + public void onDarkIntensityChange(float intensity) { + if (intensity == 0) { + mTrackPaint.setColor(mContext.getColor(R.color.quick_step_track_background_light)); + } else if (intensity == 1) { + mTrackPaint.setColor(mContext.getColor(R.color.quick_step_track_background_dark)); + } + mMaxTrackPaintAlpha = mTrackPaint.getAlpha() * 1f / 255; + mTrackPaint.setAlpha(0); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP) { + endQuickScrub(); + } + return false; + } + + @Override + public void setBarState(boolean isVertical, boolean isRTL) { + mIsVertical = isVertical; + mIsRTL = isRTL; + try { + int navbarPos = WindowManagerGlobal.getWindowManagerService().getNavBarPosition(); + mDragPositive = navbarPos == NAV_BAR_LEFT || navbarPos == NAV_BAR_BOTTOM; + if (isRTL) { + mDragPositive = !mDragPositive; + } + } catch (RemoteException e) { + Slog.e(TAG, "Failed to get nav bar position.", e); + } + } + + private void startQuickScrub() { + if (!mQuickScrubActive) { + mQuickScrubActive = true; + mTrackAnimator.setFloatValues(0, mMaxTrackPaintAlpha); + mTrackAnimator.start(); + try { + mOverviewEventSender.getProxy().onQuickScrubStart(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to send start of quick scrub.", e); + } + } + } + + private void endQuickScrub() { + mHandler.removeCallbacks(mLongPressRunnable); + if (mDraggingActive || mQuickScrubActive) { + mButtonAnimator.setIntValues((int) mTranslation, 0); + mTrackAnimator.setFloatValues(mTrackPaint.getAlpha() * 1f / 255, 0); + mQuickScrubEndAnimator.start(); + try { + mOverviewEventSender.getProxy().onQuickScrubEnd(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to send end of quick scrub.", e); + } + } + mDraggingActive = false; + } + + private int getDimensionPixelSize(Context context, @DimenRes int resId) { + return context.getResources().getDimensionPixelSize(resId); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 2da1e4d108b4..af65a86676e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1596,7 +1596,7 @@ public class StatusBar extends SystemUI implements DemoMode, final boolean hasArtwork = artworkDrawable != null; - if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK) + if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK) && !mDozing && (mState != StatusBarState.SHADE || allowWhenShade) && mFingerprintUnlockController.getMode() != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING @@ -1657,15 +1657,16 @@ public class StatusBar extends SystemUI implements DemoMode, } } } else { - // need to hide the album art, either because we are unlocked or because - // the metadata isn't there to support it + // need to hide the album art, either because we are unlocked, on AOD + // or because the metadata isn't there to support it if (mBackdrop.getVisibility() != View.GONE) { if (DEBUG_MEDIA) { Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork"); } + boolean cannotAnimateDoze = mDozing && !ScrimState.AOD.getAnimateChange(); if (mFingerprintUnlockController.getMode() == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING - || hideBecauseOccluded) { + || hideBecauseOccluded || cannotAnimateDoze) { // We are unlocking directly - no animation! mBackdrop.setVisibility(View.GONE); @@ -4644,7 +4645,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void dozeTimeTick() { - mNotificationPanel.refreshTime(); + mNotificationPanel.dozeTimeTick(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java index cc7943b85bc1..a2bec982ed58 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java @@ -26,9 +26,11 @@ import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; +import android.os.Handler; import android.view.DisplayListCanvas; import android.view.RenderNodeAnimator; import android.view.View; +import android.view.ViewConfiguration; import android.view.animation.Interpolator; import com.android.systemui.Interpolators; @@ -56,14 +58,17 @@ public class KeyButtonRipple extends Drawable { private float mGlowAlpha = 0f; private float mGlowScale = 1f; private boolean mPressed; + private boolean mVisible; private boolean mDrawingHardwareGlow; private int mMaxWidth; private boolean mLastDark; private boolean mDark; + private boolean mDelayTouchFeedback; private final Interpolator mInterpolator = new LogInterpolator(); private boolean mSupportHardware; private final View mTargetView; + private final Handler mHandler = new Handler(); private final HashSet<Animator> mRunningAnimations = new HashSet<>(); private final ArrayList<Animator> mTmpArray = new ArrayList<>(); @@ -77,6 +82,10 @@ public class KeyButtonRipple extends Drawable { mDark = darkIntensity >= 0.5f; } + public void setDelayTouchFeedback(boolean delay) { + mDelayTouchFeedback = delay; + } + private Paint getRipplePaint() { if (mRipplePaint == null) { mRipplePaint = new Paint(); @@ -211,7 +220,16 @@ public class KeyButtonRipple extends Drawable { } } + /** + * Abort the ripple while it is delayed and before shown used only when setShouldDelayStartTouch + * is enabled. + */ + public void abortDelayedRipple() { + mHandler.removeCallbacksAndMessages(null); + } + private void cancelAnimations() { + mVisible = false; mTmpArray.addAll(mRunningAnimations); int size = mTmpArray.size(); for (int i = 0; i < size; i++) { @@ -220,11 +238,21 @@ public class KeyButtonRipple extends Drawable { } mTmpArray.clear(); mRunningAnimations.clear(); + mHandler.removeCallbacksAndMessages(null); } private void setPressedSoftware(boolean pressed) { if (pressed) { - enterSoftware(); + if (mDelayTouchFeedback) { + if (mRunningAnimations.isEmpty()) { + mHandler.removeCallbacksAndMessages(null); + mHandler.postDelayed(this::enterSoftware, ViewConfiguration.getTapTimeout()); + } else if (mVisible) { + enterSoftware(); + } + } else { + enterSoftware(); + } } else { exitSoftware(); } @@ -232,6 +260,7 @@ public class KeyButtonRipple extends Drawable { private void enterSoftware() { cancelAnimations(); + mVisible = true; mGlowAlpha = getMaxGlowAlpha(); ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(this, "glowScale", 0f, GLOW_MAX_SCALE_FACTOR); @@ -240,6 +269,12 @@ public class KeyButtonRipple extends Drawable { scaleAnimator.addListener(mAnimatorListener); scaleAnimator.start(); mRunningAnimations.add(scaleAnimator); + + // With the delay, it could eventually animate the enter animation with no pressed state, + // then immediately show the exit animation. If this is skipped there will be no ripple. + if (mDelayTouchFeedback && !mPressed) { + exitSoftware(); + } } private void exitSoftware() { @@ -253,7 +288,16 @@ public class KeyButtonRipple extends Drawable { private void setPressedHardware(boolean pressed) { if (pressed) { - enterHardware(); + if (mDelayTouchFeedback) { + if (mRunningAnimations.isEmpty()) { + mHandler.removeCallbacksAndMessages(null); + mHandler.postDelayed(this::enterHardware, ViewConfiguration.getTapTimeout()); + } else if (mVisible) { + enterHardware(); + } + } else { + enterHardware(); + } } else { exitHardware(); } @@ -302,6 +346,7 @@ public class KeyButtonRipple extends Drawable { private void enterHardware() { cancelAnimations(); + mVisible = true; mDrawingHardwareGlow = true; setExtendStart(CanvasProperty.createFloat(getExtendSize() / 2)); final RenderNodeAnimator startAnim = new RenderNodeAnimator(getExtendStart(), @@ -343,6 +388,12 @@ public class KeyButtonRipple extends Drawable { mRunningAnimations.add(endAnim); invalidateSelf(); + + // With the delay, it could eventually animate the enter animation with no pressed state, + // then immediately show the exit animation. If this is skipped there will be no ripple. + if (mDelayTouchFeedback && !mPressed) { + exitHardware(); + } } private void exitHardware() { @@ -366,6 +417,7 @@ public class KeyButtonRipple extends Drawable { public void onAnimationEnd(Animator animation) { mRunningAnimations.remove(animation); if (mRunningAnimations.isEmpty() && !mPressed) { + mVisible = false; mDrawingHardwareGlow = false; invalidateSelf(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java index 05017718d42b..077c6c38c516 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java @@ -284,6 +284,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface { @Override public void abortCurrentGesture() { setPressed(false); + mRipple.abortDelayedRipple(); mGestureAborted = true; } @@ -301,6 +302,11 @@ public class KeyButtonView extends ImageView implements ButtonInterface { } @Override + public void setDelayTouchFeedback(boolean shouldDelay) { + mRipple.setDelayTouchFeedback(shouldDelay); + } + + @Override public void setVertical(boolean vertical) { //no op } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java new file mode 100644 index 000000000000..1dcdf6325809 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java @@ -0,0 +1,63 @@ +package com.android.systemui.statusbar.policy; + +import android.app.PendingIntent; +import android.app.RemoteInput; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.LinearLayout; + +import com.android.systemui.R; + +/** View which displays smart reply buttons in notifications. */ +public class SmartReplyView extends LinearLayout { + + private static final String TAG = "SmartReplyView"; + + public SmartReplyView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void setRepliesFromRemoteInput(RemoteInput remoteInput, PendingIntent pendingIntent) { + removeAllViews(); + if (remoteInput != null && pendingIntent != null) { + CharSequence[] choices = remoteInput.getChoices(); + if (choices != null) { + for (CharSequence choice : choices) { + Button replyButton = inflateReplyButton( + getContext(), this, choice, remoteInput, pendingIntent); + addView(replyButton); + } + } + } + } + + public static SmartReplyView inflate(Context context, ViewGroup root) { + return (SmartReplyView) + LayoutInflater.from(context).inflate(R.layout.smart_reply_view, root, false); + } + + private static Button inflateReplyButton(Context context, ViewGroup root, CharSequence choice, + RemoteInput remoteInput, PendingIntent pendingIntent) { + Button b = (Button) LayoutInflater.from(context).inflate( + R.layout.smart_reply_button, root, false); + b.setText(choice); + b.setOnClickListener(view -> { + Bundle results = new Bundle(); + results.putString(remoteInput.getResultKey(), choice.toString()); + Intent intent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + RemoteInput.addResultsToIntent(new RemoteInput[]{remoteInput}, intent, results); + try { + pendingIntent.send(context, 0, intent); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "Unable to send smart reply", e); + } + }); + return b; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java index bc98140f5af5..efa8386802e2 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java @@ -52,7 +52,7 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna public static final String VOLUME_UP_SILENT = "sysui_volume_up_silent"; public static final String VOLUME_SILENT_DO_NOT_DISTURB = "sysui_do_not_disturb"; - public static final boolean DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT = true; + public static final boolean DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT = false; public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = true; public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = true; diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 7b91f1475ca2..5a19a7677649 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -20,6 +20,7 @@ import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC; import static com.android.systemui.volume.Events.DISMISS_REASON_OUTPUT_CHOOSER; +import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; import static com.android.systemui.volume.Events.DISMISS_REASON_TOUCH_OUTSIDE; import android.accessibilityservice.AccessibilityServiceInfo; @@ -30,14 +31,13 @@ import android.app.Dialog; import android.app.KeyguardManager; import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; -import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.media.AudioSystem; import android.os.Debug; @@ -45,9 +45,8 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.SystemClock; +import android.provider.Settings; import android.provider.Settings.Global; -import android.transition.AutoTransition; -import android.transition.TransitionManager; import android.util.Log; import android.util.Slog; import android.util.SparseBooleanArray; @@ -72,7 +71,6 @@ import android.widget.TextView; import com.android.settingslib.Utils; import com.android.systemui.Dependency; -import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.plugins.VolumeDialog; import com.android.systemui.plugins.VolumeDialogController; @@ -104,7 +102,6 @@ public class VolumeDialogImpl implements VolumeDialog { private CustomDialog mDialog; private ViewGroup mDialogView; private ViewGroup mDialogRowsView; - private ImageButton mExpandButton; private ImageButton mRingerIcon; private ImageButton mOutputChooser; private TextView mRingerStatus; @@ -120,8 +117,6 @@ public class VolumeDialogImpl implements VolumeDialog { private final ColorStateList mInactiveSliderTint; private boolean mShowing; - private boolean mExpanded; - private boolean mExpandButtonAnimationRunning; private boolean mShowA11yStream; private int mActiveStream; @@ -182,11 +177,11 @@ public class VolumeDialogImpl implements VolumeDialog { mDialog.setContentView(R.layout.volume_dialog); mDialog.setOnShowListener(dialog -> { - mDialogView.setTranslationY(-mDialogView.getHeight()); + mDialogView.setTranslationX(mDialogView.getWidth() / 2); mDialogView.setAlpha(0); mDialogView.animate() .alpha(1) - .translationY(0) + .translationX(0) .setDuration(300) .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator()) .withEndAction(() -> { @@ -205,20 +200,10 @@ public class VolumeDialogImpl implements VolumeDialog { VolumeUiLayout hardwareLayout = VolumeUiLayout.get(mDialogView); hardwareLayout.setOutsideTouchListener(view -> dismiss(DISMISS_REASON_TOUCH_OUTSIDE)); - ViewGroup dialogContentView = mDialog.findViewById(R.id.volume_dialog_content); - mDialogRowsView = dialogContentView.findViewById(R.id.volume_dialog_rows); + mDialogRowsView = mDialog.findViewById(R.id.volume_dialog_rows); mRingerIcon = mDialog.findViewById(R.id.ringer_icon); mRingerStatus = mDialog.findViewById(R.id.ringer_status); - mExpanded = false; - mExpandButton = mDialogView.findViewById(R.id.volume_expand_button); - mExpandButton.setOnClickListener(mClickExpand); - mExpandButton.setVisibility( - AudioSystem.isSingleVolume(mContext) ? View.GONE : View.VISIBLE); - - mOutputChooser = mDialogView.findViewById(R.id.output_chooser); - mOutputChooser.setOnClickListener(mClickOutputChooser); - if (mRows.isEmpty()) { addRow(AudioManager.STREAM_MUSIC, R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true); @@ -239,6 +224,10 @@ public class VolumeDialogImpl implements VolumeDialog { } else { addExistingRows(); } + + mOutputChooser = mDialogView.findViewById(R.id.output_chooser); + mOutputChooser.setOnClickListener(mClickOutputChooser); + updateRowsH(getActiveRow()); initRingerH(); } @@ -273,11 +262,9 @@ public class VolumeDialogImpl implements VolumeDialog { VolumeRow row = new VolumeRow(); initRow(row, stream, iconRes, iconMuteRes, important, defaultStream); int rowSize; - int viewSize; - if (mShowA11yStream && dynamic && (rowSize = mRows.size()) > 1 - && (viewSize = mDialogRowsView.getChildCount()) > 1) { - // A11y Stream should be the last in the list - mDialogRowsView.addView(row.view, viewSize - 2); + if (mShowA11yStream && dynamic && (rowSize = mRows.size()) > 1) { + // A11y Stream should be the first in the list, so it's shown to start of other rows + mDialogRowsView.addView(row.view, 0); mRows.add(rowSize - 2, row); } else { mDialogRowsView.addView(row.view); @@ -315,7 +302,6 @@ public class VolumeDialogImpl implements VolumeDialog { public void dump(PrintWriter writer) { writer.println(VolumeDialogImpl.class.getSimpleName() + " state:"); writer.print(" mShowing: "); writer.println(mShowing); - writer.print(" mExpanded: "); writer.println(mExpanded); writer.print(" mActiveStream: "); writer.println(mActiveStream); writer.print(" mDynamic: "); writer.println(mDynamic); writer.print(" mAutomute: "); writer.println(mAutomute); @@ -432,6 +418,13 @@ public class VolumeDialogImpl implements VolumeDialog { } updateRingerH(); }); + mRingerIcon.setOnLongClickListener(v -> { + Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + dismissH(DISMISS_REASON_SETTINGS_CLICKED); + mContext.startActivity(intent); + return true; + }); updateRingerH(); } @@ -468,7 +461,6 @@ public class VolumeDialogImpl implements VolumeDialog { private int computeTimeoutH() { if (mAccessibility.mFeedbackEnabled) return 20000; if (mHovering) return 16000; - if (mExpanded) return 5000; if (mSafetyWarning != null) return 5000; return 3000; } @@ -480,13 +472,11 @@ public class VolumeDialogImpl implements VolumeDialog { mDialogView.animate().cancel(); mShowing = false; - updateExpandedH(false /* expanding */, true /* dismissing */); - - mDialogView.setTranslationY(0); + mDialogView.setTranslationX(0); mDialogView.setAlpha(1); mDialogView.animate() .alpha(0) - .translationY(-mDialogView.getHeight()) + .translationX(mDialogView.getWidth() / 2) .setDuration(250) .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator()) .withEndAction(() -> mHandler.postDelayed(() -> { @@ -514,67 +504,6 @@ public class VolumeDialogImpl implements VolumeDialog { } } - private void updateExpandedH(final boolean expanded, final boolean dismissing) { - if (D.BUG) Log.d(TAG, "updateExpandedH " + expanded); - - if (mExpanded == expanded) return; - mExpanded = expanded; - mExpandButtonAnimationRunning = isAttached(); - updateExpandButtonH(); - TransitionManager.endTransitions(mDialogView); - final VolumeRow activeRow = getActiveRow(); - if (!dismissing) { - mWindow.setLayout(mWindow.getAttributes().width, ViewGroup.LayoutParams.MATCH_PARENT); - TransitionManager.beginDelayedTransition(mDialogView, getTransition()); - } - updateRowsH(activeRow); - rescheduleTimeoutH(); - } - - private AutoTransition getTransition() { - AutoTransition transition = new AutoTransition(); - transition.setDuration(300); - transition.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); - return transition; - } - - private void updateExpandButtonH() { - if (D.BUG) Log.d(TAG, "updateExpandButtonH"); - - mExpandButton.setClickable(!mExpandButtonAnimationRunning); - if (!(mExpandButtonAnimationRunning && isAttached())) { - final int res = mExpanded ? R.drawable.ic_volume_collapse_animation - : R.drawable.ic_volume_expand_animation; - if (hasTouchFeature()) { - mExpandButton.setImageResource(res); - } else { - // if there is no touch feature, show the volume ringer instead - mExpandButton.setImageResource(R.drawable.ic_volume_ringer); - mExpandButton.setBackgroundResource(0); // remove gray background emphasis - } - mExpandButton.setContentDescription(mContext.getString(mExpanded ? - R.string.accessibility_volume_collapse : R.string.accessibility_volume_expand)); - } - if (mExpandButtonAnimationRunning) { - final Drawable d = mExpandButton.getDrawable(); - if (d instanceof AnimatedVectorDrawable) { - // workaround to reset drawable - final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) d.getConstantState() - .newDrawable(); - mExpandButton.setImageDrawable(avd); - avd.start(); - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - mExpandButtonAnimationRunning = false; - updateExpandButtonH(); - rescheduleTimeoutH(); - } - }, 300); - } - } - } - private boolean isAttached() { return mDialogView != null && mDialogView.isAttachedToWindow(); } @@ -597,7 +526,7 @@ public class VolumeDialogImpl implements VolumeDialog { return true; } - return row.defaultStream || isActive || (mExpanded && row.important); + return row.defaultStream || isActive; } private void updateRowsH(final VolumeRow activeRow) { @@ -954,16 +883,6 @@ public class VolumeDialogImpl implements VolumeDialog { } } - private final OnClickListener mClickExpand = new OnClickListener() { - @Override - public void onClick(View v) { - mExpandButton.animate().cancel(); - final boolean newExpand = !mExpanded; - Events.writeEvent(mContext, Events.EVENT_EXPAND, newExpand); - updateExpandedH(newExpand, false /* dismissing */); - } - }; - private final OnClickListener mClickOutputChooser = new OnClickListener() { @Override public void onClick(View v) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java index 49ac9b6b7ffe..1c9cbc139504 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java @@ -14,15 +14,37 @@ package com.android.systemui.volume; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import android.content.Context; +import android.content.res.Configuration; import android.util.AttributeSet; +import android.view.Gravity; import android.view.View; +import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver; import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.SeekBar; + +import com.android.systemui.R; +import com.android.systemui.util.leak.RotationUtils; public class VolumeUiLayout extends FrameLayout { + private View mChild; + private int mOldHeight; + private boolean mAnimating; + private AnimatorSet mAnimation; + private boolean mHasOutsideTouch; + private int mRotation = ROTATION_NONE; public VolumeUiLayout(Context context, AttributeSet attrs) { super(context, attrs); } @@ -40,11 +62,245 @@ public class VolumeUiLayout extends FrameLayout { } @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (mChild == null) { + if (getChildCount() != 0) { + mChild = getChildAt(0); + mOldHeight = mChild.getMeasuredHeight(); + updateRotation(); + } else { + return; + } + } + int newHeight = mChild.getMeasuredHeight(); + if (newHeight != mOldHeight) { + animateChild(mOldHeight, newHeight); + } + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + updateRotation(); + } + + private void updateRotation() { + int rotation = RotationUtils.getRotation(getContext()); + if (rotation != mRotation) { + rotate(mRotation, rotation); + mRotation = rotation; + } + } + + private void rotate(View view, int from, int to, boolean swapDimens) { + if (from != ROTATION_NONE && to != ROTATION_NONE) { + // Rather than handling this confusing case, just do 2 rotations. + rotate(view, from, ROTATION_NONE, swapDimens); + rotate(view, ROTATION_NONE, to, swapDimens); + return; + } + if (from == ROTATION_LANDSCAPE || to == ROTATION_SEASCAPE) { + rotateRight(view); + } else { + rotateLeft(view); + } + if (to != ROTATION_NONE) { + if (swapDimens && view instanceof LinearLayout) { + LinearLayout linearLayout = (LinearLayout) view; + linearLayout.setOrientation(LinearLayout.HORIZONTAL); + swapDimens(view); + } + } else { + if (swapDimens && view instanceof LinearLayout) { + LinearLayout linearLayout = (LinearLayout) view; + linearLayout.setOrientation(LinearLayout.VERTICAL); + swapDimens(view); + } + } + } + + private void rotate(int from, int to) { + View footer = mChild.findViewById(R.id.footer); + rotate(footer, from, to, false); + rotate(this, from, to, true); + rotate(mChild, from, to, true); + ViewGroup rows = mChild.findViewById(R.id.volume_dialog_rows); + rotate(rows, from, to, true); + int rowCount = rows.getChildCount(); + for (int i = 0; i < rowCount; i++) { + View child = rows.getChildAt(i); + if (to == ROTATION_SEASCAPE) { + rotateSeekBars(to, 0); + } else if (to == ROTATION_LANDSCAPE) { + rotateSeekBars(to, 180); + } else { + rotateSeekBars(to, 270); + } + rotate(child, from, to, true); + } + } + + private void swapDimens(View v) { + if (v == null) { + return; + } + ViewGroup.LayoutParams params = v.getLayoutParams(); + int h = params.width; + params.width = params.height; + params.height = h; + v.setLayoutParams(params); + } + + private void rotateSeekBars(int to, int rotation) { + SeekBar seekbar = mChild.findViewById(R.id.volume_row_slider); + if (seekbar != null) { + seekbar.setRotation((float) rotation); + } + + View parent = mChild.findViewById(R.id.volume_row_slider_frame); + swapDimens(parent); + ViewGroup.LayoutParams params = seekbar.getLayoutParams(); + ViewGroup.LayoutParams parentParams = parent.getLayoutParams(); + if (to != ROTATION_NONE) { + params.height = parentParams.height; + params.width = parentParams.width; + } else { + params.height = parentParams.width; + params.width = parentParams.height; + } + seekbar.setLayoutParams(params); + } + + private int rotateGravityRight(int gravity) { + int retGravity = 0; + int layoutDirection = getLayoutDirection(); + final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); + final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; + + switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { + case Gravity.CENTER_HORIZONTAL: + retGravity |= Gravity.CENTER_VERTICAL; + break; + case Gravity.RIGHT: + retGravity |= Gravity.BOTTOM; + break; + case Gravity.LEFT: + default: + retGravity |= Gravity.TOP; + break; + } + + switch (verticalGravity) { + case Gravity.CENTER_VERTICAL: + retGravity |= Gravity.CENTER_HORIZONTAL; + break; + case Gravity.BOTTOM: + retGravity |= Gravity.LEFT; + break; + case Gravity.TOP: + default: + retGravity |= Gravity.RIGHT; + break; + } + return retGravity; + } + + private int rotateGravityLeft(int gravity) { + if (gravity == -1) { + gravity = Gravity.TOP | Gravity.START; + } + int retGravity = 0; + int layoutDirection = getLayoutDirection(); + final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); + final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; + + switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { + case Gravity.CENTER_HORIZONTAL: + retGravity |= Gravity.CENTER_VERTICAL; + break; + case Gravity.RIGHT: + retGravity |= Gravity.TOP; + break; + case Gravity.LEFT: + default: + retGravity |= Gravity.BOTTOM; + break; + } + + switch (verticalGravity) { + case Gravity.CENTER_VERTICAL: + retGravity |= Gravity.CENTER_HORIZONTAL; + break; + case Gravity.BOTTOM: + retGravity |= Gravity.RIGHT; + break; + case Gravity.TOP: + default: + retGravity |= Gravity.LEFT; + break; + } + return retGravity; + } + + private void rotateLeft(View v) { + if (v.getParent() instanceof FrameLayout) { + LayoutParams p = (LayoutParams) v.getLayoutParams(); + p.gravity = rotateGravityLeft(p.gravity); + } + + v.setPadding(v.getPaddingTop(), v.getPaddingRight(), v.getPaddingBottom(), + v.getPaddingLeft()); + MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams(); + params.setMargins(params.topMargin, params.rightMargin, params.bottomMargin, + params.leftMargin); + v.setLayoutParams(params); + } + + private void rotateRight(View v) { + if (v.getParent() instanceof FrameLayout) { + LayoutParams p = (LayoutParams) v.getLayoutParams(); + p.gravity = rotateGravityRight(p.gravity); + } + + v.setPadding(v.getPaddingBottom(), v.getPaddingLeft(), v.getPaddingTop(), + v.getPaddingRight()); + MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams(); + params.setMargins(params.bottomMargin, params.leftMargin, params.topMargin, + params.rightMargin); + v.setLayoutParams(params); + } + + private void animateChild(int oldHeight, int newHeight) { + if (true) return; + if (mAnimating) { + mAnimation.cancel(); + } + mAnimating = true; + mAnimation = new AnimatorSet(); + mAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mAnimating = false; + } + }); + int fromTop = mChild.getTop(); + int fromBottom = mChild.getBottom(); + int toTop = fromTop - ((newHeight - oldHeight) / 2); + int toBottom = fromBottom + ((newHeight - oldHeight) / 2); + ObjectAnimator top = ObjectAnimator.ofInt(mChild, "top", fromTop, toTop); + mAnimation.playTogether(top, + ObjectAnimator.ofInt(mChild, "bottom", fromBottom, toBottom)); + } + + + @Override public ViewOutlineProvider getOutlineProvider() { return super.getOutlineProvider(); } public void setOutsideTouchListener(OnClickListener onClickListener) { + mHasOutsideTouch = true; requestLayout(); setOnClickListener(onClickListener); setClickable(true); @@ -60,7 +316,14 @@ public class VolumeUiLayout extends FrameLayout { } private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener = inoutInfo -> { + if (mHasOutsideTouch || (mChild == null)) { + inoutInfo.setTouchableInsets( + ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); + return; + } inoutInfo.setTouchableInsets( - ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); + ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT); + inoutInfo.contentInsets.set(mChild.getLeft(), mChild.getTop(), + 0, getBottom() - mChild.getBottom()); }; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java index 7f07e0c70e8a..3e37cfe75e0f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java @@ -38,6 +38,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.SysuiTestCase; import com.android.systemui.util.NotificationChannels; +import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,6 +47,9 @@ import org.mockito.ArgumentCaptor; @SmallTest @RunWith(AndroidJUnit4.class) public class PowerNotificationWarningsTest extends SysuiTestCase { + + public static final String FORMATTED_45M = "0h 45m"; + public static final String FORMATTED_HOUR = "1h 0m"; private final NotificationManager mMockNotificationManager = mock(NotificationManager.class); private PowerNotificationWarnings mPowerNotificationWarnings; @@ -147,4 +151,22 @@ public class PowerNotificationWarningsTest extends SysuiTestCase { verify(mMockNotificationManager, times(1)).cancelAsUser(anyString(), eq(SystemMessage.NOTE_THERMAL_SHUTDOWN), any()); } + + @Test + public void testGetTimeRemainingFormatted_roundsDownTo15() { + mPowerNotificationWarnings.updateEstimate( + new Estimate(TimeUnit.MINUTES.toMillis(57), true)); + String time = mPowerNotificationWarnings.getTimeRemainingFormatted(); + + assertTrue("time:" + time + ", expected: " + FORMATTED_45M, time.equals(FORMATTED_45M)); + } + + @Test + public void testGetTimeRemainingFormatted_keepsMinutesWhenZero() { + mPowerNotificationWarnings.updateEstimate( + new Estimate(TimeUnit.MINUTES.toMillis(65), true)); + String time = mPowerNotificationWarnings.getTimeRemainingFormatted(); + + assertTrue("time:" + time + ", expected: " + FORMATTED_HOUR, time.equals(FORMATTED_HOUR)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java index e4734a474b62..fdb7f8d005b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java @@ -19,12 +19,15 @@ import static android.os.HardwarePropertiesManager.TEMPERATURE_CURRENT; import static android.os.HardwarePropertiesManager.TEMPERATURE_SHUTDOWN; import static android.provider.Settings.Global.SHOW_TEMPERATURE_WARNING; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.os.BatteryManager; import android.os.HardwarePropertiesManager; import android.provider.Settings; import android.testing.AndroidTestingRunner; @@ -37,6 +40,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.power.PowerUI.WarningsUI; import com.android.systemui.statusbar.phone.StatusBar; +import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,14 +50,23 @@ import org.junit.runner.RunWith; @SmallTest public class PowerUITest extends SysuiTestCase { + private static final boolean UNPLUGGED = false; + private static final boolean POWER_SAVER_OFF = false; + private static final int ABOVE_WARNING_BUCKET = 1; + public static final int BELOW_WARNING_BUCKET = -1; + public static final long BELOW_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(2); + public static final long ABOVE_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(4); private HardwarePropertiesManager mHardProps; private WarningsUI mMockWarnings; private PowerUI mPowerUI; + private EnhancedEstimates mEnhacedEstimates; @Before public void setup() { mMockWarnings = mDependency.injectMockDependency(WarningsUI.class); + mEnhacedEstimates = mDependency.injectMockDependency(EnhancedEstimates.class); mHardProps = mock(HardwarePropertiesManager.class); + mContext.putComponent(StatusBar.class, mock(StatusBar.class)); mContext.addMockSystemService(Context.HARDWARE_PROPERTIES_SERVICE, mHardProps); @@ -128,6 +141,180 @@ public class PowerUITest extends SysuiTestCase { verify(mMockWarnings).showHighTemperatureWarning(); } + @Test + public void testShouldShowLowBatteryWarning_showHybridOnly_returnsShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + mPowerUI.start(); + + // unplugged device that would not show the non-hybrid notification but would show the + // hybrid + boolean shouldShow = + mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, + ABOVE_WARNING_BUCKET, Long.MAX_VALUE, BELOW_HYBRID_THRESHOLD, + POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertTrue(shouldShow); + } + + @Test + public void testShouldShowLowBatteryWarning_showHybrid_showStandard_returnsShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + mPowerUI.start(); + + // unplugged device that would show the non-hybrid notification and the hybrid + boolean shouldShow = + mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, + BELOW_WARNING_BUCKET, Long.MAX_VALUE, BELOW_HYBRID_THRESHOLD, + POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertTrue(shouldShow); + } + + @Test + public void testShouldShowLowBatteryWarning_showStandardOnly_returnsShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + mPowerUI.start(); + + // unplugged device that would show the non-hybrid but not the hybrid + boolean shouldShow = + mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, + BELOW_WARNING_BUCKET, Long.MAX_VALUE, ABOVE_HYBRID_THRESHOLD, + POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertTrue(shouldShow); + } + + @Test + public void testShouldShowLowBatteryWarning_deviceHighBattery_returnsNoShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + mPowerUI.start(); + + // unplugged device that would show the neither due to battery level being good + boolean shouldShow = + mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, + ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, ABOVE_HYBRID_THRESHOLD, + POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertFalse(shouldShow); + } + + @Test + public void testShouldShowLowBatteryWarning_devicePlugged_returnsNoShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + mPowerUI.start(); + + // plugged device that would show the neither due to being plugged + boolean shouldShow = + mPowerUI.shouldShowLowBatteryWarning(!UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, + BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD, + POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertFalse(shouldShow); + } + + @Test + public void testShouldShowLowBatteryWarning_deviceBatteryStatusUnkown_returnsNoShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + mPowerUI.start(); + + // Unknown battery status device that would show the neither due + boolean shouldShow = + mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, + BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD, + !POWER_SAVER_OFF, BatteryManager.BATTERY_STATUS_UNKNOWN); + assertFalse(shouldShow); + } + + @Test + public void testShouldShowLowBatteryWarning_batterySaverEnabled_returnsNoShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + mPowerUI.start(); + + // BatterySaverEnabled device that would show the neither due to battery saver + boolean shouldShow = + mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, + BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD, + !POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertFalse(shouldShow); + } + + @Test + public void testShouldDismissLowBatteryWarning_dismissWhenPowerSaverEnabled() { + mPowerUI.start(); + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + // device that gets power saver turned on should dismiss + boolean shouldDismiss = + mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, + BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, !POWER_SAVER_OFF); + assertTrue(shouldDismiss); + } + + @Test + public void testShouldDismissLowBatteryWarning_dismissWhenPlugged() { + mPowerUI.start(); + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + + // device that gets plugged in should dismiss + boolean shouldDismiss = + mPowerUI.shouldDismissLowBatteryWarning(!UNPLUGGED, BELOW_WARNING_BUCKET, + BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF); + assertTrue(shouldDismiss); + } + + @Test + public void testShouldDismissLowBatteryWarning_dismissHybridSignal_showStandardSignal_shouldShow() { + mPowerUI.start(); + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + // would dismiss hybrid but not non-hybrid should not dismiss + boolean shouldDismiss = + mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, + BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF); + assertFalse(shouldDismiss); + } + + @Test + public void testShouldDismissLowBatteryWarning_showHybridSignal_dismissStandardSignal_shouldShow() { + mPowerUI.start(); + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + + // would dismiss non-hybrid but not hybrid should not dismiss + boolean shouldDismiss = + mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, + ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, POWER_SAVER_OFF); + assertFalse(shouldDismiss); + } + + @Test + public void testShouldDismissLowBatteryWarning_showBothSignal_shouldShow() { + mPowerUI.start(); + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + + // should not dismiss when both would not dismiss + boolean shouldDismiss = + mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, + BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, POWER_SAVER_OFF); + assertFalse(shouldDismiss); + } + + @Test + public void testShouldDismissLowBatteryWarning_dismissBothSignal_shouldDismiss() { + mPowerUI.start(); + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + + //should dismiss if both would dismiss + boolean shouldDismiss = + mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, + ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF); + assertTrue(shouldDismiss); + } + + @Test + public void testShouldDismissLowBatteryWarning_dismissStandardSignal_hybridDisabled_shouldDismiss() { + mPowerUI.start(); + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(false); + + // would dismiss non-hybrid with hybrid disabled should dismiss + boolean shouldDismiss = + mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, + ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF); + assertTrue(shouldDismiss); + } + private void setCurrentTemp(float temp) { when(mHardProps.getDeviceTemperatures(DEVICE_TEMPERATURE_SKIN, TEMPERATURE_CURRENT)) .thenReturn(new float[] { temp }); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java index 0a683891e66f..f9ec3f92181f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java @@ -32,11 +32,14 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.Notification; +import android.app.NotificationManager; import android.content.Context; +import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; import android.service.notification.NotificationListenerService; +import android.service.notification.NotificationRankingUpdate; import android.service.notification.StatusBarNotification; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; @@ -120,6 +123,23 @@ public class NotificationEntryManagerTest extends SysuiTestCase { } } + private void setUserSentiment(String key, int sentiment) { + doAnswer(invocationOnMock -> { + NotificationListenerService.Ranking ranking = (NotificationListenerService.Ranking) + invocationOnMock.getArguments()[1]; + ranking.populate( + key, + 0, + false, + 0, + 0, + NotificationManager.IMPORTANCE_DEFAULT, + null, null, + null, null, null, true, sentiment); + return true; + }).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class)); + } + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -158,6 +178,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntryManager = new TestableNotificationEntryManager(mContext, mBarService); mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mCallback, mHeadsUpManager); + + setUserSentiment(mEntry.key, NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL); } @Test @@ -196,6 +218,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { assertEquals(mEntryManager.getNotificationData().get(mSbn.getKey()), entry); assertNotNull(entry.row); + assertEquals(mEntry.userSentiment, + NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL); } @Test @@ -204,6 +228,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntryManager.getNotificationData().add(mEntry); + setUserSentiment(mEntry.key, NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE); + mHandler.post(() -> { mEntryManager.updateNotification(mSbn, mRankingMap); }); @@ -219,6 +245,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { verify(mForegroundServiceController).updateNotification(eq(mSbn), anyInt()); verify(mCallback).onNotificationUpdated(mSbn); assertNotNull(mEntry.row); + assertEquals(mEntry.userSentiment, + NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE); } @Test diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 10a809aaae08..04cee676075d 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -5134,6 +5134,15 @@ message MetricsEvent { // OS: P ACTION_SCREENSHOT_POWER_MENU = 1282; + // OPEN: Settings > Apps & Notifications -> Special app access -> Storage Access + // CATEGORY: SETTINGS + // OS: P + STORAGE_ACCESS = 1283; + + // OPEN: Settings > Apps & Notifications -> Special app access -> Storage Access -> Package + // CATEGORY: SETTINGS + // OS: P + APPLICATIONS_STORAGE_DETAIL = 1284; // ---- End P Constants, all P constants go above this line ---- // Add new aosp constants above this line. diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java index 307f74d36ac5..f96fa7c237f7 100644 --- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java @@ -231,8 +231,7 @@ final class SaveUi { final Window window = mDialog.getWindow(); window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); - window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM + window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH); window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS); diff --git a/services/backup/java/com/android/server/backup/BackupPolicyEnforcer.java b/services/backup/java/com/android/server/backup/BackupPolicyEnforcer.java new file mode 100644 index 000000000000..158084a4be21 --- /dev/null +++ b/services/backup/java/com/android/server/backup/BackupPolicyEnforcer.java @@ -0,0 +1,25 @@ +package com.android.server.backup; + +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * A helper class to decouple this service from {@link DevicePolicyManager} in order to improve + * testability. + */ +@VisibleForTesting +public class BackupPolicyEnforcer { + private DevicePolicyManager mDevicePolicyManager; + + public BackupPolicyEnforcer(Context context) { + mDevicePolicyManager = + (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); + } + + public ComponentName getMandatoryBackupTransport() { + return mDevicePolicyManager.getMandatoryBackupTransport(); + } +} diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java index 03591a812bdd..518891006b37 100644 --- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java @@ -38,6 +38,7 @@ import android.app.AppGlobals; import android.app.IActivityManager; import android.app.IBackupAgent; import android.app.PendingIntent; +import android.app.admin.DevicePolicyManager; import android.app.backup.BackupManager; import android.app.backup.BackupManagerMonitor; import android.app.backup.FullBackup; @@ -207,6 +208,10 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter public static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED"; public static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName"; + // Time delay for initialization operations that can be delayed so as not to consume too much CPU + // on bring-up and increase time-to-UI. + private static final long INITIALIZATION_DELAY_MILLIS = 3000; + // Timeout interval for deciding that a bind or clear-data has taken too long private static final long TIMEOUT_INTERVAL = 10 * 1000; @@ -282,6 +287,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter private final BackupPasswordManager mBackupPasswordManager; + // Time when we post the transport registration operation + private final long mRegisterTransportsRequestedTime; + @GuardedBy("mPendingRestores") private boolean mIsRestoreInProgress; @GuardedBy("mPendingRestores") @@ -674,6 +682,8 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter @GuardedBy("mQueueLock") private ArrayList<FullBackupEntry> mFullBackupQueue; + private BackupPolicyEnforcer mBackupPolicyEnforcer; + // Utility: build a new random integer token. The low bits are the ordinal of the // operation for near-time uniqueness, and the upper bits are random for app- // side unpredictability. @@ -735,6 +745,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter // Set up our transport options and initialize the default transport SystemConfig systemConfig = SystemConfig.getInstance(); Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist(); + if (transportWhitelist == null) { + transportWhitelist = Collections.emptySet(); + } String transport = Settings.Secure.getString( @@ -749,8 +762,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter new TransportManager( context, transportWhitelist, - transport, - backupThread.getLooper()); + transport); // If encrypted file systems is enabled or disabled, this call will return the // correct directory. @@ -852,15 +864,19 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter } mTransportManager = transportManager; - mTransportManager.setTransportBoundListener(mTransportBoundListener); - mTransportManager.registerAllTransports(); + mTransportManager.setOnTransportRegisteredListener(this::onTransportRegistered); + mRegisterTransportsRequestedTime = SystemClock.elapsedRealtime(); + mBackupHandler.postDelayed( + mTransportManager::registerTransports, INITIALIZATION_DELAY_MILLIS); - // Now that we know about valid backup participants, parse any - // leftover journal files into the pending backup set - mBackupHandler.post(this::parseLeftoverJournals); + // Now that we know about valid backup participants, parse any leftover journal files into + // the pending backup set + mBackupHandler.postDelayed(this::parseLeftoverJournals, INITIALIZATION_DELAY_MILLIS); // Power management mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*"); + + mBackupPolicyEnforcer = new BackupPolicyEnforcer(context); } private void initPackageTracking() { @@ -1151,39 +1167,28 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter } } - private TransportManager.TransportBoundListener mTransportBoundListener = - new TransportManager.TransportBoundListener() { - @Override - public boolean onTransportBound(IBackupTransport transport) { - // If the init sentinel file exists, we need to be sure to perform the init - // as soon as practical. We also create the state directory at registration - // time to ensure it's present from the outset. - String name = null; - try { - name = transport.name(); - String transportDirName = transport.transportDirName(); - File stateDir = new File(mBaseStateDir, transportDirName); - stateDir.mkdirs(); + private void onTransportRegistered(String transportName, String transportDirName) { + if (DEBUG) { + long timeMs = SystemClock.elapsedRealtime() - mRegisterTransportsRequestedTime; + Slog.d(TAG, "Transport " + transportName + " registered " + timeMs + + "ms after first request (delay = " + INITIALIZATION_DELAY_MILLIS + "ms)"); + } - File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME); - if (initSentinel.exists()) { - synchronized (mQueueLock) { - mPendingInits.add(name); + File stateDir = new File(mBaseStateDir, transportDirName); + stateDir.mkdirs(); - // TODO: pick a better starting time than now + 1 minute - long delay = 1000 * 60; // one minute, in milliseconds - mAlarmManager.set(AlarmManager.RTC_WAKEUP, - System.currentTimeMillis() + delay, mRunInitIntent); - } - } - return true; - } catch (Exception e) { - // the transport threw when asked its file naming prefs; declare it invalid - Slog.w(TAG, "Failed to regiser transport: " + name); - return false; - } - } - }; + File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME); + if (initSentinel.exists()) { + synchronized (mQueueLock) { + mPendingInits.add(transportName); + + // TODO: pick a better starting time than now + 1 minute + long delay = 1000 * 60; // one minute, in milliseconds + mAlarmManager.set(AlarmManager.RTC_WAKEUP, + System.currentTimeMillis() + delay, mRunInitIntent); + } + } + } // ----- Track installation/removal of packages ----- private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @@ -2774,6 +2779,10 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter public void setBackupEnabled(boolean enable) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "setBackupEnabled"); + if (!enable && mBackupPolicyEnforcer.getMandatoryBackupTransport() != null) { + Slog.w(TAG, "Cannot disable backups when the mandatory backups policy is active."); + return; + } Slog.i(TAG, "Backup enabled => " + enable); @@ -2891,14 +2900,14 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "listAllTransports"); - return mTransportManager.getBoundTransportNames(); + return mTransportManager.getRegisteredTransportNames(); } @Override public ComponentName[] listAllTransportComponents() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "listAllTransportComponents"); - return mTransportManager.getAllTransportComponents(); + return mTransportManager.getRegisteredTransportComponents(); } @Override @@ -3003,10 +3012,18 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter /** Selects transport {@code transportName} and returns previous selected transport. */ @Override + @Deprecated + @Nullable public String selectBackupTransport(String transportName) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BACKUP, "selectBackupTransport"); + if (!isAllowedByMandatoryBackupTransportPolicy(transportName)) { + // Don't change the transport if it is not allowed. + Slog.w(TAG, "Failed to select transport - disallowed by device owner policy."); + return mTransportManager.getCurrentTransportName(); + } + final long oldId = Binder.clearCallingIdentity(); try { String previousTransportName = mTransportManager.selectTransport(transportName); @@ -3021,10 +3038,20 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter @Override public void selectBackupTransportAsync( - ComponentName transportComponent, ISelectBackupTransportCallback listener) { + ComponentName transportComponent, @Nullable ISelectBackupTransportCallback listener) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BACKUP, "selectBackupTransportAsync"); - + if (!isAllowedByMandatoryBackupTransportPolicy(transportComponent)) { + try { + if (listener != null) { + Slog.w(TAG, "Failed to select transport - disallowed by device owner policy."); + listener.onFailure(BackupManager.ERROR_BACKUP_NOT_ALLOWED); + } + } catch (RemoteException e) { + Slog.e(TAG, "ISelectBackupTransportCallback listener not available"); + } + return; + } final long oldId = Binder.clearCallingIdentity(); try { String transportString = transportComponent.flattenToShortString(); @@ -3046,10 +3073,12 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter } try { - if (transportName != null) { - listener.onSuccess(transportName); - } else { - listener.onFailure(result); + if (listener != null) { + if (transportName != null) { + listener.onSuccess(transportName); + } else { + listener.onFailure(result); + } } } catch (RemoteException e) { Slog.e(TAG, "ISelectBackupTransportCallback listener not available"); @@ -3060,6 +3089,38 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter } } + /** + * Returns if the specified transport can be set as the current transport without violating the + * mandatory backup transport policy. + */ + private boolean isAllowedByMandatoryBackupTransportPolicy(String transportName) { + ComponentName mandatoryBackupTransport = mBackupPolicyEnforcer.getMandatoryBackupTransport(); + if (mandatoryBackupTransport == null) { + return true; + } + final String mandatoryBackupTransportName; + try { + mandatoryBackupTransportName = + mTransportManager.getTransportName(mandatoryBackupTransport); + } catch (TransportNotRegisteredException e) { + Slog.e(TAG, "mandatory backup transport not registered!"); + return false; + } + return TextUtils.equals(mandatoryBackupTransportName, transportName); + } + + /** + * Returns if the specified transport can be set as the current transport without violating the + * mandatory backup transport policy. + */ + private boolean isAllowedByMandatoryBackupTransportPolicy(ComponentName transport) { + ComponentName mandatoryBackupTransport = mBackupPolicyEnforcer.getMandatoryBackupTransport(); + if (mandatoryBackupTransport == null) { + return true; + } + return mandatoryBackupTransport.equals(transport); + } + private void updateStateForTransport(String newTransportName) { // Publish the name change Settings.Secure.putString(mContext.getContentResolver(), @@ -3079,6 +3140,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter mCurrentToken = 0; Slog.w(TAG, "Transport " + newTransportName + " not available: current token = 0"); } + mTransportManager.disposeOfTransportClient(transportClient, callerLogString); } else { Slog.w(TAG, "Transport " + newTransportName + " not registered: current token = 0"); // The named transport isn't registered, so we can't know what its current dataset token diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java index a628c9dbb1a9..540f5a15e6f7 100644 --- a/services/backup/java/com/android/server/backup/Trampoline.java +++ b/services/backup/java/com/android/server/backup/Trampoline.java @@ -177,12 +177,15 @@ public class Trampoline extends IBackupManager.Stub { } } + // IBackupManager binder API + /** * Querying activity state of backup service. Calling this method before initialize yields * undefined result. * @param userHandle The user in which the activity state of backup service is queried. * @return true if the service is active. */ + @Override public boolean isBackupServiceActive(final int userHandle) { // TODO: http://b/22388012 if (userHandle == UserHandle.USER_SYSTEM) { @@ -193,7 +196,6 @@ public class Trampoline extends IBackupManager.Stub { return false; } - // IBackupManager binder API @Override public void dataChanged(String packageName) throws RemoteException { BackupManagerServiceInterface svc = mService; diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java index 34b8935939ff..30fd25a92484 100644 --- a/services/backup/java/com/android/server/backup/TransportManager.java +++ b/services/backup/java/com/android/server/backup/TransportManager.java @@ -16,93 +16,54 @@ package com.android.server.backup; -import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; - import android.annotation.Nullable; +import android.annotation.WorkerThread; import android.app.backup.BackupManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; import android.os.RemoteException; import android.os.UserHandle; -import android.provider.Settings; import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.EventLog; -import android.util.Log; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.backup.IBackupTransport; -import com.android.server.EventLogTags; +import com.android.internal.util.Preconditions; +import com.android.server.backup.transport.OnTransportRegisteredListener; import com.android.server.backup.transport.TransportClient; import com.android.server.backup.transport.TransportClientManager; import com.android.server.backup.transport.TransportConnectionListener; import com.android.server.backup.transport.TransportNotAvailableException; import com.android.server.backup.transport.TransportNotRegisteredException; -import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; -/** - * Handles in-memory bookkeeping of all BackupTransport objects. - */ +/** Handles in-memory bookkeeping of all BackupTransport objects. */ public class TransportManager { - private static final String TAG = "BackupTransportManager"; @VisibleForTesting public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST"; - private static final long REBINDING_TIMEOUT_UNPROVISIONED_MS = 30 * 1000; // 30 sec - private static final long REBINDING_TIMEOUT_PROVISIONED_MS = 5 * 60 * 1000; // 5 mins - private static final int REBINDING_TIMEOUT_MSG = 1; - private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST); private final Context mContext; private final PackageManager mPackageManager; private final Set<ComponentName> mTransportWhitelist; - private final Handler mHandler; private final TransportClientManager mTransportClientManager; - - /** - * This listener is called after we bind to any transport. If it returns true, this is a valid - * transport. - */ - private TransportBoundListener mTransportBoundListener; - private final Object mTransportLock = new Object(); - - /** - * We have detected these transports on the device. Unless in exceptional cases, we are also - * bound to all of these. - */ - @GuardedBy("mTransportLock") - private final Map<ComponentName, TransportConnection> mValidTransports = new ArrayMap<>(); - - /** We are currently bound to these transports. */ - @GuardedBy("mTransportLock") - private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>(); - - /** @see #getEligibleTransportComponents() */ - @GuardedBy("mTransportLock") - private final Set<ComponentName> mEligibleTransports = new ArraySet<>(); + private OnTransportRegisteredListener mOnTransportRegisteredListener = (c, n) -> {}; /** @see #getRegisteredTransportNames() */ @GuardedBy("mTransportLock") @@ -110,120 +71,98 @@ public class TransportManager { new ArrayMap<>(); @GuardedBy("mTransportLock") + @Nullable private volatile String mCurrentTransportName; - TransportManager( - Context context, - Set<ComponentName> whitelist, - String defaultTransport, - TransportBoundListener listener, - Looper looper) { - this(context, whitelist, defaultTransport, looper); - mTransportBoundListener = listener; + TransportManager(Context context, Set<ComponentName> whitelist, String selectedTransport) { + this(context, whitelist, selectedTransport, new TransportClientManager(context)); } + @VisibleForTesting TransportManager( Context context, Set<ComponentName> whitelist, - String defaultTransport, - Looper looper) { + String selectedTransport, + TransportClientManager transportClientManager) { mContext = context; mPackageManager = context.getPackageManager(); - if (whitelist != null) { - mTransportWhitelist = whitelist; - } else { - mTransportWhitelist = new ArraySet<>(); - } - mCurrentTransportName = defaultTransport; - mHandler = new RebindOnTimeoutHandler(looper); - mTransportClientManager = new TransportClientManager(context); + mTransportWhitelist = Preconditions.checkNotNull(whitelist); + mCurrentTransportName = selectedTransport; + mTransportClientManager = transportClientManager; } - public void setTransportBoundListener(TransportBoundListener transportBoundListener) { - mTransportBoundListener = transportBoundListener; + /* Sets a listener to be called whenever a transport is registered. */ + public void setOnTransportRegisteredListener(OnTransportRegisteredListener listener) { + mOnTransportRegisteredListener = listener; } + @WorkerThread void onPackageAdded(String packageName) { - // New package added. Bind to all transports it contains. - synchronized (mTransportLock) { - log_verbose("Package added. Binding to all transports. " + packageName); - bindToAllInternal(packageName, null /* all components */); - } + registerTransportsFromPackage(packageName, transportComponent -> true); } void onPackageRemoved(String packageName) { - // Package removed. Remove all its transports from our list. These transports have already - // been removed from mBoundTransports because onServiceDisconnected would already been - // called on TransportConnection objects. synchronized (mTransportLock) { - Iterator<Map.Entry<ComponentName, TransportConnection>> iter = - mValidTransports.entrySet().iterator(); - while (iter.hasNext()) { - Map.Entry<ComponentName, TransportConnection> validTransport = iter.next(); - ComponentName componentName = validTransport.getKey(); - if (componentName.getPackageName().equals(packageName)) { - TransportConnection transportConnection = validTransport.getValue(); - iter.remove(); - if (transportConnection != null) { - mContext.unbindService(transportConnection); - log_verbose("Package removed, removing transport: " - + componentName.flattenToShortString()); - } - } - } - removeTransportsIfLocked( - componentName -> packageName.equals(componentName.getPackageName())); + mRegisteredTransportsDescriptionMap.keySet().removeIf(fromPackageFilter(packageName)); } } - void onPackageChanged(String packageName, String[] components) { + @WorkerThread + void onPackageChanged(String packageName, String... components) { synchronized (mTransportLock) { - // Remove all changed components from mValidTransports. We'll bind to them again - // and re-add them if still valid. - Set<ComponentName> transportsToBeRemoved = new ArraySet<>(); - for (String component : components) { - ComponentName componentName = new ComponentName(packageName, component); - transportsToBeRemoved.add(componentName); - TransportConnection removed = mValidTransports.remove(componentName); - if (removed != null) { - mContext.unbindService(removed); - log_verbose("Package changed. Removing transport: " + - componentName.flattenToShortString()); - } - } - removeTransportsIfLocked(transportsToBeRemoved::contains); - bindToAllInternal(packageName, components); + Set<ComponentName> transportComponents = + Stream.of(components) + .map(component -> new ComponentName(packageName, component)) + .collect(Collectors.toSet()); + + mRegisteredTransportsDescriptionMap.keySet().removeIf(transportComponents::contains); + registerTransportsFromPackage(packageName, transportComponents::contains); } } - @GuardedBy("mTransportLock") - private void removeTransportsIfLocked(Predicate<ComponentName> filter) { - mEligibleTransports.removeIf(filter); - mRegisteredTransportsDescriptionMap.keySet().removeIf(filter); + /** + * Returns the {@link ComponentName}s of the registered transports. + * + * <p>A *registered* transport is a transport that satisfies intent with action + * android.backup.TRANSPORT_HOST, returns true for {@link #isTransportTrusted(ComponentName)} + * and that we have successfully connected to once. + */ + ComponentName[] getRegisteredTransportComponents() { + synchronized (mTransportLock) { + return mRegisteredTransportsDescriptionMap + .keySet() + .toArray(new ComponentName[mRegisteredTransportsDescriptionMap.size()]); + } } - public IBackupTransport getTransportBinder(String transportName) { + /** + * Returns the names of the registered transports. + * + * @see #getRegisteredTransportComponents() + */ + String[] getRegisteredTransportNames() { synchronized (mTransportLock) { - ComponentName component = mBoundTransports.get(transportName); - if (component == null) { - Slog.w(TAG, "Transport " + transportName + " not bound."); - return null; - } - TransportConnection conn = mValidTransports.get(component); - if (conn == null) { - Slog.w(TAG, "Transport " + transportName + " not valid."); - return null; - } - return conn.getBinder(); + return mRegisteredTransportsDescriptionMap + .values() + .stream() + .map(transportDescription -> transportDescription.name) + .toArray(String[]::new); } } - public IBackupTransport getCurrentTransportBinder() { - return getTransportBinder(mCurrentTransportName); + /** Returns a set with the whitelisted transports. */ + Set<ComponentName> getTransportWhitelist() { + return mTransportWhitelist; + } + + @Nullable + String getCurrentTransportName() { + return mCurrentTransportName; } /** * Returns the transport name associated with {@code transportComponent}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ public String getTransportName(ComponentName transportComponent) @@ -234,33 +173,46 @@ public class TransportManager { } /** - * Retrieve the configuration intent of {@code transportName}. + * Retrieves the transport dir name of {@code transportComponent}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ - @Nullable - public Intent getTransportConfigurationIntent(String transportName) + public String getTransportDirName(ComponentName transportComponent) throws TransportNotRegisteredException { synchronized (mTransportLock) { - return getRegisteredTransportDescriptionOrThrowLocked(transportName) - .configurationIntent; + return getRegisteredTransportDescriptionOrThrowLocked(transportComponent) + .transportDirName; } } /** - * Retrieve the data management intent of {@code transportName}. + * Retrieves the transport dir name of {@code transportName}. + * + * @throws TransportNotRegisteredException if the transport is not registered. + */ + public String getTransportDirName(String transportName) throws TransportNotRegisteredException { + synchronized (mTransportLock) { + return getRegisteredTransportDescriptionOrThrowLocked(transportName).transportDirName; + } + } + + /** + * Retrieves the configuration intent of {@code transportName}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ @Nullable - public Intent getTransportDataManagementIntent(String transportName) + public Intent getTransportConfigurationIntent(String transportName) throws TransportNotRegisteredException { synchronized (mTransportLock) { return getRegisteredTransportDescriptionOrThrowLocked(transportName) - .dataManagementIntent; + .configurationIntent; } } /** - * Retrieve the current destination string of {@code transportName}. + * Retrieves the current destination string of {@code transportName}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ public String getTransportCurrentDestinationString(String transportName) @@ -272,66 +224,101 @@ public class TransportManager { } /** - * Retrieve the data management label of {@code transportName}. + * Retrieves the data management intent of {@code transportName}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ @Nullable - public String getTransportDataManagementLabel(String transportName) + public Intent getTransportDataManagementIntent(String transportName) throws TransportNotRegisteredException { synchronized (mTransportLock) { return getRegisteredTransportDescriptionOrThrowLocked(transportName) - .dataManagementLabel; + .dataManagementIntent; } } /** - * Retrieve the transport dir name of {@code transportName}. + * Retrieves the data management label of {@code transportName}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ - public String getTransportDirName(String transportName) + @Nullable + public String getTransportDataManagementLabel(String transportName) throws TransportNotRegisteredException { synchronized (mTransportLock) { return getRegisteredTransportDescriptionOrThrowLocked(transportName) - .transportDirName; + .dataManagementLabel; } } - /** - * Retrieve the transport dir name of {@code transportComponent}. - * @throws TransportNotRegisteredException if the transport is not registered. - */ - public String getTransportDirName(ComponentName transportComponent) - throws TransportNotRegisteredException { + /* Returns true if the transport identified by {@code transportName} is registered. */ + public boolean isTransportRegistered(String transportName) { synchronized (mTransportLock) { - return getRegisteredTransportDescriptionOrThrowLocked(transportComponent) - .transportDirName; + return getRegisteredTransportEntryLocked(transportName) != null; } } /** * Execute {@code transportConsumer} for each registered transport passing the transport name. * This is called with an internal lock held, ensuring that the transport will remain registered - * while {@code transportConsumer} is being executed. Don't do heavy operations in - * {@code transportConsumer}. + * while {@code transportConsumer} is being executed. Don't do heavy operations in {@code + * transportConsumer}. */ public void forEachRegisteredTransport(Consumer<String> transportConsumer) { synchronized (mTransportLock) { - for (TransportDescription transportDescription - : mRegisteredTransportsDescriptionMap.values()) { + for (TransportDescription transportDescription : + mRegisteredTransportsDescriptionMap.values()) { transportConsumer.accept(transportDescription.name); } } } - public String getTransportName(IBackupTransport binder) { + /** + * Updates given values for the transport already registered and identified with {@param + * transportComponent}. If the transport is not registered it will log and return. + */ + public void updateTransportAttributes( + ComponentName transportComponent, + String name, + @Nullable Intent configurationIntent, + String currentDestinationString, + @Nullable Intent dataManagementIntent, + @Nullable String dataManagementLabel) { synchronized (mTransportLock) { - for (TransportConnection conn : mValidTransports.values()) { - if (conn.getBinder() == binder) { - return conn.getName(); - } + TransportDescription description = + mRegisteredTransportsDescriptionMap.get(transportComponent); + if (description == null) { + Slog.e(TAG, "Transport " + name + " not registered tried to change description"); + return; } + description.name = name; + description.configurationIntent = configurationIntent; + description.currentDestinationString = currentDestinationString; + description.dataManagementIntent = dataManagementIntent; + description.dataManagementLabel = dataManagementLabel; + Slog.d(TAG, "Transport " + name + " updated its attributes"); } - return null; + } + + @GuardedBy("mTransportLock") + private TransportDescription getRegisteredTransportDescriptionOrThrowLocked( + ComponentName transportComponent) throws TransportNotRegisteredException { + TransportDescription description = + mRegisteredTransportsDescriptionMap.get(transportComponent); + if (description == null) { + throw new TransportNotRegisteredException(transportComponent); + } + return description; + } + + @GuardedBy("mTransportLock") + private TransportDescription getRegisteredTransportDescriptionOrThrowLocked( + String transportName) throws TransportNotRegisteredException { + TransportDescription description = getRegisteredTransportDescriptionLocked(transportName); + if (description == null) { + throw new TransportNotRegisteredException(transportName); + } + return description; } @GuardedBy("mTransportLock") @@ -351,21 +338,11 @@ public class TransportManager { } @GuardedBy("mTransportLock") - private TransportDescription getRegisteredTransportDescriptionOrThrowLocked( - String transportName) throws TransportNotRegisteredException { - TransportDescription description = getRegisteredTransportDescriptionLocked(transportName); - if (description == null) { - throw new TransportNotRegisteredException(transportName); - } - return description; - } - - @GuardedBy("mTransportLock") @Nullable private Map.Entry<ComponentName, TransportDescription> getRegisteredTransportEntryLocked( String transportName) { - for (Map.Entry<ComponentName, TransportDescription> entry - : mRegisteredTransportsDescriptionMap.entrySet()) { + for (Map.Entry<ComponentName, TransportDescription> entry : + mRegisteredTransportsDescriptionMap.entrySet()) { TransportDescription description = entry.getValue(); if (transportName.equals(description.name)) { return entry; @@ -374,17 +351,16 @@ public class TransportManager { return null; } - @GuardedBy("mTransportLock") - private TransportDescription getRegisteredTransportDescriptionOrThrowLocked( - ComponentName transportComponent) throws TransportNotRegisteredException { - TransportDescription description = - mRegisteredTransportsDescriptionMap.get(transportComponent); - if (description == null) { - throw new TransportNotRegisteredException(transportComponent); - } - return description; - } - + /** + * Returns a {@link TransportClient} for {@code transportName} or {@code null} if not + * registered. + * + * @param transportName The name of the transport. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + * @return A {@link TransportClient} or null if not registered. + */ @Nullable public TransportClient getTransportClient(String transportName, String caller) { try { @@ -395,6 +371,16 @@ public class TransportManager { } } + /** + * Returns a {@link TransportClient} for {@code transportName} or throws if not registered. + * + * @param transportName The name of the transport. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + * @return A {@link TransportClient}. + * @throws TransportNotRegisteredException if the transport is not registered. + */ public TransportClient getTransportClientOrThrow(String transportName, String caller) throws TransportNotRegisteredException { synchronized (mTransportLock) { @@ -406,19 +392,14 @@ public class TransportManager { } } - public boolean isTransportRegistered(String transportName) { - synchronized (mTransportLock) { - return getRegisteredTransportEntryLocked(transportName) != null; - } - } - /** - * Returns a {@link TransportClient} for the current transport or null if not found. + * Returns a {@link TransportClient} for the current transport or {@code null} if not + * registered. * * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more * details. - * @return A {@link TransportClient} or null if not found. + * @return A {@link TransportClient} or null if not registered. */ @Nullable public TransportClient getCurrentTransportClient(String caller) { @@ -455,130 +436,88 @@ public class TransportManager { mTransportClientManager.disposeOfTransportClient(transportClient, caller); } - String[] getBoundTransportNames() { - synchronized (mTransportLock) { - return mBoundTransports.keySet().toArray(new String[mBoundTransports.size()]); - } - } - - ComponentName[] getAllTransportComponents() { - synchronized (mTransportLock) { - return mValidTransports.keySet().toArray(new ComponentName[mValidTransports.size()]); - } - } - /** - * An *eligible* transport is a service component that satisfies intent with action - * android.backup.TRANSPORT_HOST and returns true for - * {@link #isTransportTrusted(ComponentName)}. It may be registered or not registered. - * This method returns the {@link ComponentName}s of those transports. + * Sets {@code transportName} as selected transport and returns previously selected transport + * name. If there was no previous transport it returns null. + * + * <p>You should NOT call this method in new code. This won't make any checks against {@code + * transportName}, putting any operation at risk of a {@link TransportNotRegisteredException} or + * another error at the time it's being executed. + * + * <p>{@link Deprecated} as public, this method can be used as private. */ - ComponentName[] getEligibleTransportComponents() { + @Deprecated + @Nullable + String selectTransport(String transportName) { synchronized (mTransportLock) { - return mEligibleTransports.toArray(new ComponentName[mEligibleTransports.size()]); + String prevTransport = mCurrentTransportName; + mCurrentTransportName = transportName; + return prevTransport; } } - Set<ComponentName> getTransportWhitelist() { - return mTransportWhitelist; - } - /** - * A *registered* transport is an eligible transport that has been successfully connected and - * that returned true for method - * {@link TransportBoundListener#onTransportBound(IBackupTransport)} of TransportBoundListener - * provided in the constructor. This method returns the names of the registered transports. + * Tries to register the transport if not registered. If successful also selects the transport. + * + * @param transportComponent Host of the transport. + * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID} + * or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}. */ - String[] getRegisteredTransportNames() { + @WorkerThread + public int registerAndSelectTransport(ComponentName transportComponent) { synchronized (mTransportLock) { - return mRegisteredTransportsDescriptionMap.values().stream() - .map(transportDescription -> transportDescription.name) - .toArray(String[]::new); - } - } + if (!mRegisteredTransportsDescriptionMap.containsKey(transportComponent)) { + int result = registerTransport(transportComponent); + if (result != BackupManager.SUCCESS) { + return result; + } + } - /** - * Updates given values for the transport already registered and identified with - * {@param transportComponent}. If the transport is not registered it will log and return. - */ - public void updateTransportAttributes( - ComponentName transportComponent, - String name, - @Nullable Intent configurationIntent, - String currentDestinationString, - @Nullable Intent dataManagementIntent, - @Nullable String dataManagementLabel) { - synchronized (mTransportLock) { - TransportDescription description = - mRegisteredTransportsDescriptionMap.get(transportComponent); - if (description == null) { - Slog.e(TAG, "Transport " + name + " not registered tried to change description"); - return; + try { + selectTransport(getTransportName(transportComponent)); + return BackupManager.SUCCESS; + } catch (TransportNotRegisteredException e) { + // Shouldn't happen because we are holding the lock + Slog.wtf(TAG, "Transport unexpectedly not registered"); + return BackupManager.ERROR_TRANSPORT_UNAVAILABLE; } - description.name = name; - description.configurationIntent = configurationIntent; - description.currentDestinationString = currentDestinationString; - description.dataManagementIntent = dataManagementIntent; - description.dataManagementLabel = dataManagementLabel; - Slog.d(TAG, "Transport " + name + " updated its attributes"); } } - @Nullable - String getCurrentTransportName() { - return mCurrentTransportName; - } - - // This is for mocking, Mockito can't mock if package-protected and in the same package but - // different class loaders. Checked with the debugger and class loaders are different - // See https://github.com/mockito/mockito/issues/796 - @VisibleForTesting(visibility = PACKAGE) - public void registerAllTransports() { - bindToAllInternal(null /* all packages */, null /* all components */); + @WorkerThread + public void registerTransports() { + registerTransportsForIntent(mTransportServiceIntent, transportComponent -> true); } - /** - * Bind to all transports belonging to the given package and the given component list. - * null acts a wildcard. - * - * If packageName is null, bind to all transports in all packages. - * If components is null, bind to all transports in the given package. - */ - private void bindToAllInternal(String packageName, String[] components) { - PackageInfo pkgInfo = null; - if (packageName != null) { - try { - pkgInfo = mPackageManager.getPackageInfo(packageName, 0); - } catch (PackageManager.NameNotFoundException e) { - Slog.w(TAG, "Package not found: " + packageName); - return; - } + @WorkerThread + private void registerTransportsFromPackage( + String packageName, Predicate<ComponentName> transportComponentFilter) { + try { + mPackageManager.getPackageInfo(packageName, 0); + } catch (PackageManager.NameNotFoundException e) { + Slog.e(TAG, "Trying to register transports from package not found " + packageName); + return; } - Intent intent = new Intent(mTransportServiceIntent); - if (packageName != null) { - intent.setPackage(packageName); - } + registerTransportsForIntent( + new Intent(mTransportServiceIntent).setPackage(packageName), + transportComponentFilter.and(fromPackageFilter(packageName))); + } - List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser( - intent, 0, UserHandle.USER_SYSTEM); - if (hosts != null) { + @WorkerThread + private void registerTransportsForIntent( + Intent intent, Predicate<ComponentName> transportComponentFilter) { + List<ResolveInfo> hosts = + mPackageManager.queryIntentServicesAsUser(intent, 0, UserHandle.USER_SYSTEM); + if (hosts == null) { + return; + } + synchronized (mTransportLock) { for (ResolveInfo host : hosts) { - final ComponentName infoComponentName = getComponentName(host.serviceInfo); - boolean shouldBind = false; - if (components != null && packageName != null) { - for (String component : components) { - ComponentName cn = new ComponentName(pkgInfo.packageName, component); - if (infoComponentName.equals(cn)) { - shouldBind = true; - break; - } - } - } else { - shouldBind = true; - } - if (shouldBind && isTransportTrusted(infoComponentName)) { - tryBindTransport(infoComponentName); + ComponentName transportComponent = host.serviceInfo.getComponentName(); + if (transportComponentFilter.test(transportComponent) + && isTransportTrusted(transportComponent)) { + registerTransport(transportComponent); } } } @@ -605,64 +544,6 @@ public class TransportManager { return true; } - private void tryBindTransport(ComponentName transportComponentName) { - Slog.d(TAG, "Binding to transport: " + transportComponentName.flattenToShortString()); - // TODO: b/22388012 (Multi user backup and restore) - TransportConnection connection = new TransportConnection(transportComponentName); - synchronized (mTransportLock) { - mEligibleTransports.add(transportComponentName); - } - if (bindToTransport(transportComponentName, connection)) { - synchronized (mTransportLock) { - mValidTransports.put(transportComponentName, connection); - } - } else { - Slog.w(TAG, "Couldn't bind to transport " + transportComponentName); - } - } - - private boolean bindToTransport(ComponentName componentName, ServiceConnection connection) { - Intent intent = new Intent(mTransportServiceIntent) - .setComponent(componentName); - return mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE, - createSystemUserHandle()); - } - - String selectTransport(String transportName) { - synchronized (mTransportLock) { - String prevTransport = mCurrentTransportName; - mCurrentTransportName = transportName; - return prevTransport; - } - } - - /** - * Tries to register the transport if not registered. If successful also selects the transport. - * - * @param transportComponent Host of the transport. - * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID} - * or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}. - */ - public int registerAndSelectTransport(ComponentName transportComponent) { - synchronized (mTransportLock) { - if (!mRegisteredTransportsDescriptionMap.containsKey(transportComponent)) { - int result = registerTransport(transportComponent); - if (result != BackupManager.SUCCESS) { - return result; - } - } - - try { - selectTransport(getTransportName(transportComponent)); - return BackupManager.SUCCESS; - } catch (TransportNotRegisteredException e) { - // Shouldn't happen because we are holding the lock - Slog.wtf(TAG, "Transport unexpectedly not registered"); - return BackupManager.ERROR_TRANSPORT_UNAVAILABLE; - } - } - } - /** * Tries to register transport represented by {@code transportComponent}. * @@ -670,7 +551,12 @@ public class TransportManager { * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID} * or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}. */ + @WorkerThread private int registerTransport(ComponentName transportComponent) { + if (!isTransportTrusted(transportComponent)) { + return BackupManager.ERROR_TRANSPORT_INVALID; + } + String transportString = transportComponent.flattenToShortString(); String callerLogString = "TransportManager.registerTransport()"; @@ -686,29 +572,21 @@ public class TransportManager { return BackupManager.ERROR_TRANSPORT_UNAVAILABLE; } - EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 1); - int result; - if (isTransportValid(transport)) { - try { - registerTransport(transportComponent, transport); - // If registerTransport() hasn't thrown... - result = BackupManager.SUCCESS; - } catch (RemoteException e) { - Slog.e(TAG, "Transport " + transportString + " died while registering"); - result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE; - } - } else { - Slog.w(TAG, "Can't register invalid transport " + transportString); - result = BackupManager.ERROR_TRANSPORT_INVALID; + try { + String transportName = transport.name(); + String transportDirName = transport.transportDirName(); + registerTransport(transportComponent, transport); + // If registerTransport() hasn't thrown... + Slog.d(TAG, "Transport " + transportString + " registered"); + mOnTransportRegisteredListener.onTransportRegistered(transportName, transportDirName); + result = BackupManager.SUCCESS; + } catch (RemoteException e) { + Slog.e(TAG, "Transport " + transportString + " died while registering"); + result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE; } mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString); - if (result == BackupManager.SUCCESS) { - Slog.d(TAG, "Transport " + transportString + " registered"); - } else { - EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 0); - } return result; } @@ -717,204 +595,20 @@ public class TransportManager { throws RemoteException { synchronized (mTransportLock) { String name = transport.name(); - TransportDescription description = new TransportDescription( - name, - transport.transportDirName(), - transport.configurationIntent(), - transport.currentDestinationString(), - transport.dataManagementIntent(), - transport.dataManagementLabel()); + TransportDescription description = + new TransportDescription( + name, + transport.transportDirName(), + transport.configurationIntent(), + transport.currentDestinationString(), + transport.dataManagementIntent(), + transport.dataManagementLabel()); mRegisteredTransportsDescriptionMap.put(transportComponent, description); } } - private boolean isTransportValid(IBackupTransport transport) { - if (mTransportBoundListener == null) { - Slog.w(TAG, "setTransportBoundListener() not called, assuming transport invalid"); - return false; - } - return mTransportBoundListener.onTransportBound(transport); - } - - private class TransportConnection implements ServiceConnection { - - // Hold mTransportLock to access these fields so as to provide a consistent view of them. - private volatile IBackupTransport mBinder; - private volatile String mTransportName; - - private final ComponentName mTransportComponent; - - private TransportConnection(ComponentName transportComponent) { - mTransportComponent = transportComponent; - } - - @Override - public void onServiceConnected(ComponentName component, IBinder binder) { - synchronized (mTransportLock) { - mBinder = IBackupTransport.Stub.asInterface(binder); - boolean success = false; - - EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, - component.flattenToShortString(), 1); - - try { - mTransportName = mBinder.name(); - // BackupManager requests some fields from the transport. If they are - // invalid, throw away this transport. - if (isTransportValid(mBinder)) { - // We're now using the always-bound connection to do the registration but - // when we remove the always-bound code this will be in the first binding - // TODO: Move registration to first binding - registerTransport(component, mBinder); - // If registerTransport() hasn't thrown... - success = true; - } - } catch (RemoteException e) { - success = false; - Slog.e(TAG, "Couldn't get transport name.", e); - } finally { - // we need to intern() the String of the component, so that we can use it with - // Handler's removeMessages(), which uses == operator to compare the tokens - String componentShortString = component.flattenToShortString().intern(); - if (success) { - Slog.d(TAG, "Bound to transport: " + componentShortString); - mBoundTransports.put(mTransportName, component); - // cancel rebinding on timeout for this component as we've already connected - mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString); - } else { - Slog.w(TAG, "Bound to transport " + componentShortString + - " but it is invalid"); - EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, - componentShortString, 0); - mContext.unbindService(this); - mValidTransports.remove(component); - mEligibleTransports.remove(component); - mBinder = null; - } - } - } - } - - @Override - public void onServiceDisconnected(ComponentName component) { - synchronized (mTransportLock) { - mBinder = null; - mBoundTransports.remove(mTransportName); - } - String componentShortString = component.flattenToShortString(); - EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, componentShortString, 0); - Slog.w(TAG, "Disconnected from transport " + componentShortString); - scheduleRebindTimeout(component); - } - - /** - * We'll attempt to explicitly rebind to a transport if it hasn't happened automatically - * for a few minutes after the binding went away. - */ - private void scheduleRebindTimeout(ComponentName component) { - // we need to intern() the String of the component, so that we can use it with Handler's - // removeMessages(), which uses == operator to compare the tokens - final String componentShortString = component.flattenToShortString().intern(); - final long rebindTimeout = getRebindTimeout(); - mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString); - Message msg = mHandler.obtainMessage(REBINDING_TIMEOUT_MSG); - msg.obj = componentShortString; - mHandler.sendMessageDelayed(msg, rebindTimeout); - Slog.d(TAG, "Scheduled explicit rebinding for " + componentShortString + " in " - + rebindTimeout + "ms"); - } - - // Intentionally not synchronized -- the variable is volatile and changes to its value - // are inside synchronized blocks, providing a memory sync barrier; and this method - // does not touch any other state protected by that lock. - private IBackupTransport getBinder() { - return mBinder; - } - - // Intentionally not synchronized; same as getBinder() - private String getName() { - return mTransportName; - } - - // Intentionally not synchronized; same as getBinder() - private void bindIfUnbound() { - if (mBinder == null) { - Slog.d(TAG, - "Rebinding to transport " + mTransportComponent.flattenToShortString()); - bindToTransport(mTransportComponent, this); - } - } - - private long getRebindTimeout() { - final boolean isDeviceProvisioned = Settings.Global.getInt( - mContext.getContentResolver(), - Settings.Global.DEVICE_PROVISIONED, 0) != 0; - return isDeviceProvisioned - ? REBINDING_TIMEOUT_PROVISIONED_MS - : REBINDING_TIMEOUT_UNPROVISIONED_MS; - } - } - - public interface TransportBoundListener { - /** Should return true if this is a valid transport. */ - boolean onTransportBound(IBackupTransport binder); - } - - private class RebindOnTimeoutHandler extends Handler { - - RebindOnTimeoutHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - if (msg.what == REBINDING_TIMEOUT_MSG) { - String componentShortString = (String) msg.obj; - ComponentName transportComponent = - ComponentName.unflattenFromString(componentShortString); - synchronized (mTransportLock) { - if (mBoundTransports.containsValue(transportComponent)) { - Slog.d(TAG, "Explicit rebinding timeout passed, but already bound to " - + componentShortString + " so not attempting to rebind"); - return; - } - Slog.d(TAG, "Explicit rebinding timeout passed, attempting rebinding to: " - + componentShortString); - // unbind the existing (broken) connection - TransportConnection conn = mValidTransports.get(transportComponent); - if (conn != null) { - mContext.unbindService(conn); - Slog.d(TAG, "Unbinding the existing (broken) connection to transport: " - + componentShortString); - } - } - // rebind to transport - tryBindTransport(transportComponent); - } else { - Slog.e(TAG, "Unknown message sent to RebindOnTimeoutHandler, msg.what: " - + msg.what); - } - } - } - - private static void log_verbose(String message) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Slog.v(TAG, message); - } - } - - // These only exists to make it testable with Robolectric, which is not updated to API level 24 - // yet. - // TODO: Get rid of this once Robolectric is updated. - private static ComponentName getComponentName(ServiceInfo serviceInfo) { - return new ComponentName(serviceInfo.packageName, serviceInfo.name); - } - - // These only exists to make it testable with Robolectric, which is not updated to API level 24 - // yet. - // TODO: Get rid of this once Robolectric is updated. - public static UserHandle createSystemUserHandle() { - return new UserHandle(UserHandle.USER_SYSTEM); + private static Predicate<ComponentName> fromPackageFilter(String packageName) { + return transportComponent -> packageName.equals(transportComponent.getPackageName()); } private static class TransportDescription { diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java index c2322413d2a7..cc3af8c8c933 100644 --- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java +++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java @@ -907,7 +907,15 @@ public class PerformBackupTask implements BackupRestoreTask { backupData = ParcelFileDescriptor.open(mBackupDataName, ParcelFileDescriptor.MODE_READ_ONLY); backupManagerService.addBackupTrace("sending data to transport"); - int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0; + + int userInitiatedFlag = + mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0; + int incrementalFlag = + mSavedStateName.length() == 0 + ? BackupTransport.FLAG_NON_INCREMENTAL + : BackupTransport.FLAG_INCREMENTAL; + int flags = userInitiatedFlag | incrementalFlag; + mStatus = transport.performBackup(mCurrentPackage, backupData, flags); } diff --git a/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java b/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java new file mode 100644 index 000000000000..391ec2d7f294 --- /dev/null +++ b/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.transport; + +import com.android.server.backup.TransportManager; + +/** + * Listener called when a transport is registered with the {@link TransportManager}. Can be set + * using {@link TransportManager#setOnTransportRegisteredListener(OnTransportRegisteredListener)}. + */ +@FunctionalInterface +public interface OnTransportRegisteredListener { + /** + * Called when a transport is successfully registered. + * @param transportName The name of the transport. + * @param transportDirName The dir name of the transport. + */ + public void onTransportRegistered(String transportName, String transportDirName); +} diff --git a/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/java/com/android/server/backup/transport/TransportClient.java index 7bd9111028cb..399f338d26b2 100644 --- a/services/backup/java/com/android/server/backup/transport/TransportClient.java +++ b/services/backup/java/com/android/server/backup/transport/TransportClient.java @@ -29,12 +29,14 @@ import android.os.IBinder; import android.os.Looper; import android.os.UserHandle; import android.util.ArrayMap; +import android.util.EventLog; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.backup.IBackupTransport; import com.android.internal.util.Preconditions; +import com.android.server.EventLogTags; import com.android.server.backup.TransportManager; import java.lang.annotation.Retention; @@ -236,7 +238,7 @@ public class TransportClient { mBindIntent, mConnection, Context.BIND_AUTO_CREATE, - TransportManager.createSystemUserHandle()); + UserHandle.SYSTEM); if (hasBound) { // We don't need to set a time-out because we are guaranteed to get a call // back in ServiceConnection, either an onServiceConnected() or @@ -419,10 +421,45 @@ public class TransportClient { @GuardedBy("mStateLock") private void setStateLocked(@State int state, @Nullable IBackupTransport transport) { log(Log.VERBOSE, "State: " + stateToString(mState) + " => " + stateToString(state)); + onStateTransition(mState, state); mState = state; mTransport = transport; } + private void onStateTransition(int oldState, int newState) { + String transport = mTransportComponent.flattenToShortString(); + int bound = transitionThroughState(oldState, newState, State.BOUND_AND_CONNECTING); + int connected = transitionThroughState(oldState, newState, State.CONNECTED); + if (bound != Transition.NO_TRANSITION) { + int value = (bound == Transition.UP) ? 1 : 0; // 1 is bound, 0 is not bound + EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transport, value); + } + if (connected != Transition.NO_TRANSITION) { + int value = (connected == Transition.UP) ? 1 : 0; // 1 is connected, 0 is not connected + EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_CONNECTION, transport, value); + } + } + + /** + * Returns: + * + * <ul> + * <li>{@link Transition#UP}, if oldState < stateReference <= newState + * <li>{@link Transition#DOWN}, if oldState >= stateReference > newState + * <li>{@link Transition#NO_TRANSITION}, otherwise + */ + @Transition + private int transitionThroughState( + @State int oldState, @State int newState, @State int stateReference) { + if (oldState < stateReference && stateReference <= newState) { + return Transition.UP; + } + if (oldState >= stateReference && stateReference > newState) { + return Transition.DOWN; + } + return Transition.NO_TRANSITION; + } + @GuardedBy("mStateLock") private void checkStateIntegrityLocked() { switch (mState) { @@ -481,6 +518,14 @@ public class TransportClient { // CharSequence time = DateFormat.format("yyyy-MM-dd HH:mm:ss", System.currentTimeMillis()); } + @IntDef({Transition.DOWN, Transition.NO_TRANSITION, Transition.UP}) + @Retention(RetentionPolicy.SOURCE) + private @interface Transition { + int DOWN = -1; + int NO_TRANSITION = 0; + int UP = 1; + } + @IntDef({State.UNUSABLE, State.IDLE, State.BOUND_AND_CONNECTING, State.CONNECTED}) @Retention(RetentionPolicy.SOURCE) private @interface State { diff --git a/services/core/Android.bp b/services/core/Android.bp index 9fb26819dae0..3369458595a8 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -16,6 +16,7 @@ java_library_static { ":installd_aidl", ":storaged_aidl", ":vold_aidl", + ":mediaupdateservice_aidl", "java/com/android/server/EventLogTags.logtags", "java/com/android/server/am/EventLogTags.logtags", ], diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index af0b66daad50..04d292fa1ae4 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -293,15 +293,10 @@ public final class BatteryService extends SystemService { private void updateBatteryWarningLevelLocked() { final ContentResolver resolver = mContext.getContentResolver(); - final int defWarnLevel = mContext.getResources().getInteger( + int defWarnLevel = mContext.getResources().getInteger( com.android.internal.R.integer.config_lowBatteryWarningLevel); - final int lowPowerModeTriggerLevel = Settings.Global.getInt(resolver, + mLowBatteryWarningLevel = Settings.Global.getInt(resolver, Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, defWarnLevel); - - // NOTE: Keep the logic in sync with PowerUI.java in systemUI. - // TODO: Propagate this value from BatteryService to system UI, really. - mLowBatteryWarningLevel = Math.min(defWarnLevel, lowPowerModeTriggerLevel); - if (mLowBatteryWarningLevel == 0) { mLowBatteryWarningLevel = defWarnLevel; } diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index d9713a517a94..337406d58f9d 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -60,6 +60,7 @@ import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.util.Slog; +import com.android.internal.R; import com.android.internal.util.DumpUtils; import com.android.server.pm.UserRestrictionsUtils; @@ -415,9 +416,14 @@ class BluetoothManagerService extends IBluetoothManager.Stub { int systemUiUid = -1; try { - systemUiUid = mContext.getPackageManager() - .getPackageUidAsUser("com.android.systemui", PackageManager.MATCH_SYSTEM_ONLY, - UserHandle.USER_SYSTEM); + // Check if device is configured with no home screen, which implies no SystemUI. + boolean noHome = mContext.getResources().getBoolean(R.bool.config_noHomeScreen); + if (!noHome) { + systemUiUid = mContext.getPackageManager() + .getPackageUidAsUser("com.android.systemui", PackageManager.MATCH_SYSTEM_ONLY, + UserHandle.USER_SYSTEM); + } + Slog.d(TAG, "Detected SystemUiUid: " + Integer.toString(systemUiUid)); } catch (PackageManager.NameNotFoundException e) { // Some platforms, such as wearables do not have a system ui. Slog.w(TAG, "Unable to resolve SystemUI's UID.", e); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index bbba878b4ff2..77521df7e06a 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -226,7 +226,11 @@ public class ConnectivityService extends IConnectivityManager.Stub @GuardedBy("mVpns") private final SparseArray<Vpn> mVpns = new SparseArray<Vpn>(); + // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by + // a direct call to LockdownVpnTracker.isEnabled(). + @GuardedBy("mVpns") private boolean mLockdownEnabled; + @GuardedBy("mVpns") private LockdownVpnTracker mLockdownTracker; final private Context mContext; @@ -997,9 +1001,9 @@ public class ConnectivityService extends IConnectivityManager.Stub } private Network[] getVpnUnderlyingNetworks(int uid) { - if (!mLockdownEnabled) { - int user = UserHandle.getUserId(uid); - synchronized (mVpns) { + synchronized (mVpns) { + if (!mLockdownEnabled) { + int user = UserHandle.getUserId(uid); Vpn vpn = mVpns.get(user); if (vpn != null && vpn.appliesToUid(uid)) { return vpn.getUnderlyingNetworks(); @@ -1087,8 +1091,10 @@ public class ConnectivityService extends IConnectivityManager.Stub if (isNetworkWithLinkPropertiesBlocked(state.linkProperties, uid, ignoreBlocked)) { state.networkInfo.setDetailedState(DetailedState.BLOCKED, null, null); } - if (mLockdownTracker != null) { - mLockdownTracker.augmentNetworkInfo(state.networkInfo); + synchronized (mVpns) { + if (mLockdownTracker != null) { + mLockdownTracker.augmentNetworkInfo(state.networkInfo); + } } } @@ -1253,8 +1259,8 @@ public class ConnectivityService extends IConnectivityManager.Stub result.put(nai.network, nc); } - if (!mLockdownEnabled) { - synchronized (mVpns) { + synchronized (mVpns) { + if (!mLockdownEnabled) { Vpn vpn = mVpns.get(userId); if (vpn != null) { Network[] networks = vpn.getUnderlyingNetworks(); @@ -1580,9 +1586,11 @@ public class ConnectivityService extends IConnectivityManager.Stub } private Intent makeGeneralIntent(NetworkInfo info, String bcastType) { - if (mLockdownTracker != null) { - info = new NetworkInfo(info); - mLockdownTracker.augmentNetworkInfo(info); + synchronized (mVpns) { + if (mLockdownTracker != null) { + info = new NetworkInfo(info); + mLockdownTracker.augmentNetworkInfo(info); + } } Intent intent = new Intent(bcastType); @@ -3436,9 +3444,9 @@ public class ConnectivityService extends IConnectivityManager.Stub public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage, int userId) { enforceCrossUserPermission(userId); - throwIfLockdownEnabled(); synchronized (mVpns) { + throwIfLockdownEnabled(); Vpn vpn = mVpns.get(userId); if (vpn != null) { return vpn.prepare(oldPackage, newPackage); @@ -3482,9 +3490,9 @@ public class ConnectivityService extends IConnectivityManager.Stub */ @Override public ParcelFileDescriptor establishVpn(VpnConfig config) { - throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { + throwIfLockdownEnabled(); return mVpns.get(user).establish(config); } } @@ -3495,13 +3503,13 @@ public class ConnectivityService extends IConnectivityManager.Stub */ @Override public void startLegacyVpn(VpnProfile profile) { - throwIfLockdownEnabled(); + int user = UserHandle.getUserId(Binder.getCallingUid()); final LinkProperties egress = getActiveLinkProperties(); if (egress == null) { throw new IllegalStateException("Missing active network connection"); } - int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { + throwIfLockdownEnabled(); mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress); } } @@ -3527,11 +3535,11 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public VpnInfo[] getAllVpnInfo() { enforceConnectivityInternalPermission(); - if (mLockdownEnabled) { - return new VpnInfo[0]; - } - synchronized (mVpns) { + if (mLockdownEnabled) { + return new VpnInfo[0]; + } + List<VpnInfo> infoList = new ArrayList<>(); for (int i = 0; i < mVpns.size(); i++) { VpnInfo info = createVpnInfo(mVpns.valueAt(i)); @@ -3596,33 +3604,33 @@ public class ConnectivityService extends IConnectivityManager.Stub return false; } - // Tear down existing lockdown if profile was removed - mLockdownEnabled = LockdownVpnTracker.isEnabled(); - if (mLockdownEnabled) { - byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN); - if (profileTag == null) { - Slog.e(TAG, "Lockdown VPN configured but cannot be read from keystore"); - return false; - } - String profileName = new String(profileTag); - final VpnProfile profile = VpnProfile.decode( - profileName, mKeyStore.get(Credentials.VPN + profileName)); - if (profile == null) { - Slog.e(TAG, "Lockdown VPN configured invalid profile " + profileName); - setLockdownTracker(null); - return true; - } - int user = UserHandle.getUserId(Binder.getCallingUid()); - synchronized (mVpns) { + synchronized (mVpns) { + // Tear down existing lockdown if profile was removed + mLockdownEnabled = LockdownVpnTracker.isEnabled(); + if (mLockdownEnabled) { + byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN); + if (profileTag == null) { + Slog.e(TAG, "Lockdown VPN configured but cannot be read from keystore"); + return false; + } + String profileName = new String(profileTag); + final VpnProfile profile = VpnProfile.decode( + profileName, mKeyStore.get(Credentials.VPN + profileName)); + if (profile == null) { + Slog.e(TAG, "Lockdown VPN configured invalid profile " + profileName); + setLockdownTracker(null); + return true; + } + int user = UserHandle.getUserId(Binder.getCallingUid()); Vpn vpn = mVpns.get(user); if (vpn == null) { Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown"); return false; } setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, vpn, profile)); + } else { + setLockdownTracker(null); } - } else { - setLockdownTracker(null); } return true; @@ -3632,6 +3640,7 @@ public class ConnectivityService extends IConnectivityManager.Stub * Internally set new {@link LockdownVpnTracker}, shutting down any existing * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown. */ + @GuardedBy("mVpns") private void setLockdownTracker(LockdownVpnTracker tracker) { // Shutdown any existing tracker final LockdownVpnTracker existing = mLockdownTracker; @@ -3646,6 +3655,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + @GuardedBy("mVpns") private void throwIfLockdownEnabled() { if (mLockdownEnabled) { throw new IllegalStateException("Unavailable in lockdown mode"); @@ -3693,12 +3703,12 @@ public class ConnectivityService extends IConnectivityManager.Stub enforceConnectivityInternalPermission(); enforceCrossUserPermission(userId); - // Can't set always-on VPN if legacy VPN is already in lockdown mode. - if (LockdownVpnTracker.isEnabled()) { - return false; - } - synchronized (mVpns) { + // Can't set always-on VPN if legacy VPN is already in lockdown mode. + if (LockdownVpnTracker.isEnabled()) { + return false; + } + Vpn vpn = mVpns.get(userId); if (vpn == null) { Slog.w(TAG, "User " + userId + " has no Vpn configuration"); @@ -3874,9 +3884,9 @@ public class ConnectivityService extends IConnectivityManager.Stub } userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId); mVpns.put(userId, userVpn); - } - if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) { - updateLockdownVpn(); + if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) { + updateLockdownVpn(); + } } } @@ -3913,11 +3923,13 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void onUserUnlocked(int userId) { - // User present may be sent because of an unlock, which might mean an unlocked keystore. - if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) { - updateLockdownVpn(); - } else { - startAlwaysOnVpn(userId); + synchronized (mVpns) { + // User present may be sent because of an unlock, which might mean an unlocked keystore. + if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) { + updateLockdownVpn(); + } else { + startAlwaysOnVpn(userId); + } } } @@ -4597,51 +4609,67 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * Update the NetworkCapabilities for {@code networkAgent} to {@code networkCapabilities} - * augmented with any stateful capabilities implied from {@code networkAgent} - * (e.g., validated status and captive portal status). - * - * @param oldScore score of the network before any of the changes that prompted us - * to call this function. - * @param nai the network having its capabilities updated. - * @param networkCapabilities the new network capabilities. + * 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, + * and foreground status). */ - private void updateCapabilities( - int oldScore, NetworkAgentInfo nai, NetworkCapabilities networkCapabilities) { + private NetworkCapabilities mixInCapabilities(NetworkAgentInfo nai, NetworkCapabilities nc) { // Once a NetworkAgent is connected, complain if some immutable capabilities are removed. - if (nai.everConnected && !nai.networkCapabilities.satisfiedByImmutableNetworkCapabilities( - networkCapabilities)) { - // TODO: consider not complaining when a network agent degrade its capabilities if this + if (nai.everConnected && + !nai.networkCapabilities.satisfiedByImmutableNetworkCapabilities(nc)) { + // TODO: consider not complaining when a network agent degrades its capabilities if this // does not cause any request (that is not a listen) currently matching that agent to // stop being matched by the updated agent. - String diff = nai.networkCapabilities.describeImmutableDifferences(networkCapabilities); + String diff = nai.networkCapabilities.describeImmutableDifferences(nc); if (!TextUtils.isEmpty(diff)) { Slog.wtf(TAG, "BUG: " + nai + " lost immutable capabilities:" + diff); } } // Don't modify caller's NetworkCapabilities. - networkCapabilities = new NetworkCapabilities(networkCapabilities); + NetworkCapabilities newNc = new NetworkCapabilities(nc); if (nai.lastValidated) { - networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED); + newNc.addCapability(NET_CAPABILITY_VALIDATED); } else { - networkCapabilities.removeCapability(NET_CAPABILITY_VALIDATED); + newNc.removeCapability(NET_CAPABILITY_VALIDATED); } if (nai.lastCaptivePortalDetected) { - networkCapabilities.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL); + newNc.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL); } else { - networkCapabilities.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL); + newNc.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL); } if (nai.isBackgroundNetwork()) { - networkCapabilities.removeCapability(NET_CAPABILITY_FOREGROUND); + newNc.removeCapability(NET_CAPABILITY_FOREGROUND); } else { - networkCapabilities.addCapability(NET_CAPABILITY_FOREGROUND); + newNc.addCapability(NET_CAPABILITY_FOREGROUND); } - if (Objects.equals(nai.networkCapabilities, networkCapabilities)) return; + return newNc; + } + + /** + * Update the NetworkCapabilities for {@code nai} to {@code nc}. Specifically: + * + * 1. Calls mixInCapabilities to merge the passed-in NetworkCapabilities {@code nc} with the + * capabilities we manage and store in {@code nai}, such as validated status and captive + * portal status) + * 2. Takes action on the result: changes network permissions, sends CAP_CHANGED callbacks, and + * potentially triggers rematches. + * 3. Directly informs other network stack components (NetworkStatsService, VPNs, etc. of the + * change.) + * + * @param oldScore score of the network before any of the changes that prompted us + * to call this function. + * @param nai the network having its capabilities updated. + * @param nc the new network capabilities. + */ + private void updateCapabilities(int oldScore, NetworkAgentInfo nai, NetworkCapabilities nc) { + NetworkCapabilities newNc = mixInCapabilities(nai, nc); + + if (Objects.equals(nai.networkCapabilities, newNc)) return; final String oldPermission = getNetworkPermission(nai.networkCapabilities); - final String newPermission = getNetworkPermission(networkCapabilities); + final String newPermission = getNetworkPermission(newNc); if (!Objects.equals(oldPermission, newPermission) && nai.created && !nai.isVPN()) { try { mNetd.setNetworkPermission(nai.network.netId, newPermission); @@ -4653,11 +4681,10 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkCapabilities prevNc; synchronized (nai) { prevNc = nai.networkCapabilities; - nai.networkCapabilities = networkCapabilities; + nai.networkCapabilities = newNc; } - if (nai.getCurrentScore() == oldScore && - networkCapabilities.equalRequestableCapabilities(prevNc)) { + if (nai.getCurrentScore() == oldScore && newNc.equalRequestableCapabilities(prevNc)) { // If the requestable capabilities haven't changed, and the score hasn't changed, then // the change we're processing can't affect any requests, it can only affect the listens // on this network. We might have been called by rematchNetworkAndRequests when a @@ -4673,15 +4700,15 @@ public class ConnectivityService extends IConnectivityManager.Stub // Report changes that are interesting for network statistics tracking. if (prevNc != null) { final boolean meteredChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_METERED) != - networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED); + newNc.hasCapability(NET_CAPABILITY_NOT_METERED); final boolean roamingChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING) != - networkCapabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING); + newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING); if (meteredChanged || roamingChanged) { notifyIfacesChangedForNetworkStats(); } } - if (!networkCapabilities.hasTransport(TRANSPORT_VPN)) { + if (!newNc.hasTransport(TRANSPORT_VPN)) { // Tell VPNs about updated capabilities, since they may need to // bubble those changes through. synchronized (mVpns) { @@ -5205,11 +5232,13 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void notifyLockdownVpn(NetworkAgentInfo nai) { - if (mLockdownTracker != null) { - if (nai != null && nai.isVPN()) { - mLockdownTracker.onVpnStateChanged(nai.networkInfo); - } else { - mLockdownTracker.onNetworkInfoChanged(); + synchronized (mVpns) { + if (mLockdownTracker != null) { + if (nai != null && nai.isVPN()) { + mLockdownTracker.onVpnStateChanged(nai.networkInfo); + } else { + mLockdownTracker.onNetworkInfoChanged(); + } } } } @@ -5439,28 +5468,28 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public boolean addVpnAddress(String address, int prefixLength) { - throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { + throwIfLockdownEnabled(); return mVpns.get(user).addAddress(address, prefixLength); } } @Override public boolean removeVpnAddress(String address, int prefixLength) { - throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { + throwIfLockdownEnabled(); return mVpns.get(user).removeAddress(address, prefixLength); } } @Override public boolean setUnderlyingNetworksForVpn(Network[] networks) { - throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); - boolean success; + final boolean success; synchronized (mVpns) { + throwIfLockdownEnabled(); success = mVpns.get(user).setUnderlyingNetworks(networks); } if (success) { @@ -5520,31 +5549,31 @@ public class ConnectivityService extends IConnectivityManager.Stub setAlwaysOnVpnPackage(userId, null, false); setVpnPackageAuthorization(alwaysOnPackage, userId, false); } - } - // Turn Always-on VPN off - if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) { - final long ident = Binder.clearCallingIdentity(); - try { - mKeyStore.delete(Credentials.LOCKDOWN_VPN); - mLockdownEnabled = false; - setLockdownTracker(null); - } finally { - Binder.restoreCallingIdentity(ident); + // Turn Always-on VPN off + if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) { + final long ident = Binder.clearCallingIdentity(); + try { + mKeyStore.delete(Credentials.LOCKDOWN_VPN); + mLockdownEnabled = false; + setLockdownTracker(null); + } finally { + Binder.restoreCallingIdentity(ident); + } } - } - // Turn VPN off - VpnConfig vpnConfig = getVpnConfig(userId); - if (vpnConfig != null) { - if (vpnConfig.legacy) { - prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId); - } else { - // Prevent this app (packagename = vpnConfig.user) from initiating VPN connections - // in the future without user intervention. - setVpnPackageAuthorization(vpnConfig.user, userId, false); + // Turn VPN off + VpnConfig vpnConfig = getVpnConfig(userId); + if (vpnConfig != null) { + if (vpnConfig.legacy) { + prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId); + } else { + // Prevent this app (packagename = vpnConfig.user) from initiating + // VPN connections in the future without user intervention. + setVpnPackageAuthorization(vpnConfig.user, userId, false); - prepareVpn(null, VpnConfig.LEGACY_VPN, userId); + prepareVpn(null, VpnConfig.LEGACY_VPN, userId); + } } } } diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index 985f16d910bc..a12c85aef85e 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -1591,6 +1591,8 @@ public class DeviceIdleController extends SystemService mPowerSaveWhitelistExceptIdleAppIdArray = buildAppIdArray( mPowerSaveWhitelistAppsExceptIdle, mPowerSaveWhitelistUserApps, mPowerSaveWhitelistExceptIdleAppIds); + + passWhiteListToForceAppStandbyTrackerLocked(); } return true; } catch (PackageManager.NameNotFoundException e) { @@ -1608,6 +1610,8 @@ public class DeviceIdleController extends SystemService mPowerSaveWhitelistAppsExceptIdle, mPowerSaveWhitelistUserApps, mPowerSaveWhitelistExceptIdleAppIds); mPowerSaveWhitelistUserAppsExceptIdle.clear(); + + passWhiteListToForceAppStandbyTrackerLocked(); } } } @@ -2572,7 +2576,7 @@ public class DeviceIdleController extends SystemService private void passWhiteListToForceAppStandbyTrackerLocked() { ForceAppStandbyTracker.getInstance(getContext()).setPowerSaveWhitelistAppIds( - mPowerSaveWhitelistAllAppIdArray, + mPowerSaveWhitelistExceptIdleAppIdArray, mTempWhitelistAppIdArray); } diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags index 8361132f24bc..732ac66b41de 100644 --- a/services/core/java/com/android/server/EventLogTags.logtags +++ b/services/core/java/com/android/server/EventLogTags.logtags @@ -133,6 +133,7 @@ option java_package com.android.server 2846 full_backup_cancelled (Package|3),(Message|3) 2850 backup_transport_lifecycle (Transport|3),(Bound|1|1) +2851 backup_transport_connection (Transport|3),(Connected|1|1) # --------------------------- diff --git a/services/core/java/com/android/server/ForceAppStandbyTracker.java b/services/core/java/com/android/server/ForceAppStandbyTracker.java index 8776f3a9f6c8..45516115b629 100644 --- a/services/core/java/com/android/server/ForceAppStandbyTracker.java +++ b/services/core/java/com/android/server/ForceAppStandbyTracker.java @@ -26,6 +26,8 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; +import android.net.Uri; +import android.os.BatteryManager; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -89,6 +91,9 @@ public class ForceAppStandbyTracker { private final MyHandler mHandler; + @VisibleForTesting + FeatureFlagsObserver mFlagsObserver; + /** * Pair of (uid (not user-id), packageName) with OP_RUN_ANY_IN_BACKGROUND *not* allowed. */ @@ -98,6 +103,9 @@ public class ForceAppStandbyTracker { @GuardedBy("mLock") final SparseBooleanArray mForegroundUids = new SparseBooleanArray(); + /** + * System except-idle + user whitelist in the device idle controller. + */ @GuardedBy("mLock") private int[] mPowerWhitelistedAllAppIds = new int[0]; @@ -111,13 +119,32 @@ public class ForceAppStandbyTracker { boolean mStarted; @GuardedBy("mLock") - boolean mForceAllAppsStandby; // True if device is in extreme battery saver mode + boolean mIsCharging; + + @GuardedBy("mLock") + boolean mBatterySaverEnabled; + + /** + * True if the forced app standby is currently enabled + */ + @GuardedBy("mLock") + boolean mForceAllAppsStandby; + + /** + * True if the forced app standby for small battery devices feature is enabled in settings + */ + @GuardedBy("mLock") + boolean mForceAllAppStandbyForSmallBattery; + /** + * True if the forced app standby feature is enabled in settings + */ @GuardedBy("mLock") - boolean mForcedAppStandbyEnabled; // True if the forced app standby feature is enabled + boolean mForcedAppStandbyEnabled; - private class FeatureFlagObserver extends ContentObserver { - FeatureFlagObserver() { + @VisibleForTesting + class FeatureFlagsObserver extends ContentObserver { + FeatureFlagsObserver() { super(null); } @@ -125,6 +152,9 @@ public class ForceAppStandbyTracker { mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor(Settings.Global.FORCED_APP_STANDBY_ENABLED), false, this); + + mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor( + Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED), false, this); } boolean isForcedAppStandbyEnabled() { @@ -132,20 +162,43 @@ public class ForceAppStandbyTracker { Settings.Global.FORCED_APP_STANDBY_ENABLED, 1) == 1; } + boolean isForcedAppStandbyForSmallBatteryEnabled() { + return Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED, 0) == 1; + } + @Override - public void onChange(boolean selfChange) { - final boolean enabled = isForcedAppStandbyEnabled(); - synchronized (mLock) { - if (mForcedAppStandbyEnabled == enabled) { - return; + public void onChange(boolean selfChange, Uri uri) { + if (Settings.Global.getUriFor(Settings.Global.FORCED_APP_STANDBY_ENABLED).equals(uri)) { + final boolean enabled = isForcedAppStandbyEnabled(); + synchronized (mLock) { + if (mForcedAppStandbyEnabled == enabled) { + return; + } + mForcedAppStandbyEnabled = enabled; + if (DEBUG) { + Slog.d(TAG, + "Forced app standby feature flag changed: " + mForcedAppStandbyEnabled); + } } - mForcedAppStandbyEnabled = enabled; - if (DEBUG) { - Slog.d(TAG, - "Forced app standby feature flag changed: " + mForcedAppStandbyEnabled); + mHandler.notifyForcedAppStandbyFeatureFlagChanged(); + } else if (Settings.Global.getUriFor( + Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED).equals(uri)) { + final boolean enabled = isForcedAppStandbyForSmallBatteryEnabled(); + synchronized (mLock) { + if (mForceAllAppStandbyForSmallBattery == enabled) { + return; + } + mForceAllAppStandbyForSmallBattery = enabled; + if (DEBUG) { + Slog.d(TAG, "Forced app standby for small battery feature flag changed: " + + mForceAllAppStandbyForSmallBattery); + } + updateForceAllAppStandbyState(); } + } else { + Slog.w(TAG, "Unexpected feature flag uri encountered: " + uri); } - mHandler.notifyFeatureFlagChanged(); } } @@ -286,9 +339,11 @@ public class ForceAppStandbyTracker { mAppOpsManager = Preconditions.checkNotNull(injectAppOpsManager()); mAppOpsService = Preconditions.checkNotNull(injectIAppOpsService()); mPowerManagerInternal = Preconditions.checkNotNull(injectPowerManagerInternal()); - final FeatureFlagObserver flagObserver = new FeatureFlagObserver(); - flagObserver.register(); - mForcedAppStandbyEnabled = flagObserver.isForcedAppStandbyEnabled(); + mFlagsObserver = new FeatureFlagsObserver(); + mFlagsObserver.register(); + mForcedAppStandbyEnabled = mFlagsObserver.isForcedAppStandbyEnabled(); + mForceAllAppStandbyForSmallBattery = + mFlagsObserver.isForcedAppStandbyForSmallBatteryEnabled(); try { mIActivityManager.registerUidObserver(new UidObserver(), @@ -303,16 +358,24 @@ public class ForceAppStandbyTracker { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_REMOVED); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); mContext.registerReceiver(new MyReceiver(), filter); refreshForcedAppStandbyUidPackagesLocked(); mPowerManagerInternal.registerLowPowerModeObserver( ServiceType.FORCE_ALL_APPS_STANDBY, - (state) -> updateForceAllAppsStandby(state.batterySaverEnabled)); + (state) -> { + synchronized (mLock) { + mBatterySaverEnabled = state.batterySaverEnabled; + updateForceAllAppStandbyState(); + } + }); + + mBatterySaverEnabled = mPowerManagerInternal.getLowPowerState( + ServiceType.FORCE_ALL_APPS_STANDBY).batterySaverEnabled; - updateForceAllAppsStandby(mPowerManagerInternal.getLowPowerState( - ServiceType.FORCE_ALL_APPS_STANDBY).batterySaverEnabled); + updateForceAllAppStandbyState(); } } @@ -337,6 +400,11 @@ public class ForceAppStandbyTracker { return LocalServices.getService(PowerManagerInternal.class); } + @VisibleForTesting + boolean isSmallBatteryDevice() { + return ActivityManager.isSmallBatteryDevice(); + } + /** * Update {@link #mRunAnyRestrictedPackages} with the current app ops state. */ @@ -366,18 +434,29 @@ public class ForceAppStandbyTracker { } } - /** - * Update {@link #mForceAllAppsStandby} and notifies the listeners. - */ - void updateForceAllAppsStandby(boolean enable) { + private void updateForceAllAppStandbyState() { synchronized (mLock) { - if (enable == mForceAllAppsStandby) { - return; + if (mIsCharging) { + toggleForceAllAppsStandbyLocked(false); + } else if (mForceAllAppStandbyForSmallBattery + && isSmallBatteryDevice()) { + toggleForceAllAppsStandbyLocked(true); + } else { + toggleForceAllAppsStandbyLocked(mBatterySaverEnabled); } - mForceAllAppsStandby = enable; + } + } - mHandler.notifyForceAllAppsStandbyChanged(); + /** + * Update {@link #mForceAllAppsStandby} and notifies the listeners. + */ + private void toggleForceAllAppsStandbyLocked(boolean enable) { + if (enable == mForceAllAppsStandby) { + return; } + mForceAllAppsStandby = enable; + + mHandler.notifyForceAllAppsStandbyChanged(); } private int findForcedAppStandbyUidPackageIndexLocked(int uid, @NonNull String packageName) { @@ -512,6 +591,13 @@ public class ForceAppStandbyTracker { if (userId > 0) { mHandler.doUserRemoved(userId); } + } else if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { + int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1); + synchronized (mLock) { + mIsCharging = (status == BatteryManager.BATTERY_STATUS_CHARGING + || status == BatteryManager.BATTERY_STATUS_FULL); + } + updateForceAllAppStandbyState(); } } } @@ -530,7 +616,7 @@ public class ForceAppStandbyTracker { private static final int MSG_TEMP_WHITELIST_CHANGED = 5; private static final int MSG_FORCE_ALL_CHANGED = 6; private static final int MSG_USER_REMOVED = 7; - private static final int MSG_FEATURE_FLAG_CHANGED = 8; + private static final int MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 8; public MyHandler(Looper looper) { super(looper); @@ -560,8 +646,8 @@ public class ForceAppStandbyTracker { obtainMessage(MSG_FORCE_ALL_CHANGED).sendToTarget(); } - public void notifyFeatureFlagChanged() { - obtainMessage(MSG_FEATURE_FLAG_CHANGED).sendToTarget(); + public void notifyForcedAppStandbyFeatureFlagChanged() { + obtainMessage(MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED).sendToTarget(); } public void doUserRemoved(int userId) { @@ -615,7 +701,7 @@ public class ForceAppStandbyTracker { l.onForceAllAppsStandbyChanged(sender); } return; - case MSG_FEATURE_FLAG_CHANGED: + case MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED: // Feature flag for forced app standby changed. final boolean unblockAlarms; synchronized (mLock) { @@ -839,6 +925,18 @@ public class ForceAppStandbyTracker { pw.println(isForceAllAppsStandbyEnabled()); pw.print(indent); + pw.print("Small Battery Device: "); + pw.println(isSmallBatteryDevice()); + + pw.print(indent); + pw.print("Force all apps standby for small battery device: "); + pw.println(mForceAllAppStandbyForSmallBattery); + + pw.print(indent); + pw.print("Charging: "); + pw.println(mIsCharging); + + pw.print(indent); pw.print("Foreground uids: ["); String sep = ""; @@ -877,6 +975,11 @@ public class ForceAppStandbyTracker { final long token = proto.start(fieldId); proto.write(ForceAppStandbyTrackerProto.FORCE_ALL_APPS_STANDBY, mForceAllAppsStandby); + proto.write(ForceAppStandbyTrackerProto.IS_SMALL_BATTERY_DEVICE, + isSmallBatteryDevice()); + proto.write(ForceAppStandbyTrackerProto.FORCE_ALL_APPS_STANDBY_FOR_SMALL_BATTERY, + mForceAllAppStandbyForSmallBattery); + proto.write(ForceAppStandbyTrackerProto.IS_CHARGING, mIsCharging); for (int i = 0; i < mForegroundUids.size(); i++) { if (mForegroundUids.valueAt(i)) { diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index 02cfe3dc75e5..46a35ec800ba 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -25,6 +25,7 @@ import static android.system.OsConstants.SOCK_DGRAM; import static com.android.internal.util.Preconditions.checkNotNull; import android.content.Context; +import android.net.ConnectivityManager; import android.net.IIpSecService; import android.net.INetd; import android.net.IpSecAlgorithm; @@ -62,7 +63,6 @@ import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; import libcore.io.IoUtils; @@ -83,7 +83,7 @@ public class IpSecService extends IIpSecService.Stub { private static final String NETD_SERVICE_NAME = "netd"; private static final int[] DIRECTIONS = - new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN}; + new int[] {IpSecManager.DIRECTION_OUT, IpSecManager.DIRECTION_IN}; private static final int NETD_FETCH_TIMEOUT_MS = 5000; // ms private static final int MAX_PORT_BIND_ATTEMPTS = 10; @@ -104,10 +104,10 @@ public class IpSecService extends IIpSecService.Stub { private final Context mContext; /** - * The next non-repeating global ID for tracking resources between users, this service, - * and kernel data structures. Accessing this variable is not thread safe, so it is - * only read or modified within blocks synchronized on IpSecService.this. We want to - * avoid -1 (INVALID_RESOURCE_ID) and 0 (we probably forgot to initialize it). + * The next non-repeating global ID for tracking resources between users, this service, and + * kernel data structures. Accessing this variable is not thread safe, so it is only read or + * modified within blocks synchronized on IpSecService.this. We want to avoid -1 + * (INVALID_RESOURCE_ID) and 0 (we probably forgot to initialize it). */ @GuardedBy("IpSecService.this") private int mNextResourceId = 1; @@ -536,14 +536,14 @@ public class IpSecService extends IIpSecService.Stub { private final class TransformRecord extends KernelResourceRecord { private final IpSecConfig mConfig; - private final SpiRecord[] mSpis; + private final SpiRecord mSpi; private final EncapSocketRecord mSocket; TransformRecord( - int resourceId, IpSecConfig config, SpiRecord[] spis, EncapSocketRecord socket) { + int resourceId, IpSecConfig config, SpiRecord spi, EncapSocketRecord socket) { super(resourceId); mConfig = config; - mSpis = spis; + mSpi = spi; mSocket = socket; } @@ -551,29 +551,26 @@ public class IpSecService extends IIpSecService.Stub { return mConfig; } - public SpiRecord getSpiRecord(int direction) { - return mSpis[direction]; + public SpiRecord getSpiRecord() { + return mSpi; } /** always guarded by IpSecService#this */ @Override public void freeUnderlyingResources() { - for (int direction : DIRECTIONS) { - int spi = mSpis[direction].getSpi(); - try { - mSrvConfig - .getNetdInstance() - .ipSecDeleteSecurityAssociation( - mResourceId, - direction, - mConfig.getLocalAddress(), - mConfig.getRemoteAddress(), - spi); - } catch (ServiceSpecificException e) { - // FIXME: get the error code and throw is at an IOException from Errno Exception - } catch (RemoteException e) { - Log.e(TAG, "Failed to delete SA with ID: " + mResourceId); - } + int spi = mSpi.getSpi(); + try { + mSrvConfig + .getNetdInstance() + .ipSecDeleteSecurityAssociation( + mResourceId, + mConfig.getSourceAddress(), + mConfig.getDestinationAddress(), + spi); + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + } catch (RemoteException e) { + Log.e(TAG, "Failed to delete SA with ID: " + mResourceId); } getResourceTracker().give(); @@ -597,10 +594,8 @@ public class IpSecService extends IIpSecService.Stub { .append(super.toString()) .append(", mSocket=") .append(mSocket) - .append(", mSpis[OUT].mResourceId=") - .append(mSpis[IpSecTransform.DIRECTION_OUT].mResourceId) - .append(", mSpis[IN].mResourceId=") - .append(mSpis[IpSecTransform.DIRECTION_IN].mResourceId) + .append(", mSpi.mResourceId=") + .append(mSpi.mResourceId) .append(", mConfig=") .append(mConfig) .append("}"); @@ -609,23 +604,16 @@ public class IpSecService extends IIpSecService.Stub { } private final class SpiRecord extends KernelResourceRecord { - private final int mDirection; - private final String mLocalAddress; - private final String mRemoteAddress; + private final String mSourceAddress; + private final String mDestinationAddress; private int mSpi; private boolean mOwnedByTransform = false; - SpiRecord( - int resourceId, - int direction, - String localAddress, - String remoteAddress, - int spi) { + SpiRecord(int resourceId, String sourceAddress, String destinationAddress, int spi) { super(resourceId); - mDirection = direction; - mLocalAddress = localAddress; - mRemoteAddress = remoteAddress; + mSourceAddress = sourceAddress; + mDestinationAddress = destinationAddress; mSpi = spi; } @@ -646,7 +634,7 @@ public class IpSecService extends IIpSecService.Stub { mSrvConfig .getNetdInstance() .ipSecDeleteSecurityAssociation( - mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi); + mResourceId, mSourceAddress, mDestinationAddress, mSpi); } catch (ServiceSpecificException e) { // FIXME: get the error code and throw is at an IOException from Errno Exception } catch (RemoteException e) { @@ -662,6 +650,10 @@ public class IpSecService extends IIpSecService.Stub { return mSpi; } + public String getDestinationAddress() { + return mDestinationAddress; + } + public void setOwnedByTransform() { if (mOwnedByTransform) { // Programming error @@ -689,12 +681,10 @@ public class IpSecService extends IIpSecService.Stub { .append(super.toString()) .append(", mSpi=") .append(mSpi) - .append(", mDirection=") - .append(mDirection) - .append(", mLocalAddress=") - .append(mLocalAddress) - .append(", mRemoteAddress=") - .append(mRemoteAddress) + .append(", mSourceAddress=") + .append(mSourceAddress) + .append(", mDestinationAddress=") + .append(mDestinationAddress) .append(", mOwnedByTransform=") .append(mOwnedByTransform) .append("}"); @@ -772,14 +762,17 @@ public class IpSecService extends IIpSecService.Stub { /** @hide */ @VisibleForTesting public IpSecService(Context context, IpSecServiceConfiguration config) { - this(context, config, (fd, uid) -> { - try{ - TrafficStats.setThreadStatsUid(uid); - TrafficStats.tagFileDescriptor(fd); - } finally { - TrafficStats.clearThreadStatsUid(); - } - }); + this( + context, + config, + (fd, uid) -> { + try { + TrafficStats.setThreadStatsUid(uid); + TrafficStats.tagFileDescriptor(fd); + } finally { + TrafficStats.clearThreadStatsUid(); + } + }); } /** @hide */ @@ -845,8 +838,8 @@ public class IpSecService extends IIpSecService.Stub { */ private static void checkDirection(int direction) { switch (direction) { - case IpSecTransform.DIRECTION_OUT: - case IpSecTransform.DIRECTION_IN: + case IpSecManager.DIRECTION_OUT: + case IpSecManager.DIRECTION_IN: return; } throw new IllegalArgumentException("Invalid Direction: " + direction); @@ -855,10 +848,8 @@ public class IpSecService extends IIpSecService.Stub { /** Get a new SPI and maintain the reservation in the system server */ @Override public synchronized IpSecSpiResponse allocateSecurityParameterIndex( - int direction, String remoteAddress, int requestedSpi, IBinder binder) - throws RemoteException { - checkDirection(direction); - checkInetAddress(remoteAddress); + String destinationAddress, int requestedSpi, IBinder binder) throws RemoteException { + checkInetAddress(destinationAddress); /* requestedSpi can be anything in the int range, so no check is needed. */ checkNotNull(binder, "Null Binder passed to allocateSecurityParameterIndex"); @@ -866,28 +857,21 @@ public class IpSecService extends IIpSecService.Stub { final int resourceId = mNextResourceId++; int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX; - String localAddress = ""; - try { if (!userRecord.mSpiQuotaTracker.isAvailable()) { return new IpSecSpiResponse( IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi); } + spi = mSrvConfig .getNetdInstance() - .ipSecAllocateSpi( - resourceId, - direction, - localAddress, - remoteAddress, - requestedSpi); + .ipSecAllocateSpi(resourceId, "", destinationAddress, requestedSpi); Log.d(TAG, "Allocated SPI " + spi); userRecord.mSpiRecords.put( resourceId, new RefcountedResource<SpiRecord>( - new SpiRecord(resourceId, direction, localAddress, remoteAddress, spi), - binder)); + new SpiRecord(resourceId, "", destinationAddress, spi), binder)); } catch (ServiceSpecificException e) { // TODO: Add appropriate checks when other ServiceSpecificException types are supported return new IpSecSpiResponse( @@ -1032,27 +1016,27 @@ public class IpSecService extends IIpSecService.Stub { } @VisibleForTesting - void validateAlgorithms(IpSecConfig config, int direction) throws IllegalArgumentException { - IpSecAlgorithm auth = config.getAuthentication(direction); - IpSecAlgorithm crypt = config.getEncryption(direction); - IpSecAlgorithm aead = config.getAuthenticatedEncryption(direction); - - // Validate the algorithm set - Preconditions.checkArgument( - aead != null || crypt != null || auth != null, - "No Encryption or Authentication algorithms specified"); - Preconditions.checkArgument( - auth == null || auth.isAuthentication(), - "Unsupported algorithm for Authentication"); - Preconditions.checkArgument( + void validateAlgorithms(IpSecConfig config) throws IllegalArgumentException { + IpSecAlgorithm auth = config.getAuthentication(); + IpSecAlgorithm crypt = config.getEncryption(); + IpSecAlgorithm aead = config.getAuthenticatedEncryption(); + + // Validate the algorithm set + Preconditions.checkArgument( + aead != null || crypt != null || auth != null, + "No Encryption or Authentication algorithms specified"); + Preconditions.checkArgument( + auth == null || auth.isAuthentication(), + "Unsupported algorithm for Authentication"); + Preconditions.checkArgument( crypt == null || crypt.isEncryption(), "Unsupported algorithm for Encryption"); - Preconditions.checkArgument( - aead == null || aead.isAead(), - "Unsupported algorithm for Authenticated Encryption"); - Preconditions.checkArgument( - aead == null || (auth == null && crypt == null), - "Authenticated Encryption is mutually exclusive with other Authentication " - + "or Encryption algorithms"); + Preconditions.checkArgument( + aead == null || aead.isAead(), + "Unsupported algorithm for Authenticated Encryption"); + Preconditions.checkArgument( + aead == null || (auth == null && crypt == null), + "Authenticated Encryption is mutually exclusive with other Authentication " + + "or Encryption algorithms"); } /** @@ -1062,29 +1046,6 @@ public class IpSecService extends IIpSecService.Stub { private void checkIpSecConfig(IpSecConfig config) { UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); - if (config.getLocalAddress() == null) { - throw new IllegalArgumentException("Invalid null Local InetAddress"); - } - - if (config.getRemoteAddress() == null) { - throw new IllegalArgumentException("Invalid null Remote InetAddress"); - } - - switch (config.getMode()) { - case IpSecTransform.MODE_TRANSPORT: - if (!config.getLocalAddress().isEmpty()) { - throw new IllegalArgumentException("Non-empty Local Address"); - } - // Must be valid, and not a wildcard - checkInetAddress(config.getRemoteAddress()); - break; - case IpSecTransform.MODE_TUNNEL: - break; - default: - throw new IllegalArgumentException( - "Invalid IpSecTransform.mode: " + config.getMode()); - } - switch (config.getEncapType()) { case IpSecTransform.ENCAP_NONE: break; @@ -1103,11 +1064,36 @@ public class IpSecService extends IIpSecService.Stub { throw new IllegalArgumentException("Invalid Encap Type: " + config.getEncapType()); } - for (int direction : DIRECTIONS) { - validateAlgorithms(config, direction); + validateAlgorithms(config); + + // Retrieve SPI record; will throw IllegalArgumentException if not found + SpiRecord s = userRecord.mSpiRecords.getResourceOrThrow(config.getSpiResourceId()); + + // If no remote address is supplied, then use one from the SPI. + if (TextUtils.isEmpty(config.getDestinationAddress())) { + config.setDestinationAddress(s.getDestinationAddress()); + } + + // All remote addresses must match + if (!config.getDestinationAddress().equals(s.getDestinationAddress())) { + throw new IllegalArgumentException("Mismatched remote addresseses."); + } + + // This check is technically redundant due to the chain of custody between the SPI and + // the IpSecConfig, but in the future if the dest is allowed to be set explicitly in + // the transform, this will prevent us from messing up. + checkInetAddress(config.getDestinationAddress()); + + // Require a valid source address for all transforms. + checkInetAddress(config.getSourceAddress()); - // Retrieve SPI record; will throw IllegalArgumentException if not found - userRecord.mSpiRecords.getResourceOrThrow(config.getSpiResourceId(direction)); + switch (config.getMode()) { + case IpSecTransform.MODE_TRANSPORT: + case IpSecTransform.MODE_TUNNEL: + break; + default: + throw new IllegalArgumentException( + "Invalid IpSecTransform.mode: " + config.getMode()); } } @@ -1127,13 +1113,12 @@ public class IpSecService extends IIpSecService.Stub { UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); - // Avoid resizing by creating a dependency array of min-size 3 (1 UDP encap + 2 SPIs) - List<RefcountedResource> dependencies = new ArrayList<>(3); + // Avoid resizing by creating a dependency array of min-size 2 (1 UDP encap + 1 SPI) + List<RefcountedResource> dependencies = new ArrayList<>(2); if (!userRecord.mTransformQuotaTracker.isAvailable()) { return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); } - SpiRecord[] spis = new SpiRecord[DIRECTIONS.length]; int encapType, encapLocalPort = 0, encapRemotePort = 0; EncapSocketRecord socketRecord = null; @@ -1149,51 +1134,46 @@ public class IpSecService extends IIpSecService.Stub { encapRemotePort = c.getEncapRemotePort(); } - for (int direction : DIRECTIONS) { - IpSecAlgorithm auth = c.getAuthentication(direction); - IpSecAlgorithm crypt = c.getEncryption(direction); - IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption(direction); + IpSecAlgorithm auth = c.getAuthentication(); + IpSecAlgorithm crypt = c.getEncryption(); + IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption(); - RefcountedResource<SpiRecord> refcountedSpiRecord = - userRecord.mSpiRecords.getRefcountedResourceOrThrow( - c.getSpiResourceId(direction)); - dependencies.add(refcountedSpiRecord); + RefcountedResource<SpiRecord> refcountedSpiRecord = + userRecord.mSpiRecords.getRefcountedResourceOrThrow(c.getSpiResourceId()); + dependencies.add(refcountedSpiRecord); + SpiRecord spiRecord = refcountedSpiRecord.getResource(); - spis[direction] = refcountedSpiRecord.getResource(); - int spi = spis[direction].getSpi(); - try { - mSrvConfig - .getNetdInstance() - .ipSecAddSecurityAssociation( - resourceId, - c.getMode(), - direction, - c.getLocalAddress(), - c.getRemoteAddress(), - (c.getNetwork() != null) ? c.getNetwork().getNetworkHandle() : 0, - spi, - (auth != null) ? auth.getName() : "", - (auth != null) ? auth.getKey() : new byte[] {}, - (auth != null) ? auth.getTruncationLengthBits() : 0, - (crypt != null) ? crypt.getName() : "", - (crypt != null) ? crypt.getKey() : new byte[] {}, - (crypt != null) ? crypt.getTruncationLengthBits() : 0, - (authCrypt != null) ? authCrypt.getName() : "", - (authCrypt != null) ? authCrypt.getKey() : new byte[] {}, - (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0, - encapType, - encapLocalPort, - encapRemotePort); - } catch (ServiceSpecificException e) { - // FIXME: get the error code and throw is at an IOException from Errno Exception - return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); - } + try { + mSrvConfig + .getNetdInstance() + .ipSecAddSecurityAssociation( + resourceId, + c.getMode(), + c.getSourceAddress(), + c.getDestinationAddress(), + (c.getNetwork() != null) ? c.getNetwork().netId : 0, + spiRecord.getSpi(), + (auth != null) ? auth.getName() : "", + (auth != null) ? auth.getKey() : new byte[] {}, + (auth != null) ? auth.getTruncationLengthBits() : 0, + (crypt != null) ? crypt.getName() : "", + (crypt != null) ? crypt.getKey() : new byte[] {}, + (crypt != null) ? crypt.getTruncationLengthBits() : 0, + (authCrypt != null) ? authCrypt.getName() : "", + (authCrypt != null) ? authCrypt.getKey() : new byte[] {}, + (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0, + encapType, + encapLocalPort, + encapRemotePort); + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); } // Both SAs were created successfully, time to construct a record and lock it away userRecord.mTransformRecords.put( resourceId, new RefcountedResource<TransformRecord>( - new TransformRecord(resourceId, c, spis, socketRecord), + new TransformRecord(resourceId, c, spiRecord, socketRecord), binder, dependencies.toArray(new RefcountedResource[dependencies.size()]))); return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId); @@ -1217,9 +1197,9 @@ public class IpSecService extends IIpSecService.Stub { */ @Override public synchronized void applyTransportModeTransform( - ParcelFileDescriptor socket, int resourceId) throws RemoteException { + ParcelFileDescriptor socket, int direction, int resourceId) throws RemoteException { UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); - + checkDirection(direction); // Get transform record; if no transform is found, will throw IllegalArgumentException TransformRecord info = userRecord.mTransformRecords.getResourceOrThrow(resourceId); @@ -1230,17 +1210,15 @@ public class IpSecService extends IIpSecService.Stub { IpSecConfig c = info.getConfig(); try { - for (int direction : DIRECTIONS) { - mSrvConfig - .getNetdInstance() - .ipSecApplyTransportModeTransform( - socket.getFileDescriptor(), - resourceId, - direction, - c.getLocalAddress(), - c.getRemoteAddress(), - info.getSpiRecord(direction).getSpi()); - } + mSrvConfig + .getNetdInstance() + .ipSecApplyTransportModeTransform( + socket.getFileDescriptor(), + resourceId, + direction, + c.getSourceAddress(), + c.getDestinationAddress(), + info.getSpiRecord().getSpi()); } catch (ServiceSpecificException e) { if (e.errorCode == EINVAL) { throw new IllegalArgumentException(e.toString()); @@ -1251,13 +1229,13 @@ public class IpSecService extends IIpSecService.Stub { } /** - * Remove a transport mode transform from a socket, applying the default (empty) policy. This - * will ensure that NO IPsec policy is applied to the socket (would be the equivalent of - * applying a policy that performs no IPsec). Today the resourceId parameter is passed but not - * used: reserved for future improved input validation. + * Remove transport mode transforms from a socket, applying the default (empty) policy. This + * ensures that NO IPsec policy is applied to the socket (would be the equivalent of applying a + * policy that performs no IPsec). Today the resourceId parameter is passed but not used: + * reserved for future improved input validation. */ @Override - public synchronized void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId) + public synchronized void removeTransportModeTransforms(ParcelFileDescriptor socket) throws RemoteException { try { mSrvConfig diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index 35f83e41071c..30432df418ed 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -84,6 +84,7 @@ public class Watchdog extends Thread { "/system/bin/sdcard", "/system/bin/surfaceflinger", "media.extractor", // system/bin/mediaextractor + "media.metrics", // system/bin/mediametrics "media.codec", // vendor/bin/hw/android.hardware.media.omx@1.0-service "com.android.bluetooth", // Bluetooth service }; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c9b9a4038d4e..48c678ebd0e8 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -19,6 +19,7 @@ package com.android.server.am; import static android.Manifest.permission.BIND_VOICE_INTERACTION; import static android.Manifest.permission.CHANGE_CONFIGURATION; import static android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST; +import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; @@ -192,11 +193,11 @@ import static com.android.server.am.TaskRecord.INVALID_TASK_ID; import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK; import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT; import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE; -import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN; -import static com.android.server.wm.AppTransition.TRANSIT_NONE; -import static com.android.server.wm.AppTransition.TRANSIT_TASK_IN_PLACE; -import static com.android.server.wm.AppTransition.TRANSIT_TASK_OPEN; -import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_FRONT; +import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN; +import static android.view.WindowManager.TRANSIT_NONE; +import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE; +import static android.view.WindowManager.TRANSIT_TASK_OPEN; +import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; @@ -370,6 +371,7 @@ import android.util.Xml; import android.util.proto.ProtoOutputStream; import android.view.Gravity; import android.view.LayoutInflater; +import android.view.RemoteAnimationDefinition; import android.view.View; import android.view.WindowManager; @@ -397,6 +399,7 @@ import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.TransferPipe; import com.android.internal.os.Zygote; import com.android.internal.policy.IKeyguardDismissCallback; +import com.android.internal.policy.KeyguardDismissCallback; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; @@ -6242,7 +6245,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Clear its pending alarms AlarmManagerInternal ami = LocalServices.getService(AlarmManagerInternal.class); - ami.removeAlarmsForUid(uid); + ami.removeAlarmsForUid(appInfo.uid); } } catch (RemoteException e) { } @@ -7260,15 +7263,22 @@ public class ActivityManagerService extends IActivityManager.Stub } ProfilerInfo profilerInfo = null; - String agent = null; + String preBindAgent = null; if (mProfileApp != null && mProfileApp.equals(processName)) { mProfileProc = app; - profilerInfo = (mProfilerInfo != null && mProfilerInfo.profileFile != null) ? - new ProfilerInfo(mProfilerInfo) : null; - agent = mProfilerInfo != null ? mProfilerInfo.agent : null; + if (mProfilerInfo != null) { + // Send a profiler info object to the app if either a file is given, or + // an agent should be loaded at bind-time. + boolean needsInfo = mProfilerInfo.profileFile != null + || mProfilerInfo.attachAgentDuringBind; + profilerInfo = needsInfo ? new ProfilerInfo(mProfilerInfo) : null; + if (!mProfilerInfo.attachAgentDuringBind) { + preBindAgent = mProfilerInfo.agent; + } + } } else if (app.instr != null && app.instr.mProfileFile != null) { profilerInfo = new ProfilerInfo(app.instr.mProfileFile, null, 0, false, false, - null); + null, false); } boolean enableTrackAllocation = false; @@ -7337,8 +7347,8 @@ public class ActivityManagerService extends IActivityManager.Stub // If we were asked to attach an agent on startup, do so now, before we're binding // application code. - if (agent != null) { - thread.attachAgent(agent); + if (preBindAgent != null) { + thread.attachAgent(preBindAgent); } checkTime(startTime, "attachApplicationLocked: immediately before bindApplication"); @@ -8386,21 +8396,11 @@ public class ActivityManagerService extends IActivityManager.Stub // entering picture-in-picture (this will prompt the user to authenticate if the // device is currently locked). try { - dismissKeyguard(token, new IKeyguardDismissCallback.Stub() { - @Override - public void onDismissError() throws RemoteException { - // Do nothing - } - + dismissKeyguard(token, new KeyguardDismissCallback() { @Override public void onDismissSucceeded() throws RemoteException { mHandler.post(enterPipRunnable); } - - @Override - public void onDismissCancelled() throws RemoteException { - // Do nothing - } }, null /* message */); } catch (RemoteException e) { // Local call @@ -13676,7 +13676,8 @@ public class ActivityManagerService extends IActivityManager.Stub * not. */ private void enforceSystemHasVrFeature() { - if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) { + if (!mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) { throw new UnsupportedOperationException("VR mode not supported on this device!"); } } @@ -13735,9 +13736,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public int setVrMode(IBinder token, boolean enabled, ComponentName packageName) { - if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) { - throw new UnsupportedOperationException("VR mode not supported on this device!"); - } + enforceSystemHasVrFeature(); final VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class); @@ -13769,9 +13768,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public boolean isVrModePackageEnabled(ComponentName packageName) { - if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) { - throw new UnsupportedOperationException("VR mode not supported on this device!"); - } + enforceSystemHasVrFeature(); final VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class); @@ -25135,6 +25132,16 @@ public class ActivityManagerService extends IActivityManager.Stub public boolean canStartMoreUsers() { return mUserController.canStartMoreUsers(); } + + @Override + public void setSwitchingFromSystemUserMessage(String switchingFromSystemUserMessage) { + mUserController.setSwitchingFromSystemUserMessage(switchingFromSystemUserMessage); + } + + @Override + public void setSwitchingToSystemUserMessage(String switchingToSystemUserMessage) { + mUserController.setSwitchingToSystemUserMessage(switchingToSystemUserMessage); + } } /** @@ -25439,4 +25446,23 @@ public class ActivityManagerService extends IActivityManager.Stub } } } + + @Override + public void registerRemoteAnimations(IBinder token, RemoteAnimationDefinition definition) + throws RemoteException { + enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, + "registerRemoteAnimations"); + synchronized (this) { + final ActivityRecord r = ActivityRecord.isInStackLocked(token); + if (r == null) { + return; + } + final long origId = Binder.clearCallingIdentity(); + try { + r.registerRemoteAnimations(definition); + } finally { + Binder.restoreCallingIdentity(origId); + } + } + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 4f60e1731a50..1240f5e664aa 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -109,6 +109,7 @@ final class ActivityManagerShellCommand extends ShellCommand { private boolean mAutoStop; private boolean mStreaming; // Streaming the profiling output to a file. private String mAgent; // Agent to attach on startup. + private boolean mAttachAgentDuringBind; // Whether agent should be attached late. private int mDisplayId; private int mWindowingMode; private int mActivityType; @@ -296,7 +297,21 @@ final class ActivityManagerShellCommand extends ShellCommand { } else if (opt.equals("--streaming")) { mStreaming = true; } else if (opt.equals("--attach-agent")) { + if (mAgent != null) { + cmd.getErrPrintWriter().println( + "Multiple --attach-agent(-bind) not supported"); + return false; + } + mAgent = getNextArgRequired(); + mAttachAgentDuringBind = false; + } else if (opt.equals("--attach-agent-bind")) { + if (mAgent != null) { + cmd.getErrPrintWriter().println( + "Multiple --attach-agent(-bind) not supported"); + return false; + } mAgent = getNextArgRequired(); + mAttachAgentDuringBind = true; } else if (opt.equals("-R")) { mRepeat = Integer.parseInt(getNextArgRequired()); } else if (opt.equals("-S")) { @@ -384,7 +399,7 @@ final class ActivityManagerShellCommand extends ShellCommand { } } profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop, - mStreaming, mAgent); + mStreaming, mAgent, mAttachAgentDuringBind); } pw.println("Starting: " + intent); @@ -766,7 +781,7 @@ final class ActivityManagerShellCommand extends ShellCommand { return -1; } profilerInfo = new ProfilerInfo(profileFile, fd, mSamplingInterval, false, mStreaming, - null); + null, false); } try { @@ -2544,6 +2559,7 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" (use with --start-profiler)"); pw.println(" -P <FILE>: like above, but profiling stops when app goes idle"); pw.println(" --attach-agent <agent>: attach the given agent before binding"); + pw.println(" --attach-agent-bind <agent>: attach the given agent during binding"); pw.println(" -R: repeat the activity launch <COUNT> times. Prior to each repeat,"); pw.println(" the top activity will be finished."); pw.println(" -S: force stop the target app before starting the activity"); diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index b74c8da611a8..3bef87794c24 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -21,6 +21,7 @@ import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.app.ActivityManager.TaskDescription.ATTR_TASKDESCRIPTION_PREFIX; import static android.app.ActivityOptions.ANIM_CLIP_REVEAL; import static android.app.ActivityOptions.ANIM_CUSTOM; +import static android.app.ActivityOptions.ANIM_REMOTE_ANIMATION; import static android.app.ActivityOptions.ANIM_SCALE_UP; import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION; import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS; @@ -170,6 +171,7 @@ import android.util.proto.ProtoOutputStream; import android.view.AppTransitionAnimationSpec; import android.view.IAppTransitionAnimationSpecsFuture; import android.view.IApplicationToken; +import android.view.RemoteAnimationDefinition; import android.view.WindowManager.LayoutParams; import com.android.internal.R; @@ -372,6 +374,10 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } } + String getLifecycleDescription(String reason) { + return "packageName=" + packageName + ", state=" + state + ", reason=" + reason; + } + void dump(PrintWriter pw, String prefix) { final long now = SystemClock.uptimeMillis(); pw.print(prefix); pw.print("packageName="); pw.print(packageName); @@ -1481,6 +1487,10 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo case ANIM_OPEN_CROSS_PROFILE_APPS: service.mWindowManager.overridePendingAppTransitionStartCrossProfileApps(); break; + case ANIM_REMOTE_ANIMATION: + service.mWindowManager.overridePendingAppTransitionRemote( + pendingOptions.getRemoteAnimationAdapter()); + break; default: Slog.e(TAG, "applyOptionsLocked: Unknown animationType=" + animationType); break; @@ -1609,15 +1619,18 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo mStackSupervisor.mStoppingActivities.remove(this); mStackSupervisor.mGoingToSleepActivities.remove(this); - // If an activity is not in the paused state when becoming visible, cycle to the paused - // state. - if (state != PAUSED) { + // If the activity is stopped or stopping, cycle to the paused state. + if (state == STOPPED || state == STOPPING) { + // Capture reason before state change + final String reason = getLifecycleDescription("makeVisibleIfNeeded"); + // An activity must be in the {@link PAUSING} state for the system to validate // the move to {@link PAUSED}. state = PAUSING; service.mLifecycleManager.scheduleTransaction(app.thread, appToken, PauseActivityItem.obtain(finishing, false /* userLeaving */, - configChangeFlags, false /* dontReport */)); + configChangeFlags, false /* dontReport */) + .setDescription(reason)); } } catch (Exception e) { // Just skip on any failure; we'll make it visible when it next restarts. @@ -2778,6 +2791,10 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo return mStackSupervisor.topRunningActivityLocked() == this; } + void registerRemoteAnimations(RemoteAnimationDefinition definition) { + mWindowContainerController.registerRemoteAnimations(definition); + } + @Override public String toString() { if (stringName != null) { diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index a343965ab848..0904c8e71df2 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -84,14 +84,14 @@ import static com.android.server.am.proto.ActivityStackProto.FULLSCREEN; import static com.android.server.am.proto.ActivityStackProto.ID; import static com.android.server.am.proto.ActivityStackProto.RESUMED_ACTIVITY; import static com.android.server.am.proto.ActivityStackProto.TASKS; -import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_CLOSE; -import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN; -import static com.android.server.wm.AppTransition.TRANSIT_NONE; -import static com.android.server.wm.AppTransition.TRANSIT_TASK_CLOSE; -import static com.android.server.wm.AppTransition.TRANSIT_TASK_OPEN; -import static com.android.server.wm.AppTransition.TRANSIT_TASK_OPEN_BEHIND; -import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_BACK; -import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_FRONT; +import static android.view.WindowManager.TRANSIT_ACTIVITY_CLOSE; +import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN; +import static android.view.WindowManager.TRANSIT_NONE; +import static android.view.WindowManager.TRANSIT_TASK_CLOSE; +import static android.view.WindowManager.TRANSIT_TASK_OPEN; +import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND; +import static android.view.WindowManager.TRANSIT_TASK_TO_BACK; +import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT; import static java.lang.Integer.MAX_VALUE; @@ -2639,7 +2639,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai next.clearOptionsLocked(); mService.mLifecycleManager.scheduleTransaction(next.app.thread, next.appToken, ResumeActivityItem.obtain(next.app.repProcState, - mService.isNextTransitionForward())); + mService.isNextTransitionForward()) + .setDescription(next.getLifecycleDescription( + "resumeTopActivityInnerLocked"))); if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed " + next); @@ -3419,7 +3421,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai EventLogTags.writeAmStopActivity( r.userId, System.identityHashCode(r), r.shortComponentName); mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken, - StopActivityItem.obtain(r.visible, r.configChangeFlags)); + StopActivityItem.obtain(r.visible, r.configChangeFlags) + .setDescription(r.getLifecycleDescription("stopActivityLocked"))); if (shouldSleepOrShutDownActivities()) { r.setSleeping(true); } @@ -4225,7 +4228,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai try { if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + r); mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken, - DestroyActivityItem.obtain(r.finishing, r.configChangeFlags)); + DestroyActivityItem.obtain(r.finishing, r.configChangeFlags) + .setDescription(r.getLifecycleDescription("destroyActivityLocked"))); } catch (Exception e) { // We can just ignore exceptions here... if the process // has crashed, our death notification will clean things diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index b567303b3ae5..8168cba213f5 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -17,6 +17,7 @@ package com.android.server.am; import static android.Manifest.permission.ACTIVITY_EMBEDDING; +import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.Manifest.permission.START_ANY_ACTIVITY; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; @@ -93,7 +94,7 @@ import static com.android.server.am.proto.ActivityStackSupervisorProto.DISPLAYS; import static com.android.server.am.proto.ActivityStackSupervisorProto.FOCUSED_STACK_ID; import static com.android.server.am.proto.ActivityStackSupervisorProto.KEYGUARD_CONTROLLER; import static com.android.server.am.proto.ActivityStackSupervisorProto.RESUMED_ACTIVITY; -import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS; +import static android.view.WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS; import static java.lang.Integer.MAX_VALUE; @@ -162,6 +163,7 @@ import android.util.SparseIntArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.view.Display; +import android.view.RemoteAnimationAdapter; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.ReferrerIntent; @@ -1422,7 +1424,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // Set desired final state. final ActivityLifecycleItem lifecycleItem; if (andResume) { - lifecycleItem = ResumeActivityItem.obtain(mService.isNextTransitionForward()); + lifecycleItem = ResumeActivityItem.obtain(mService.isNextTransitionForward()) + .setDescription(r.getLifecycleDescription("realStartActivityLocked")); } else { lifecycleItem = PauseActivityItem.obtain(); } @@ -1680,6 +1683,18 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D Slog.w(TAG, msg); throw new SecurityException(msg); } + + // Check permission for remote animations + final RemoteAnimationAdapter adapter = options.getRemoteAnimationAdapter(); + if (adapter != null && mService.checkPermission( + CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, callingPid, callingUid) + != PERMISSION_GRANTED) { + final String msg = "Permission Denial: starting " + intent.toString() + + " from " + callerApp + " (pid=" + callingPid + + ", uid=" + callingUid + ") with remoteAnimationAdapter"; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } } return true; diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java index 4d7bc1e4af56..79f3fe3ffd9f 100644 --- a/services/core/java/com/android/server/am/KeyguardController.java +++ b/services/core/java/com/android/server/am/KeyguardController.java @@ -27,13 +27,13 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NA import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; import static com.android.server.am.proto.KeyguardControllerProto.KEYGUARD_OCCLUDED; import static com.android.server.am.proto.KeyguardControllerProto.KEYGUARD_SHOWING; -import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; -import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; -import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; -import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_GOING_AWAY; -import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_OCCLUDE; -import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_UNOCCLUDE; -import static com.android.server.wm.AppTransition.TRANSIT_UNSET; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; +import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; +import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; +import static android.view.WindowManager.TRANSIT_UNSET; import android.app.ActivityManagerInternal.SleepToken; import android.os.IBinder; diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index a327a018bc25..c7210a835767 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -217,6 +217,18 @@ class UserController implements Handler.Callback { private volatile ArraySet<String> mCurWaitingUserSwitchCallbacks; /** + * Messages for for switching from {@link android.os.UserHandle#SYSTEM}. + */ + @GuardedBy("mLock") + private String mSwitchingFromSystemUserMessage; + + /** + * Messages for for switching to {@link android.os.UserHandle#SYSTEM}. + */ + @GuardedBy("mLock") + private String mSwitchingToSystemUserMessage; + + /** * Callbacks that are still active after {@link #USER_SWITCH_TIMEOUT_MS} */ @GuardedBy("mLock") @@ -1189,7 +1201,8 @@ class UserController implements Handler.Callback { private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) { // The dialog will show and then initiate the user switch by calling startUserInForeground - mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second); + mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second, + getSwitchingFromSystemUserMessage(), getSwitchingToSystemUserMessage()); } private void dispatchForegroundProfileChanged(int userId) { @@ -1799,6 +1812,30 @@ class UserController implements Handler.Callback { return mLockPatternUtils.isLockScreenDisabled(userId); } + void setSwitchingFromSystemUserMessage(String switchingFromSystemUserMessage) { + synchronized (mLock) { + mSwitchingFromSystemUserMessage = switchingFromSystemUserMessage; + } + } + + void setSwitchingToSystemUserMessage(String switchingToSystemUserMessage) { + synchronized (mLock) { + mSwitchingToSystemUserMessage = switchingToSystemUserMessage; + } + } + + private String getSwitchingFromSystemUserMessage() { + synchronized (mLock) { + return mSwitchingFromSystemUserMessage; + } + } + + private String getSwitchingToSystemUserMessage() { + synchronized (mLock) { + return mSwitchingToSystemUserMessage; + } + } + void dump(PrintWriter pw, boolean dumpAll) { synchronized (mLock) { pw.println(" mStartedUsers:"); @@ -2079,9 +2116,11 @@ class UserController implements Handler.Callback { mService.installEncryptionUnawareProviders(userId); } - void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser) { + void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser, + String switchingFromSystemUserMessage, String switchingToSystemUserMessage) { Dialog d = new UserSwitchingDialog(mService, mService.mContext, fromUser, toUser, - true /* above system */); + true /* above system */, switchingFromSystemUserMessage, + switchingToSystemUserMessage); d.show(); } diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java index 3e6934f67e4e..afcba3bfe330 100644 --- a/services/core/java/com/android/server/am/UserSwitchingDialog.java +++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java @@ -53,7 +53,8 @@ final class UserSwitchingDialog extends AlertDialog private boolean mStartedUser; public UserSwitchingDialog(ActivityManagerService service, Context context, UserInfo oldUser, - UserInfo newUser, boolean aboveSystem) { + UserInfo newUser, boolean aboveSystem, String switchingFromSystemUserMessage, + String switchingToSystemUserMessage) { super(context); mService = service; @@ -65,7 +66,7 @@ final class UserSwitchingDialog extends AlertDialog // Custom view due to alignment and font size requirements View view = LayoutInflater.from(getContext()).inflate(R.layout.user_switching_dialog, null); - String viewMessage; + String viewMessage = null; if (UserManager.isSplitSystemUser() && newUser.id == UserHandle.USER_SYSTEM) { viewMessage = res.getString(R.string.user_logging_out_message, oldUser.name); } else if (UserManager.isDeviceInDemoMode(context)) { @@ -75,7 +76,17 @@ final class UserSwitchingDialog extends AlertDialog viewMessage = res.getString(R.string.demo_starting_message); } } else { - viewMessage = res.getString(R.string.user_switching_message, newUser.name); + if (oldUser.id == UserHandle.USER_SYSTEM) { + viewMessage = switchingFromSystemUserMessage; + } else if (newUser.id == UserHandle.USER_SYSTEM) { + viewMessage = switchingToSystemUserMessage; + } + + // If switchingFromSystemUserMessage or switchingToSystemUserMessage is null, fallback + // to system message. + if (viewMessage == null) { + viewMessage = res.getString(R.string.user_switching_message, newUser.name); + } } ((TextView) view.findViewById(R.id.message)).setText(viewMessage); setView(view); diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java index 40a93c1c2808..51499f735bcf 100644 --- a/services/core/java/com/android/server/content/SyncJobService.java +++ b/services/core/java/com/android/server/content/SyncJobService.java @@ -122,10 +122,12 @@ public class SyncJobService extends JobService { final long startUptime = mJobStartUptimes.get(jobId); final long nowUptime = SystemClock.uptimeMillis(); + final long runtime = nowUptime - startUptime; + if (startUptime == 0) { wtf("Job " + jobId + " start uptime not found: " + " params=" + jobParametersToString(params)); - } else if ((nowUptime - startUptime) > 60 * 1000) { + } else if (runtime > 60 * 1000) { // WTF if startSyncH() hasn't happened, *unless* onStopJob() was called too soon. // (1 minute threshold.) if (!mStartedSyncs.get(jobId)) { @@ -134,6 +136,12 @@ public class SyncJobService extends JobService { + " nowUptime=" + nowUptime + " params=" + jobParametersToString(params)); } + } else if (runtime < 10 * 1000) { + // Job stopped too soon. WTF. + wtf("Job " + jobId + " stopped in " + runtime + " ms: " + + " startUptime=" + startUptime + + " nowUptime=" + nowUptime + + " params=" + jobParametersToString(params)); } mStartedSyncs.delete(jobId); @@ -183,6 +191,7 @@ public class SyncJobService extends JobService { return "job:null"; } else { return "job:#" + params.getJobId() + ":" + + "sr=[" + params.getStopReason() + "/" + params.getDebugStopReason() + "]:" + SyncOperation.maybeCreateFromJobExtras(params.getExtras()); } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 02e4fe00f893..a55fec5246d3 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -2009,5 +2009,14 @@ public final class DisplayManagerService extends SystemService { mDisplayPowerController.persistBrightnessSliderEvents(); } } + + @Override + public void onOverlayChanged() { + synchronized (mSyncRoot) { + if (updateLogicalDisplaysLocked()) { + scheduleTraversalLocked(false); + } + } + } } } diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index 733ed3d81555..bd1dbf9c46e8 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -934,12 +934,14 @@ public final class JobSchedulerService extends com.android.server.SystemService * @param uid Uid of the calling client. * @param jobId Id of the job, provided at schedule-time. */ - public boolean cancelJob(int uid, int jobId) { + public boolean cancelJob(int uid, int jobId, int callingUid) { JobStatus toCancel; synchronized (mLock) { toCancel = mJobs.getJobByUidAndJobId(uid, jobId); if (toCancel != null) { - cancelJobImplLocked(toCancel, null, "cancel() called by app"); + cancelJobImplLocked(toCancel, null, + "cancel() called by app, callingUid=" + callingUid + + " uid=" + uid + " jobId=" + jobId); } return (toCancel != null); } @@ -2341,7 +2343,8 @@ public final class JobSchedulerService extends com.android.server.SystemService final int uid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); try { - JobSchedulerService.this.cancelJobsForUid(uid, "cancelAll() called by app"); + JobSchedulerService.this.cancelJobsForUid(uid, + "cancelAll() called by app, callingUid=" + uid); } finally { Binder.restoreCallingIdentity(ident); } @@ -2353,7 +2356,7 @@ public final class JobSchedulerService extends com.android.server.SystemService long ident = Binder.clearCallingIdentity(); try { - JobSchedulerService.this.cancelJob(uid, jobId); + JobSchedulerService.this.cancelJob(uid, jobId, uid); } finally { Binder.restoreCallingIdentity(ident); } @@ -2466,7 +2469,7 @@ public final class JobSchedulerService extends com.android.server.SystemService for (int i=0; i<mActiveServices.size(); i++) { final JobServiceContext jc = mActiveServices.get(i); final JobStatus js = jc.getRunningJobLocked(); - if (jc.timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId)) { + if (jc.timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId, "shell")) { foundSome = true; pw.print("Timing out: "); js.printUniqueId(pw); @@ -2506,7 +2509,7 @@ public final class JobSchedulerService extends com.android.server.SystemService } } else { pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId); - if (!cancelJob(pkgUid, jobId)) { + if (!cancelJob(pkgUid, jobId, Process.SHELL_UID)) { pw.println("No matching job found."); } } diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java index 709deeb81ac9..83a3c1993d4b 100644 --- a/services/core/java/com/android/server/job/JobServiceContext.java +++ b/services/core/java/com/android/server/job/JobServiceContext.java @@ -312,13 +312,14 @@ public final class JobServiceContext implements ServiceConnection { return mTimeoutElapsed; } - boolean timeoutIfExecutingLocked(String pkgName, int userId, boolean matchJobId, int jobId) { + boolean timeoutIfExecutingLocked(String pkgName, int userId, boolean matchJobId, int jobId, + String reason) { final JobStatus executing = getRunningJobLocked(); if (executing != null && (userId == UserHandle.USER_ALL || userId == executing.getUserId()) && (pkgName == null || pkgName.equals(executing.getSourcePackageName())) && (!matchJobId || jobId == executing.getJobId())) { if (mVerb == VERB_EXECUTING) { - mParams.setStopReason(JobParameters.REASON_TIMEOUT); + mParams.setStopReason(JobParameters.REASON_TIMEOUT, reason); sendStopMessageLocked("force timeout from shell"); return true; } @@ -537,7 +538,7 @@ public final class JobServiceContext implements ServiceConnection { } return; } - mParams.setStopReason(arg1); + mParams.setStopReason(arg1, debugReason); if (arg1 == JobParameters.REASON_PREEMPT) { mPreferredUid = mRunningJob != null ? mRunningJob.getUid() : NO_PREFERRED_UID; @@ -687,7 +688,7 @@ public final class JobServiceContext implements ServiceConnection { // Not an error - client ran out of time. Slog.i(TAG, "Client timed out while executing (no jobFinished received), " + "sending onStop: " + getRunningJobNameLocked()); - mParams.setStopReason(JobParameters.REASON_TIMEOUT); + mParams.setStopReason(JobParameters.REASON_TIMEOUT, "client timed out"); sendStopMessageLocked("timeout while executing"); break; default: diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index ee08c3874028..879c0242d936 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -63,7 +63,6 @@ import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; -import android.os.ServiceSpecificException; import android.os.ShellCallback; import android.os.StrictMode; import android.os.SystemProperties; @@ -78,11 +77,10 @@ import android.security.KeyStore; import android.security.keystore.AndroidKeyStoreProvider; import android.security.keystore.KeyProperties; import android.security.keystore.KeyProtection; +import android.security.keystore.KeychainProtectionParameter; import android.security.keystore.UserNotAuthenticatedException; -import android.security.keystore.EntryRecoveryData; -import android.security.keystore.RecoveryData; -import android.security.keystore.RecoveryMetadata; -import android.security.keystore.RecoveryManagerException; +import android.security.keystore.WrappedApplicationKey; +import android.security.keystore.KeychainSnapshot; import android.service.gatekeeper.GateKeeperResponse; import android.service.gatekeeper.IGateKeeperService; import android.text.TextUtils; @@ -90,6 +88,7 @@ import android.util.ArrayMap; import android.util.EventLog; import android.util.Log; import android.util.Slog; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -1874,6 +1873,7 @@ public class LockSettingsService extends ILockSettings.Stub { mSpManager.removeUser(userId); mStorage.removeUser(userId); mStrongAuth.removeUser(userId); + cleanSpCache(); final KeyStore ks = KeyStore.getInstance(); ks.onUserRemoved(userId); @@ -1968,7 +1968,7 @@ public class LockSettingsService extends ILockSettings.Stub { } @Override - public RecoveryData getRecoveryData(@NonNull byte[] account) throws RemoteException { + public KeychainSnapshot getRecoveryData(@NonNull byte[] account) throws RemoteException { return mRecoverableKeyStoreManager.getRecoveryData(account); } @@ -1997,7 +1997,7 @@ public class LockSettingsService extends ILockSettings.Stub { } @Override - public void setRecoverySecretTypes(@NonNull @RecoveryMetadata.UserSecretType + public void setRecoverySecretTypes(@NonNull @KeychainProtectionParameter.UserSecretType int[] secretTypes) throws RemoteException { mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes); } @@ -2014,7 +2014,7 @@ public class LockSettingsService extends ILockSettings.Stub { } @Override - public void recoverySecretAvailable(@NonNull RecoveryMetadata recoverySecret) + public void recoverySecretAvailable(@NonNull KeychainProtectionParameter recoverySecret) throws RemoteException { mRecoverableKeyStoreManager.recoverySecretAvailable(recoverySecret); } @@ -2022,7 +2022,7 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public byte[] startRecoverySession(@NonNull String sessionId, @NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams, - @NonNull byte[] vaultChallenge, @NonNull List<RecoveryMetadata> secrets) + @NonNull byte[] vaultChallenge, @NonNull List<KeychainProtectionParameter> secrets) throws RemoteException { return mRecoverableKeyStoreManager.startRecoverySession(sessionId, verifierPublicKey, vaultParams, vaultChallenge, secrets); @@ -2030,7 +2030,7 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public Map<String, byte[]> recoverKeys(@NonNull String sessionId, - @NonNull byte[] recoveryKeyBlob, @NonNull List<EntryRecoveryData> applicationKeys) + @NonNull byte[] recoveryKeyBlob, @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException { return mRecoverableKeyStoreManager.recoverKeys( sessionId, recoveryKeyBlob, applicationKeys); @@ -2112,6 +2112,63 @@ public class LockSettingsService extends ILockSettings.Stub { } /** + * A user's synthetic password does not change so it must be cached in certain circumstances to + * enable untrusted credential reset. + * + * Untrusted credential reset will be removed in a future version (b/68036371) at which point + * this cache is no longer needed as the SP will always be known when changing the user's + * credential. + */ + @GuardedBy("mSpManager") + private SparseArray<AuthenticationToken> mSpCache = new SparseArray(); + + private void onAuthTokenKnownForUser(@UserIdInt int userId, AuthenticationToken auth) { + // Update the SP cache, removing the entry when allowed + synchronized (mSpManager) { + if (shouldCacheSpForUser(userId)) { + Slog.i(TAG, "Caching SP for user " + userId); + mSpCache.put(userId, auth); + } else { + Slog.i(TAG, "Not caching SP for user " + userId); + mSpCache.delete(userId); + } + } + } + + /** Clean up the SP cache by removing unneeded entries. */ + private void cleanSpCache() { + synchronized (mSpManager) { + // Preserve indicies after removal by iterating backwards + for (int i = mSpCache.size() - 1; i >= 0; --i) { + final int userId = mSpCache.keyAt(i); + if (!shouldCacheSpForUser(userId)) { + Slog.i(TAG, "Uncaching SP for user " + userId); + mSpCache.removeAt(i); + } + } + } + } + + private boolean shouldCacheSpForUser(@UserIdInt int userId) { + // Before the user setup has completed, an admin could be installed that requires the SP to + // be cached (see below). + if (Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, 0, userId) == 0) { + return true; + } + + // If the user has an admin which can perform an untrusted credential reset, the SP needs to + // be cached. If there isn't a DevicePolicyManager then there can't be an admin in the first + // place so caching is not necessary. + final DevicePolicyManagerInternal dpmi = LocalServices.getService( + DevicePolicyManagerInternal.class); + if (dpmi == null) { + return false; + } + return dpmi.canUserHaveUntrustedCredentialReset(userId); + } + + /** * Precondition: vold and keystore unlocked. * * Create new synthetic password, set up synthetic password blob protected by the supplied @@ -2126,9 +2183,7 @@ public class LockSettingsService extends ILockSettings.Stub { * 3. Once a user is migrated to have synthetic password, its value will never change, no matter * whether the user changes his lockscreen PIN or clear/reset it. When the user clears its * lockscreen PIN, we still maintain the existing synthetic password in a password blob - * protected by a default PIN. The only exception is when the DPC performs an untrusted - * credential change, in which case we have no way to derive the existing synthetic password - * and has to create a new one. + * protected by a default PIN. * 4. The user SID is linked with synthetic password, but its cleared/re-created when the user * clears/re-creates his lockscreen PIN. * @@ -2148,13 +2203,23 @@ public class LockSettingsService extends ILockSettings.Stub { * This is the untrusted credential reset, OR the user sets a new lockscreen password * FOR THE FIRST TIME on a SP-enabled device. New credential and new SID will be created */ + @GuardedBy("mSpManager") @VisibleForTesting protected AuthenticationToken initializeSyntheticPasswordLocked(byte[] credentialHash, String credential, int credentialType, int requestedQuality, int userId) throws RemoteException { Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId); - AuthenticationToken auth = mSpManager.newSyntheticPasswordAndSid(getGateKeeperService(), - credentialHash, credential, userId); + // Load from the cache or a make a new one + AuthenticationToken auth = mSpCache.get(userId); + if (auth != null) { + // If the synthetic password has been cached, we can only be in case 3., described + // above, for an untrusted credential reset so a new SID is still needed. + mSpManager.newSidForUser(getGateKeeperService(), auth, userId); + } else { + auth = mSpManager.newSyntheticPasswordAndSid(getGateKeeperService(), + credentialHash, credential, userId); + } + onAuthTokenKnownForUser(userId, auth); if (auth == null) { Slog.wtf(TAG, "initializeSyntheticPasswordLocked returns null auth token"); return null; @@ -2269,6 +2334,8 @@ public class LockSettingsService extends ILockSettings.Stub { trustManager.setDeviceLockedForUser(userId, false); } mStrongAuth.reportSuccessfulStrongAuthUnlock(userId); + + onAuthTokenKnownForUser(userId, authResult.authToken); } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) { if (response.getTimeout() > 0) { requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId); @@ -2287,6 +2354,7 @@ public class LockSettingsService extends ILockSettings.Stub { * SID is gone. We also clear password from (software-based) keystore and vold, which will be * added back when new password is set in future. */ + @GuardedBy("mSpManager") private long setLockCredentialWithAuthTokenLocked(String credential, int credentialType, AuthenticationToken auth, int requestedQuality, int userId) throws RemoteException { if (DEBUG) Slog.d(TAG, "setLockCredentialWithAuthTokenLocked: user=" + userId); @@ -2334,6 +2402,7 @@ public class LockSettingsService extends ILockSettings.Stub { return newHandle; } + @GuardedBy("mSpManager") private void spBasedSetLockCredentialInternalLocked(String credential, int credentialType, String savedCredential, int requestedQuality, int userId) throws RemoteException { if (DEBUG) Slog.d(TAG, "spBasedSetLockCredentialInternalLocked: user=" + userId); @@ -2369,13 +2438,19 @@ public class LockSettingsService extends ILockSettings.Stub { setLockCredentialWithAuthTokenLocked(credential, credentialType, auth, requestedQuality, userId); mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId); + onAuthTokenKnownForUser(userId, auth); } else if (response != null - && response.getResponseCode() == VerifyCredentialResponse.RESPONSE_ERROR){ + && response.getResponseCode() == VerifyCredentialResponse.RESPONSE_ERROR) { // We are performing an untrusted credential change i.e. by DevicePolicyManager. // So provision a new SP and SID. This would invalidate existing escrow tokens. // Still support this for now but this flow will be removed in the next release. - Slog.w(TAG, "Untrusted credential change invoked"); + + if (mSpCache.get(userId) == null) { + throw new IllegalStateException( + "Untrusted credential reset not possible without cached SP"); + } + initializeSyntheticPasswordLocked(null, credential, credentialType, requestedQuality, userId); synchronizeUnifiedWorkChallengeForProfiles(userId, null); @@ -2486,8 +2561,9 @@ public class LockSettingsService extends ILockSettings.Stub { private boolean setLockCredentialWithTokenInternal(String credential, int type, long tokenHandle, byte[] token, int requestedQuality, int userId) throws RemoteException { + final AuthenticationResult result; synchronized (mSpManager) { - AuthenticationResult result = mSpManager.unwrapTokenBasedSyntheticPassword( + result = mSpManager.unwrapTokenBasedSyntheticPassword( getGateKeeperService(), tokenHandle, token, userId); if (result.authToken == null) { Slog.w(TAG, "Invalid escrow token supplied"); @@ -2508,8 +2584,9 @@ public class LockSettingsService extends ILockSettings.Stub { setLockCredentialWithAuthTokenLocked(credential, type, result.authToken, requestedQuality, userId); mSpManager.destroyPasswordBasedSyntheticPassword(oldHandle, userId); - return true; } + onAuthTokenKnownForUser(userId, result.authToken); + return true; } @Override @@ -2529,6 +2606,7 @@ public class LockSettingsService extends ILockSettings.Stub { } } unlockUser(userId, null, authResult.authToken.deriveDiskEncryptionKey()); + onAuthTokenKnownForUser(userId, authResult.authToken); } @Override @@ -2610,6 +2688,8 @@ public class LockSettingsService extends ILockSettings.Stub { private class DeviceProvisionedObserver extends ContentObserver { private final Uri mDeviceProvisionedUri = Settings.Global.getUriFor( Settings.Global.DEVICE_PROVISIONED); + private final Uri mUserSetupCompleteUri = Settings.Secure.getUriFor( + Settings.Secure.USER_SETUP_COMPLETE); private boolean mRegistered; @@ -2627,6 +2707,8 @@ public class LockSettingsService extends ILockSettings.Stub { reportDeviceSetupComplete(); clearFrpCredentialIfOwnerNotSecure(); } + } else if (mUserSetupCompleteUri.equals(uri)) { + cleanSpCache(); } } @@ -2678,6 +2760,8 @@ public class LockSettingsService extends ILockSettings.Stub { if (register) { mContext.getContentResolver().registerContentObserver(mDeviceProvisionedUri, false, this); + mContext.getContentResolver().registerContentObserver(mUserSetupCompleteUri, + false, this, UserHandle.USER_ALL); } else { mContext.getContentResolver().unregisterContentObserver(this); } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java index 5fe11b11fa5a..38745f6cfd28 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java @@ -16,15 +16,14 @@ package com.android.server.locksettings.recoverablekeystore; -import static android.security.keystore.RecoveryMetadata.TYPE_LOCKSCREEN; +import static android.security.keystore.KeychainProtectionParameter.TYPE_LOCKSCREEN; -import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.security.keystore.KeyDerivationParams; -import android.security.keystore.EntryRecoveryData; -import android.security.keystore.RecoveryData; -import android.security.keystore.RecoveryMetadata; +import android.security.keystore.KeychainProtectionParameter; +import android.security.keystore.KeychainSnapshot; +import android.security.keystore.WrappedApplicationKey; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -251,12 +250,12 @@ public class KeySyncTask implements Runnable { } // TODO: store raw data in RecoveryServiceMetadataEntry and generate Parcelables later // TODO: use Builder. - RecoveryMetadata metadata = new RecoveryMetadata( + KeychainProtectionParameter metadata = new KeychainProtectionParameter( /*userSecretType=*/ TYPE_LOCKSCREEN, /*lockScreenUiFormat=*/ getUiFormat(mCredentialType, mCredential), /*keyDerivationParams=*/ KeyDerivationParams.createSha256Params(salt), /*secret=*/ new byte[0]); - ArrayList<RecoveryMetadata> metadataList = new ArrayList<>(); + ArrayList<KeychainProtectionParameter> metadataList = new ArrayList<>(); metadataList.add(metadata); int snapshotVersion = incrementSnapshotVersion(recoveryAgentUid); @@ -265,7 +264,7 @@ public class KeySyncTask implements Runnable { mRecoverableKeyStoreDb.setShouldCreateSnapshot(mUserId, recoveryAgentUid, false); // TODO: use Builder. - mRecoverySnapshotStorage.put(recoveryAgentUid, new RecoveryData( + mRecoverySnapshotStorage.put(recoveryAgentUid, new KeychainSnapshot( snapshotVersion, /*recoveryMetadata=*/ metadataList, /*applicationKeyBlobs=*/ createApplicationKeyEntries(encryptedApplicationKeys), @@ -308,7 +307,7 @@ public class KeySyncTask implements Runnable { */ private boolean shoudCreateSnapshot(int recoveryAgentUid) { int[] types = mRecoverableKeyStoreDb.getRecoverySecretTypes(mUserId, recoveryAgentUid); - if (!ArrayUtils.contains(types, RecoveryMetadata.TYPE_LOCKSCREEN)) { + if (!ArrayUtils.contains(types, KeychainProtectionParameter.TYPE_LOCKSCREEN)) { // Only lockscreen type is supported. // We will need to pass extra argument to KeySyncTask to support custom pass phrase. return false; @@ -331,14 +330,14 @@ public class KeySyncTask implements Runnable { * @return The format - either pattern, pin, or password. */ @VisibleForTesting - @RecoveryMetadata.LockScreenUiFormat static int getUiFormat( + @KeychainProtectionParameter.LockScreenUiFormat static int getUiFormat( int credentialType, String credential) { if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) { - return RecoveryMetadata.TYPE_PATTERN; + return KeychainProtectionParameter.TYPE_PATTERN; } else if (isPin(credential)) { - return RecoveryMetadata.TYPE_PIN; + return KeychainProtectionParameter.TYPE_PIN; } else { - return RecoveryMetadata.TYPE_PASSWORD; + return KeychainProtectionParameter.TYPE_PASSWORD; } } @@ -401,12 +400,12 @@ public class KeySyncTask implements Runnable { return keyGenerator.generateKey(); } - private static List<EntryRecoveryData> createApplicationKeyEntries( + private static List<WrappedApplicationKey> createApplicationKeyEntries( Map<String, byte[]> encryptedApplicationKeys) { - ArrayList<EntryRecoveryData> keyEntries = new ArrayList<>(); + ArrayList<WrappedApplicationKey> keyEntries = new ArrayList<>(); for (String alias : encryptedApplicationKeys.keySet()) { keyEntries.add( - new EntryRecoveryData( + new WrappedApplicationKey( alias, encryptedApplicationKeys.get(alias))); } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java index 7658178d43da..f14af4b52a2f 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -34,9 +34,9 @@ import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserHandle; -import android.security.keystore.EntryRecoveryData; -import android.security.keystore.RecoveryData; -import android.security.keystore.RecoveryMetadata; +import android.security.keystore.KeychainProtectionParameter; +import android.security.keystore.KeychainSnapshot; +import android.security.keystore.WrappedApplicationKey; import android.security.keystore.RecoveryManager; import android.util.Log; @@ -45,7 +45,6 @@ import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKe import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage; import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage; -import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.KeyStoreException; import java.security.KeyFactory; @@ -171,11 +170,12 @@ public class RecoverableKeyStoreManager { * @return recovery data * @hide */ - public @NonNull RecoveryData getRecoveryData(@NonNull byte[] account) + public @NonNull + KeychainSnapshot getRecoveryData(@NonNull byte[] account) throws RemoteException { checkRecoverKeyStorePermission(); int uid = Binder.getCallingUid(); - RecoveryData snapshot = mSnapshotStorage.get(uid); + KeychainSnapshot snapshot = mSnapshotStorage.get(uid); if (snapshot == null) { throw new ServiceSpecificException(ERROR_NO_SNAPSHOT_PENDING); } @@ -257,7 +257,7 @@ public class RecoverableKeyStoreManager { * @hide */ public void setRecoverySecretTypes( - @NonNull @RecoveryMetadata.UserSecretType int[] secretTypes) + @NonNull @KeychainProtectionParameter.UserSecretType int[] secretTypes) throws RemoteException { checkRecoverKeyStorePermission(); int userId = UserHandle.getCallingUserId(); @@ -292,9 +292,9 @@ public class RecoverableKeyStoreManager { } public void recoverySecretAvailable( - @NonNull RecoveryMetadata recoverySecret) throws RemoteException { + @NonNull KeychainProtectionParameter recoverySecret) throws RemoteException { int uid = Binder.getCallingUid(); - if (recoverySecret.getLockScreenUiFormat() == RecoveryMetadata.TYPE_LOCKSCREEN) { + if (recoverySecret.getLockScreenUiFormat() == KeychainProtectionParameter.TYPE_LOCKSCREEN) { throw new SecurityException( "Caller " + uid + " is not allowed to set lock screen secret"); } @@ -320,13 +320,13 @@ public class RecoverableKeyStoreManager { @NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams, @NonNull byte[] vaultChallenge, - @NonNull List<RecoveryMetadata> secrets) + @NonNull List<KeychainProtectionParameter> secrets) throws RemoteException { checkRecoverKeyStorePermission(); int uid = Binder.getCallingUid(); if (secrets.size() != 1) { - throw new UnsupportedOperationException("Only a single RecoveryMetadata is supported"); + throw new UnsupportedOperationException("Only a single KeychainProtectionParameter is supported"); } PublicKey publicKey; @@ -384,7 +384,7 @@ public class RecoverableKeyStoreManager { public Map<String, byte[]> recoverKeys( @NonNull String sessionId, @NonNull byte[] encryptedRecoveryKey, - @NonNull List<EntryRecoveryData> applicationKeys) + @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException { checkRecoverKeyStorePermission(); int uid = Binder.getCallingUid(); @@ -474,9 +474,9 @@ public class RecoverableKeyStoreManager { */ private Map<String, byte[]> recoverApplicationKeys( @NonNull byte[] recoveryKey, - @NonNull List<EntryRecoveryData> applicationKeys) throws RemoteException { + @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException { HashMap<String, byte[]> keyMaterialByAlias = new HashMap<>(); - for (EntryRecoveryData applicationKey : applicationKeys) { + for (WrappedApplicationKey applicationKey : applicationKeys) { String alias = applicationKey.getAlias(); byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial(); diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java index eb2da8077b36..8bba2122787d 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java @@ -404,7 +404,7 @@ public class RecoverableKeyStoreDb { /** * Updates the list of user secret types used for end-to-end encryption. * If no secret types are set, recovery snapshot will not be created. - * See {@code RecoveryMetadata} + * See {@code KeychainProtectionParameter} * * @param userId The userId of the profile the application is running under. * @param uid The uid of the application. diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java index 158b1e34d019..62bb41ec48aa 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java @@ -17,7 +17,7 @@ package com.android.server.locksettings.recoverablekeystore.storage; import android.annotation.Nullable; -import android.security.keystore.RecoveryData; +import android.security.keystore.KeychainSnapshot; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; @@ -34,12 +34,12 @@ import com.android.internal.annotations.GuardedBy; */ public class RecoverySnapshotStorage { @GuardedBy("this") - private final SparseArray<RecoveryData> mSnapshotByUid = new SparseArray<>(); + private final SparseArray<KeychainSnapshot> mSnapshotByUid = new SparseArray<>(); /** * Sets the latest {@code snapshot} for the recovery agent {@code uid}. */ - public synchronized void put(int uid, RecoveryData snapshot) { + public synchronized void put(int uid, KeychainSnapshot snapshot) { mSnapshotByUid.put(uid, snapshot); } @@ -47,7 +47,7 @@ public class RecoverySnapshotStorage { * Returns the latest snapshot for the recovery agent {@code uid}, or null if none exists. */ @Nullable - public synchronized RecoveryData get(int uid) { + public synchronized KeychainSnapshot get(int uid) { return mSnapshotByUid.get(uid); } diff --git a/services/core/java/com/android/server/media/MediaUpdateService.java b/services/core/java/com/android/server/media/MediaUpdateService.java new file mode 100644 index 000000000000..016d062db726 --- /dev/null +++ b/services/core/java/com/android/server/media/MediaUpdateService.java @@ -0,0 +1,151 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media; + +import android.content.Context; +import android.content.Intent; +import android.media.IMediaResourceMonitor; +import android.os.Binder; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.Log; +import android.util.Slog; +import com.android.server.SystemService; + +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.IBinder; +import android.os.PatternMatcher; +import android.os.ServiceManager; +import android.media.IMediaExtractorUpdateService; + +import java.lang.Exception; + +/** This class provides a system service that manages media framework updates. */ +public class MediaUpdateService extends SystemService { + private static final String TAG = "MediaUpdateService"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final String MEDIA_UPDATE_PACKAGE_NAME = "com.android.media.update"; + private static final String EXTRACTOR_UPDATE_SERVICE_NAME = "media.extractor.update"; + + private IMediaExtractorUpdateService mMediaExtractorUpdateService; + + public MediaUpdateService(Context context) { + super(context); + } + + @Override + public void onStart() { + // TODO: Uncomment below once sepolicy change is landed. + /* + if ("userdebug".equals(android.os.Build.TYPE) || "eng".equals(android.os.Build.TYPE)) { + connect(); + registerBroadcastReceiver(); + } + */ + } + + private void connect() { + IBinder binder = ServiceManager.getService(EXTRACTOR_UPDATE_SERVICE_NAME); + if (binder != null) { + try { + binder.linkToDeath(new IBinder.DeathRecipient() { + @Override + public void binderDied() { + Slog.w(TAG, "mediaextractor died; reconnecting"); + mMediaExtractorUpdateService = null; + connect(); + } + }, 0); + } catch (Exception e) { + binder = null; + } + } + if (binder != null) { + mMediaExtractorUpdateService = IMediaExtractorUpdateService.Stub.asInterface(binder); + packageStateChanged(); + } else { + Slog.w(TAG, EXTRACTOR_UPDATE_SERVICE_NAME + " not found."); + } + } + + private void registerBroadcastReceiver() { + BroadcastReceiver updateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM) + != UserHandle.USER_SYSTEM) { + // Ignore broadcast for non system users. We don't want to update system + // service multiple times. + return; + } + switch (intent.getAction()) { + case Intent.ACTION_PACKAGE_REMOVED: + if (intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)) { + // The existing package is updated. Will be handled with the + // following ACTION_PACKAGE_ADDED case. + return; + } + packageStateChanged(); + break; + case Intent.ACTION_PACKAGE_CHANGED: + packageStateChanged(); + break; + case Intent.ACTION_PACKAGE_ADDED: + packageStateChanged(); + break; + } + } + }; + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addDataScheme("package"); + filter.addDataSchemeSpecificPart(MEDIA_UPDATE_PACKAGE_NAME, PatternMatcher.PATTERN_LITERAL); + + getContext().registerReceiverAsUser(updateReceiver, UserHandle.ALL, filter, + null /* broadcast permission */, null /* handler */); + } + + private void packageStateChanged() { + ApplicationInfo packageInfo = null; + boolean pluginsAvailable = false; + try { + packageInfo = getContext().getPackageManager().getApplicationInfo( + MEDIA_UPDATE_PACKAGE_NAME, PackageManager.MATCH_SYSTEM_ONLY); + pluginsAvailable = packageInfo.enabled; + } catch (Exception e) { + Slog.v(TAG, "package '" + MEDIA_UPDATE_PACKAGE_NAME + "' not installed"); + } + loadExtractorPlugins( + (packageInfo != null && pluginsAvailable) ? packageInfo.sourceDir : ""); + } + + private void loadExtractorPlugins(String apkPath) { + try { + if (mMediaExtractorUpdateService != null) { + mMediaExtractorUpdateService.loadPlugins(apkPath); + } + } catch (Exception e) { + Slog.w(TAG, "Error in loadPlugins", e); + } + } +} diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 37e6ae98cb0a..42093e87e455 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -71,6 +71,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -98,6 +99,9 @@ abstract public class ManagedServices { static final String ATT_APPROVED_LIST = "approved"; static final String ATT_USER_ID = "user"; static final String ATT_IS_PRIMARY = "primary"; + static final String ATT_VERSION = "version"; + + static final int DB_VERSION = 1; static final int APPROVAL_BY_PACKAGE = 0; static final int APPROVAL_BY_COMPONENT = 1; @@ -295,6 +299,8 @@ abstract public class ManagedServices { public void writeXml(XmlSerializer out, boolean forBackup) throws IOException { out.startTag(null, getConfig().xmlTag); + out.attribute(null, ATT_VERSION, String.valueOf(DB_VERSION)); + if (forBackup) { trimApprovedListsAccordingToInstalledServices(); } @@ -336,6 +342,14 @@ abstract public class ManagedServices { public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException { + // upgrade xml + int xmlVersion = XmlUtils.readIntAttribute(parser, ATT_VERSION, 0); + final List<UserInfo> activeUsers = mUm.getUsers(true); + for (UserInfo userInfo : activeUsers) { + upgradeXml(xmlVersion, userInfo.getUserHandle().getIdentifier()); + } + + // read grants int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { String tag = parser.getName(); @@ -346,6 +360,7 @@ abstract public class ManagedServices { if (type == XmlPullParser.START_TAG) { if (TAG_MANAGED_SERVICES.equals(tag)) { Slog.i(TAG, "Read " + mConfig.caption + " permissions from xml"); + final String approved = XmlUtils.readStringAttribute(parser, ATT_APPROVED_LIST); final int userId = XmlUtils.readIntAttribute(parser, ATT_USER_ID, 0); final boolean isPrimary = @@ -360,6 +375,8 @@ abstract public class ManagedServices { rebindServices(false); } + protected void upgradeXml(final int xmlVersion, final int userId) {} + private void loadAllowedComponentsFromSettings() { UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); @@ -379,7 +396,7 @@ abstract public class ManagedServices { Slog.d(TAG, "Done loading approved values from settings"); } - private void addApprovedList(String approved, int userId, boolean isPrimary) { + protected void addApprovedList(String approved, int userId, boolean isPrimary) { if (TextUtils.isEmpty(approved)) { approved = ""; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 575c44d15209..2f6618f56a80 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -434,6 +434,7 @@ public class NotificationManagerService extends SystemService { } } } + String defaultDndAccess = getContext().getResources().getString( com.android.internal.R.string.config_defaultDndAccessPackages); if (defaultListenerAccess != null) { @@ -446,6 +447,29 @@ public class NotificationManagerService extends SystemService { } } } + + readDefaultAssistant(userId); + } + + protected void readDefaultAssistant(int userId) { + String defaultAssistantAccess = getContext().getResources().getString( + com.android.internal.R.string.config_defaultAssistantAccessPackage); + if (defaultAssistantAccess != null) { + // Gather all notification assistant components for candidate pkg. There should + // only be one + Set<ComponentName> approvedAssistants = + mAssistants.queryPackageForServices(defaultAssistantAccess, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); + for (ComponentName cn : approvedAssistants) { + try { + getBinderService().setNotificationAssistantAccessGrantedForUser(cn, + userId, true); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } } void readPolicyXml(InputStream stream, boolean forRestore) @@ -1155,6 +1179,7 @@ public class NotificationManagerService extends SystemService { } } + @VisibleForTesting void clearNotifications() { mEnqueuedNotifications.clear(); mNotificationList.clear(); @@ -1374,7 +1399,8 @@ public class NotificationManagerService extends SystemService { AppGlobals.getPackageManager(), getContext().getPackageManager(), getLocalService(LightsManager.class), new NotificationListeners(AppGlobals.getPackageManager()), - new NotificationAssistants(AppGlobals.getPackageManager()), + new NotificationAssistants(getContext(), mNotificationLock, mUserProfiles, + AppGlobals.getPackageManager()), new ConditionProviders(getContext(), mUserProfiles, AppGlobals.getPackageManager()), null, snoozeHelper, new NotificationUsageStats(getContext()), new AtomicFile(new File(systemDir, "notification_policy.xml")), @@ -2917,12 +2943,34 @@ public class NotificationManagerService extends SystemService { } } + /** + * Sets the notification policy. Apps that target API levels below + * {@link android.os.Build.VERSION_CODES#P} cannot make DND silence + * {@link Policy#PRIORITY_CATEGORY_ALARMS} or + * {@link Policy#PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER} + */ @Override public void setNotificationPolicy(String pkg, Policy policy) { enforcePolicyAccess(pkg, "setNotificationPolicy"); final long identity = Binder.clearCallingIdentity(); try { + final ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(pkg, + 0, UserHandle.getUserId(MY_UID)); + + if (applicationInfo.targetSdkVersion <= Build.VERSION_CODES.O_MR1) { + Policy currPolicy = mZenModeHelper.getNotificationPolicy(); + + int priorityCategories = policy.priorityCategories + | (currPolicy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS) + | (currPolicy.priorityCategories & + Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER); + policy = new Policy(priorityCategories, + policy.priorityCallSenders, policy.priorityMessageSenders, + policy.suppressedVisualEffects); + } + mZenModeHelper.setNotificationPolicy(policy); + } catch (RemoteException e) { } finally { Binder.restoreCallingIdentity(identity); } @@ -5531,8 +5579,9 @@ public class NotificationManagerService extends SystemService { public class NotificationAssistants extends ManagedServices { static final String TAG_ENABLED_NOTIFICATION_ASSISTANTS = "enabled_assistants"; - public NotificationAssistants(IPackageManager pm) { - super(getContext(), mNotificationLock, mUserProfiles, pm); + public NotificationAssistants(Context context, Object lock, UserProfiles up, + IPackageManager pm) { + super(context, lock, up, pm); } @Override @@ -5637,6 +5686,14 @@ public class NotificationManagerService extends SystemService { public boolean isEnabled() { return !getServices().isEmpty(); } + + protected void upgradeXml(final int xmlVersion, final int userId) { + if (xmlVersion == 0) { + // one time approval of the OOB assistant + Slog.d(TAG, "Approving default notification assistant for user " + userId); + readDefaultAssistant(userId); + } + } } public class NotificationListeners extends ManagedServices { @@ -6050,7 +6107,7 @@ public class NotificationManagerService extends SystemService { public static final String USAGE = "help\n" + "allow_listener COMPONENT [user_id]\n" + "disallow_listener COMPONENT [user_id]\n" - + "set_assistant COMPONENT\n" + + "allow_assistant COMPONENT\n" + "remove_assistant COMPONENT\n" + "allow_dnd PACKAGE\n" + "disallow_dnd PACKAGE"; diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index be66fe22ca1b..4b3758d2e293 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -281,13 +281,14 @@ public class Installer extends SystemService { public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet, int dexoptNeeded, @Nullable String outputPath, int dexFlags, String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries, - @Nullable String seInfo, boolean downgrade) + @Nullable String seInfo, boolean downgrade, int targetSdkVersion) throws InstallerException { assertValidInstructionSet(instructionSet); if (!checkBeforeRemote()) return; try { mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath, - dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo, downgrade); + dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo, downgrade, + targetSdkVersion); } catch (Exception e) { throw InstallerException.from(e); } diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index b06b58385cc3..1717b3d1a5f7 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -652,6 +652,7 @@ public class LauncherAppsService extends SystemService { activityInfo.name.equals(component.getClassName())) { // Found an activity with category launcher that matches // this component so ok to launch. + launchIntent.setPackage(null); launchIntent.setComponent(component); mContext.startActivityAsUser(launchIntent, opts, user); return; diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java index 03f662a49762..03950119ea13 100644 --- a/services/core/java/com/android/server/pm/OtaDexoptService.java +++ b/services/core/java/com/android/server/pm/OtaDexoptService.java @@ -260,12 +260,13 @@ public class OtaDexoptService extends IOtaDexopt.Stub { public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet, int dexoptNeeded, @Nullable String outputPath, int dexFlags, String compilerFilter, @Nullable String volumeUuid, - @Nullable String sharedLibraries, @Nullable String seInfo, boolean downgrade) + @Nullable String sharedLibraries, @Nullable String seInfo, boolean downgrade, + int targetSdkVersion) throws InstallerException { final StringBuilder builder = new StringBuilder(); - // The version. Right now it's 3. - builder.append("3 "); + // The version. Right now it's 4. + builder.append("4 "); builder.append("dexopt"); @@ -281,6 +282,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub { encodeParameter(builder, sharedLibraries); encodeParameter(builder, seInfo); encodeParameter(builder, downgrade); + encodeParameter(builder, targetSdkVersion); commands.add(builder.toString()); } diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 730a9fdad420..91df87b713e8 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -274,7 +274,7 @@ public class PackageDexOptimizer { // primary dex files. mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags, compilerFilter, pkg.volumeUuid, classLoaderContext, pkg.applicationInfo.seInfo, - false /* downgrade*/); + false /* downgrade*/, pkg.applicationInfo.targetSdkVersion); if (packageStats != null) { long endTime = System.currentTimeMillis(); @@ -395,7 +395,7 @@ public class PackageDexOptimizer { mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0, /*oatDir*/ null, dexoptFlags, compilerFilter, info.volumeUuid, classLoaderContext, info.seInfoUser, - options.isDowngrade()); + options.isDowngrade(), info.targetSdkVersion); } return DEX_OPT_PERFORMED; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 1e2f2b2df863..711ea13a7c15 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2697,6 +2697,12 @@ public class PackageManagerService extends IPackageManager.Stub mSettings.getDisabledSystemPkgLPr(ps.name); if (disabledPs.codePath == null || !disabledPs.codePath.exists() || disabledPs.pkg == null) { +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "Possibly deleted app: " + ps.dumpState_temp() + + "; path: " + (disabledPs.codePath == null ? "<<NULL>>":disabledPs.codePath) + + "; pkg: " + (disabledPs.pkg==null?"<<NULL>>":disabledPs.pkg.toString())); +} possiblyDeletedUpdatedSystemApps.add(ps.name); } } @@ -2748,6 +2754,10 @@ public class PackageManagerService extends IPackageManager.Stub for (String deletedAppName : possiblyDeletedUpdatedSystemApps) { PackageParser.Package deletedPkg = mPackages.get(deletedAppName); mSettings.removeDisabledSystemPackageLPw(deletedAppName); +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "remove update; name: " + deletedAppName + ", exists? " + (deletedPkg != null)); +} final String msg; if (deletedPkg == null) { // should have found an update, but, we didn't; remove everything @@ -8311,6 +8321,8 @@ public class PackageManagerService extends IPackageManager.Stub return scannedPkg; } + // Temporary to catch potential issues with refactoring + private static boolean REFACTOR_DEBUG = true; /** * Adds a new package to the internal data structures during platform initialization. * <p>After adding, the package is known to the system and available for querying. @@ -8351,6 +8363,10 @@ public class PackageManagerService extends IPackageManager.Stub synchronized (mPackages) { renamedPkgName = mSettings.getRenamedPackageLPr(pkg.mRealPackage); final String realPkgName = getRealPackageName(pkg, renamedPkgName); +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "Add pkg: " + pkg.packageName + (realPkgName==null?"":", realName: " + realPkgName)); +} if (realPkgName != null) { ensurePackageRenamed(pkg, renamedPkgName); } @@ -8365,6 +8381,12 @@ public class PackageManagerService extends IPackageManager.Stub if (DEBUG_INSTALL && isSystemPkgUpdated) { Slog.d(TAG, "updatedPkg = " + disabledPkgSetting); } +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "SSP? " + scanSystemPartition + + ", exists? " + pkgAlreadyExists + (pkgAlreadyExists?" "+pkgSetting.toString():"") + + ", upgraded? " + isSystemPkgUpdated + (isSystemPkgUpdated?" "+disabledPkgSetting.toString():"")); +} final SharedUserSetting sharedUserSetting = (pkg.mSharedUserId != null) ? mSettings.getSharedUserLPw(pkg.mSharedUserId, @@ -8376,6 +8398,12 @@ public class PackageManagerService extends IPackageManager.Stub Log.d(TAG, "Shared UserID " + pkg.mSharedUserId + " (uid=" + sharedUserSetting.userId + "):" + " packages=" + sharedUserSetting.packages); +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "Shared UserID " + pkg.mSharedUserId + + " (uid=" + sharedUserSetting.userId + "):" + + " packages=" + sharedUserSetting.packages); +} } if (scanSystemPartition) { @@ -8384,6 +8412,10 @@ public class PackageManagerService extends IPackageManager.Stub // version on /data, cycle through all of its children packages and // remove children that are no longer defined. if (isSystemPkgUpdated) { +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "Disable child packages"); +} final int scannedChildCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; final int disabledChildCount = disabledPkgSetting.childPackageNames != null @@ -8395,11 +8427,19 @@ public class PackageManagerService extends IPackageManager.Stub for (int j = 0; j < scannedChildCount; j++) { PackageParser.Package childPkg = pkg.childPackages.get(j); if (childPkg.packageName.equals(disabledChildPackageName)) { +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "Ignore " + disabledChildPackageName); +} disabledPackageAvailable = true; break; } } if (!disabledPackageAvailable) { +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "Disable " + disabledChildPackageName); +} mSettings.removeDisabledSystemPackageLPw(disabledChildPackageName); } } @@ -8408,17 +8448,44 @@ public class PackageManagerService extends IPackageManager.Stub disabledPkgSetting /* pkgSetting */, null /* disabledPkgSetting */, null /* originalPkgSetting */, null, parseFlags, scanFlags, (pkg == mPlatformPackage), user); +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "Scan disabled system package"); +Slog.e("TODD", + "Pre: " + request.pkgSetting.dumpState_temp()); +} +final ScanResult result = scanPackageOnlyLI(request, mFactoryTest, -1L); +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "Post: " + (result.success?result.pkgSetting.dumpState_temp():"FAILED scan")); +} } } } final boolean newPkgChangedPaths = pkgAlreadyExists && !pkgSetting.codePathString.equals(pkg.codePath); +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "paths changed? " + newPkgChangedPaths + + "; old: " + pkg.codePath + + ", new: " + (pkgSetting==null?"<<NULL>>":pkgSetting.codePathString)); +} final boolean newPkgVersionGreater = pkgAlreadyExists && pkg.getLongVersionCode() > pkgSetting.versionCode; +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "version greater? " + newPkgVersionGreater + + "; old: " + pkg.getLongVersionCode() + + ", new: " + (pkgSetting==null?"<<NULL>>":pkgSetting.versionCode)); +} final boolean isSystemPkgBetter = scanSystemPartition && isSystemPkgUpdated && newPkgChangedPaths && newPkgVersionGreater; +if (REFACTOR_DEBUG) { + Slog.e("TODD", + "system better? " + isSystemPkgBetter); +} if (isSystemPkgBetter) { // The version of the application on /system is greater than the version on // /data. Switch back to the application on /system. @@ -8434,6 +8501,13 @@ public class PackageManagerService extends IPackageManager.Stub + " name: " + pkgSetting.name + "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode() + "; " + pkgSetting.codePathString + " --> " + pkg.codePath); +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "System package changed;" + + " name: " + pkgSetting.name + + "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode() + + "; " + pkgSetting.codePathString + " --> " + pkg.codePath); +} final InstallArgs args = createInstallArgsForExisting( packageFlagsToInstallFlags(pkgSetting), pkgSetting.codePathString, @@ -8445,6 +8519,10 @@ public class PackageManagerService extends IPackageManager.Stub } if (scanSystemPartition && isSystemPkgUpdated && !isSystemPkgBetter) { +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "THROW exception; system pkg version not good enough"); +} // The version of the application on the /system partition is less than or // equal to the version on the /data partition. Throw an exception and use // the application already installed on the /data partition. @@ -8470,6 +8548,11 @@ public class PackageManagerService extends IPackageManager.Stub logCriticalInfo(Log.WARN, "System package signature mismatch;" + " name: " + pkgSetting.name); +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "System package signature mismatch;" + + " name: " + pkgSetting.name); +} try (PackageFreezer freezer = freezePackage(pkg.packageName, "scanPackageInternalLI")) { deletePackageLIF(pkg.packageName, null, true, null, 0, null, false, null); @@ -8484,6 +8567,13 @@ public class PackageManagerService extends IPackageManager.Stub + " name: " + pkgSetting.name + "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode() + "; " + pkgSetting.codePathString + " --> " + pkg.codePath); +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "System package enabled;" + + " name: " + pkgSetting.name + + "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode() + + "; " + pkgSetting.codePathString + " --> " + pkg.codePath); +} InstallArgs args = createInstallArgsForExisting( packageFlagsToInstallFlags(pkgSetting), pkgSetting.codePathString, pkgSetting.resourcePathString, getAppDexInstructionSets(pkgSetting)); @@ -8500,13 +8590,35 @@ public class PackageManagerService extends IPackageManager.Stub + " name: " + pkgSetting.name + "; old: " + pkgSetting.codePathString + " @ " + pkgSetting.versionCode + "; new: " + pkg.codePath + " @ " + pkg.codePath); +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "System package disabled;" + + " name: " + pkgSetting.name + + "; old: " + pkgSetting.codePathString + " @ " + pkgSetting.versionCode + + "; new: " + pkg.codePath + " @ " + pkg.codePath); +} } } +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "Scan package"); +Slog.e("TODD", + "Pre: " + (pkgSetting==null?"<<NONE>>":pkgSetting.dumpState_temp())); +} final PackageParser.Package scannedPkg = scanPackageNewLI(pkg, parseFlags, scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user); +if (REFACTOR_DEBUG) { +pkgSetting = mSettings.getPackageLPr(pkg.packageName); +Slog.e("TODD", + "Post: " + (pkgSetting==null?"<<NONE>>":pkgSetting.dumpState_temp())); +} if (shouldHideSystemApp) { +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "Disable package: " + pkg.packageName); +} synchronized (mPackages) { mSettings.disableSystemPackageLPw(pkg.packageName, true); } @@ -10652,6 +10764,26 @@ public class PackageManagerService extends IPackageManager.Stub } } } + + // Verify that packages sharing a user with a privileged app are marked as privileged. + if (!pkg.isPrivileged() && (pkg.mSharedUserId != null)) { + SharedUserSetting sharedUserSetting = null; + try { + sharedUserSetting = mSettings.getSharedUserLPw(pkg.mSharedUserId, 0, 0, false); + } catch (PackageManagerException ignore) {} + if (sharedUserSetting != null && sharedUserSetting.isPrivileged()) { + // Exempt SharedUsers signed with the platform key. + PackageSetting platformPkgSetting = mSettings.mPackages.get("android"); + if ((platformPkgSetting.signatures.mSignatures != null) && + (compareSignatures(platformPkgSetting.signatures.mSignatures, + pkg.mSigningDetails.signatures) != PackageManager.SIGNATURE_MATCH)) { + throw new PackageManagerException("Apps that share a user with a " + + "privileged app must themselves be marked as privileged. " + + pkg.packageName + " shares privileged user " + + pkg.mSharedUserId + "."); + } + } + } } } diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 2b91b7d38b4f..2a2430c07980 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -97,6 +97,35 @@ public final class PackageSetting extends PackageSettingBase { + " " + name + "/" + appId + "}"; } + // Temporary to catch potential issues with refactoring + public String dumpState_temp() { + String flags = ""; + flags += ((pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 ? "U" : ""); + flags += ((pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0 ? "S" : ""); + if ("".equals(flags)) { + flags = "-"; + } + String privFlags = ""; + privFlags += ((pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0 ? "P" : ""); + privFlags += ((pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0 ? "O" : ""); + privFlags += ((pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0 ? "V" : ""); + if ("".equals(privFlags)) { + privFlags = "-"; + } + return "PackageSetting{" + + Integer.toHexString(System.identityHashCode(this)) + + " " + name + (realName == null ? "" : "("+realName+")") + "/" + appId + (sharedUser==null?"":" u:" + sharedUser.name+"("+sharedUserId+")") + + ", ver:" + versionCode + + ", path: " + codePath + + ", pABI: " + primaryCpuAbiString + + ", sABI: " + secondaryCpuAbiString + + ", oABI: " + cpuAbiOverrideString + + ", flags: " + flags + + ", privFlags: " + privFlags + + ", pkg: " + (pkg == null ? "<<NULL>>" : pkg.dumpState_temp()) + + "}"; + } + public void copyFrom(PackageSetting orig) { super.copyFrom(orig); doCopy(orig); diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java index 877da144730f..244613180d00 100644 --- a/services/core/java/com/android/server/pm/SharedUserSetting.java +++ b/services/core/java/com/android/server/pm/SharedUserSetting.java @@ -17,6 +17,7 @@ package com.android.server.pm; import android.annotation.Nullable; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageParser; import android.service.pm.PackageServiceDumpProto; import android.util.ArraySet; @@ -102,4 +103,8 @@ public final class SharedUserSetting extends SettingBase { } return pkgList; } + + public boolean isPrivileged() { + return (this.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0; + } } diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 50690cb0f33e..cc07d82554e9 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -25,12 +25,14 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; import android.os.Binder; import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.os.UserManagerInternal; +import android.provider.Settings; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.util.Log; @@ -119,7 +121,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_AIRPLANE_MODE, UserManager.DISALLOW_CONFIG_BRIGHTNESS, UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, - UserManager.DISALLOW_AMBIENT_DISPLAY + UserManager.DISALLOW_AMBIENT_DISPLAY, + UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT }); /** @@ -542,6 +545,22 @@ public class UserRestrictionsUtils { android.provider.Settings.Global.SAFE_BOOT_DISALLOWED, newValue ? 1 : 0); break; + case UserManager.DISALLOW_AIRPLANE_MODE: + if (newValue) { + final boolean airplaneMode = Settings.Global.getInt( + context.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) == 1; + if (airplaneMode) { + android.provider.Settings.Global.putInt( + context.getContentResolver(), + android.provider.Settings.Global.AIRPLANE_MODE_ON, 0); + // Post the intent. + Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.putExtra("state", 0); + context.sendBroadcastAsUser(intent, UserHandle.ALL); + } + } + break; } } finally { Binder.restoreCallingIdentity(id); diff --git a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java index d6281c51b3fa..a517d6d1a99e 100644 --- a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java +++ b/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java @@ -111,6 +111,7 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { final long ident = mInjector.clearCallingIdentity(); try { + launchIntent.setPackage(null); launchIntent.setComponent(component); mContext.startActivityAsUser(launchIntent, ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(), user); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 0470607e26c9..88e4270ad07e 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -48,7 +48,6 @@ import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW; -import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON; import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN; @@ -65,6 +64,9 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_SYSTEM_WINDOW; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; import static android.view.WindowManager.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND; @@ -267,6 +269,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IShortcutService; +import com.android.internal.policy.KeyguardDismissCallback; import com.android.internal.policy.PhoneWindow; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.ArrayUtils; @@ -4209,20 +4212,23 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (isKeyguardShowingAndNotOccluded()) { // don't launch home if keyguard showing return; - } - - if (!mKeyguardOccluded && mKeyguardDelegate.isInputRestricted()) { + } else if (mKeyguardOccluded && mKeyguardDelegate.isShowing()) { + mKeyguardDelegate.dismiss(new KeyguardDismissCallback() { + @Override + public void onDismissSucceeded() throws RemoteException { + mHandler.post(() -> { + startDockOrHome(true /*fromHomeKey*/, awakenFromDreams); + }); + } + }, null /* message */); + return; + } else if (!mKeyguardOccluded && mKeyguardDelegate.isInputRestricted()) { // when in keyguard restricted mode, must first verify unlock // before launching home mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() { @Override public void onKeyguardExitResult(boolean success) { if (success) { - try { - ActivityManager.getService().stopAppSwitches(); - } catch (RemoteException e) { - } - sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY); startDockOrHome(true /*fromHomeKey*/, awakenFromDreams); } } @@ -4232,11 +4238,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } // no keyguard stuff to worry about, just launch home! - try { - ActivityManager.getService().stopAppSwitches(); - } catch (RemoteException e) { - } if (mRecentsVisible) { + try { + ActivityManager.getService().stopAppSwitches(); + } catch (RemoteException e) {} + // Hide Recents and notify it to launch Home if (awakenFromDreams) { awakenDreams(); @@ -4244,7 +4250,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { hideRecentApps(false, true); } else { // Otherwise, just launch Home - sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY); startDockOrHome(true /*fromHomeKey*/, awakenFromDreams); } } @@ -4867,7 +4872,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { final int type = attrs.type; final int fl = PolicyControl.getWindowFlags(win, attrs); - final long fl2 = attrs.flags2; final int pfl = attrs.privateFlags; final int sim = attrs.softInputMode; final int requestedSysUiFl = PolicyControl.getSystemUiVisibility(win, null); @@ -4889,12 +4893,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { final int adjust = sim & SOFT_INPUT_MASK_ADJUST; final boolean requestedFullscreen = (fl & FLAG_FULLSCREEN) != 0 - || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0 - || (requestedSysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0; + || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0; final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN; final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR; - final boolean layoutInCutout = (fl2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0; sf.set(displayFrames.mStable); @@ -5054,9 +5056,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { // moving from a window that is not hiding the status bar to one that is. cf.set(displayFrames.mRestricted); } - if (requestedFullscreen && !layoutInCutout) { - pf.intersectUnchecked(displayFrames.mDisplayCutoutSafe); - } applyStableConstraints(sysUiFl, fl, cf, displayFrames); if (adjust != SOFT_INPUT_ADJUST_NOTHING) { vf.set(displayFrames.mCurrent); @@ -5142,9 +5141,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { of.set(displayFrames.mUnrestricted); df.set(displayFrames.mUnrestricted); pf.set(displayFrames.mUnrestricted); - if (requestedFullscreen && !layoutInCutout) { - pf.intersectUnchecked(displayFrames.mDisplayCutoutSafe); - } } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0) { of.set(displayFrames.mRestricted); df.set(displayFrames.mRestricted); @@ -5219,15 +5215,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - // Ensure that windows that did not request to be laid out in the cutout don't get laid - // out there. - if (!layoutInCutout) { + final int cutoutMode = attrs.layoutInDisplayCutoutMode; + // Ensure that windows with a DEFAULT or NEVER display cutout mode are laid out in + // the cutout safe zone. + if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) { final Rect displayCutoutSafeExceptMaybeTop = mTmpRect; displayCutoutSafeExceptMaybeTop.set(displayFrames.mDisplayCutoutSafe); - if (layoutInScreen && layoutInsetDecor) { + if (layoutInScreen && layoutInsetDecor && !requestedFullscreen + && cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) { // At the top we have the status bar, so apps that are - // LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR already expect that there's an inset - // there and we don't need to exclude the window from that area. + // LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR but not FULLSCREEN + // already expect that there's an inset there and we don't need to exclude + // the window from that area. displayCutoutSafeExceptMaybeTop.top = Integer.MIN_VALUE; } pf.intersectUnchecked(displayCutoutSafeExceptMaybeTop); @@ -5656,11 +5655,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public boolean allowAppAnimationsLw() { - if (mShowingDream) { - // If keyguard or dreams is currently visible, no reason to animate behind it. - return false; - } - return true; + return !mShowingDream; } @Override @@ -7637,6 +7632,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } void startDockOrHome(boolean fromHomeKey, boolean awakenFromDreams) { + try { + ActivityManager.getService().stopAppSwitches(); + } catch (RemoteException e) {} + sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY); + if (awakenFromDreams) { awakenDreams(); } @@ -7676,11 +7676,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } if (false) { // This code always brings home to the front. - try { - ActivityManager.getService().stopAppSwitches(); - } catch (RemoteException e) { - } - sendCloseSystemWindows(); startDockOrHome(false /*fromHomeKey*/, true /* awakenFromDreams */); } else { // This code brings home to the front or, if it is already diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 64a280c1cbe7..c05dd2af3e5b 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -138,11 +138,6 @@ import java.lang.annotation.RetentionPolicy; * </dl> */ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { - // Navigation bar position values - int NAV_BAR_LEFT = 1 << 0; - int NAV_BAR_RIGHT = 1 << 1; - int NAV_BAR_BOTTOM = 1 << 2; - @Retention(SOURCE) @IntDef({NAV_BAR_LEFT, NAV_BAR_RIGHT, NAV_BAR_BOTTOM}) @interface NavigationBarPosition {} @@ -1211,13 +1206,12 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { /** * Return true if it is okay to perform animations for an app transition - * that is about to occur. You may return false for this if, for example, - * the lock screen is currently displayed so the switch should happen + * that is about to occur. You may return false for this if, for example, + * the dream window is currently displayed so the switch should happen * immediately. */ public boolean allowAppAnimationsLw(); - /** * A new window has been focused. */ diff --git a/services/core/java/com/android/server/policy/WindowOrientationListener.java b/services/core/java/com/android/server/policy/WindowOrientationListener.java index 1b5a5216909d..48a196dfcff1 100644 --- a/services/core/java/com/android/server/policy/WindowOrientationListener.java +++ b/services/core/java/com/android/server/policy/WindowOrientationListener.java @@ -32,6 +32,7 @@ import android.util.proto.ProtoOutputStream; import android.view.Surface; import java.io.PrintWriter; +import java.util.List; /** * A special helper class used by the WindowManager @@ -90,7 +91,28 @@ public abstract class WindowOrientationListener { mHandler = handler; mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); mRate = rate; - mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_DEVICE_ORIENTATION); + List<Sensor> l = mSensorManager.getSensorList(Sensor.TYPE_DEVICE_ORIENTATION); + Sensor wakeUpDeviceOrientationSensor = null; + Sensor nonWakeUpDeviceOrientationSensor = null; + /** + * Prefer the wakeup form of the sensor if implemented. + * It's OK to look for just two types of this sensor and use + * the last found. Typical devices will only have one sensor of + * this type. + */ + for (Sensor s : l) { + if (s.isWakeUpSensor()) { + wakeUpDeviceOrientationSensor = s; + } else { + nonWakeUpDeviceOrientationSensor = s; + } + } + + if (wakeUpDeviceOrientationSensor != null) { + mSensor = wakeUpDeviceOrientationSensor; + } else { + mSensor = nonWakeUpDeviceOrientationSensor; + } if (mSensor != null) { mOrientationJudge = new OrientationSensorJudge(); diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 163b1600e0bd..659253f9d603 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -346,12 +346,12 @@ final class AccessibilityController { final boolean magnifying = mMagnifedViewport.isMagnifyingLocked(); if (magnifying) { switch (transition) { - case AppTransition.TRANSIT_ACTIVITY_OPEN: - case AppTransition.TRANSIT_TASK_OPEN: - case AppTransition.TRANSIT_TASK_TO_FRONT: - case AppTransition.TRANSIT_WALLPAPER_OPEN: - case AppTransition.TRANSIT_WALLPAPER_CLOSE: - case AppTransition.TRANSIT_WALLPAPER_INTRA_OPEN: { + case WindowManager.TRANSIT_ACTIVITY_OPEN: + case WindowManager.TRANSIT_TASK_OPEN: + case WindowManager.TRANSIT_TASK_TO_FRONT: + case WindowManager.TRANSIT_WALLPAPER_OPEN: + case WindowManager.TRANSIT_WALLPAPER_CLOSE: + case WindowManager.TRANSIT_WALLPAPER_INTRA_OPEN: { mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_USER_CONTEXT_CHANGED); } } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index d4b437a5a25e..fc7ad09d5182 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -16,6 +16,28 @@ package com.android.server.wm; +import static android.view.WindowManager.LayoutParams; +import static android.view.WindowManager.TRANSIT_ACTIVITY_CLOSE; +import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN; +import static android.view.WindowManager.TRANSIT_ACTIVITY_RELAUNCH; +import static android.view.WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; +import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER; +import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; +import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; +import static android.view.WindowManager.TRANSIT_NONE; +import static android.view.WindowManager.TRANSIT_TASK_CLOSE; +import static android.view.WindowManager.TRANSIT_TASK_OPEN; +import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND; +import static android.view.WindowManager.TRANSIT_TASK_TO_BACK; +import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT; +import static android.view.WindowManager.TRANSIT_UNSET; +import static android.view.WindowManager.TRANSIT_WALLPAPER_CLOSE; +import static android.view.WindowManager.TRANSIT_WALLPAPER_INTRA_CLOSE; +import static android.view.WindowManager.TRANSIT_WALLPAPER_INTRA_OPEN; +import static android.view.WindowManager.TRANSIT_WALLPAPER_OPEN; import static com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation; import static com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation; import static com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation; @@ -75,9 +97,11 @@ import android.util.proto.ProtoOutputStream; import android.view.AppTransitionAnimationSpec; import android.view.DisplayListCanvas; import android.view.IAppTransitionAnimationSpecsFuture; +import android.view.RemoteAnimationAdapter; import android.view.RenderNode; import android.view.ThreadedRenderer; -import android.view.WindowManager; +import android.view.WindowManager.TransitionFlags; +import android.view.WindowManager.TransitionType; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; @@ -109,63 +133,6 @@ public class AppTransition implements Dump { private static final String TAG = TAG_WITH_CLASS_NAME ? "AppTransition" : TAG_WM; private static final int CLIP_REVEAL_TRANSLATION_Y_DP = 8; - /** Not set up for a transition. */ - public static final int TRANSIT_UNSET = -1; - /** No animation for transition. */ - public static final int TRANSIT_NONE = 0; - /** A window in a new activity is being opened on top of an existing one in the same task. */ - public static final int TRANSIT_ACTIVITY_OPEN = 6; - /** The window in the top-most activity is being closed to reveal the - * previous activity in the same task. */ - public static final int TRANSIT_ACTIVITY_CLOSE = 7; - /** A window in a new task is being opened on top of an existing one - * in another activity's task. */ - public static final int TRANSIT_TASK_OPEN = 8; - /** A window in the top-most activity is being closed to reveal the - * previous activity in a different task. */ - public static final int TRANSIT_TASK_CLOSE = 9; - /** A window in an existing task is being displayed on top of an existing one - * in another activity's task. */ - public static final int TRANSIT_TASK_TO_FRONT = 10; - /** A window in an existing task is being put below all other tasks. */ - public static final int TRANSIT_TASK_TO_BACK = 11; - /** A window in a new activity that doesn't have a wallpaper is being opened on top of one that - * does, effectively closing the wallpaper. */ - public static final int TRANSIT_WALLPAPER_CLOSE = 12; - /** A window in a new activity that does have a wallpaper is being opened on one that didn't, - * effectively opening the wallpaper. */ - public static final int TRANSIT_WALLPAPER_OPEN = 13; - /** A window in a new activity is being opened on top of an existing one, and both are on top - * of the wallpaper. */ - public static final int TRANSIT_WALLPAPER_INTRA_OPEN = 14; - /** The window in the top-most activity is being closed to reveal the previous activity, and - * both are on top of the wallpaper. */ - public static final int TRANSIT_WALLPAPER_INTRA_CLOSE = 15; - /** A window in a new task is being opened behind an existing one in another activity's task. - * The new window will show briefly and then be gone. */ - public static final int TRANSIT_TASK_OPEN_BEHIND = 16; - /** A window in a task is being animated in-place. */ - public static final int TRANSIT_TASK_IN_PLACE = 17; - /** An activity is being relaunched (e.g. due to configuration change). */ - public static final int TRANSIT_ACTIVITY_RELAUNCH = 18; - /** A task is being docked from recents. */ - public static final int TRANSIT_DOCK_TASK_FROM_RECENTS = 19; - /** Keyguard is going away */ - public static final int TRANSIT_KEYGUARD_GOING_AWAY = 20; - /** Keyguard is going away with showing an activity behind that requests wallpaper */ - public static final int TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER = 21; - /** Keyguard is being occluded */ - public static final int TRANSIT_KEYGUARD_OCCLUDE = 22; - /** Keyguard is being unoccluded */ - public static final int TRANSIT_KEYGUARD_UNOCCLUDE = 23; - - /** Transition flag: Keyguard is going away, but keeping the notification shade open */ - public static final int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE = 0x1; - /** Transition flag: Keyguard is going away, but doesn't want an animation for it */ - public static final int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION = 0x2; - /** Transition flag: Keyguard is going away while it was showing the system wallpaper. */ - public static final int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER = 0x4; - /** Fraction of animation at which the recents thumbnail stays completely transparent */ private static final float RECENTS_THUMBNAIL_FADEIN_FRACTION = 0.5f; /** Fraction of animation at which the recents thumbnail becomes completely transparent */ @@ -191,8 +158,8 @@ public class AppTransition implements Dump { private final Context mContext; private final WindowManagerService mService; - private int mNextAppTransition = TRANSIT_UNSET; - private int mNextAppTransitionFlags = 0; + private @TransitionType int mNextAppTransition = TRANSIT_UNSET; + private @TransitionFlags int mNextAppTransitionFlags = 0; private int mLastUsedAppTransition = TRANSIT_UNSET; private String mLastOpeningApp; private String mLastClosingApp; @@ -213,6 +180,8 @@ public class AppTransition implements Dump { * }. */ private static final int NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS = 9; + private static final int NEXT_TRANSIT_TYPE_REMOTE = 10; + private int mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE; // These are the possible states for the enter/exit activities during a thumbnail transition @@ -275,6 +244,8 @@ public class AppTransition implements Dump { private final boolean mGridLayoutRecentsEnabled; private final boolean mLowRamRecentsEnabled; + private RemoteAnimationController mRemoteAnimationController; + AppTransition(Context context, WindowManagerService service) { mContext = context; mService = service; @@ -321,11 +292,11 @@ public class AppTransition implements Dump { return mNextAppTransition != TRANSIT_UNSET; } - boolean isTransitionEqual(int transit) { + boolean isTransitionEqual(@TransitionType int transit) { return mNextAppTransition == transit; } - int getAppTransition() { + @TransitionType int getAppTransition() { return mNextAppTransition; } @@ -454,6 +425,9 @@ public class AppTransition implements Dump { app.startDelayingAnimationStart(); } } + if (mRemoteAnimationController != null) { + mRemoteAnimationController.goodToGo(); + } return redoLayout; } @@ -468,6 +442,7 @@ public class AppTransition implements Dump { mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE; mNextAppTransitionPackage = null; mNextAppTransitionAnimationsSpecs.clear(); + mRemoteAnimationController = null; mNextAppTransitionAnimationsSpecsFuture = null; mDefaultNextAppTransitionAnimationSpec = null; mAnimationFinishedCallback = null; @@ -476,7 +451,7 @@ public class AppTransition implements Dump { void freeze() { final int transit = mNextAppTransition; - setAppTransition(AppTransition.TRANSIT_UNSET, 0 /* flags */); + setAppTransition(TRANSIT_UNSET, 0 /* flags */); clear(); setReady(); notifyAppTransitionCancelledLocked(transit); @@ -531,7 +506,7 @@ public class AppTransition implements Dump { return redoLayout; } - private AttributeCache.Entry getCachedAnimations(WindowManager.LayoutParams lp) { + private AttributeCache.Entry getCachedAnimations(LayoutParams lp) { if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: layout params pkg=" + (lp != null ? lp.packageName : null) + " resId=0x" + (lp != null ? Integer.toHexString(lp.windowAnimations) : null)); @@ -567,7 +542,7 @@ public class AppTransition implements Dump { return null; } - Animation loadAnimationAttr(WindowManager.LayoutParams lp, int animAttr) { + Animation loadAnimationAttr(LayoutParams lp, int animAttr) { int anim = 0; Context context = mContext; if (animAttr >= 0) { @@ -583,7 +558,7 @@ public class AppTransition implements Dump { return null; } - Animation loadAnimationRes(WindowManager.LayoutParams lp, int resId) { + Animation loadAnimationRes(LayoutParams lp, int resId) { Context context = mContext; if (resId >= 0) { AttributeCache.Entry ent = getCachedAnimations(lp); @@ -1551,6 +1526,10 @@ public class AppTransition implements Dump { && mNextAppTransition != TRANSIT_KEYGUARD_GOING_AWAY; } + RemoteAnimationController getRemoteAnimationController() { + return mRemoteAnimationController; + } + /** * * @param frame These are the bounds of the window when it finishes the animation. This is where @@ -1572,7 +1551,7 @@ public class AppTransition implements Dump { * to the recents thumbnail and hence need to account for the surface being * bigger. */ - Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, int uiMode, + Animation loadAnimation(LayoutParams lp, int transit, boolean enter, int uiMode, int orientation, Rect frame, Rect displayFrame, Rect insets, @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean isVoiceInteraction, boolean freeform, int taskId) { @@ -1788,7 +1767,7 @@ public class AppTransition implements Dump { void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim, IRemoteCallback startedCallback) { - if (isTransitionSet()) { + if (canOverridePendingAppTransition()) { clear(); mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM; mNextAppTransitionPackage = packageName; @@ -1796,14 +1775,12 @@ public class AppTransition implements Dump { mNextAppTransitionExit = exitAnim; postAnimationCallback(); mNextAppTransitionCallback = startedCallback; - } else { - postAnimationCallback(); } } void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth, int startHeight) { - if (isTransitionSet()) { + if (canOverridePendingAppTransition()) { clear(); mNextAppTransitionType = NEXT_TRANSIT_TYPE_SCALE_UP; putDefaultNextAppTransitionCoordinates(startX, startY, startWidth, startHeight, null); @@ -1813,7 +1790,7 @@ public class AppTransition implements Dump { void overridePendingAppTransitionClipReveal(int startX, int startY, int startWidth, int startHeight) { - if (isTransitionSet()) { + if (canOverridePendingAppTransition()) { clear(); mNextAppTransitionType = NEXT_TRANSIT_TYPE_CLIP_REVEAL; putDefaultNextAppTransitionCoordinates(startX, startY, startWidth, startHeight, null); @@ -1823,7 +1800,7 @@ public class AppTransition implements Dump { void overridePendingAppTransitionThumb(GraphicBuffer srcThumb, int startX, int startY, IRemoteCallback startedCallback, boolean scaleUp) { - if (isTransitionSet()) { + if (canOverridePendingAppTransition()) { clear(); mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP : NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN; @@ -1831,14 +1808,12 @@ public class AppTransition implements Dump { putDefaultNextAppTransitionCoordinates(startX, startY, 0, 0, srcThumb); postAnimationCallback(); mNextAppTransitionCallback = startedCallback; - } else { - postAnimationCallback(); } } void overridePendingAppTransitionAspectScaledThumb(GraphicBuffer srcThumb, int startX, int startY, int targetWidth, int targetHeight, IRemoteCallback startedCallback, boolean scaleUp) { - if (isTransitionSet()) { + if (canOverridePendingAppTransition()) { clear(); mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN; @@ -1847,15 +1822,13 @@ public class AppTransition implements Dump { srcThumb); postAnimationCallback(); mNextAppTransitionCallback = startedCallback; - } else { - postAnimationCallback(); } } - public void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs, + void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs, IRemoteCallback onAnimationStartedCallback, IRemoteCallback onAnimationFinishedCallback, boolean scaleUp) { - if (isTransitionSet()) { + if (canOverridePendingAppTransition()) { clear(); mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN; @@ -1878,15 +1851,13 @@ public class AppTransition implements Dump { postAnimationCallback(); mNextAppTransitionCallback = onAnimationStartedCallback; mAnimationFinishedCallback = onAnimationFinishedCallback; - } else { - postAnimationCallback(); } } void overridePendingAppTransitionMultiThumbFuture( IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback callback, boolean scaleUp) { - if (isTransitionSet()) { + if (canOverridePendingAppTransition()) { clear(); mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN; @@ -1896,14 +1867,21 @@ public class AppTransition implements Dump { } } - void overrideInPlaceAppTransition(String packageName, int anim) { + void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter) { if (isTransitionSet()) { clear(); + mNextAppTransitionType = NEXT_TRANSIT_TYPE_REMOTE; + mRemoteAnimationController = new RemoteAnimationController(mService, + remoteAnimationAdapter, mService.mH); + } + } + + void overrideInPlaceAppTransition(String packageName, int anim) { + if (canOverridePendingAppTransition()) { + clear(); mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE; mNextAppTransitionPackage = packageName; mNextAppTransitionInPlace = anim; - } else { - postAnimationCallback(); } } @@ -1911,13 +1889,18 @@ public class AppTransition implements Dump { * @see {@link #NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS} */ void overridePendingAppTransitionStartCrossProfileApps() { - if (isTransitionSet()) { + if (canOverridePendingAppTransition()) { clear(); mNextAppTransitionType = NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS; postAnimationCallback(); } } + private boolean canOverridePendingAppTransition() { + // Remote animations always take precedence + return isTransitionSet() && mNextAppTransitionType != NEXT_TRANSIT_TYPE_REMOTE; + } + /** * If a future is set for the app transition specs, fetch it in another thread. */ @@ -2140,8 +2123,8 @@ public class AppTransition implements Dump { * @return true if transition is not running and should not be skipped, false if transition is * already running */ - boolean prepareAppTransitionLocked(int transit, boolean alwaysKeepCurrent, int flags, - boolean forceOverride) { + boolean prepareAppTransitionLocked(@TransitionType int transit, boolean alwaysKeepCurrent, + @TransitionFlags int flags, boolean forceOverride) { if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Prepare app transition:" + " transit=" + appTransitionToString(transit) + " " + this diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java index ae9f28bcb6a6..e9efd4ec9e3d 100644 --- a/services/core/java/com/android/server/wm/AppWindowContainerController.java +++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java @@ -19,8 +19,8 @@ package com.android.server.wm; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; -import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS; -import static com.android.server.wm.AppTransition.TRANSIT_UNSET; +import static android.view.WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS; +import static android.view.WindowManager.TRANSIT_UNSET; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION; @@ -32,7 +32,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.app.ActivityManager.TaskSnapshot; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; -import android.graphics.Rect; import android.os.Debug; import android.os.Handler; import android.os.IBinder; @@ -40,6 +39,8 @@ import android.os.Looper; import android.os.Message; import android.util.Slog; import android.view.IApplicationToken; +import android.view.RemoteAnimationDefinition; +import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; import com.android.server.AttributeCache; @@ -393,7 +394,7 @@ public class AppWindowContainerController wtoken.mEnteringAnimation = false; } if (mService.mAppTransition.getAppTransition() - == AppTransition.TRANSIT_TASK_OPEN_BEHIND) { + == WindowManager.TRANSIT_TASK_OPEN_BEHIND) { // We're launchingBehind, add the launching activity to mOpeningApps. final WindowState win = mService.getDefaultDisplayContentLocked().findFocusedWindow(); @@ -695,6 +696,17 @@ public class AppWindowContainerController } } + public void registerRemoteAnimations(RemoteAnimationDefinition definition) { + synchronized (mWindowMap) { + if (mContainer == null) { + Slog.w(TAG_WM, "Attempted to register remote animations with non-existing app" + + " token: " + mToken); + return; + } + mContainer.registerRemoteAnimations(definition); + } + } + void reportStartingWindowDrawn() { mHandler.sendMessage(mHandler.obtainMessage(H.NOTIFY_STARTING_WINDOW_DRAWN)); } diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 2c84cb1a4d6f..261065f05889 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -34,7 +34,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; -import static com.android.server.wm.AppTransition.TRANSIT_UNSET; +import static android.view.WindowManager.TRANSIT_UNSET; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS; @@ -91,6 +91,7 @@ import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; import android.view.IApplicationToken; +import android.view.RemoteAnimationDefinition; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.WindowManager; @@ -106,7 +107,6 @@ import com.android.server.wm.WindowManagerService.H; import java.io.PrintWriter; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.LinkedList; class AppTokenList extends ArrayList<AppWindowToken> { } @@ -250,6 +250,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree private final Point mTmpPoint = new Point(); private final Rect mTmpRect = new Rect(); + private RemoteAnimationDefinition mRemoteAnimationDefinition; AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen, @@ -378,7 +379,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree // Reset the state of mHiddenSetFromTransferredStartingWindow since visibility is actually // been set by the app now. mHiddenSetFromTransferredStartingWindow = false; - setClientHidden(!visible); // Allow for state changes and animation to be applied if: // * token is transitioning visibility state @@ -394,7 +394,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree boolean runningAppAnimation = false; - if (transit != AppTransition.TRANSIT_UNSET) { + if (transit != WindowManager.TRANSIT_UNSET) { if (applyAnimationLocked(lp, transit, visible, isVoiceInteraction)) { delayed = runningAppAnimation = true; } @@ -463,6 +463,12 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree mService.mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(token); } + // Update the client visibility if we are not running an animation. Otherwise, we'll + // update client visibility state in onAnimationFinished. + if (!visible && !delayed) { + setClientHidden(true); + } + // If we are hidden but there is no delay needed we immediately // apply the Surface transaction so that the ActivityManager // can have some guarantee on the Surface state following @@ -1427,13 +1433,13 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree mLetterbox.setDimensions(mPendingTransaction, getParent().getBounds(), w.mFrame); } else if (mLetterbox != null) { final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + // Make sure we have a transaction here, in case we're called outside of a transaction. + // This does not use mPendingTransaction, because SurfaceAnimator uses a + // global transaction in onAnimationEnd. SurfaceControl.openTransaction(); try { mLetterbox.hide(t); } finally { - // TODO: This should use pendingTransaction eventually, but right now things - // happening on the animation finished callback are happening on the global - // transaction. SurfaceControl.mergeToGlobalTransaction(t); SurfaceControl.closeTransaction(); } @@ -1611,27 +1617,37 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree // different animation is running. Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AWT#applyAnimationLocked"); if (okToAnimate()) { - final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction); - if (a != null) { - final TaskStack stack = getStack(); - mTmpPoint.set(0, 0); - mTmpRect.setEmpty(); - if (stack != null) { - stack.getRelativePosition(mTmpPoint); - stack.getBounds(mTmpRect); - mTmpRect.offsetTo(0, 0); - } - final AnimationAdapter adapter = new LocalAnimationAdapter( - new WindowAnimationSpec(a, mTmpPoint, mTmpRect, - mService.mAppTransition.canSkipFirstFrame(), - mService.mAppTransition.getAppStackClipMode()), - mService.mSurfaceAnimationRunner); - if (a.getZAdjustment() == Animation.ZORDER_TOP) { - mNeedsZBoost = true; + final AnimationAdapter adapter; + final TaskStack stack = getStack(); + mTmpPoint.set(0, 0); + mTmpRect.setEmpty(); + if (stack != null) { + stack.getRelativePosition(mTmpPoint); + stack.getBounds(mTmpRect); + mTmpRect.offsetTo(0, 0); + } + if (mService.mAppTransition.getRemoteAnimationController() != null) { + adapter = mService.mAppTransition.getRemoteAnimationController() + .createAnimationAdapter(this, mTmpPoint, mTmpRect); + } else { + final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction); + if (a != null) { + adapter = new LocalAnimationAdapter( + new WindowAnimationSpec(a, mTmpPoint, mTmpRect, + mService.mAppTransition.canSkipFirstFrame(), + mService.mAppTransition.getAppStackClipMode()), + mService.mSurfaceAnimationRunner); + if (a.getZAdjustment() == Animation.ZORDER_TOP) { + mNeedsZBoost = true; + } + mTransit = transit; + mTransitFlags = mService.mAppTransition.getTransitFlags(); + } else { + adapter = null; } + } + if (adapter != null) { startAnimation(getPendingTransaction(), adapter, !isVisible()); - mTransit = transit; - mTransitFlags = mService.mAppTransition.getTransitFlags(); } } else { cancelAnimation(); @@ -1754,6 +1770,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree "AppWindowToken"); clearThumbnail(); + setClientHidden(isHidden()); if (mService.mInputMethodTarget != null && mService.mInputMethodTarget.mAppToken == this) { getDisplayContent().computeImeTarget(true /* updateImeTarget */); @@ -1884,6 +1901,14 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree mThumbnail = null; } + void registerRemoteAnimations(RemoteAnimationDefinition definition) { + mRemoteAnimationDefinition = definition; + } + + RemoteAnimationDefinition getRemoteAnimationDefinition() { + return mRemoteAnimationDefinition; + } + @Override void dump(PrintWriter pw, String prefix, boolean dumpAll) { super.dump(pw, prefix, dumpAll); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index a8e00dd53526..ef486614fd2a 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -63,7 +63,7 @@ import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_C import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates; -import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_UNOCCLUDE; +import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY; diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java index 03c0768cdec0..0cf8d2f85b62 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java @@ -29,7 +29,7 @@ import static android.view.WindowManager.DOCKED_RIGHT; import static android.view.WindowManager.DOCKED_TOP; import static com.android.server.wm.AppTransition.DEFAULT_APP_TRANSITION_DURATION; import static com.android.server.wm.AppTransition.TOUCH_RESPONSE_INTERPOLATOR; -import static com.android.server.wm.AppTransition.TRANSIT_NONE; +import static android.view.WindowManager.TRANSIT_NONE; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.H.NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED; diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java index 28b4c1dbeb8e..0171b56ffc47 100644 --- a/services/core/java/com/android/server/wm/DragDropController.java +++ b/services/core/java/com/android/server/wm/DragDropController.java @@ -136,7 +136,7 @@ class DragDropController { outSurface.copyFrom(surface); final IBinder winBinder = window.asBinder(); IBinder token = new Binder(); - mDragState = new DragState(mService, token, surface, flags, winBinder); + mDragState = new DragState(mService, this, token, surface, flags, winBinder); mDragState.mPid = callerPid; mDragState.mUid = callerUid; mDragState.mOriginalAlpha = alpha; diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index b9f437af20e4..1ac9b88749a7 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -42,6 +42,7 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.os.IUserManager; +import android.os.UserManagerInternal; import android.util.Slog; import android.view.Display; import android.view.DragEvent; @@ -55,6 +56,7 @@ import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import com.android.internal.view.IDragAndDropPermissions; +import com.android.server.LocalServices; import com.android.server.input.InputApplicationHandle; import com.android.server.input.InputWindowHandle; @@ -116,10 +118,10 @@ class DragState { private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f); private Point mDisplaySize = new Point(); - DragState(WindowManagerService service, IBinder token, SurfaceControl surface, - int flags, IBinder localWin) { + DragState(WindowManagerService service, DragDropController controller, IBinder token, + SurfaceControl surface, int flags, IBinder localWin) { mService = service; - mDragDropController = service.mDragDropController; + mDragDropController = controller; mToken = token; mSurfaceControl = surface; mFlags = flags; @@ -318,15 +320,9 @@ class DragState { mSourceUserId = UserHandle.getUserId(mUid); - final IUserManager userManager = - (IUserManager) ServiceManager.getService(Context.USER_SERVICE); - try { - mCrossProfileCopyAllowed = !userManager.getUserRestrictions(mSourceUserId).getBoolean( - UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE); - } catch (RemoteException e) { - Slog.e(TAG_WM, "Remote Exception calling UserManager: " + e); - mCrossProfileCopyAllowed = false; - } + final UserManagerInternal userManager = LocalServices.getService(UserManagerInternal.class); + mCrossProfileCopyAllowed = !userManager.getUserRestriction( + mSourceUserId, UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE); if (DEBUG_DRAG) { Slog.d(TAG_WM, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")"); @@ -534,7 +530,8 @@ class DragState { final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid()); final DragAndDropPermissionsHandler dragAndDropPermissions; - if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0) { + if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0 + && mData != null) { dragAndDropPermissions = new DragAndDropPermissionsHandler( mData, mUid, @@ -546,7 +543,9 @@ class DragState { dragAndDropPermissions = null; } if (mSourceUserId != targetUserId){ - mData.fixUris(mSourceUserId); + if (mData != null) { + mData.fixUris(mSourceUserId); + } } final int myPid = Process.myPid(); final IBinder token = touchedWin.mClient.asBinder(); diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java index 69cbe4607cf1..62519e12d5d9 100644 --- a/services/core/java/com/android/server/wm/PinnedStackController.java +++ b/services/core/java/com/android/server/wm/PinnedStackController.java @@ -360,14 +360,19 @@ class PinnedStackController { * Sets the Ime state and height. */ void setAdjustedForIme(boolean adjustedForIme, int imeHeight) { - // Return early if there is no state change - if (mIsImeShowing == adjustedForIme && mImeHeight == imeHeight) { + // Due to the order of callbacks from the system, we may receive an ime height even when + // {@param adjustedForIme} is false, and also a zero height when {@param adjustedForIme} + // is true. Instead, ensure that the ime state changes with the height and if the ime is + // showing, then the height is non-zero. + final boolean imeShowing = adjustedForIme && imeHeight > 0; + imeHeight = imeShowing ? imeHeight : 0; + if (imeShowing == mIsImeShowing && imeHeight == mImeHeight) { return; } - mIsImeShowing = adjustedForIme; + mIsImeShowing = imeShowing; mImeHeight = imeHeight; - notifyImeVisibilityChanged(adjustedForIme, imeHeight); + notifyImeVisibilityChanged(imeShowing, imeHeight); notifyMovementBoundsChanged(true /* fromImeAdjustment */); } diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java new file mode 100644 index 000000000000..688b4ffcee4b --- /dev/null +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; + +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Handler; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Slog; +import android.view.IRemoteAnimationFinishedCallback; +import android.view.IRemoteAnimationFinishedCallback.Stub; +import android.view.RemoteAnimationAdapter; +import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; + +import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; + +import java.util.ArrayList; + +/** + * Helper class to run app animations in a remote process. + */ +class RemoteAnimationController { + private static final String TAG = TAG_WITH_CLASS_NAME ? "RemoteAnimationController" : TAG_WM; + private static final long TIMEOUT_MS = 2000; + + private final WindowManagerService mService; + private final RemoteAnimationAdapter mRemoteAnimationAdapter; + private final ArrayList<RemoteAnimationAdapterWrapper> mPendingAnimations = new ArrayList<>(); + private final Rect mTmpRect = new Rect(); + private final Handler mHandler; + + private final IRemoteAnimationFinishedCallback mFinishedCallback = new Stub() { + @Override + public void onAnimationFinished() throws RemoteException { + RemoteAnimationController.this.onAnimationFinished(); + } + }; + + private final Runnable mTimeoutRunnable = () -> { + onAnimationFinished(); + invokeAnimationCancelled(); + }; + + RemoteAnimationController(WindowManagerService service, + RemoteAnimationAdapter remoteAnimationAdapter, Handler handler) { + mService = service; + mRemoteAnimationAdapter = remoteAnimationAdapter; + mHandler = handler; + } + + /** + * Creates an animation for each individual {@link AppWindowToken}. + * + * @param appWindowToken The app to animate. + * @param position The position app bounds, in screen coordinates. + * @param stackBounds The stack bounds of the app. + * @return The adapter to be run on the app. + */ + AnimationAdapter createAnimationAdapter(AppWindowToken appWindowToken, Point position, + Rect stackBounds) { + final RemoteAnimationAdapterWrapper adapter = new RemoteAnimationAdapterWrapper( + appWindowToken, position, stackBounds); + mPendingAnimations.add(adapter); + return adapter; + } + + /** + * Called when the transition is ready to be started, and all leashes have been set up. + */ + void goodToGo() { + mHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MS); + try { + mRemoteAnimationAdapter.getRunner().onAnimationStart(createAnimations(), + mFinishedCallback); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to start remote animation", e); + onAnimationFinished(); + } + } + + private RemoteAnimationTarget[] createAnimations() { + final RemoteAnimationTarget[] result = new RemoteAnimationTarget[mPendingAnimations.size()]; + for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { + result[i] = mPendingAnimations.get(i).createRemoteAppAnimation(); + } + return result; + } + + private void onAnimationFinished() { + mHandler.removeCallbacks(mTimeoutRunnable); + synchronized (mService.mWindowMap) { + mService.openSurfaceTransaction(); + try { + for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { + final RemoteAnimationAdapterWrapper adapter = mPendingAnimations.get(i); + adapter.mCapturedFinishCallback.onAnimationFinished(adapter); + } + } finally { + mService.closeSurfaceTransaction("RemoteAnimationController#finished"); + } + } + } + + private void invokeAnimationCancelled() { + try { + mRemoteAnimationAdapter.getRunner().onAnimationCancelled(); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to notify cancel", e); + } + } + + private class RemoteAnimationAdapterWrapper implements AnimationAdapter { + + private final AppWindowToken mAppWindowToken; + private SurfaceControl mCapturedLeash; + private OnAnimationFinishedCallback mCapturedFinishCallback; + private final Point mPosition = new Point(); + private final Rect mStackBounds = new Rect(); + + RemoteAnimationAdapterWrapper(AppWindowToken appWindowToken, Point position, + Rect stackBounds) { + mAppWindowToken = appWindowToken; + mPosition.set(position.x, position.y); + mStackBounds.set(stackBounds); + } + + RemoteAnimationTarget createRemoteAppAnimation() { + return new RemoteAnimationTarget(mAppWindowToken.getTask().mTaskId, getMode(), + mCapturedLeash, !mAppWindowToken.fillsParent(), + mAppWindowToken.findMainWindow().mWinAnimator.mLastClipRect, + mAppWindowToken.getPrefixOrderIndex(), mPosition, mStackBounds); + } + + private int getMode() { + if (mService.mOpeningApps.contains(mAppWindowToken)) { + return RemoteAnimationTarget.MODE_OPENING; + } else { + return RemoteAnimationTarget.MODE_CLOSING; + } + } + + @Override + public boolean getDetachWallpaper() { + return false; + } + + @Override + public int getBackgroundColor() { + return 0; + } + + @Override + public void startAnimation(SurfaceControl animationLeash, Transaction t, + OnAnimationFinishedCallback finishCallback) { + + // Restore z-layering, position and stack crop until client has a chance to modify it. + t.setLayer(animationLeash, mAppWindowToken.getPrefixOrderIndex()); + t.setPosition(animationLeash, mPosition.x, mPosition.y); + mTmpRect.set(mStackBounds); + mTmpRect.offsetTo(0, 0); + t.setWindowCrop(animationLeash, mTmpRect); + mCapturedLeash = animationLeash; + mCapturedFinishCallback = finishCallback; + } + + @Override + public void onAnimationCancelled(SurfaceControl animationLeash) { + mPendingAnimations.remove(this); + if (mPendingAnimations.isEmpty()) { + mHandler.removeCallbacks(mTimeoutRunnable); + invokeAnimationCancelled(); + } + } + + @Override + public long getDurationHint() { + return mRemoteAnimationAdapter.getDuration(); + } + + @Override + public long getStatusBarTransitionsStartTime() { + return SystemClock.uptimeMillis() + + mRemoteAnimationAdapter.getStatusBarTransitionDelay(); + } + } +} diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index ac0919d93166..1218d3bc1b9b 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -25,7 +25,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; -import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 7b3353375914..0fedc795c16c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; import static android.Manifest.permission.MANAGE_APP_TOKENS; import static android.Manifest.permission.READ_FRAME_BUFFER; import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS; @@ -87,7 +88,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREEN_ON; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT; @@ -210,17 +210,18 @@ import android.view.KeyEvent; import android.view.MagnificationSpec; import android.view.MotionEvent; import android.view.PointerIcon; +import android.view.RemoteAnimationAdapter; import android.view.Surface; import android.view.SurfaceControl; -import android.view.SurfaceControl.Builder; import android.view.SurfaceSession; import android.view.View; import android.view.WindowContentFrameStats; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; +import android.view.WindowManager.TransitionFlags; +import android.view.WindowManager.TransitionType; import android.view.WindowManagerGlobal; import android.view.WindowManagerPolicyConstants.PointerEventListener; -import android.view.animation.Animation; import android.view.inputmethod.InputMethodManagerInternal; import com.android.internal.R; @@ -1540,7 +1541,7 @@ public class WindowManagerService extends IWindowManager.Stub // We treat this as if this activity was opening, so we can trigger the app transition // animation and piggy-back on existing transition animation infrastructure. mOpeningApps.add(atoken); - prepareAppTransition(AppTransition.TRANSIT_ACTIVITY_RELAUNCH, ALWAYS_KEEP_CURRENT); + prepareAppTransition(WindowManager.TRANSIT_ACTIVITY_RELAUNCH, ALWAYS_KEEP_CURRENT); mAppTransition.overridePendingAppTransitionClipReveal(frame.left, frame.top, frame.width(), frame.height()); executeAppTransition(); @@ -1554,7 +1555,7 @@ public class WindowManagerService extends IWindowManager.Stub // we don't set up the transition anymore and just let it go. if (mDisplayFrozen && !mOpeningApps.contains(atoken) && atoken.isRelaunching()) { mOpeningApps.add(atoken); - prepareAppTransition(AppTransition.TRANSIT_NONE, !ALWAYS_KEEP_CURRENT); + prepareAppTransition(WindowManager.TRANSIT_NONE, !ALWAYS_KEEP_CURRENT); executeAppTransition(); } } @@ -2509,7 +2510,7 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void prepareAppTransition(int transit, boolean alwaysKeepCurrent) { + public void prepareAppTransition(@TransitionType int transit, boolean alwaysKeepCurrent) { prepareAppTransition(transit, alwaysKeepCurrent, 0 /* flags */, false /* forceOverride */); } @@ -2522,8 +2523,8 @@ public class WindowManagerService extends IWindowManager.Stub * AppTransition.TRANSIT_FLAG_*. * @param forceOverride Always override the transit, not matter what was set previously. */ - public void prepareAppTransition(int transit, boolean alwaysKeepCurrent, int flags, - boolean forceOverride) { + public void prepareAppTransition(@TransitionType int transit, boolean alwaysKeepCurrent, + @TransitionFlags int flags, boolean forceOverride) { if (!checkCallingPermission(MANAGE_APP_TOKENS, "prepareAppTransition()")) { throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); } @@ -2539,7 +2540,7 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public int getPendingAppTransition() { + public @TransitionType int getPendingAppTransition() { return mAppTransition.getAppTransition(); } @@ -2624,6 +2625,18 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter) { + if (!checkCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, + "overridePendingAppTransitionRemote()")) { + throw new SecurityException( + "Requires CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS permission"); + } + synchronized (mWindowMap) { + mAppTransition.overridePendingAppTransitionRemote(remoteAnimationAdapter); + } + } + + @Override public void endProlongedAnimations() { synchronized (mWindowMap) { for (final WindowState win : mWindowMap.values()) { @@ -5894,6 +5907,7 @@ public class WindowManagerService extends IWindowManager.Stub * the screen is. * @see WindowManagerPolicy#getNavBarPosition() */ + @Override @WindowManagerPolicy.NavigationBarPosition public int getNavBarPosition() { synchronized (mWindowMap) { @@ -6599,6 +6613,7 @@ public class WindowManagerService extends IWindowManager.Stub public void onOverlayChanged() { synchronized (mWindowMap) { mPolicy.onOverlayChangedLw(); + mDisplayManagerInternal.onOverlayChanged(); requestTraversal(); } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 5d7423814ca9..db30db074838 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -21,14 +21,12 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.SurfaceControl.Transaction; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; -import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW; -import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; @@ -43,6 +41,8 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; import static android.view.WindowManager.LayoutParams.FORMAT_CHANGED; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; import static android.view.WindowManager.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; @@ -193,6 +193,7 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ToBooleanFunction; import com.android.server.input.InputWindowHandle; import com.android.server.policy.WindowManagerPolicy; @@ -2994,16 +2995,19 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // No cutout, no letterbox. return false; } - if ((mAttrs.flags2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0) { + if (mAttrs.layoutInDisplayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) { // Layout in cutout, no letterbox. return false; } // TODO: handle dialogs and other non-filling windows + if (mAttrs.layoutInDisplayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER) { + // Never layout in cutout, always letterbox. + return true; + } // Letterbox if any fullscreen mode is set. final int fl = mAttrs.flags; final int sysui = mSystemUiVisibility; - return (fl & FLAG_FULLSCREEN) != 0 - || (sysui & (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_FULLSCREEN)) != 0; + return (fl & FLAG_FULLSCREEN) != 0 || (sysui & (SYSTEM_UI_FLAG_FULLSCREEN)) != 0; } boolean isDragResizeChanged() { @@ -3949,6 +3953,22 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return null; } + /** + * @return True if we our one of our ancestors has {@link #mAnimatingExit} set to true, false + * otherwise. + */ + @VisibleForTesting + boolean isSelfOrAncestorWindowAnimatingExit() { + WindowState window = this; + do { + if (window.mAnimatingExit) { + return true; + } + window = window.getParentWindow(); + } while (window != null); + return false; + } + void onExitAnimationDone() { if (DEBUG_ANIM) Slog.v(TAG, "onExitAnimationDone in " + this + ": exiting=" + mAnimatingExit + " remove=" + mRemoveOnExit @@ -3984,7 +4004,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked(); } - if (!mAnimatingExit) { + if (!isSelfOrAncestorWindowAnimatingExit()) { return; } diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index 017b3251696e..7364e87227e4 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -23,23 +23,23 @@ import static android.app.ActivityManagerInternal.APP_TRANSITION_WINDOWS_DRAWN; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; -import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_CLOSE; -import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN; -import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; -import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; -import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; -import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_GOING_AWAY; -import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER; -import static com.android.server.wm.AppTransition.TRANSIT_NONE; -import static com.android.server.wm.AppTransition.TRANSIT_TASK_CLOSE; -import static com.android.server.wm.AppTransition.TRANSIT_TASK_IN_PLACE; -import static com.android.server.wm.AppTransition.TRANSIT_TASK_OPEN; -import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_BACK; -import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_FRONT; -import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_CLOSE; -import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_INTRA_CLOSE; -import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_INTRA_OPEN; -import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_OPEN; +import static android.view.WindowManager.TRANSIT_ACTIVITY_CLOSE; +import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; +import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER; +import static android.view.WindowManager.TRANSIT_NONE; +import static android.view.WindowManager.TRANSIT_TASK_CLOSE; +import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE; +import static android.view.WindowManager.TRANSIT_TASK_OPEN; +import static android.view.WindowManager.TRANSIT_TASK_TO_BACK; +import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT; +import static android.view.WindowManager.TRANSIT_WALLPAPER_CLOSE; +import static android.view.WindowManager.TRANSIT_WALLPAPER_INTRA_CLOSE; +import static android.view.WindowManager.TRANSIT_WALLPAPER_INTRA_OPEN; +import static android.view.WindowManager.TRANSIT_WALLPAPER_OPEN; import static com.android.server.wm.AppTransition.isKeyguardGoingAwayTransit; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS; @@ -57,14 +57,19 @@ import android.util.ArraySet; import android.util.Slog; import android.util.SparseIntArray; import android.view.Display; +import android.view.RemoteAnimationAdapter; +import android.view.RemoteAnimationDefinition; import android.view.SurfaceControl; +import android.view.WindowManager; import android.view.WindowManager.LayoutParams; +import android.view.WindowManager.TransitionType; import android.view.animation.Animation; import com.android.server.wm.WindowManagerService.H; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.function.Predicate; /** * Positions windows and their surfaces. @@ -247,7 +252,7 @@ class WindowSurfacePlacer { if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO"); int transit = mService.mAppTransition.getAppTransition(); if (mService.mSkipAppTransitionAnimation && !isKeyguardGoingAwayTransit(transit)) { - transit = AppTransition.TRANSIT_UNSET; + transit = WindowManager.TRANSIT_UNSET; } mService.mSkipAppTransitionAnimation = false; mService.mNoAnimationNotifyOnTransitionFinished.clear(); @@ -255,17 +260,9 @@ class WindowSurfacePlacer { mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT); final DisplayContent displayContent = mService.getDefaultDisplayContentLocked(); - // TODO: Don't believe this is really needed... - //mService.mWindowsChanged = true; mService.mRoot.mWallpaperMayChange = false; - // The top-most window will supply the layout params, and we will determine it below. - LayoutParams animLp = null; - int bestAnimLayer = -1; - boolean fullscreenAnim = false; - boolean voiceInteraction = false; - int i; for (i = 0; i < appsCount; i++) { final AppWindowToken wtoken = mService.mOpeningApps.valueAt(i); @@ -274,7 +271,6 @@ class WindowSurfacePlacer { // visibility. We need to clear it *before* maybeUpdateTransitToWallpaper() as the // transition selection depends on wallpaper target visibility. wtoken.clearAnimatingFlags(); - } // Adjust wallpaper before we pull the lower/upper target, since pending changes @@ -283,62 +279,30 @@ class WindowSurfacePlacer { mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded(displayContent, mService.mOpeningApps); - final WindowState wallpaperTarget = mWallpaperControllerLocked.getWallpaperTarget(); - boolean openingAppHasWallpaper = false; - boolean closingAppHasWallpaper = false; - - // Do a first pass through the tokens for two things: - // (1) Determine if both the closing and opening app token sets are wallpaper targets, in - // which case special animations are needed (since the wallpaper needs to stay static behind - // them). - // (2) Find the layout params of the top-most application window in the tokens, which is - // what will control the animation theme. - final int closingAppsCount = mService.mClosingApps.size(); - appsCount = closingAppsCount + mService.mOpeningApps.size(); - for (i = 0; i < appsCount; i++) { - final AppWindowToken wtoken; - if (i < closingAppsCount) { - wtoken = mService.mClosingApps.valueAt(i); - if (wallpaperTarget != null && wtoken.windowsCanBeWallpaperTarget()) { - closingAppHasWallpaper = true; - } - } else { - wtoken = mService.mOpeningApps.valueAt(i - closingAppsCount); - if (wallpaperTarget != null && wtoken.windowsCanBeWallpaperTarget()) { - openingAppHasWallpaper = true; - } - } - - voiceInteraction |= wtoken.mVoiceInteraction; - - if (wtoken.fillsParent()) { - final WindowState ws = wtoken.findMainWindow(); - if (ws != null) { - animLp = ws.mAttrs; - bestAnimLayer = ws.mLayer; - fullscreenAnim = true; - } - } else if (!fullscreenAnim) { - final WindowState ws = wtoken.findMainWindow(); - if (ws != null) { - if (ws.mLayer > bestAnimLayer) { - animLp = ws.mAttrs; - bestAnimLayer = ws.mLayer; - } - } - } - } + // Determine if closing and opening app token sets are wallpaper targets, in which case + // special animations are needed. + final boolean hasWallpaperTarget = mWallpaperControllerLocked.getWallpaperTarget() != null; + final boolean openingAppHasWallpaper = canBeWallpaperTarget(mService.mOpeningApps) + && hasWallpaperTarget; + final boolean closingAppHasWallpaper = canBeWallpaperTarget(mService.mClosingApps) + && hasWallpaperTarget; transit = maybeUpdateTransitToWallpaper(transit, openingAppHasWallpaper, closingAppHasWallpaper); - // If all closing windows are obscured, then there is no need to do an animation. This is - // the case, for example, when this transition is being done behind the lock screen. - if (!mService.mPolicy.allowAppAnimationsLw()) { - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "Animations disallowed by keyguard or dream."); - animLp = null; - } + // Find the layout params of the top-most application window in the tokens, which is + // what will control the animation theme. If all closing windows are obscured, then there is + // no need to do an animation. This is the case, for example, when this transition is being + // done behind a dream window. + final AppWindowToken animLpToken = mService.mPolicy.allowAppAnimationsLw() + ? findAnimLayoutParamsToken(transit) + : null; + + final LayoutParams animLp = getAnimLp(animLpToken); + overrideWithRemoteAnimationIfSet(animLpToken, transit); + + final boolean voiceInteraction = containsVoiceInteraction(mService.mOpeningApps) + || containsVoiceInteraction(mService.mOpeningApps); final int layoutRedo; mService.mSurfaceAnimationRunner.deferStartingAnimations(); @@ -388,6 +352,81 @@ class WindowSurfacePlacer { return layoutRedo | FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_CONFIG; } + private static LayoutParams getAnimLp(AppWindowToken wtoken) { + final WindowState mainWindow = wtoken != null ? wtoken.findMainWindow() : null; + return mainWindow != null ? mainWindow.mAttrs : null; + } + + /** + * Overrides the pending transition with the remote animation defined for the transition in the + * set of defined remote animations in the app window token. + */ + private void overrideWithRemoteAnimationIfSet(AppWindowToken animLpToken, int transit) { + if (animLpToken == null) { + return; + } + final RemoteAnimationDefinition definition = animLpToken.getRemoteAnimationDefinition(); + if (definition != null) { + final RemoteAnimationAdapter adapter = definition.getAdapter(transit); + if (adapter != null) { + mService.mAppTransition.overridePendingAppTransitionRemote(adapter); + } + } + } + + /** + * @return The window token that determines the animation theme. + */ + private AppWindowToken findAnimLayoutParamsToken(@TransitionType int transit) { + AppWindowToken result; + + // Remote animations always win, but fullscreen tokens override non-fullscreen tokens. + result = lookForHighestTokenWithFilter(mService.mClosingApps, mService.mOpeningApps, + w -> w.getRemoteAnimationDefinition() != null + && w.getRemoteAnimationDefinition().hasTransition(transit)); + if (result != null) { + return result; + } + result = lookForHighestTokenWithFilter(mService.mClosingApps, mService.mOpeningApps, + w -> w.fillsParent() && w.findMainWindow() != null); + if (result != null) { + return result; + } + return lookForHighestTokenWithFilter(mService.mClosingApps, mService.mOpeningApps, + w -> w.findMainWindow() != null); + } + + private AppWindowToken lookForHighestTokenWithFilter(ArraySet<AppWindowToken> array1, + ArraySet<AppWindowToken> array2, Predicate<AppWindowToken> filter) { + final int array1count = array1.size(); + final int count = array1count + array2.size(); + int bestPrefixOrderIndex = Integer.MIN_VALUE; + AppWindowToken bestToken = null; + for (int i = 0; i < count; i++) { + final AppWindowToken wtoken; + if (i < array1count) { + wtoken = array1.valueAt(i); + } else { + wtoken = array2.valueAt(i - array1count); + } + final int prefixOrderIndex = wtoken.getPrefixOrderIndex(); + if (filter.test(wtoken) && prefixOrderIndex > bestPrefixOrderIndex) { + bestPrefixOrderIndex = prefixOrderIndex; + bestToken = wtoken; + } + } + return bestToken; + } + + private boolean containsVoiceInteraction(ArraySet<AppWindowToken> apps) { + for (int i = apps.size() - 1; i >= 0; i--) { + if (apps.valueAt(i).mVoiceInteraction) { + return true; + } + } + return false; + } + private AppWindowToken handleOpeningApps(int transit, LayoutParams animLp, boolean voiceInteraction) { AppWindowToken topOpeningApp = null; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index 36de3d12a743..384b416b0201 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -109,4 +109,21 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { public boolean startUserInBackground(ComponentName who, UserHandle userHandle) { return false; } + + @Override + public void setStartUserSessionMessage( + ComponentName admin, CharSequence startUserSessionMessage) {} + + @Override + public void setEndUserSessionMessage(ComponentName admin, CharSequence endUserSessionMessage) {} + + @Override + public String getStartUserSessionMessage(ComponentName admin) { + return null; + } + + @Override + public String getEndUserSessionMessage(ComponentName admin) { + return null; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index bf2b137f65b8..cb4f5c1177df 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -200,6 +200,7 @@ import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo; import com.android.server.pm.UserRestrictionsUtils; + import com.google.android.collect.Sets; import org.xmlpull.v1.XmlPullParser; @@ -226,6 +227,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map.Entry; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -790,6 +792,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification"; private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications"; private static final String TAG_IS_LOGOUT_ENABLED = "is_logout_enabled"; + private static final String TAG_MANDATORY_BACKUP_TRANSPORT = "mandatory_backup_transport"; + private static final String TAG_START_USER_SESSION_MESSAGE = "start_user_session_message"; + private static final String TAG_END_USER_SESSION_MESSAGE = "end_user_session_message"; DeviceAdminInfo info; @@ -906,6 +911,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // The blacklist data is stored in a file whose name is stored in the XML String passwordBlacklistFile = null; + // The component name of the backup transport which has to be used if backups are mandatory + // or null if backups are not mandatory. + ComponentName mandatoryBackupTransport = null; + + // Message for user switcher + String startUserSessionMessage = null; + String endUserSessionMessage = null; + ActiveAdmin(DeviceAdminInfo _info, boolean parent) { info = _info; isParent = parent; @@ -1169,6 +1182,21 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { out.attribute(null, ATTR_VALUE, Boolean.toString(isLogoutEnabled)); out.endTag(null, TAG_IS_LOGOUT_ENABLED); } + if (mandatoryBackupTransport != null) { + out.startTag(null, TAG_MANDATORY_BACKUP_TRANSPORT); + out.attribute(null, ATTR_VALUE, mandatoryBackupTransport.flattenToString()); + out.endTag(null, TAG_MANDATORY_BACKUP_TRANSPORT); + } + if (startUserSessionMessage != null) { + out.startTag(null, TAG_START_USER_SESSION_MESSAGE); + out.text(startUserSessionMessage); + out.endTag(null, TAG_START_USER_SESSION_MESSAGE); + } + if (endUserSessionMessage != null) { + out.startTag(null, TAG_END_USER_SESSION_MESSAGE); + out.text(endUserSessionMessage); + out.endTag(null, TAG_END_USER_SESSION_MESSAGE); + } } void writePackageListToXml(XmlSerializer out, String outerTag, @@ -1347,6 +1375,23 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } else if (TAG_IS_LOGOUT_ENABLED.equals(tag)) { isLogoutEnabled = Boolean.parseBoolean( parser.getAttributeValue(null, ATTR_VALUE)); + } else if (TAG_MANDATORY_BACKUP_TRANSPORT.equals(tag)) { + mandatoryBackupTransport = ComponentName.unflattenFromString( + parser.getAttributeValue(null, ATTR_VALUE)); + } else if (TAG_START_USER_SESSION_MESSAGE.equals(tag)) { + type = parser.next(); + if (type == XmlPullParser.TEXT) { + startUserSessionMessage = parser.getText(); + } else { + Log.w(LOG_TAG, "Missing text when loading start session message"); + } + } else if (TAG_END_USER_SESSION_MESSAGE.equals(tag)) { + type = parser.next(); + if (type == XmlPullParser.TEXT) { + endUserSessionMessage = parser.getText(); + } else { + Log.w(LOG_TAG, "Missing text when loading end session message"); + } } else { Slog.w(LOG_TAG, "Unknown admin tag: " + tag); XmlUtils.skipCurrentTag(parser); @@ -3201,10 +3246,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } synchronized (this) { - // push the force-ephemeral-users policy to the user manager. ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); if (deviceOwner != null) { + // Push the force-ephemeral-users policy to the user manager. mUserManagerInternal.setForceEphemeralUsers(deviceOwner.forceEphemeralUsers); + + // Update user switcher message to activity manager. + ActivityManagerInternal activityManagerInternal = + mInjector.getActivityManagerInternal(); + activityManagerInternal.setSwitchingFromSystemUserMessage( + deviceOwner.startUserSessionMessage); + activityManagerInternal.setSwitchingToSystemUserMessage( + deviceOwner.endUserSessionMessage); } } } @@ -4503,6 +4556,28 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + private boolean canPOorDOCallResetPassword(ActiveAdmin admin, @UserIdInt int userId) { + // Only if the admins targets a pre-O SDK + return getTargetSdk(admin.info.getPackageName(), userId) < Build.VERSION_CODES.O; + } + + /* PO or DO could do an untrusted reset in certain conditions. */ + private boolean canUserHaveUntrustedCredentialReset(@UserIdInt int userId) { + synchronized (this) { + // An active DO or PO might be able to fo an untrusted credential reset + for (final ActiveAdmin admin : getUserData(userId).mAdminList) { + if (!isActiveAdminWithPolicyForUserLocked(admin, + DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, userId)) { + continue; + } + if (canPOorDOCallResetPassword(admin, userId)) { + return true; + } + } + return false; + } + } + @Override public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException { final int callingUid = mInjector.binderGetCallingUid(); @@ -4521,12 +4596,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { null, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, callingUid); final boolean preN; if (admin != null) { - final int targetSdk = getTargetSdk(admin.info.getPackageName(), userHandle); - if (targetSdk >= Build.VERSION_CODES.O) { + if (!canPOorDOCallResetPassword(admin, userHandle)) { throw new SecurityException("resetPassword() is deprecated for DPC targeting O" + " or later"); } - preN = targetSdk <= android.os.Build.VERSION_CODES.M; + preN = getTargetSdk(admin.info.getPackageName(), + userHandle) <= android.os.Build.VERSION_CODES.M; } else { // Otherwise, make sure the caller has any active admin with the right policy. admin = getActiveAdminForCallerLocked(null, @@ -7319,6 +7394,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { policy.mUserProvisioningState = DevicePolicyManager.STATE_USER_UNMANAGED; policy.mAffiliationIds.clear(); policy.mLockTaskPackages.clear(); + updateLockTaskPackagesLocked(policy.mLockTaskPackages, userId); policy.mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE; saveSettingsLocked(userId); @@ -10096,6 +10172,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { updateMaximumTimeToLockLocked(userId); } } + + @Override + public boolean canUserHaveUntrustedCredentialReset(@UserIdInt int userId) { + return DevicePolicyManagerService.this.canUserHaveUntrustedCredentialReset(userId); + } } private Intent createShowAdminSupportIntent(ComponentName admin, int userId) { @@ -11337,7 +11418,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Preconditions.checkNotNull(admin); synchronized (this) { - getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + ActiveAdmin activeAdmin = getActiveAdminForCallerLocked( + admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + if (!enabled) { + activeAdmin.mandatoryBackupTransport = null; + saveSettingsLocked(UserHandle.USER_SYSTEM); + } } final long ident = mInjector.binderClearCallingIdentity(); @@ -11372,6 +11458,50 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + public void setMandatoryBackupTransport( + ComponentName admin, ComponentName backupTransportComponent) { + if (!mHasFeature) { + return; + } + Preconditions.checkNotNull(admin); + synchronized (this) { + ActiveAdmin activeAdmin = + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + if (!Objects.equals(backupTransportComponent, activeAdmin.mandatoryBackupTransport)) { + activeAdmin.mandatoryBackupTransport = backupTransportComponent; + saveSettingsLocked(UserHandle.USER_SYSTEM); + } + } + final long identity = mInjector.binderClearCallingIdentity(); + try { + IBackupManager ibm = mInjector.getIBackupManager(); + if (ibm != null && backupTransportComponent != null) { + if (!ibm.isBackupServiceActive(UserHandle.USER_SYSTEM)) { + ibm.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + } + ibm.selectBackupTransportAsync(backupTransportComponent, null); + ibm.setBackupEnabled(true); + } + } catch (RemoteException e) { + throw new IllegalStateException("Failed to set mandatory backup transport.", e); + } finally { + mInjector.binderRestoreCallingIdentity(identity); + } + } + + @Override + public ComponentName getMandatoryBackupTransport() { + if (!mHasFeature) { + return null; + } + synchronized (this) { + ActiveAdmin activeAdmin = getDeviceOwnerAdminLocked(); + return activeAdmin == null ? null : activeAdmin.mandatoryBackupTransport; + } + } + + + @Override public boolean bindDeviceAdminServiceAsUser( @NonNull ComponentName admin, @NonNull IApplicationThread caller, @Nullable IBinder activtiyToken, @NonNull Intent serviceIntent, @@ -11943,15 +12073,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } Preconditions.checkNotNull(admin); - getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); - if (enabled == isLogoutEnabledInternalLocked()) { - // already in the requested state - return; + synchronized (this) { + ActiveAdmin deviceOwner = + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + + if (deviceOwner.isLogoutEnabled == enabled) { + // already in the requested state + return; + } + deviceOwner.isLogoutEnabled = enabled; + saveSettingsLocked(mInjector.userHandleGetCallingUserId()); } - ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); - deviceOwner.isLogoutEnabled = enabled; - saveSettingsLocked(mInjector.userHandleGetCallingUserId()); } @Override @@ -11960,15 +12093,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } synchronized (this) { - return isLogoutEnabledInternalLocked(); + ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); + return (deviceOwner != null) && deviceOwner.isLogoutEnabled; } } - private boolean isLogoutEnabledInternalLocked() { - ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); - return (deviceOwner != null) && deviceOwner.isLogoutEnabled; - } - @Override public List<String> getDisallowedSystemApps(ComponentName admin, int userId, String provisioningAction) throws RemoteException { @@ -12005,6 +12134,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final DeviceAdminInfo incomingDeviceInfo = findAdmin(target, callingUserId, /* throwForMissingPermission= */ true); checkActiveAdminPrecondition(target, incomingDeviceInfo, policy); + if (!incomingDeviceInfo.getActivityInfo().metaData + .getBoolean(DeviceAdminReceiver.SUPPORT_TRANSFER_OWNERSHIP_META_DATA, false)) { + throw new IllegalArgumentException("Provided target does not support " + + "ownership transfer."); + } final long id = mInjector.binderClearCallingIdentity(); try { @@ -12061,4 +12195,83 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } return extras; } + + @Override + public void setStartUserSessionMessage( + ComponentName admin, CharSequence startUserSessionMessage) { + if (!mHasFeature) { + return; + } + Preconditions.checkNotNull(admin); + + final String startUserSessionMessageString = + startUserSessionMessage != null ? startUserSessionMessage.toString() : null; + + synchronized (this) { + final ActiveAdmin deviceOwner = + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + + if (TextUtils.equals(deviceOwner.startUserSessionMessage, startUserSessionMessage)) { + return; + } + deviceOwner.startUserSessionMessage = startUserSessionMessageString; + saveSettingsLocked(mInjector.userHandleGetCallingUserId()); + } + + mInjector.getActivityManagerInternal() + .setSwitchingFromSystemUserMessage(startUserSessionMessageString); + } + + @Override + public void setEndUserSessionMessage(ComponentName admin, CharSequence endUserSessionMessage) { + if (!mHasFeature) { + return; + } + Preconditions.checkNotNull(admin); + + final String endUserSessionMessageString = + endUserSessionMessage != null ? endUserSessionMessage.toString() : null; + + synchronized (this) { + final ActiveAdmin deviceOwner = + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + + if (TextUtils.equals(deviceOwner.endUserSessionMessage, endUserSessionMessage)) { + return; + } + deviceOwner.endUserSessionMessage = endUserSessionMessageString; + saveSettingsLocked(mInjector.userHandleGetCallingUserId()); + } + + mInjector.getActivityManagerInternal() + .setSwitchingToSystemUserMessage(endUserSessionMessageString); + } + + @Override + public String getStartUserSessionMessage(ComponentName admin) { + if (!mHasFeature) { + return null; + } + Preconditions.checkNotNull(admin); + + synchronized (this) { + final ActiveAdmin deviceOwner = + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + return deviceOwner.startUserSessionMessage; + } + } + + @Override + public String getEndUserSessionMessage(ComponentName admin) { + if (!mHasFeature) { + return null; + } + Preconditions.checkNotNull(admin); + + synchronized (this) { + final ActiveAdmin deviceOwner = + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + return deviceOwner.endUserSessionMessage; + } + } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 4310a98d8000..3199bfa49455 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -80,6 +80,7 @@ import com.android.server.job.JobSchedulerService; import com.android.server.lights.LightsService; import com.android.server.media.MediaResourceMonitorService; import com.android.server.media.MediaRouterService; +import com.android.server.media.MediaUpdateService; import com.android.server.media.MediaSessionService; import com.android.server.media.projection.MediaProjectionManagerService; import com.android.server.net.NetworkPolicyManagerService; @@ -1442,6 +1443,10 @@ public final class SystemServer { mSystemServiceManager.startService(MediaSessionService.class); traceEnd(); + traceBeginAndSlog("StartMediaUpdateService"); + mSystemServiceManager.startService(MediaUpdateService.class); + traceEnd(); + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) { traceBeginAndSlog("StartHdmiControlService"); mSystemServiceManager.startService(HdmiControlService.class); diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java index 71ba685b99c9..cd4e8f977d60 100644 --- a/services/print/java/com/android/server/print/PrintManagerService.java +++ b/services/print/java/com/android/server/print/PrintManagerService.java @@ -57,7 +57,9 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; +import com.android.internal.print.DualDumpOutputStream; import com.android.internal.util.DumpUtils; +import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.server.SystemService; @@ -670,37 +672,29 @@ public final class PrintManagerService extends SystemService { final long identity = Binder.clearCallingIdentity(); try { if (dumpAsProto) { - dump(new ProtoOutputStream(fd), userStatesToDump); + dump(new DualDumpOutputStream(new ProtoOutputStream(fd), null), + userStatesToDump); } else { - dump(fd, pw, userStatesToDump); + pw.println("PRINT MANAGER STATE (dumpsys print)"); + + dump(new DualDumpOutputStream(null, new IndentingPrintWriter(pw, " ")), + userStatesToDump); } } finally { Binder.restoreCallingIdentity(identity); } } - private void dump(@NonNull ProtoOutputStream proto, + private void dump(@NonNull DualDumpOutputStream dumpStream, @NonNull ArrayList<UserState> userStatesToDump) { final int userStateCount = userStatesToDump.size(); for (int i = 0; i < userStateCount; i++) { - long token = proto.start(PrintServiceDumpProto.USER_STATES); - userStatesToDump.get(i).dump(proto); - proto.end(token); + long token = dumpStream.start("user_states", PrintServiceDumpProto.USER_STATES); + userStatesToDump.get(i).dump(dumpStream); + dumpStream.end(token); } - proto.flush(); - } - - private void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, - @NonNull ArrayList<UserState> userStatesToDump) { - pw = Preconditions.checkNotNull(pw); - - pw.println("PRINT MANAGER STATE (dumpsys print)"); - final int userStateCount = userStatesToDump.size(); - for (int i = 0; i < userStateCount; i++) { - userStatesToDump.get(i).dump(fd, pw, ""); - pw.println(); - } + dumpStream.flush(); } private void registerContentObservers() { diff --git a/services/print/java/com/android/server/print/RemotePrintService.java b/services/print/java/com/android/server/print/RemotePrintService.java index 13462cd3e3e4..80b97cf995ae 100644 --- a/services/print/java/com/android/server/print/RemotePrintService.java +++ b/services/print/java/com/android/server/print/RemotePrintService.java @@ -47,11 +47,10 @@ import android.printservice.IPrintService; import android.printservice.IPrintServiceClient; import android.service.print.ActivePrintServiceProto; import android.util.Slog; -import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; +import com.android.internal.print.DualDumpOutputStream; -import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -532,49 +531,30 @@ final class RemotePrintService implements DeathRecipient { } } - public void dump(@NonNull ProtoOutputStream proto) { - writeComponentName(proto, ActivePrintServiceProto.COMPONENT_NAME, mComponentName); + public void dump(@NonNull DualDumpOutputStream proto) { + writeComponentName(proto, "component_name", ActivePrintServiceProto.COMPONENT_NAME, + mComponentName); - proto.write(ActivePrintServiceProto.IS_DESTROYED, mDestroyed); - proto.write(ActivePrintServiceProto.IS_BOUND, isBound()); - proto.write(ActivePrintServiceProto.HAS_DISCOVERY_SESSION, mHasPrinterDiscoverySession); - proto.write(ActivePrintServiceProto.HAS_ACTIVE_PRINT_JOBS, mHasActivePrintJobs); - proto.write(ActivePrintServiceProto.IS_DISCOVERING_PRINTERS, + proto.write("is_destroyed", ActivePrintServiceProto.IS_DESTROYED, mDestroyed); + proto.write("is_bound", ActivePrintServiceProto.IS_BOUND, isBound()); + proto.write("has_discovery_session", ActivePrintServiceProto.HAS_DISCOVERY_SESSION, + mHasPrinterDiscoverySession); + proto.write("has_active_print_jobs", ActivePrintServiceProto.HAS_ACTIVE_PRINT_JOBS, + mHasActivePrintJobs); + proto.write("is_discovering_printers", ActivePrintServiceProto.IS_DISCOVERING_PRINTERS, mDiscoveryPriorityList != null); synchronized (mLock) { if (mTrackedPrinterList != null) { int numTrackedPrinters = mTrackedPrinterList.size(); for (int i = 0; i < numTrackedPrinters; i++) { - writePrinterId(proto, ActivePrintServiceProto.TRACKED_PRINTERS, - mTrackedPrinterList.get(i)); + writePrinterId(proto, "tracked_printers", + ActivePrintServiceProto.TRACKED_PRINTERS, mTrackedPrinterList.get(i)); } } } } - public void dump(PrintWriter pw, String prefix) { - String tab = " "; - pw.append(prefix).append("service:").println(); - pw.append(prefix).append(tab).append("componentName=") - .append(mComponentName.flattenToString()).println(); - pw.append(prefix).append(tab).append("destroyed=") - .append(String.valueOf(mDestroyed)).println(); - pw.append(prefix).append(tab).append("bound=") - .append(String.valueOf(isBound())).println(); - pw.append(prefix).append(tab).append("hasDicoverySession=") - .append(String.valueOf(mHasPrinterDiscoverySession)).println(); - pw.append(prefix).append(tab).append("hasActivePrintJobs=") - .append(String.valueOf(mHasActivePrintJobs)).println(); - pw.append(prefix).append(tab).append("isDiscoveringPrinters=") - .append(String.valueOf(mDiscoveryPriorityList != null)).println(); - - synchronized (mLock) { - pw.append(prefix).append(tab).append("trackedPrinters=").append( - (mTrackedPrinterList != null) ? mTrackedPrinterList.toString() : "null"); - } - } - private boolean isBound() { return mPrintService != null; } diff --git a/services/print/java/com/android/server/print/RemotePrintSpooler.java b/services/print/java/com/android/server/print/RemotePrintSpooler.java index f654fcb60750..a69baa110f5a 100644 --- a/services/print/java/com/android/server/print/RemotePrintSpooler.java +++ b/services/print/java/com/android/server/print/RemotePrintSpooler.java @@ -43,16 +43,14 @@ import android.printservice.PrintService; import android.service.print.PrintSpoolerStateProto; import android.util.Slog; import android.util.TimedRemoteCaller; -import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.TransferPipe; +import com.android.internal.print.DualDumpOutputStream; import libcore.io.IoUtils; -import java.io.FileDescriptor; import java.io.IOException; -import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.List; import java.util.concurrent.TimeoutException; @@ -558,37 +556,25 @@ final class RemotePrintSpooler { } } - public void dump(@NonNull ProtoOutputStream proto) { + public void dump(@NonNull DualDumpOutputStream dumpStream) { synchronized (mLock) { - proto.write(PrintSpoolerStateProto.IS_DESTROYED, mDestroyed); - proto.write(PrintSpoolerStateProto.IS_BOUND, mRemoteInstance != null); + dumpStream.write("is_destroyed", PrintSpoolerStateProto.IS_DESTROYED, mDestroyed); + dumpStream.write("is_bound", PrintSpoolerStateProto.IS_BOUND, mRemoteInstance != null); } try { - proto.write(PrintSpoolerStateProto.INTERNAL_STATE, - TransferPipe.dumpAsync(getRemoteInstanceLazy().asBinder(), "--proto")); + if (dumpStream.isProto()) { + dumpStream.write(null, PrintSpoolerStateProto.INTERNAL_STATE, + TransferPipe.dumpAsync(getRemoteInstanceLazy().asBinder(), "--proto")); + } else { + dumpStream.writeNested("internal_state", TransferPipe.dumpAsync( + getRemoteInstanceLazy().asBinder())); + } } catch (IOException | TimeoutException | RemoteException | InterruptedException e) { Slog.e(LOG_TAG, "Failed to dump remote instance", e); } } - public void dump(FileDescriptor fd, PrintWriter pw, String prefix) { - synchronized (mLock) { - pw.append(prefix).append("destroyed=") - .append(String.valueOf(mDestroyed)).println(); - pw.append(prefix).append("bound=") - .append((mRemoteInstance != null) ? "true" : "false").println(); - - pw.flush(); - try { - TransferPipe.dumpAsync(getRemoteInstanceLazy().asBinder(), fd, - new String[] { prefix }); - } catch (IOException | TimeoutException | RemoteException | InterruptedException e) { - pw.println("Failed to dump remote instance: " + e); - } - } - } - private void onAllPrintJobsHandled() { synchronized (mLock) { throwIfDestroyedLocked(); diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java index 364bbc035a29..e2808e8245e5 100644 --- a/services/print/java/com/android/server/print/UserState.java +++ b/services/print/java/com/android/server/print/UserState.java @@ -76,19 +76,17 @@ import android.util.ArraySet; import android.util.Log; import android.util.Slog; import android.util.SparseArray; -import android.util.proto.ProtoOutputStream; import com.android.internal.R; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.os.BackgroundThread; +import com.android.internal.print.DualDumpOutputStream; import com.android.server.print.RemotePrintService.PrintServiceCallbacks; import com.android.server.print.RemotePrintServiceRecommendationService .RemotePrintServiceRecommendationServiceCallbacks; import com.android.server.print.RemotePrintSpooler.PrintSpoolerCallbacks; -import java.io.FileDescriptor; -import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -817,112 +815,63 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks, mDestroyed = true; } - public void dump(@NonNull ProtoOutputStream proto) { + public void dump(@NonNull DualDumpOutputStream dumpStream) { synchronized (mLock) { - proto.write(PrintUserStateProto.USER_ID, mUserId); + dumpStream.write("user_id", PrintUserStateProto.USER_ID, mUserId); final int installedServiceCount = mInstalledServices.size(); for (int i = 0; i < installedServiceCount; i++) { - long token = proto.start(PrintUserStateProto.INSTALLED_SERVICES); + long token = dumpStream.start("installed_services", + PrintUserStateProto.INSTALLED_SERVICES); PrintServiceInfo installedService = mInstalledServices.get(i); ResolveInfo resolveInfo = installedService.getResolveInfo(); - writeComponentName(proto, InstalledPrintServiceProto.COMPONENT_NAME, + writeComponentName(dumpStream, "component_name", + InstalledPrintServiceProto.COMPONENT_NAME, new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name)); - writeStringIfNotNull(proto, InstalledPrintServiceProto.SETTINGS_ACTIVITY, + writeStringIfNotNull(dumpStream, "settings_activity", + InstalledPrintServiceProto.SETTINGS_ACTIVITY, installedService.getSettingsActivityName()); - writeStringIfNotNull(proto, InstalledPrintServiceProto.ADD_PRINTERS_ACTIVITY, + writeStringIfNotNull(dumpStream, "add_printers_activity", + InstalledPrintServiceProto.ADD_PRINTERS_ACTIVITY, installedService.getAddPrintersActivityName()); - writeStringIfNotNull(proto, InstalledPrintServiceProto.ADVANCED_OPTIONS_ACTIVITY, + writeStringIfNotNull(dumpStream, "advanced_options_activity", + InstalledPrintServiceProto.ADVANCED_OPTIONS_ACTIVITY, installedService.getAdvancedOptionsActivityName()); - proto.end(token); + dumpStream.end(token); } for (ComponentName disabledService : mDisabledServices) { - writeComponentName(proto, PrintUserStateProto.DISABLED_SERVICES, disabledService); + writeComponentName(dumpStream, "disabled_services", + PrintUserStateProto.DISABLED_SERVICES, disabledService); } final int activeServiceCount = mActiveServices.size(); for (int i = 0; i < activeServiceCount; i++) { - long token = proto.start(PrintUserStateProto.ACTIVE_SERVICES); - mActiveServices.valueAt(i).dump(proto); - proto.end(token); + long token = dumpStream.start("actives_services", + PrintUserStateProto.ACTIVE_SERVICES); + mActiveServices.valueAt(i).dump(dumpStream); + dumpStream.end(token); } - mPrintJobForAppCache.dumpLocked(proto); + mPrintJobForAppCache.dumpLocked(dumpStream); if (mPrinterDiscoverySession != null) { - long token = proto.start(PrintUserStateProto.DISCOVERY_SESSIONS); - mPrinterDiscoverySession.dumpLocked(proto); - proto.end(token); + long token = dumpStream.start("discovery_service", + PrintUserStateProto.DISCOVERY_SESSIONS); + mPrinterDiscoverySession.dumpLocked(dumpStream); + dumpStream.end(token); } } - long token = proto.start(PrintUserStateProto.PRINT_SPOOLER_STATE); - mSpooler.dump(proto); - proto.end(token); - } - - public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String prefix) { - pw.append(prefix).append("user state ").append(String.valueOf(mUserId)).append(":"); - pw.println(); - - String tab = " "; - - synchronized (mLock) { - pw.append(prefix).append(tab).append("installed services:").println(); - final int installedServiceCount = mInstalledServices.size(); - for (int i = 0; i < installedServiceCount; i++) { - PrintServiceInfo installedService = mInstalledServices.get(i); - String installedServicePrefix = prefix + tab + tab; - pw.append(installedServicePrefix).append("service:").println(); - ResolveInfo resolveInfo = installedService.getResolveInfo(); - ComponentName componentName = new ComponentName( - resolveInfo.serviceInfo.packageName, - resolveInfo.serviceInfo.name); - pw.append(installedServicePrefix).append(tab).append("componentName=") - .append(componentName.flattenToString()).println(); - pw.append(installedServicePrefix).append(tab).append("settingsActivity=") - .append(installedService.getSettingsActivityName()).println(); - pw.append(installedServicePrefix).append(tab).append("addPrintersActivity=") - .append(installedService.getAddPrintersActivityName()).println(); - pw.append(installedServicePrefix).append(tab).append("avancedOptionsActivity=") - .append(installedService.getAdvancedOptionsActivityName()).println(); - } - - pw.append(prefix).append(tab).append("disabled services:").println(); - for (ComponentName disabledService : mDisabledServices) { - String disabledServicePrefix = prefix + tab + tab; - pw.append(disabledServicePrefix).append("service:").println(); - pw.append(disabledServicePrefix).append(tab).append("componentName=") - .append(disabledService.flattenToString()); - pw.println(); - } - - pw.append(prefix).append(tab).append("active services:").println(); - final int activeServiceCount = mActiveServices.size(); - for (int i = 0; i < activeServiceCount; i++) { - RemotePrintService activeService = mActiveServices.valueAt(i); - activeService.dump(pw, prefix + tab + tab); - pw.println(); - } - - pw.append(prefix).append(tab).append("cached print jobs:").println(); - mPrintJobForAppCache.dumpLocked(pw, prefix + tab + tab); - - pw.append(prefix).append(tab).append("discovery mediator:").println(); - if (mPrinterDiscoverySession != null) { - mPrinterDiscoverySession.dumpLocked(pw, prefix + tab + tab); - } - } - - pw.append(prefix).append(tab).append("print spooler:").println(); - mSpooler.dump(fd, pw, prefix + tab + tab); - pw.println(); + long token = dumpStream.start("print_spooler_state", + PrintUserStateProto.PRINT_SPOOLER_STATE); + mSpooler.dump(dumpStream); + dumpStream.end(token); } private void readConfigurationLocked() { @@ -1650,15 +1599,17 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks, } } - public void dumpLocked(@NonNull ProtoOutputStream proto) { - proto.write(PrinterDiscoverySessionProto.IS_DESTROYED, mDestroyed); - proto.write(PrinterDiscoverySessionProto.IS_PRINTER_DISCOVERY_IN_PROGRESS, + public void dumpLocked(@NonNull DualDumpOutputStream dumpStream) { + dumpStream.write("is_destroyed", PrinterDiscoverySessionProto.IS_DESTROYED, mDestroyed); + dumpStream.write("is_printer_discovery_in_progress", + PrinterDiscoverySessionProto.IS_PRINTER_DISCOVERY_IN_PROGRESS, !mStartedPrinterDiscoveryTokens.isEmpty()); final int observerCount = mDiscoveryObservers.beginBroadcast(); for (int i = 0; i < observerCount; i++) { IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i); - proto.write(PrinterDiscoverySessionProto.PRINTER_DISCOVERY_OBSERVERS, + dumpStream.write("printer_discovery_observers", + PrinterDiscoverySessionProto.PRINTER_DISCOVERY_OBSERVERS, observer.toString()); } mDiscoveryObservers.finishBroadcast(); @@ -1666,61 +1617,22 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks, final int tokenCount = this.mStartedPrinterDiscoveryTokens.size(); for (int i = 0; i < tokenCount; i++) { IBinder token = mStartedPrinterDiscoveryTokens.get(i); - proto.write(PrinterDiscoverySessionProto.DISCOVERY_REQUESTS, token.toString()); + dumpStream.write("discovery_requests", + PrinterDiscoverySessionProto.DISCOVERY_REQUESTS, token.toString()); } final int trackedPrinters = mStateTrackedPrinters.size(); for (int i = 0; i < trackedPrinters; i++) { PrinterId printer = mStateTrackedPrinters.get(i); - writePrinterId(proto, PrinterDiscoverySessionProto.TRACKED_PRINTER_REQUESTS, - printer); + writePrinterId(dumpStream, "tracked_printer_requests", + PrinterDiscoverySessionProto.TRACKED_PRINTER_REQUESTS, printer); } final int printerCount = mPrinters.size(); for (int i = 0; i < printerCount; i++) { PrinterInfo printer = mPrinters.valueAt(i); - writePrinterInfo(mContext, proto, PrinterDiscoverySessionProto.PRINTER, printer); - } - } - - public void dumpLocked(PrintWriter pw, String prefix) { - pw.append(prefix).append("destroyed=") - .append(String.valueOf(mDestroyed)).println(); - - pw.append(prefix).append("printDiscoveryInProgress=") - .append(String.valueOf(!mStartedPrinterDiscoveryTokens.isEmpty())).println(); - - String tab = " "; - - pw.append(prefix).append(tab).append("printer discovery observers:").println(); - final int observerCount = mDiscoveryObservers.beginBroadcast(); - for (int i = 0; i < observerCount; i++) { - IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i); - pw.append(prefix).append(prefix).append(observer.toString()); - pw.println(); - } - mDiscoveryObservers.finishBroadcast(); - - pw.append(prefix).append(tab).append("start discovery requests:").println(); - final int tokenCount = this.mStartedPrinterDiscoveryTokens.size(); - for (int i = 0; i < tokenCount; i++) { - IBinder token = mStartedPrinterDiscoveryTokens.get(i); - pw.append(prefix).append(tab).append(tab).append(token.toString()).println(); - } - - pw.append(prefix).append(tab).append("tracked printer requests:").println(); - final int trackedPrinters = mStateTrackedPrinters.size(); - for (int i = 0; i < trackedPrinters; i++) { - PrinterId printer = mStateTrackedPrinters.get(i); - pw.append(prefix).append(tab).append(tab).append(printer.toString()).println(); - } - - pw.append(prefix).append(tab).append("printers:").println(); - final int pritnerCount = mPrinters.size(); - for (int i = 0; i < pritnerCount; i++) { - PrinterInfo printer = mPrinters.valueAt(i); - pw.append(prefix).append(tab).append(tab).append( - printer.toString()).println(); + writePrinterInfo(mContext, dumpStream, "printer", + PrinterDiscoverySessionProto.PRINTER, printer); } } @@ -1933,36 +1845,22 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks, } } - public void dumpLocked(PrintWriter pw, String prefix) { - String tab = " "; - final int bucketCount = mPrintJobsForRunningApp.size(); - for (int i = 0; i < bucketCount; i++) { - final int appId = mPrintJobsForRunningApp.keyAt(i); - pw.append(prefix).append("appId=" + appId).append(':').println(); - List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i); - final int printJobCount = bucket.size(); - for (int j = 0; j < printJobCount; j++) { - PrintJobInfo printJob = bucket.get(j); - pw.append(prefix).append(tab).append(printJob.toString()).println(); - } - } - } - - public void dumpLocked(@NonNull ProtoOutputStream proto) { + public void dumpLocked(@NonNull DualDumpOutputStream dumpStream) { final int bucketCount = mPrintJobsForRunningApp.size(); for (int i = 0; i < bucketCount; i++) { final int appId = mPrintJobsForRunningApp.keyAt(i); List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i); final int printJobCount = bucket.size(); for (int j = 0; j < printJobCount; j++) { - long token = proto.start(PrintUserStateProto.CACHED_PRINT_JOBS); + long token = dumpStream.start("cached_print_jobs", + PrintUserStateProto.CACHED_PRINT_JOBS); - proto.write(CachedPrintJobProto.APP_ID, appId); + dumpStream.write("app_id", CachedPrintJobProto.APP_ID, appId); - writePrintJobInfo(mContext, proto, CachedPrintJobProto.PRINT_JOB, - bucket.get(j)); + writePrintJobInfo(mContext, dumpStream, "print_job", + CachedPrintJobProto.PRINT_JOB, bucket.get(j)); - proto.end(token); + dumpStream.end(token); } } } diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java index f9ebd28418cd..03d28b79b5dd 100644 --- a/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java +++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java @@ -16,14 +16,17 @@ package com.android.server.backup; -import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAMES; - +import static com.android.server.backup.testing.TransportData.backupTransport; +import static com.android.server.backup.testing.TransportData.d2dTransport; +import static com.android.server.backup.testing.TransportData.localTransport; +import static com.android.server.backup.testing.TransportTestUtils.setUpCurrentTransport; +import static com.android.server.backup.testing.TransportTestUtils.setUpTransports; import static com.google.common.truth.Truth.assertThat; - import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; @@ -34,17 +37,21 @@ import android.app.backup.ISelectBackupTransportCallback; import android.content.ComponentName; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.os.HandlerThread; import android.platform.test.annotations.Presubmit; import android.provider.Settings; - import com.android.server.backup.testing.ShadowAppBackupUtils; -import com.android.server.backup.testing.TransportTestUtils; -import com.android.server.backup.testing.TransportTestUtils.TransportData; +import com.android.server.backup.testing.ShadowBackupPolicyEnforcer; +import com.android.server.backup.testing.TransportData; +import com.android.server.backup.testing.TransportTestUtils.TransportMock; import com.android.server.backup.transport.TransportNotRegisteredException; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderClasses; - +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -56,39 +63,38 @@ import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowContextWrapper; import org.robolectric.shadows.ShadowLog; import org.robolectric.shadows.ShadowLooper; +import org.robolectric.shadows.ShadowPackageManager; import org.robolectric.shadows.ShadowSettings; - -import java.io.File; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import org.robolectric.shadows.ShadowSystemClock; @RunWith(FrameworkRobolectricTestRunner.class) @Config( manifest = Config.NONE, sdk = 26, - shadows = {ShadowAppBackupUtils.class} + shadows = {ShadowAppBackupUtils.class, ShadowBackupPolicyEnforcer.class} ) @SystemLoaderClasses({RefactoredBackupManagerService.class, TransportManager.class}) @Presubmit public class BackupManagerServiceRoboTest { private static final String TAG = "BMSTest"; - private static final String TRANSPORT_NAME = - "com.google.android.gms/.backup.BackupTransportService"; @Mock private TransportManager mTransportManager; private HandlerThread mBackupThread; private ShadowLooper mShadowBackupLooper; private File mBaseStateDir; private File mDataDir; - private RefactoredBackupManagerService mBackupManagerService; private ShadowContextWrapper mShadowContext; private Context mContext; + private TransportData mTransport; + private String mTransportName; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mTransport = backupTransport(); + mTransportName = mTransport.transportName; + mBackupThread = new HandlerThread("backup-test"); mBackupThread.setUncaughtExceptionHandler( (t, e) -> ShadowLog.e(TAG, "Uncaught exception in test thread " + t.getName(), e)); @@ -103,20 +109,14 @@ public class BackupManagerServiceRoboTest { mBaseStateDir = new File(cacheDir, "base_state_dir"); mDataDir = new File(cacheDir, "data_dir"); - mBackupManagerService = - new RefactoredBackupManagerService( - mContext, - new Trampoline(mContext), - mBackupThread, - mBaseStateDir, - mDataDir, - mTransportManager); + ShadowBackupPolicyEnforcer.setMandatoryBackupTransport(null); } @After public void tearDown() throws Exception { mBackupThread.quit(); ShadowAppBackupUtils.reset(); + ShadowBackupPolicyEnforcer.setMandatoryBackupTransport(null); } /* Tests for destination string */ @@ -124,10 +124,12 @@ public class BackupManagerServiceRoboTest { @Test public void testDestinationString() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME))) + when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName))) .thenReturn("destinationString"); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); - String destination = mBackupManagerService.getDestinationString(TRANSPORT_NAME); + String destination = backupManagerService.getDestinationString(mTransportName); assertThat(destination).isEqualTo("destinationString"); } @@ -135,10 +137,12 @@ public class BackupManagerServiceRoboTest { @Test public void testDestinationString_whenTransportNotRegistered() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME))) + when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName))) .thenThrow(TransportNotRegisteredException.class); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); - String destination = mBackupManagerService.getDestinationString(TRANSPORT_NAME); + String destination = backupManagerService.getDestinationString(mTransportName); assertThat(destination).isNull(); } @@ -146,12 +150,14 @@ public class BackupManagerServiceRoboTest { @Test public void testDestinationString_withoutPermission() throws Exception { mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME))) + when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName))) .thenThrow(TransportNotRegisteredException.class); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); expectThrows( SecurityException.class, - () -> mBackupManagerService.getDestinationString(TRANSPORT_NAME)); + () -> backupManagerService.getDestinationString(mTransportName)); } /* Tests for app eligibility */ @@ -159,24 +165,28 @@ public class BackupManagerServiceRoboTest { @Test public void testIsAppEligibleForBackup_whenAppEligible() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - TransportData transport = - TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME); + TransportMock transportMock = setUpCurrentTransport(mTransportManager, backupTransport()); ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> true; + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); - boolean result = mBackupManagerService.isAppEligibleForBackup("app.package"); + boolean result = backupManagerService.isAppEligibleForBackup("app.package"); assertThat(result).isTrue(); + verify(mTransportManager) - .disposeOfTransportClient(eq(transport.transportClientMock), any()); + .disposeOfTransportClient(eq(transportMock.transportClient), any()); } @Test public void testIsAppEligibleForBackup_whenAppNotEligible() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME); + setUpCurrentTransport(mTransportManager, mTransport); ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> false; + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); - boolean result = mBackupManagerService.isAppEligibleForBackup("app.package"); + boolean result = backupManagerService.isAppEligibleForBackup("app.package"); assertThat(result).isFalse(); } @@ -184,38 +194,43 @@ public class BackupManagerServiceRoboTest { @Test public void testIsAppEligibleForBackup_withoutPermission() throws Exception { mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME); + setUpCurrentTransport(mTransportManager, mTransport); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); expectThrows( SecurityException.class, - () -> mBackupManagerService.isAppEligibleForBackup("app.package")); + () -> backupManagerService.isAppEligibleForBackup("app.package")); } @Test public void testFilterAppsEligibleForBackup() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - TransportData transport = - TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME); + TransportMock transportMock = setUpCurrentTransport(mTransportManager, mTransport); Map<String, Boolean> packagesMap = new HashMap<>(); packagesMap.put("package.a", true); packagesMap.put("package.b", false); ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = packagesMap::get; + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); String[] packages = packagesMap.keySet().toArray(new String[packagesMap.size()]); - String[] filtered = mBackupManagerService.filterAppsEligibleForBackup(packages); + String[] filtered = backupManagerService.filterAppsEligibleForBackup(packages); assertThat(filtered).asList().containsExactly("package.a"); verify(mTransportManager) - .disposeOfTransportClient(eq(transport.transportClientMock), any()); + .disposeOfTransportClient(eq(transportMock.transportClient), any()); } @Test public void testFilterAppsEligibleForBackup_whenNoneIsEligible() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> false; + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); String[] filtered = - mBackupManagerService.filterAppsEligibleForBackup( + backupManagerService.filterAppsEligibleForBackup( new String[] {"package.a", "package.b"}); assertThat(filtered).isEmpty(); @@ -224,28 +239,35 @@ public class BackupManagerServiceRoboTest { @Test public void testFilterAppsEligibleForBackup_withoutPermission() throws Exception { mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME); + setUpCurrentTransport(mTransportManager, mTransport); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); expectThrows( SecurityException.class, () -> - mBackupManagerService.filterAppsEligibleForBackup( + backupManagerService.filterAppsEligibleForBackup( new String[] {"package.a", "package.b"})); } /* Tests for select transport */ + private ComponentName mNewTransportComponent; private TransportData mNewTransport; + private TransportMock mNewTransportMock; + private ComponentName mOldTransportComponent; private TransportData mOldTransport; - private ComponentName mNewTransportComponent; - private ISelectBackupTransportCallback mCallback; + private TransportMock mOldTransportMock; private void setUpForSelectTransport() throws Exception { - List<TransportData> transports = - TransportTestUtils.setUpTransports(mTransportManager, TRANSPORT_NAMES); - mNewTransport = transports.get(0); - mNewTransportComponent = mNewTransport.transportClientMock.getTransportComponent(); - mOldTransport = transports.get(1); + mNewTransport = backupTransport(); + mNewTransportComponent = mNewTransport.getTransportComponent(); + mOldTransport = d2dTransport(); + mOldTransportComponent = mOldTransport.getTransportComponent(); + List<TransportMock> transportMocks = + setUpTransports(mTransportManager, mNewTransport, mOldTransport, localTransport()); + mNewTransportMock = transportMocks.get(0); + mOldTransportMock = transportMocks.get(1); when(mTransportManager.selectTransport(eq(mNewTransport.transportName))) .thenReturn(mOldTransport.transportName); } @@ -254,22 +276,28 @@ public class BackupManagerServiceRoboTest { public void testSelectBackupTransport() throws Exception { setUpForSelectTransport(); mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); String oldTransport = - mBackupManagerService.selectBackupTransport(mNewTransport.transportName); + backupManagerService.selectBackupTransport(mNewTransport.transportName); assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName); assertThat(oldTransport).isEqualTo(mOldTransport.transportName); + verify(mTransportManager) + .disposeOfTransportClient(eq(mNewTransportMock.transportClient), any()); } @Test public void testSelectBackupTransport_withoutPermission() throws Exception { setUpForSelectTransport(); mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); expectThrows( SecurityException.class, - () -> mBackupManagerService.selectBackupTransport(mNewTransport.transportName)); + () -> backupManagerService.selectBackupTransport(mNewTransport.transportName)); } @Test @@ -278,13 +306,55 @@ public class BackupManagerServiceRoboTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent))) .thenReturn(BackupManager.SUCCESS); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); + ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); + + backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); + + mShadowBackupLooper.runToEndOfTasks(); + assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName); + verify(callback).onSuccess(eq(mNewTransport.transportName)); + verify(mTransportManager) + .disposeOfTransportClient(eq(mNewTransportMock.transportClient), any()); + } + + @Test + public void testSelectBackupTransportAsync_whenMandatoryTransport() throws Exception { + setUpForSelectTransport(); + ShadowBackupPolicyEnforcer.setMandatoryBackupTransport(mNewTransportComponent); + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent))) + .thenReturn(BackupManager.SUCCESS); ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); - mBackupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); + backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); mShadowBackupLooper.runToEndOfTasks(); assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName); verify(callback).onSuccess(eq(mNewTransport.transportName)); + verify(mTransportManager) + .disposeOfTransportClient(eq(mNewTransportMock.transportClient), any()); + } + + @Test + public void testSelectBackupTransportAsync_whenOtherThanMandatoryTransport() throws Exception { + setUpForSelectTransport(); + ShadowBackupPolicyEnforcer.setMandatoryBackupTransport(mOldTransportComponent); + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent))) + .thenReturn(BackupManager.SUCCESS); + ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); + + backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); + + mShadowBackupLooper.runToEndOfTasks(); + assertThat(getSettingsTransport()).isNotEqualTo(mNewTransport.transportName); + verify(callback).onFailure(eq(BackupManager.ERROR_BACKUP_NOT_ALLOWED)); } @Test @@ -293,9 +363,11 @@ public class BackupManagerServiceRoboTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent))) .thenReturn(BackupManager.ERROR_TRANSPORT_UNAVAILABLE); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); - mBackupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); + backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); mShadowBackupLooper.runToEndOfTasks(); assertThat(getSettingsTransport()).isNotEqualTo(mNewTransport.transportName); @@ -304,19 +376,19 @@ public class BackupManagerServiceRoboTest { @Test public void testSelectBackupTransportAsync_whenTransportGetsUnregistered() throws Exception { - TransportTestUtils.setUpTransports( - mTransportManager, new TransportData(TRANSPORT_NAME, null, null)); - ComponentName newTransportComponent = - TransportTestUtils.transportComponentName(TRANSPORT_NAME); + setUpTransports(mTransportManager, mTransport.unregistered()); + ComponentName newTransportComponent = mTransport.getTransportComponent(); mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.registerAndSelectTransport(eq(newTransportComponent))) .thenReturn(BackupManager.SUCCESS); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); - mBackupManagerService.selectBackupTransportAsync(newTransportComponent, callback); + backupManagerService.selectBackupTransportAsync(newTransportComponent, callback); mShadowBackupLooper.runToEndOfTasks(); - assertThat(getSettingsTransport()).isNotEqualTo(TRANSPORT_NAME); + assertThat(getSettingsTransport()).isNotEqualTo(mTransportName); verify(callback).onFailure(anyInt()); } @@ -324,13 +396,14 @@ public class BackupManagerServiceRoboTest { public void testSelectBackupTransportAsync_withoutPermission() throws Exception { setUpForSelectTransport(); mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - ComponentName newTransportComponent = - mNewTransport.transportClientMock.getTransportComponent(); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); + ComponentName newTransportComponent = mNewTransport.getTransportComponent(); expectThrows( SecurityException.class, () -> - mBackupManagerService.selectBackupTransportAsync( + backupManagerService.selectBackupTransportAsync( newTransportComponent, mock(ISelectBackupTransportCallback.class))); } @@ -338,4 +411,269 @@ public class BackupManagerServiceRoboTest { return ShadowSettings.ShadowSecure.getString( mContext.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT); } + + /* Tests for updating transport attributes */ + + private static final int PACKAGE_UID = 10; + private ComponentName mTransportComponent; + private int mTransportUid; + + private void setUpForUpdateTransportAttributes() throws Exception { + mTransportComponent = mTransport.getTransportComponent(); + String transportPackage = mTransportComponent.getPackageName(); + + ShadowPackageManager shadowPackageManager = shadowOf(mContext.getPackageManager()); + shadowPackageManager.addPackage(transportPackage); + shadowPackageManager.setPackagesForUid(PACKAGE_UID, transportPackage); + + mTransportUid = mContext.getPackageManager().getPackageUid(transportPackage, 0); + } + + @Test + public void + testUpdateTransportAttributes_whenTransportUidEqualsToCallingUid_callsThroughToTransportManager() + throws Exception { + setUpForUpdateTransportAttributes(); + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + Intent configurationIntent = new Intent(); + Intent dataManagementIntent = new Intent(); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); + + backupManagerService.updateTransportAttributes( + mTransportUid, + mTransportComponent, + mTransportName, + configurationIntent, + "currentDestinationString", + dataManagementIntent, + "dataManagementLabel"); + + verify(mTransportManager) + .updateTransportAttributes( + eq(mTransportComponent), + eq(mTransportName), + eq(configurationIntent), + eq("currentDestinationString"), + eq(dataManagementIntent), + eq("dataManagementLabel")); + } + + @Test + public void testUpdateTransportAttributes_whenTransportUidNotEqualToCallingUid_throwsException() + throws Exception { + setUpForUpdateTransportAttributes(); + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); + + expectThrows( + SecurityException.class, + () -> + backupManagerService.updateTransportAttributes( + mTransportUid + 1, + mTransportComponent, + mTransportName, + new Intent(), + "currentDestinationString", + new Intent(), + "dataManagementLabel")); + } + + @Test + public void testUpdateTransportAttributes_whenTransportComponentNull_throwsException() + throws Exception { + setUpForUpdateTransportAttributes(); + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); + + expectThrows( + RuntimeException.class, + () -> + backupManagerService.updateTransportAttributes( + mTransportUid, + null, + mTransportName, + new Intent(), + "currentDestinationString", + new Intent(), + "dataManagementLabel")); + } + + @Test + public void testUpdateTransportAttributes_whenNameNull_throwsException() throws Exception { + setUpForUpdateTransportAttributes(); + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); + + expectThrows( + RuntimeException.class, + () -> + backupManagerService.updateTransportAttributes( + mTransportUid, + mTransportComponent, + null, + new Intent(), + "currentDestinationString", + new Intent(), + "dataManagementLabel")); + } + + @Test + public void testUpdateTransportAttributes_whenCurrentDestinationStringNull_throwsException() + throws Exception { + setUpForUpdateTransportAttributes(); + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); + + expectThrows( + RuntimeException.class, + () -> + backupManagerService.updateTransportAttributes( + mTransportUid, + mTransportComponent, + mTransportName, + new Intent(), + null, + new Intent(), + "dataManagementLabel")); + } + + @Test + public void + testUpdateTransportAttributes_whenDataManagementArgumentsNullityDontMatch_throwsException() + throws Exception { + setUpForUpdateTransportAttributes(); + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); + + expectThrows( + RuntimeException.class, + () -> + backupManagerService.updateTransportAttributes( + mTransportUid, + mTransportComponent, + mTransportName, + new Intent(), + "currentDestinationString", + null, + "dataManagementLabel")); + + expectThrows( + RuntimeException.class, + () -> + backupManagerService.updateTransportAttributes( + mTransportUid, + mTransportComponent, + mTransportName, + new Intent(), + "currentDestinationString", + new Intent(), + null)); + } + + @Test + public void testUpdateTransportAttributes_whenPermissionGranted_callsThroughToTransportManager() + throws Exception { + setUpForUpdateTransportAttributes(); + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + Intent configurationIntent = new Intent(); + Intent dataManagementIntent = new Intent(); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); + + backupManagerService.updateTransportAttributes( + mTransportUid, + mTransportComponent, + mTransportName, + configurationIntent, + "currentDestinationString", + dataManagementIntent, + "dataManagementLabel"); + + verify(mTransportManager) + .updateTransportAttributes( + eq(mTransportComponent), + eq(mTransportName), + eq(configurationIntent), + eq("currentDestinationString"), + eq(dataManagementIntent), + eq("dataManagementLabel")); + } + + @Test + public void testUpdateTransportAttributes_whenPermissionDenied_throwsSecurityException() + throws Exception { + setUpForUpdateTransportAttributes(); + mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); + + expectThrows( + SecurityException.class, + () -> + backupManagerService.updateTransportAttributes( + mTransportUid, + mTransportComponent, + mTransportName, + new Intent(), + "currentDestinationString", + new Intent(), + "dataManagementLabel")); + } + + /* Miscellaneous tests */ + + @Test + public void testConstructor_postRegisterTransports() { + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + + createBackupManagerService(); + + mShadowBackupLooper.runToEndOfTasks(); + verify(mTransportManager).registerTransports(); + } + + @Test + public void testConstructor_doesNotRegisterTransportsSynchronously() { + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + + createBackupManagerService(); + + // Operations posted to mBackupThread only run with mShadowBackupLooper.runToEndOfTasks() + verify(mTransportManager, never()).registerTransports(); + } + + private RefactoredBackupManagerService createBackupManagerService() { + return new RefactoredBackupManagerService( + mContext, + new Trampoline(mContext), + mBackupThread, + mBaseStateDir, + mDataDir, + mTransportManager); + } + + private RefactoredBackupManagerService createInitializedBackupManagerService() { + RefactoredBackupManagerService backupManagerService = + new RefactoredBackupManagerService( + mContext, + new Trampoline(mContext), + mBackupThread, + mBaseStateDir, + mDataDir, + mTransportManager); + mShadowBackupLooper.runToEndOfTasks(); + // Handler instances have their own clock, so advancing looper (with runToEndOfTasks()) + // above does NOT advance the handlers' clock, hence whenever a handler post messages with + // specific time to the looper the time of those messages will be before the looper's time. + // To fix this we advance SystemClock as well since that is from where the handlers read + // time. + ShadowSystemClock.setCurrentTimeMillis(mShadowBackupLooper.getScheduler().getCurrentTime()); + return backupManagerService; + } } diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java index acd670f6748c..cf0bc235dc10 100644 --- a/services/robotests/src/com/android/server/backup/TransportManagerTest.java +++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java @@ -16,108 +16,97 @@ package com.android.server.backup; +import static com.android.server.backup.testing.TransportData.genericTransport; +import static com.android.server.backup.testing.TransportTestUtils.mockTransport; +import static com.android.server.backup.testing.TransportTestUtils.setUpTransportsForTransportManager; + import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.shadow.api.Shadow.extract; import static org.testng.Assert.expectThrows; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; +import static java.util.stream.Stream.concat; + import android.annotation.Nullable; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.os.IBinder; -import android.os.RemoteException; import android.platform.test.annotations.Presubmit; -import com.android.internal.backup.IBackupTransport; -import com.android.server.backup.testing.ShadowBackupTransportStub; import com.android.server.backup.testing.ShadowContextImplForBackup; -import com.android.server.backup.testing.ShadowPackageManagerForBackup; -import com.android.server.backup.testing.TransportBoundListenerStub; +import com.android.server.testing.shadows.FrameworkShadowPackageManager; +import com.android.server.backup.testing.TransportData; +import com.android.server.backup.testing.TransportTestUtils.TransportMock; +import com.android.server.backup.transport.OnTransportRegisteredListener; import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportClientManager; import com.android.server.backup.transport.TransportNotRegisteredException; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderClasses; +import com.android.server.testing.shadows.FrameworkShadowContextImpl; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowLog; -import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowPackageManager; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; @RunWith(FrameworkRobolectricTestRunner.class) @Config( - manifest = Config.NONE, - sdk = 26, - shadows = { - ShadowContextImplForBackup.class, - ShadowBackupTransportStub.class, - ShadowPackageManagerForBackup.class - } + manifest = Config.NONE, + sdk = 26, + shadows = {FrameworkShadowPackageManager.class, FrameworkShadowContextImpl.class} ) @SystemLoaderClasses({TransportManager.class}) @Presubmit public class TransportManagerTest { - private static final String PACKAGE_NAME = "some.package.name"; - private static final String ANOTHER_PACKAGE_NAME = "another.package.name"; + private static final String PACKAGE_A = "some.package.a"; + private static final String PACKAGE_B = "some.package.b"; - private TransportInfo mTransport1; - private TransportInfo mTransport2; + @Mock private OnTransportRegisteredListener mListener; + @Mock private TransportClientManager mTransportClientManager; + private TransportData mTransportA1; + private TransportData mTransportA2; + private TransportData mTransportB1; - private ShadowPackageManager mPackageManagerShadow; - - private final TransportBoundListenerStub mTransportBoundListenerStub = - new TransportBoundListenerStub(true); + private ShadowPackageManager mShadowPackageManager; + private Context mContext; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - ShadowLog.stream = System.out; - - mPackageManagerShadow = - (ShadowPackageManagerForBackup) + mShadowPackageManager = + (FrameworkShadowPackageManager) extract(RuntimeEnvironment.application.getPackageManager()); + mContext = RuntimeEnvironment.application.getApplicationContext(); - mTransport1 = new TransportInfo( - PACKAGE_NAME, - "transport1.name", - new Intent(), - "currentDestinationString", - new Intent(), - "dataManagementLabel"); - mTransport2 = new TransportInfo( - PACKAGE_NAME, - "transport2.name", - new Intent(), - "currentDestinationString", - new Intent(), - "dataManagementLabel"); - - ShadowContextImplForBackup.sComponentBinderMap.put(mTransport1.componentName, - mTransport1.binder); - ShadowContextImplForBackup.sComponentBinderMap.put(mTransport2.componentName, - mTransport2.binder); - ShadowBackupTransportStub.sBinderTransportMap.put( - mTransport1.binder, mTransport1.binderInterface); - ShadowBackupTransportStub.sBinderTransportMap.put( - mTransport2.binder, mTransport2.binderInterface); + mTransportA1 = genericTransport(PACKAGE_A, "TransportFoo"); + mTransportA2 = genericTransport(PACKAGE_A, "TransportBar"); + mTransportB1 = genericTransport(PACKAGE_B, "TransportBaz"); } @After @@ -126,560 +115,441 @@ public class TransportManagerTest { } @Test - public void onPackageAdded_bindsToAllTransports() throws Exception { - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(Arrays.asList( - mTransport1.componentName, mTransport2.componentName)), - null /* defaultTransport */, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isTrue(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isTrue(); - } + public void testRegisterTransports() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpPackage(PACKAGE_B, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2, mTransportB1); + TransportManager transportManager = + createTransportManager(mTransportA1, mTransportA2, mTransportB1); - @Test - public void onPackageAdded_oneTransportUnavailable_bindsToOnlyOneTransport() throws Exception { - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); - - ShadowContextImplForBackup.sUnbindableComponents.add(mTransport1.componentName); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(Arrays.asList( - mTransport1.componentName, mTransport2.componentName)), - null /* defaultTransport */, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Collections.singleton(mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Collections.singleton(mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isTrue(); + transportManager.registerTransports(); + + assertRegisteredTransports( + transportManager, asList(mTransportA1, mTransportA2, mTransportB1)); + + verify(mListener) + .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName); + verify(mListener) + .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName); + verify(mListener) + .onTransportRegistered(mTransportB1.transportName, mTransportB1.transportDirName); } @Test - public void onPackageAdded_whitelistIsNull_doesNotBindToTransports() throws Exception { - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - null /* whitelist */, - null /* defaultTransport */, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).isEmpty(); - assertThat(transportManager.getBoundTransportNames()).isEmpty(); - assertThat(mTransportBoundListenerStub.isCalled()).isFalse(); + public void + testRegisterTransports_whenOneTransportUnavailable_doesNotRegisterUnavailableTransport() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + TransportData transport1 = mTransportA1.unavailable(); + TransportData transport2 = mTransportA2; + setUpTransports(transport1, transport2); + TransportManager transportManager = createTransportManager(transport1, transport2); + + transportManager.registerTransports(); + + assertRegisteredTransports(transportManager, singletonList(transport2)); + verify(mListener, never()) + .onTransportRegistered(transport1.transportName, transport1.transportDirName); + verify(mListener) + .onTransportRegistered(transport2.transportName, transport2.transportDirName); } @Test - public void onPackageAdded_onlyOneTransportWhitelisted_onlyConnectsToWhitelistedTransport() + public void testRegisterTransports_whenWhitelistIsEmpty_doesNotRegisterTransports() throws Exception { - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(Collections.singleton(mTransport2.componentName)), - null /* defaultTransport */, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Collections.singleton(mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Collections.singleton(mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isTrue(); - } + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(null); - @Test - public void onPackageAdded_appIsNotPrivileged_doesNotBindToTransports() throws Exception { - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), 0); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(Arrays.asList( - mTransport1.componentName, mTransport2.componentName)), - null /* defaultTransport */, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).isEmpty(); - assertThat(transportManager.getBoundTransportNames()).isEmpty(); - assertThat(mTransportBoundListenerStub.isCalled()).isFalse(); + transportManager.registerTransports(); + + assertRegisteredTransports(transportManager, emptyList()); + verify(mListener, never()).onTransportRegistered(any(), any()); } @Test - public void onPackageRemoved_transportsUnbound() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + public void + testRegisterTransports_whenOnlyOneTransportWhitelisted_onlyRegistersWhitelistedTransport() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(null, mTransportA1); - transportManager.onPackageRemoved(PACKAGE_NAME); + transportManager.registerTransports(); - assertThat(transportManager.getAllTransportComponents()).isEmpty(); - assertThat(transportManager.getBoundTransportNames()).isEmpty(); + assertRegisteredTransports(transportManager, singletonList(mTransportA1)); + verify(mListener) + .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName); + verify(mListener, never()) + .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName); } @Test - public void onPackageRemoved_incorrectPackageName_nothingHappens() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + public void testRegisterTransports_whenAppIsNotPrivileged_doesNotRegisterTransports() + throws Exception { + // Note ApplicationInfo.PRIVATE_FLAG_PRIVILEGED is missing from flags + setUpPackage(PACKAGE_A, 0); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = + createTransportManager(null, mTransportA1, mTransportA2); - transportManager.onPackageRemoved(ANOTHER_PACKAGE_NAME); + transportManager.registerTransports(); - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); + assertRegisteredTransports(transportManager, emptyList()); + verify(mListener, never()).onTransportRegistered(any(), any()); } @Test - public void onPackageChanged_oneComponentChanged_onlyOneTransportRebound() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - transportManager.onPackageChanged(PACKAGE_NAME, new String[]{mTransport2.name}); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isTrue(); - } + public void testOnPackageAdded_registerTransports() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); - @Test - public void onPackageChanged_nothingChanged_noTransportsRebound() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - transportManager.onPackageChanged(PACKAGE_NAME, new String[0]); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isFalse(); - } + transportManager.onPackageAdded(PACKAGE_A); - @Test - public void onPackageChanged_unexpectedComponentChanged_noTransportsRebound() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - transportManager.onPackageChanged(PACKAGE_NAME, new String[]{"unexpected.component"}); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isFalse(); + assertRegisteredTransports(transportManager, asList(mTransportA1)); + verify(mListener) + .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName); } @Test - public void onPackageChanged_transportsRebound() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - transportManager.onPackageChanged(PACKAGE_NAME, new String[]{mTransport2.name}); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isTrue(); - } + public void testOnPackageRemoved_unregisterTransports() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpPackage(PACKAGE_B, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportB1); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportB1); + transportManager.registerTransports(); - @Test - public void getTransportBinder_returnsCorrectBinder() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - assertThat(transportManager.getTransportBinder(mTransport1.name)).isEqualTo( - mTransport1.binderInterface); - assertThat(transportManager.getTransportBinder(mTransport2.name)).isEqualTo( - mTransport2.binderInterface); + transportManager.onPackageRemoved(PACKAGE_A); + + assertRegisteredTransports(transportManager, singletonList(mTransportB1)); } @Test - public void getTransportBinder_incorrectTransportName_returnsNull() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + public void testOnPackageRemoved_whenUnknownPackage_nothingHappens() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); + transportManager.registerTransports(); + + transportManager.onPackageRemoved(PACKAGE_A + "unknown"); - assertThat(transportManager.getTransportBinder("incorrect.transport")).isNull(); + assertRegisteredTransports(transportManager, singletonList(mTransportA1)); } @Test - public void getTransportBinder_oneTransportUnavailable_returnsCorrectBinder() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2), - Collections.singletonList(mTransport1), mTransport1.name); + public void testOnPackageChanged_whenOneComponentChanged_onlyOneTransportReRegistered() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); + // Reset listener to verify calls after registerTransports() above + reset(mListener); + + transportManager.onPackageChanged( + PACKAGE_A, mTransportA1.getTransportComponent().getClassName()); - assertThat(transportManager.getTransportBinder(mTransport1.name)).isNull(); - assertThat(transportManager.getTransportBinder(mTransport2.name)).isEqualTo( - mTransport2.binderInterface); + assertRegisteredTransports(transportManager, asList(mTransportA1, mTransportA2)); + verify(mListener) + .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName); + verify(mListener, never()) + .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName); } @Test - public void getCurrentTransport_selectTransportNotCalled_returnsDefaultTransport() + public void testOnPackageChanged_whenNoComponentsChanged_doesNotRegisterTransports() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); + transportManager.registerTransports(); + reset(mListener); - assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport1.name); + transportManager.onPackageChanged(PACKAGE_A); + + assertRegisteredTransports(transportManager, singletonList(mTransportA1)); + verify(mListener, never()).onTransportRegistered(any(), any()); } @Test - public void getCurrentTransport_selectTransportCalled_returnsCorrectTransport() + public void testOnPackageChanged_whenUnknownComponentChanged_noTransportsRegistered() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport1.name); + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); + transportManager.registerTransports(); + reset(mListener); - transportManager.selectTransport(mTransport2.name); + transportManager.onPackageChanged(PACKAGE_A, PACKAGE_A + ".UnknownComponent"); - assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport2.name); + assertRegisteredTransports(transportManager, singletonList(mTransportA1)); + verify(mListener, never()).onTransportRegistered(any(), any()); } @Test - public void getCurrentTransportBinder_returnsCorrectBinder() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + public void testOnPackageChanged_reRegisterTransports() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); + reset(mListener); - assertThat(transportManager.getCurrentTransportBinder()) - .isEqualTo(mTransport1.binderInterface); + transportManager.onPackageChanged( + PACKAGE_A, + mTransportA1.getTransportComponent().getClassName(), + mTransportA2.getTransportComponent().getClassName()); + + assertRegisteredTransports(transportManager, asList(mTransportA1, mTransportA2)); + verify(mListener) + .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName); + verify(mListener) + .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName); } @Test - public void getCurrentTransportBinder_transportNotBound_returnsNull() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2), - Collections.singletonList(mTransport1), mTransport2.name); + public void testGetCurrentTransportName_whenSelectTransportNotCalled_returnsDefaultTransport() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); - transportManager.selectTransport(mTransport1.name); + String currentTransportName = transportManager.getCurrentTransportName(); - assertThat(transportManager.getCurrentTransportBinder()).isNull(); + assertThat(currentTransportName).isEqualTo(mTransportA1.transportName); } @Test - public void getTransportName_returnsCorrectTransportName() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - assertThat(transportManager.getTransportName(mTransport1.binderInterface)) - .isEqualTo(mTransport1.name); - assertThat(transportManager.getTransportName(mTransport2.binderInterface)) - .isEqualTo(mTransport2.name); - } + public void testGetCurrentTransport_whenSelectTransportCalled_returnsSelectedTransport() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); + transportManager.selectTransport(mTransportA2.transportName); - @Test - public void getTransportName_transportNotBound_returnsNull() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2), - Collections.singletonList(mTransport1), mTransport1.name); + String currentTransportName = transportManager.getCurrentTransportName(); - assertThat(transportManager.getTransportName(mTransport1.binderInterface)).isNull(); - assertThat(transportManager.getTransportName(mTransport2.binderInterface)) - .isEqualTo(mTransport2.name); + assertThat(currentTransportName).isEqualTo(mTransportA2.transportName); } @Test - public void getTransportWhitelist_returnsCorrectWhiteList() throws Exception { - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(Arrays.asList(mTransport1.componentName, mTransport2.componentName)), - mTransport1.name, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - - assertThat(transportManager.getTransportWhitelist()).containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - } + public void testGetTransportWhitelist() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); - @Test - public void getTransportWhitelist_whiteListIsNull_returnsEmptyArray() throws Exception { - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - null /* whitelist */, - mTransport1.name, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - - assertThat(transportManager.getTransportWhitelist()).isEmpty(); + Set<ComponentName> transportWhitelist = transportManager.getTransportWhitelist(); + + assertThat(transportWhitelist) + .containsExactlyElementsIn( + asList( + mTransportA1.getTransportComponent(), + mTransportA2.getTransportComponent())); } @Test - public void selectTransport_setsTransportCorrectlyAndReturnsPreviousTransport() - throws Exception { - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - null /* whitelist */, - mTransport1.name, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - - assertThat(transportManager.selectTransport(mTransport2.name)).isEqualTo(mTransport1.name); - assertThat(transportManager.selectTransport(mTransport1.name)).isEqualTo(mTransport2.name); + public void testSelectTransport() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = + createTransportManager(null, mTransportA1, mTransportA2); + + String transport1 = transportManager.selectTransport(mTransportA1.transportName); + String transport2 = transportManager.selectTransport(mTransportA2.transportName); + + assertThat(transport1).isNull(); + assertThat(transport2).isEqualTo(mTransportA1.transportName); } @Test - public void getTransportClient_forRegisteredTransport_returnCorrectly() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + public void testGetTransportClient_forRegisteredTransport() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); TransportClient transportClient = - transportManager.getTransportClient(mTransport1.name, "caller"); + transportManager.getTransportClient(mTransportA1.transportName, "caller"); - assertThat(transportClient.getTransportComponent()).isEqualTo(mTransport1.componentName); + assertThat(transportClient.getTransportComponent()) + .isEqualTo(mTransportA1.getTransportComponent()); } @Test - public void getTransportClient_forOldNameOfTransportThatChangedName_returnsNull() + public void testGetTransportClient_forOldNameOfTransportThatChangedName_returnsNull() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); transportManager.updateTransportAttributes( - mTransport1.componentName, "newName", null, "destinationString", null, null); + mTransportA1.getTransportComponent(), + "newName", + null, + "destinationString", + null, + null); TransportClient transportClient = - transportManager.getTransportClient(mTransport1.name, "caller"); + transportManager.getTransportClient(mTransportA1.transportName, "caller"); assertThat(transportClient).isNull(); } @Test - public void getTransportClient_forNewNameOfTransportThatChangedName_returnsCorrectly() + public void testGetTransportClient_forNewNameOfTransportThatChangedName_returnsCorrectly() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); transportManager.updateTransportAttributes( - mTransport1.componentName, "newName", null, "destinationString", null, null); + mTransportA1.getTransportComponent(), + "newName", + null, + "destinationString", + null, + null); - TransportClient transportClient = - transportManager.getTransportClient("newName", "caller"); + TransportClient transportClient = transportManager.getTransportClient("newName", "caller"); - assertThat(transportClient.getTransportComponent()).isEqualTo(mTransport1.componentName); + assertThat(transportClient.getTransportComponent()) + .isEqualTo(mTransportA1.getTransportComponent()); } @Test - public void getTransportName_forTransportThatChangedName_returnsNewName() - throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + public void testGetTransportName_forTransportThatChangedName_returnsNewName() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); transportManager.updateTransportAttributes( - mTransport1.componentName, "newName", null, "destinationString", null, null); + mTransportA1.getTransportComponent(), + "newName", + null, + "destinationString", + null, + null); - String transportName = transportManager.getTransportName(mTransport1.componentName); + String transportName = + transportManager.getTransportName(mTransportA1.getTransportComponent()); assertThat(transportName).isEqualTo("newName"); } @Test - public void isTransportRegistered_returnsCorrectly() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Collections.singletonList(mTransport1), - Collections.singletonList(mTransport2), - mTransport1.name); + public void testIsTransportRegistered() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); + + boolean isTransportA1Registered = + transportManager.isTransportRegistered(mTransportA1.transportName); + boolean isTransportA2Registered = + transportManager.isTransportRegistered(mTransportA2.transportName); - assertThat(transportManager.isTransportRegistered(mTransport1.name)).isTrue(); - assertThat(transportManager.isTransportRegistered(mTransport2.name)).isFalse(); + assertThat(isTransportA1Registered).isTrue(); + assertThat(isTransportA2Registered).isFalse(); } @Test - public void getTransportAttributes_forRegisteredTransport_returnsCorrectValues() + public void testGetTransportAttributes_forRegisteredTransport_returnsCorrectValues() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Collections.singletonList(mTransport1), - mTransport1.name); - - assertThat(transportManager.getTransportConfigurationIntent(mTransport1.name)) - .isEqualTo(mTransport1.binderInterface.configurationIntent()); - assertThat(transportManager.getTransportDataManagementIntent(mTransport1.name)) - .isEqualTo(mTransport1.binderInterface.dataManagementIntent()); - assertThat(transportManager.getTransportDataManagementLabel(mTransport1.name)) - .isEqualTo(mTransport1.binderInterface.dataManagementLabel()); - assertThat(transportManager.getTransportDirName(mTransport1.name)) - .isEqualTo(mTransport1.binderInterface.transportDirName()); + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); + transportManager.registerTransports(); + + Intent configurationIntent = + transportManager.getTransportConfigurationIntent(mTransportA1.transportName); + Intent dataManagementIntent = + transportManager.getTransportDataManagementIntent(mTransportA1.transportName); + String dataManagementLabel = + transportManager.getTransportDataManagementLabel(mTransportA1.transportName); + String transportDirName = transportManager.getTransportDirName(mTransportA1.transportName); + + assertThat(configurationIntent).isEqualTo(mTransportA1.configurationIntent); + assertThat(dataManagementIntent).isEqualTo(mTransportA1.dataManagementIntent); + assertThat(dataManagementLabel).isEqualTo(mTransportA1.dataManagementLabel); + assertThat(transportDirName).isEqualTo(mTransportA1.transportDirName); } @Test - public void getTransportAttributes_forUnregisteredTransport_throws() - throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Collections.singletonList(mTransport1), - Collections.singletonList(mTransport2), - mTransport1.name); + public void testGetTransportAttributes_forUnregisteredTransport_throws() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); + transportManager.registerTransports(); expectThrows( TransportNotRegisteredException.class, - () -> transportManager.getTransportConfigurationIntent(mTransport2.name)); + () -> transportManager.getTransportConfigurationIntent(mTransportA2.transportName)); expectThrows( TransportNotRegisteredException.class, - () -> transportManager.getTransportDataManagementIntent( - mTransport2.name)); + () -> + transportManager.getTransportDataManagementIntent( + mTransportA2.transportName)); expectThrows( TransportNotRegisteredException.class, - () -> transportManager.getTransportDataManagementLabel(mTransport2.name)); + () -> transportManager.getTransportDataManagementLabel(mTransportA2.transportName)); expectThrows( TransportNotRegisteredException.class, - () -> transportManager.getTransportDirName(mTransport2.name)); + () -> transportManager.getTransportDirName(mTransportA2.transportName)); + } + + private List<TransportMock> setUpTransports(TransportData... transports) throws Exception { + setUpTransportsForTransportManager(mShadowPackageManager, transports); + List<TransportMock> transportMocks = new ArrayList<>(transports.length); + for (TransportData transport : transports) { + TransportMock transportMock = mockTransport(transport); + when(mTransportClientManager.getTransportClient( + eq(transport.getTransportComponent()), any())) + .thenReturn(transportMock.transportClient); + transportMocks.add(transportMock); + } + return transportMocks; } - private void setUpPackageWithTransports(String packageName, List<TransportInfo> transports, - int flags) throws Exception { + private void setUpPackage(String packageName, int flags) { PackageInfo packageInfo = new PackageInfo(); packageInfo.packageName = packageName; packageInfo.applicationInfo = new ApplicationInfo(); packageInfo.applicationInfo.privateFlags = flags; - - mPackageManagerShadow.addPackage(packageInfo); - - List<ResolveInfo> transportsInfo = new ArrayList<>(); - for (TransportInfo transport : transports) { - ResolveInfo info = new ResolveInfo(); - info.serviceInfo = new ServiceInfo(); - info.serviceInfo.packageName = packageName; - info.serviceInfo.name = transport.name; - transportsInfo.add(info); - } - - Intent intent = new Intent(TransportManager.SERVICE_ACTION_TRANSPORT_HOST); - intent.setPackage(packageName); - - mPackageManagerShadow.addResolveInfoForIntent(intent, transportsInfo); - } - - private TransportManager createTransportManagerAndSetUpTransports( - List<TransportInfo> availableTransports, String defaultTransportName) throws Exception { - return createTransportManagerAndSetUpTransports(availableTransports, - Collections.<TransportInfo>emptyList(), defaultTransportName); + mShadowPackageManager.addPackage(packageInfo); } - private TransportManager createTransportManagerAndSetUpTransports( - List<TransportInfo> availableTransports, List<TransportInfo> unavailableTransports, - String defaultTransportName) - throws Exception { - List<String> availableTransportsNames = new ArrayList<>(); - List<ComponentName> availableTransportsComponentNames = new ArrayList<>(); - for (TransportInfo transport : availableTransports) { - availableTransportsNames.add(transport.name); - availableTransportsComponentNames.add(transport.componentName); - } - - List<ComponentName> allTransportsComponentNames = new ArrayList<>(); - allTransportsComponentNames.addAll(availableTransportsComponentNames); - for (TransportInfo transport : unavailableTransports) { - allTransportsComponentNames.add(transport.componentName); - } - - for (TransportInfo transport : unavailableTransports) { - ShadowContextImplForBackup.sUnbindableComponents.add(transport.componentName); - } - - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(allTransportsComponentNames), - defaultTransportName, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - availableTransportsComponentNames); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - availableTransportsNames); - for (TransportInfo transport : availableTransports) { - assertThat(mTransportBoundListenerStub.isCalledForTransport(transport.binderInterface)) - .isTrue(); - } - for (TransportInfo transport : unavailableTransports) { - assertThat(mTransportBoundListenerStub.isCalledForTransport(transport.binderInterface)) - .isFalse(); - } - - mTransportBoundListenerStub.resetState(); - + private TransportManager createTransportManager( + @Nullable TransportData selectedTransport, TransportData... transports) { + Set<ComponentName> whitelist = + concat(Stream.of(selectedTransport), Stream.of(transports)) + .filter(Objects::nonNull) + .map(TransportData::getTransportComponent) + .collect(toSet()); + TransportManager transportManager = + new TransportManager( + mContext, + whitelist, + selectedTransport != null ? selectedTransport.transportName : null, + mTransportClientManager); + transportManager.setOnTransportRegisteredListener(mListener); return transportManager; } - private static class TransportInfo { - public final String packageName; - public final String name; - public final ComponentName componentName; - public final IBackupTransport binderInterface; - public final IBinder binder; - - TransportInfo( - String packageName, - String name, - @Nullable Intent configurationIntent, - String currentDestinationString, - @Nullable Intent dataManagementIntent, - String dataManagementLabel) { - this.packageName = packageName; - this.name = name; - this.componentName = new ComponentName(packageName, name); - this.binder = mock(IBinder.class); - IBackupTransport transport = mock(IBackupTransport.class); - try { - when(transport.name()).thenReturn(name); - when(transport.configurationIntent()).thenReturn(configurationIntent); - when(transport.currentDestinationString()).thenReturn(currentDestinationString); - when(transport.dataManagementIntent()).thenReturn(dataManagementIntent); - when(transport.dataManagementLabel()).thenReturn(dataManagementLabel); - } catch (RemoteException e) { - // Only here to mock methods that throw RemoteException - } - this.binderInterface = transport; - } + private void assertRegisteredTransports( + TransportManager transportManager, List<TransportData> transports) { + assertThat(transportManager.getRegisteredTransportComponents()) + .asList() + .containsExactlyElementsIn( + transports + .stream() + .map(TransportData::getTransportComponent) + .collect(toList())); + assertThat(transportManager.getRegisteredTransportNames()) + .asList() + .containsExactlyElementsIn( + transports.stream().map(t -> t.transportName).collect(toList())); } - } diff --git a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java index dfca9010130f..ace0441c8a4a 100644 --- a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java +++ b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java @@ -19,8 +19,10 @@ package com.android.server.backup.internal; import static android.app.backup.BackupTransport.TRANSPORT_ERROR; import static android.app.backup.BackupTransport.TRANSPORT_OK; -import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAME; -import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAMES; +import static com.android.server.backup.testing.TransportData.backupTransport; +import static com.android.server.backup.testing.TransportData.d2dTransport; +import static com.android.server.backup.testing.TransportData.localTransport; +import static com.android.server.backup.testing.TransportTestUtils.setUpTransports; import static com.google.common.truth.Truth.assertThat; @@ -28,7 +30,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -44,7 +45,8 @@ import com.android.internal.backup.IBackupTransport; import com.android.server.backup.RefactoredBackupManagerService; import com.android.server.backup.TransportManager; import com.android.server.backup.testing.TransportTestUtils; -import com.android.server.backup.testing.TransportTestUtils.TransportData; +import com.android.server.backup.testing.TransportData; +import com.android.server.backup.testing.TransportTestUtils.TransportMock; import com.android.server.backup.transport.TransportClient; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderClasses; @@ -58,7 +60,10 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import java.io.File; +import java.util.Arrays; +import java.util.Iterator; import java.util.List; +import java.util.stream.Stream; @RunWith(FrameworkRobolectricTestRunner.class) @Config(manifest = Config.NONE, sdk = 26) @@ -68,16 +73,21 @@ public class PerformInitializeTaskTest { @Mock private RefactoredBackupManagerService mBackupManagerService; @Mock private TransportManager mTransportManager; @Mock private OnTaskFinishedListener mListener; - @Mock private IBackupTransport mTransport; + @Mock private IBackupTransport mTransportBinder; @Mock private IBackupObserver mObserver; @Mock private AlarmManager mAlarmManager; @Mock private PendingIntent mRunInitIntent; private File mBaseStateDir; + private TransportData mTransport; + private String mTransportName; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mTransport = backupTransport(); + mTransportName = mTransport.transportName; + Application context = RuntimeEnvironment.application; mBaseStateDir = new File(context.getCacheDir(), "base_state_dir"); assertThat(mBaseStateDir.mkdir()).isTrue(); @@ -88,82 +98,76 @@ public class PerformInitializeTaskTest { @Test public void testRun_callsTransportCorrectly() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mTransport).initializeDevice(); - verify(mTransport).finishBackup(); + verify(mTransportBinder).initializeDevice(); + verify(mTransportBinder).finishBackup(); } @Test public void testRun_callsBackupManagerCorrectly() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); verify(mBackupManagerService) - .recordInitPending( - false, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME)); + .recordInitPending(false, mTransportName, mTransport.transportDirName); verify(mBackupManagerService) - .resetBackupState( - eq( - new File( - mBaseStateDir, - TransportTestUtils.transportDirName(TRANSPORT_NAME)))); + .resetBackupState(eq(new File(mBaseStateDir, mTransport.transportDirName))); } @Test public void testRun_callsObserverAndListenerCorrectly() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_OK)); + verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_OK)); verify(mObserver).backupFinished(eq(TRANSPORT_OK)); verify(mListener).onFinished(any()); } @Test public void testRun_whenInitializeDeviceFails() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_ERROR, 0); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_ERROR, 0); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mTransport).initializeDevice(); - verify(mTransport, never()).finishBackup(); + verify(mTransportBinder).initializeDevice(); + verify(mTransportBinder, never()).finishBackup(); verify(mBackupManagerService) - .recordInitPending( - true, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME)); + .recordInitPending(true, mTransportName, mTransport.transportDirName); } @Test public void testRun_whenInitializeDeviceFails_callsObserverAndListenerCorrectly() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_ERROR, 0); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_ERROR, 0); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR)); + verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_ERROR)); verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); verify(mListener).onFinished(any()); } @Test public void testRun_whenInitializeDeviceFails_schedulesAlarm() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_ERROR, 0); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_ERROR, 0); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); @@ -172,37 +176,36 @@ public class PerformInitializeTaskTest { @Test public void testRun_whenFinishBackupFails() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mTransport).initializeDevice(); - verify(mTransport).finishBackup(); + verify(mTransportBinder).initializeDevice(); + verify(mTransportBinder).finishBackup(); verify(mBackupManagerService) - .recordInitPending( - true, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME)); + .recordInitPending(true, mTransportName, mTransport.transportDirName); } @Test public void testRun_whenFinishBackupFails_callsObserverAndListenerCorrectly() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR)); + verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_ERROR)); verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); verify(mListener).onFinished(any()); } @Test public void testRun_whenFinishBackupFails_schedulesAlarm() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); @@ -211,64 +214,76 @@ public class PerformInitializeTaskTest { @Test public void testRun_whenOnlyOneTransportFails() throws Exception { - List<TransportData> transports = - TransportTestUtils.setUpTransports( - mTransportManager, TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); - configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0); - configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK); + TransportData transport1 = backupTransport(); + TransportData transport2 = d2dTransport(); + List<TransportMock> transportMocks = + setUpTransports(mTransportManager, transport1, transport2); + configureTransport(transportMocks.get(0).transport, TRANSPORT_ERROR, 0); + configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK); PerformInitializeTask performInitializeTask = - createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + createPerformInitializeTask(transport1.transportName, transport2.transportName); performInitializeTask.run(); - verify(transports.get(1).transportMock).initializeDevice(); - verify(mObserver).onResult(eq(TRANSPORT_NAMES[0]), eq(TRANSPORT_ERROR)); - verify(mObserver).onResult(eq(TRANSPORT_NAMES[1]), eq(TRANSPORT_OK)); + verify(transportMocks.get(1).transport).initializeDevice(); + verify(mObserver).onResult(eq(transport1.transportName), eq(TRANSPORT_ERROR)); + verify(mObserver).onResult(eq(transport2.transportName), eq(TRANSPORT_OK)); verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); } @Test public void testRun_withMultipleTransports() throws Exception { - List<TransportData> transports = - TransportTestUtils.setUpTransports(mTransportManager, TRANSPORT_NAMES); - configureTransport(transports.get(0).transportMock, TRANSPORT_OK, TRANSPORT_OK); - configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK); - configureTransport(transports.get(2).transportMock, TRANSPORT_OK, TRANSPORT_OK); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAMES); + List<TransportMock> transportMocks = + setUpTransports( + mTransportManager, backupTransport(), d2dTransport(), localTransport()); + configureTransport(transportMocks.get(0).transport, TRANSPORT_OK, TRANSPORT_OK); + configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK); + configureTransport(transportMocks.get(2).transport, TRANSPORT_OK, TRANSPORT_OK); + String[] transportNames = + Stream.of(new TransportData[] {backupTransport(), d2dTransport(), localTransport()}) + .map(t -> t.transportName) + .toArray(String[]::new); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(transportNames); performInitializeTask.run(); - for (TransportData transport : transports) { + Iterator<TransportData> transportsIterator = + Arrays.asList( + new TransportData[] { + backupTransport(), d2dTransport(), localTransport() + }) + .iterator(); + for (TransportMock transportMock : transportMocks) { + TransportData transport = transportsIterator.next(); verify(mTransportManager).getTransportClient(eq(transport.transportName), any()); verify(mTransportManager) - .disposeOfTransportClient(eq(transport.transportClientMock), any()); + .disposeOfTransportClient(eq(transportMock.transportClient), any()); } } @Test public void testRun_whenOnlyOneTransportFails_disposesAllTransports() throws Exception { - List<TransportData> transports = - TransportTestUtils.setUpTransports( - mTransportManager, TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); - configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0); - configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK); + TransportData transport1 = backupTransport(); + TransportData transport2 = d2dTransport(); + List<TransportMock> transportMocks = + setUpTransports(mTransportManager, transport1, transport2); + configureTransport(transportMocks.get(0).transport, TRANSPORT_ERROR, 0); + configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK); PerformInitializeTask performInitializeTask = - createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + createPerformInitializeTask(transport1.transportName, transport2.transportName); performInitializeTask.run(); verify(mTransportManager) - .disposeOfTransportClient(eq(transports.get(0).transportClientMock), any()); + .disposeOfTransportClient(eq(transportMocks.get(0).transportClient), any()); verify(mTransportManager) - .disposeOfTransportClient(eq(transports.get(1).transportClientMock), any()); + .disposeOfTransportClient(eq(transportMocks.get(1).transportClient), any()); } @Test public void testRun_whenTransportNotRegistered() throws Exception { - TransportTestUtils.setUpTransports( - mTransportManager, new TransportData(TRANSPORT_NAME, null, null)); - - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransports(mTransportManager, mTransport.unregistered()); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); @@ -279,16 +294,15 @@ public class PerformInitializeTaskTest { @Test public void testRun_whenOnlyOneTransportNotRegistered() throws Exception { - List<TransportData> transports = - TransportTestUtils.setUpTransports( - mTransportManager, - new TransportData(TRANSPORT_NAMES[0], null, null), - new TransportData(TRANSPORT_NAMES[1])); - String registeredTransportName = transports.get(1).transportName; - IBackupTransport registeredTransport = transports.get(1).transportMock; - TransportClient registeredTransportClient = transports.get(1).transportClientMock; + TransportData transport1 = backupTransport().unregistered(); + TransportData transport2 = d2dTransport(); + List<TransportMock> transportMocks = + setUpTransports(mTransportManager, transport1, transport2); + String registeredTransportName = transport2.transportName; + IBackupTransport registeredTransport = transportMocks.get(1).transport; + TransportClient registeredTransportClient = transportMocks.get(1).transportClient; PerformInitializeTask performInitializeTask = - createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + createPerformInitializeTask(transport1.transportName, transport2.transportName); performInitializeTask.run(); @@ -299,25 +313,24 @@ public class PerformInitializeTaskTest { @Test public void testRun_whenTransportNotAvailable() throws Exception { - TransportClient transportClient = mock(TransportClient.class); - TransportTestUtils.setUpTransports( - mTransportManager, new TransportData(TRANSPORT_NAME, null, transportClient)); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + TransportMock transportMock = setUpTransport(mTransport.unavailable()); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mTransportManager).disposeOfTransportClient(eq(transportClient), any()); + verify(mTransportManager) + .disposeOfTransportClient(eq(transportMock.transportClient), any()); verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); verify(mListener).onFinished(any()); } @Test public void testRun_whenTransportThrowsDeadObjectException() throws Exception { - TransportClient transportClient = mock(TransportClient.class); - TransportTestUtils.setUpTransports( - mTransportManager, new TransportData(TRANSPORT_NAME, mTransport, transportClient)); - when(mTransport.initializeDevice()).thenThrow(DeadObjectException.class); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + TransportMock transportMock = setUpTransport(mTransport); + IBackupTransport transport = transportMock.transport; + TransportClient transportClient = transportMock.transportClient; + when(transport.initializeDevice()).thenThrow(DeadObjectException.class); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); @@ -343,9 +356,10 @@ public class PerformInitializeTaskTest { when(transportMock.finishBackup()).thenReturn(finishBackupStatus); } - private void setUpTransport(String transportName) throws Exception { - TransportTestUtils.setUpTransport( - mTransportManager, - new TransportData(transportName, mTransport, mock(TransportClient.class))); + private TransportMock setUpTransport(TransportData transport) throws Exception { + TransportMock transportMock = + TransportTestUtils.setUpTransport(mTransportManager, transport); + mTransportBinder = transportMock.transport; + return transportMock; } } diff --git a/services/robotests/src/com/android/server/backup/testing/ShadowBackupPolicyEnforcer.java b/services/robotests/src/com/android/server/backup/testing/ShadowBackupPolicyEnforcer.java new file mode 100644 index 000000000000..88b30da46433 --- /dev/null +++ b/services/robotests/src/com/android/server/backup/testing/ShadowBackupPolicyEnforcer.java @@ -0,0 +1,24 @@ +package com.android.server.backup.testing; + +import android.content.ComponentName; + +import com.android.server.backup.BackupPolicyEnforcer; +import com.android.server.backup.RefactoredBackupManagerService; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(BackupPolicyEnforcer.class) +public class ShadowBackupPolicyEnforcer { + + private static ComponentName sMandatoryBackupTransport; + + public static void setMandatoryBackupTransport(ComponentName backupTransportComponent) { + sMandatoryBackupTransport = backupTransportComponent; + } + + @Implementation + public ComponentName getMandatoryBackupTransport() { + return sMandatoryBackupTransport; + } +} diff --git a/services/robotests/src/com/android/server/backup/testing/TestUtils.java b/services/robotests/src/com/android/server/backup/testing/TestUtils.java new file mode 100644 index 000000000000..1be298d2c8c5 --- /dev/null +++ b/services/robotests/src/com/android/server/backup/testing/TestUtils.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.testing; + +import com.android.internal.util.FunctionalUtils.ThrowingRunnable; + +import java.util.concurrent.Callable; + +public class TestUtils { + /** + * Calls {@link Runnable#run()} and returns if no exception is thrown. Otherwise, if the + * exception is unchecked, rethrow it; if it's checked wrap in a {@link RuntimeException} and + * throw. + * + * <p><b>Warning:</b>DON'T use this outside tests. A wrapped checked exception is just a failure + * in a test. + */ + public static void uncheck(ThrowingRunnable runnable) { + try { + runnable.runOrThrow(); + } catch (Exception e) { + throw wrapIfChecked(e); + } + } + + /** + * Calls {@link Callable#call()} and returns the value if no exception is thrown. Otherwise, if + * the exception is unchecked, rethrow it; if it's checked wrap in a {@link RuntimeException} + * and throw. + * + * <p><b>Warning:</b>DON'T use this outside tests. A wrapped checked exception is just a failure + * in a test. + */ + public static <T> T uncheck(Callable<T> callable) { + try { + return callable.call(); + } catch (Exception e) { + throw wrapIfChecked(e); + } + } + + /** + * Wrap {@code e} in a {@link RuntimeException} only if it's not one already, in which case it's + * returned. + */ + public static RuntimeException wrapIfChecked(Exception e) { + if (e instanceof RuntimeException) { + return (RuntimeException) e; + } + return new RuntimeException(e); + } + + private TestUtils() {} +} diff --git a/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java b/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java deleted file mode 100644 index 84ac2c212854..000000000000 --- a/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.backup.testing; - -import com.android.internal.backup.IBackupTransport; -import com.android.server.backup.TransportManager; - -import java.util.HashSet; -import java.util.Set; - -/** - * Stub implementation of TransportBoundListener, which returns given result and can tell whether - * it was called for given transport. - */ -public class TransportBoundListenerStub implements - TransportManager.TransportBoundListener { - private boolean mAlwaysReturnSuccess; - private Set<IBackupTransport> mTransportsCalledFor = new HashSet<>(); - - public TransportBoundListenerStub(boolean alwaysReturnSuccess) { - this.mAlwaysReturnSuccess = alwaysReturnSuccess; - } - - @Override - public boolean onTransportBound(IBackupTransport binder) { - mTransportsCalledFor.add(binder); - return mAlwaysReturnSuccess; - } - - /** - * Returns whether the listener was called for the specified transport at least once. - */ - public boolean isCalledForTransport(IBackupTransport binder) { - return mTransportsCalledFor.contains(binder); - } - - /** - * Returns whether the listener was called at least once. - */ - public boolean isCalled() { - return !mTransportsCalledFor.isEmpty(); - } - - /** - * Resets listener calls. - */ - public void resetState() { - mTransportsCalledFor.clear(); - } -} diff --git a/services/robotests/src/com/android/server/backup/testing/TransportData.java b/services/robotests/src/com/android/server/backup/testing/TransportData.java new file mode 100644 index 000000000000..9feaa8efcf3d --- /dev/null +++ b/services/robotests/src/com/android/server/backup/testing/TransportData.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.testing; + +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Intent; + +public class TransportData { + // No constants since new Intent() can't be called in static context because of Robolectric + public static TransportData backupTransport() { + return new TransportData( + "com.google.android.gms/.backup.BackupTransportService", + "com.google.android.gms/.backup.BackupTransportService", + "com.google.android.gms.backup.BackupTransportService", + new Intent(), + "user@gmail.com", + new Intent(), + "Google Account"); + } + + public static TransportData d2dTransport() { + return new TransportData( + "com.google.android.gms/.backup.migrate.service.D2dTransport", + "com.google.android.gms/.backup.component.D2dTransportService", + "d2dMigrateTransport", + null, + "Moving data to new device", + null, + ""); + } + + public static TransportData localTransport() { + return new TransportData( + "android/com.android.internal.backup.LocalTransport", + "android/com.android.internal.backup.LocalTransportService", + "com.android.internal.backup.LocalTransport", + null, + "Backing up to debug-only private cache", + null, + ""); + } + + public static TransportData genericTransport(String packageName, String className) { + return new TransportData( + packageName + "/." + className, + packageName + "/." + className + "Service", + packageName + "." + className, + new Intent(), + "currentDestinationString", + new Intent(), + "dataManagementLabel"); + } + + @TransportTestUtils.TransportStatus + public int transportStatus; + public final String transportName; + private final String transportComponentShort; + @Nullable + public String transportDirName; + @Nullable public Intent configurationIntent; + @Nullable public String currentDestinationString; + @Nullable public Intent dataManagementIntent; + @Nullable public String dataManagementLabel; + + private TransportData( + @TransportTestUtils.TransportStatus int transportStatus, + String transportName, + String transportComponentShort, + String transportDirName, + Intent configurationIntent, + String currentDestinationString, + Intent dataManagementIntent, + String dataManagementLabel) { + this.transportStatus = transportStatus; + this.transportName = transportName; + this.transportComponentShort = transportComponentShort; + this.transportDirName = transportDirName; + this.configurationIntent = configurationIntent; + this.currentDestinationString = currentDestinationString; + this.dataManagementIntent = dataManagementIntent; + this.dataManagementLabel = dataManagementLabel; + } + + public TransportData( + String transportName, + String transportComponentShort, + String transportDirName, + Intent configurationIntent, + String currentDestinationString, + Intent dataManagementIntent, + String dataManagementLabel) { + this( + TransportTestUtils.TransportStatus.REGISTERED_AVAILABLE, + transportName, + transportComponentShort, + transportDirName, + configurationIntent, + currentDestinationString, + dataManagementIntent, + dataManagementLabel); + } + + /** + * Not field because otherwise we'd have to call ComponentName::new in static context and + * Robolectric does not like this. + */ + public ComponentName getTransportComponent() { + return ComponentName.unflattenFromString(transportComponentShort); + } + + public TransportData unavailable() { + return new TransportData( + TransportTestUtils.TransportStatus.REGISTERED_UNAVAILABLE, + transportName, + transportComponentShort, + transportDirName, + configurationIntent, + currentDestinationString, + dataManagementIntent, + dataManagementLabel); + } + + public TransportData unregistered() { + return new TransportData( + TransportTestUtils.TransportStatus.UNREGISTERED, + transportName, + transportComponentShort, + transportDirName, + configurationIntent, + currentDestinationString, + dataManagementIntent, + dataManagementLabel); + } +} diff --git a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java index 9770e407ec72..e1dc7b5e151e 100644 --- a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java +++ b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java @@ -16,13 +16,23 @@ package com.android.server.backup.testing; +import static com.android.server.backup.testing.TestUtils.uncheck; + +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static java.util.stream.Collectors.toList; + import android.annotation.Nullable; import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.RemoteException; +import android.support.annotation.IntDef; import com.android.internal.backup.IBackupTransport; import com.android.server.backup.TransportManager; @@ -30,85 +40,82 @@ import com.android.server.backup.transport.TransportClient; import com.android.server.backup.transport.TransportNotAvailableException; import com.android.server.backup.transport.TransportNotRegisteredException; -import java.util.Arrays; +import org.robolectric.shadows.ShadowPackageManager; + import java.util.List; +import java.util.stream.Stream; public class TransportTestUtils { - public static final String[] TRANSPORT_NAMES = { - "android/com.android.internal.backup.LocalTransport", - "com.google.android.gms/.backup.migrate.service.D2dTransport", - "com.google.android.gms/.backup.BackupTransportService" - }; + /** + * Differently from {@link #setUpTransports(TransportManager, TransportData...)}, which + * configures {@link TransportManager}, this is meant to mock the environment for a real + * TransportManager. + */ + public static void setUpTransportsForTransportManager( + ShadowPackageManager shadowPackageManager, TransportData... transports) + throws Exception { + for (TransportData transport : transports) { + ComponentName transportComponent = transport.getTransportComponent(); + String packageName = transportComponent.getPackageName(); + ResolveInfo resolveInfo = resolveInfo(transportComponent); + shadowPackageManager.addResolveInfoForIntent(transportIntent(), resolveInfo); + shadowPackageManager.addResolveInfoForIntent( + transportIntent().setPackage(packageName), resolveInfo); + } + } - public static final String TRANSPORT_NAME = TRANSPORT_NAMES[0]; + private static Intent transportIntent() { + return new Intent(TransportManager.SERVICE_ACTION_TRANSPORT_HOST); + } - /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */ - public static TransportData setUpCurrentTransport( - TransportManager transportManager, String transportName) throws Exception { - TransportData transport = setUpTransports(transportManager, transportName).get(0); - when(transportManager.getCurrentTransportClient(any())) - .thenReturn(transport.transportClientMock); - return transport; + private static ResolveInfo resolveInfo(ComponentName transportComponent) { + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.serviceInfo = new ServiceInfo(); + resolveInfo.serviceInfo.packageName = transportComponent.getPackageName(); + resolveInfo.serviceInfo.name = transportComponent.getClassName(); + return resolveInfo; } /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */ - public static List<TransportData> setUpTransports( - TransportManager transportManager, String... transportNames) throws Exception { - return setUpTransports( - transportManager, - Arrays.stream(transportNames) - .map(TransportData::new) - .toArray(TransportData[]::new)); + public static TransportMock setUpCurrentTransport( + TransportManager transportManager, TransportData transport) throws Exception { + TransportMock transportMock = setUpTransports(transportManager, transport).get(0); + if (transportMock.transportClient != null) { + when(transportManager.getCurrentTransportClient(any())) + .thenReturn(transportMock.transportClient); + } + return transportMock; } /** @see #setUpTransport(TransportManager, TransportData) */ - public static List<TransportData> setUpTransports( + public static List<TransportMock> setUpTransports( TransportManager transportManager, TransportData... transports) throws Exception { - for (TransportData transport : transports) { - setUpTransport(transportManager, transport); - } - return Arrays.asList(transports); + return Stream.of(transports) + .map(transport -> uncheck(() -> setUpTransport(transportManager, transport))) + .collect(toList()); } - /** - * Configures transport according to {@link TransportData}: - * - * <ul> - * <li>{@link TransportData#transportMock} {@code null} means transport not available. - * <li>{@link TransportData#transportClientMock} {@code null} means transport not registered. - * </ul> - */ - public static void setUpTransport(TransportManager transportManager, TransportData transport) - throws Exception { + public static TransportMock setUpTransport( + TransportManager transportManager, TransportData transport) throws Exception { + int status = transport.transportStatus; String transportName = transport.transportName; - String transportDirName = transportDirName(transportName); - ComponentName transportComponent = transportComponentName(transportName); - IBackupTransport transportMock = transport.transportMock; - TransportClient transportClientMock = transport.transportClientMock; + ComponentName transportComponent = transport.getTransportComponent(); + String transportDirName = transport.transportDirName; - if (transportClientMock != null) { + TransportMock transportMock = mockTransport(transport); + if (status == TransportStatus.REGISTERED_AVAILABLE + || status == TransportStatus.REGISTERED_UNAVAILABLE) { // Transport registered when(transportManager.getTransportClient(eq(transportName), any())) - .thenReturn(transportClientMock); + .thenReturn(transportMock.transportClient); when(transportManager.getTransportClientOrThrow(eq(transportName), any())) - .thenReturn(transportClientMock); + .thenReturn(transportMock.transportClient); when(transportManager.getTransportName(transportComponent)).thenReturn(transportName); when(transportManager.getTransportDirName(eq(transportName))) .thenReturn(transportDirName); when(transportManager.getTransportDirName(eq(transportComponent))) .thenReturn(transportDirName); - when(transportClientMock.getTransportComponent()).thenReturn(transportComponent); - - if (transportMock != null) { - // Transport registered and available - when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock); - when(transportMock.name()).thenReturn(transportName); - when(transportMock.transportDirName()).thenReturn(transportDirName); - } else { - // Transport registered but unavailable - when(transportClientMock.connectOrThrow(any())) - .thenThrow(TransportNotAvailableException.class); - } + // TODO: Mock rest of description methods } else { // Transport not registered when(transportManager.getTransportClient(eq(transportName), any())).thenReturn(null); @@ -121,35 +128,74 @@ public class TransportTestUtils { when(transportManager.getTransportDirName(eq(transportComponent))) .thenThrow(TransportNotRegisteredException.class); } + return transportMock; } - /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */ - public static ComponentName transportComponentName(String transportName) { - return ComponentName.unflattenFromString(transportName); - } + public static TransportMock mockTransport(TransportData transport) throws Exception { + final TransportClient transportClientMock; + int status = transport.transportStatus; + ComponentName transportComponent = transport.getTransportComponent(); + if (status == TransportStatus.REGISTERED_AVAILABLE + || status == TransportStatus.REGISTERED_UNAVAILABLE) { + // Transport registered + transportClientMock = mock(TransportClient.class); + when(transportClientMock.getTransportComponent()).thenReturn(transportComponent); + if (status == TransportStatus.REGISTERED_AVAILABLE) { + // Transport registered and available + IBackupTransport transportMock = mockTransportBinder(transport); + when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock); + + return new TransportMock(transportClientMock, transportMock); + } else { + // Transport registered but unavailable + when(transportClientMock.connectOrThrow(any())) + .thenThrow(TransportNotAvailableException.class); - public static String transportDirName(String transportName) { - return transportName + "_dir_name"; + return new TransportMock(transportClientMock, null); + } + } else { + // Transport not registered + return new TransportMock(null, null); + } } - public static class TransportData { - public final String transportName; - @Nullable public final IBackupTransport transportMock; - @Nullable public final TransportClient transportClientMock; - - public TransportData( - String transportName, - @Nullable IBackupTransport transportMock, - @Nullable TransportClient transportClientMock) { - this.transportName = transportName; - this.transportMock = transportMock; - this.transportClientMock = transportClientMock; + private static IBackupTransport mockTransportBinder(TransportData transport) throws Exception { + IBackupTransport transportBinder = mock(IBackupTransport.class); + try { + when(transportBinder.name()).thenReturn(transport.transportName); + when(transportBinder.transportDirName()).thenReturn(transport.transportDirName); + when(transportBinder.configurationIntent()).thenReturn(transport.configurationIntent); + when(transportBinder.currentDestinationString()) + .thenReturn(transport.currentDestinationString); + when(transportBinder.dataManagementIntent()).thenReturn(transport.dataManagementIntent); + when(transportBinder.dataManagementLabel()).thenReturn(transport.dataManagementLabel); + } catch (RemoteException e) { + fail("RemoteException?"); } + return transportBinder; + } + + public static class TransportMock { + @Nullable public final TransportClient transportClient; + @Nullable public final IBackupTransport transport; - public TransportData(String transportName) { - this(transportName, mock(IBackupTransport.class), mock(TransportClient.class)); + private TransportMock( + @Nullable TransportClient transportClient, @Nullable IBackupTransport transport) { + this.transportClient = transportClient; + this.transport = transport; } } + @IntDef({ + TransportStatus.REGISTERED_AVAILABLE, + TransportStatus.REGISTERED_UNAVAILABLE, + TransportStatus.UNREGISTERED + }) + public @interface TransportStatus { + int REGISTERED_AVAILABLE = 0; + int REGISTERED_UNAVAILABLE = 1; + int UNREGISTERED = 2; + } + private TransportTestUtils() {} } diff --git a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java index 9b4dec6019f4..10442b7e467d 100644 --- a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java +++ b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java @@ -35,8 +35,10 @@ import android.os.Looper; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import com.android.internal.backup.IBackupTransport; +import com.android.server.EventLogTags; import com.android.server.backup.TransportManager; import com.android.server.testing.FrameworkRobolectricTestRunner; +import com.android.server.testing.ShadowEventLog; import com.android.server.testing.SystemLoaderClasses; import org.junit.Before; import org.junit.Test; @@ -48,7 +50,7 @@ import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowLooper; @RunWith(FrameworkRobolectricTestRunner.class) -@Config(manifest = Config.NONE, sdk = 26) +@Config(manifest = Config.NONE, sdk = 26, shadows = {ShadowEventLog.class}) @SystemLoaderClasses({TransportManager.class, TransportClient.class}) @Presubmit public class TransportClientTest { @@ -60,6 +62,7 @@ public class TransportClientTest { @Mock private IBackupTransport.Stub mIBackupTransport; private TransportClient mTransportClient; private ComponentName mTransportComponent; + private String mTransportString; private Intent mBindIntent; private ShadowLooper mShadowLooper; @@ -71,6 +74,7 @@ public class TransportClientTest { mShadowLooper = shadowOf(mainLooper); mTransportComponent = new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".transport.Transport"); + mTransportString = mTransportComponent.flattenToShortString(); mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(mTransportComponent); mTransportClient = new TransportClient( @@ -161,7 +165,7 @@ public class TransportClientTest { } @Test - public void testConnectAsync_whenFrameworkDoesntBind_releasesConnection() throws Exception { + public void testConnectAsync_whenFrameworkDoesNotBind_releasesConnection() throws Exception { when(mContext.bindServiceAsUser( eq(mBindIntent), any(ServiceConnection.class), @@ -234,6 +238,82 @@ public class TransportClientTest { .onTransportConnectionResult(isNull(), eq(mTransportClient)); } + @Test + public void testConnectAsync_beforeFrameworkCall_logsBoundTransition() { + ShadowEventLog.clearEvents(); + + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + + assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 1); + } + + @Test + public void testConnectAsync_afterOnServiceConnected_logsBoundAndConnectedTransitions() { + ShadowEventLog.clearEvents(); + + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onServiceConnected(mTransportComponent, mIBackupTransport); + + assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 1); + assertEventLogged(EventLogTags.BACKUP_TRANSPORT_CONNECTION, mTransportString, 1); + } + + @Test + public void testConnectAsync_afterOnBindingDied_logsBoundAndUnboundTransitions() { + ShadowEventLog.clearEvents(); + + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onBindingDied(mTransportComponent); + + assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 1); + assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 0); + } + + @Test + public void testUnbind_whenConnected_logsDisconnectedAndUnboundTransitions() { + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onServiceConnected(mTransportComponent, mIBackupTransport); + ShadowEventLog.clearEvents(); + + mTransportClient.unbind("caller1"); + + assertEventLogged(EventLogTags.BACKUP_TRANSPORT_CONNECTION, mTransportString, 0); + assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 0); + } + + @Test + public void testOnServiceDisconnected_whenConnected_logsDisconnectedAndUnboundTransitions() { + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onServiceConnected(mTransportComponent, mIBackupTransport); + ShadowEventLog.clearEvents(); + + connection.onServiceDisconnected(mTransportComponent); + + assertEventLogged(EventLogTags.BACKUP_TRANSPORT_CONNECTION, mTransportString, 0); + assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 0); + } + + @Test + public void testOnBindingDied_whenConnected_logsDisconnectedAndUnboundTransitions() { + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onServiceConnected(mTransportComponent, mIBackupTransport); + ShadowEventLog.clearEvents(); + + connection.onBindingDied(mTransportComponent); + + assertEventLogged(EventLogTags.BACKUP_TRANSPORT_CONNECTION, mTransportString, 0); + assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 0); + } + + private void assertEventLogged(int tag, Object... values) { + assertThat(ShadowEventLog.hasEvent(tag, values)).isTrue(); + } + private ServiceConnection verifyBindServiceAsUserAndCaptureServiceConnection(Context context) { ArgumentCaptor<ServiceConnection> connectionCaptor = ArgumentCaptor.forClass(ServiceConnection.class); diff --git a/services/robotests/src/com/android/server/testing/ShadowEventLog.java b/services/robotests/src/com/android/server/testing/ShadowEventLog.java new file mode 100644 index 000000000000..b8059f4fde87 --- /dev/null +++ b/services/robotests/src/com/android/server/testing/ShadowEventLog.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.testing; + +import android.util.EventLog; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; + +@Implements(EventLog.class) +public class ShadowEventLog { + private final static LinkedHashSet<Entry> ENTRIES = new LinkedHashSet<>(); + + @Implementation + public static int writeEvent(int tag, Object... values) { + ENTRIES.add(new Entry(tag, Arrays.asList(values))); + // Currently we don't care about the return value, if we do, estimate it correctly + return 0; + } + + public static boolean hasEvent(int tag, Object... values) { + return ENTRIES.contains(new Entry(tag, Arrays.asList(values))); + } + + public static void clearEvents() { + ENTRIES.clear(); + } + + public static class Entry { + public final int tag; + public final List<Object> values; + + public Entry(int tag, List<Object> values) { + this.tag = tag; + this.values = values; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Entry entry = (Entry) o; + return tag == entry.tag && values.equals(entry.values); + } + + @Override + public int hashCode() { + int result = tag; + result = 31 * result + values.hashCode(); + return result; + } + } +} diff --git a/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java new file mode 100644 index 000000000000..6d220737f2fb --- /dev/null +++ b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.testing.shadows; + +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.UserHandle; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowContextImpl; + +@Implements(className = ShadowContextImpl.CLASS_NAME, inheritImplementationMethods = true) +public class FrameworkShadowContextImpl extends ShadowContextImpl { + @Implementation + public boolean bindServiceAsUser( + Intent service, + ServiceConnection connection, + int flags, + UserHandle user) { + return bindService(service, connection, flags); + } +} diff --git a/services/robotests/src/com/android/server/backup/testing/ShadowPackageManagerForBackup.java b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowPackageManager.java index b64b59d238d1..5cdbe7ff09ba 100644 --- a/services/robotests/src/com/android/server/backup/testing/ShadowPackageManagerForBackup.java +++ b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowPackageManager.java @@ -14,22 +14,18 @@ * limitations under the License */ -package com.android.server.backup.testing; +package com.android.server.testing.shadows; import android.app.ApplicationPackageManager; import android.content.Intent; import android.content.pm.ResolveInfo; - +import java.util.List; import org.robolectric.annotation.Implements; import org.robolectric.shadows.ShadowApplicationPackageManager; -import java.util.List; - -/** - * Implementation of PackageManager for Robolectric which handles queryIntentServicesAsUser(). - */ +/** Extension of ShadowApplicationPackageManager */ @Implements(value = ApplicationPackageManager.class, inheritImplementationMethods = true) -public class ShadowPackageManagerForBackup extends ShadowApplicationPackageManager { +public class FrameworkShadowPackageManager extends ShadowApplicationPackageManager { @Override public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) { return queryIntentServices(intent, flags); diff --git a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java index 66d0da13fff1..6a21931e0418 100644 --- a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java @@ -42,6 +42,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.BatteryManager; import android.os.Handler; import android.os.Looper; import android.os.PowerManager.ServiceType; @@ -50,13 +51,17 @@ import android.os.PowerSaveState; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; +import android.provider.Settings; +import android.provider.Settings.Global; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import android.test.mock.MockContentResolver; import android.util.ArraySet; import android.util.Pair; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; +import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.ForceAppStandbyTracker.Listener; import org.junit.Before; @@ -102,6 +107,9 @@ public class ForceAppStandbyTrackerTest { PowerManagerInternal injectPowerManagerInternal() { return mMockPowerManagerInternal; } + + @Override + boolean isSmallBatteryDevice() { return mIsSmallBatteryDevice; }; } private static final int UID_1 = Process.FIRST_APPLICATION_UID + 1; @@ -137,7 +145,11 @@ public class ForceAppStandbyTrackerTest { private Consumer<PowerSaveState> mPowerSaveObserver; private BroadcastReceiver mReceiver; + private MockContentResolver mMockContentResolver; + private FakeSettingsProvider mFakeSettingsProvider; + private boolean mPowerSaveMode; + private boolean mIsSmallBatteryDevice; private final ArraySet<Pair<Integer, String>> mRestrictedPackages = new ArraySet(); @@ -174,13 +186,17 @@ public class ForceAppStandbyTrackerTest { } private void callStart(ForceAppStandbyTrackerTestable instance) throws RemoteException { - // Set up functions that start() calls. when(mMockPowerManagerInternal.getLowPowerState(eq(ServiceType.FORCE_ALL_APPS_STANDBY))) .thenAnswer(inv -> getPowerSaveState()); when(mMockAppOpsManager.getPackagesForOps( any(int[].class) - )).thenAnswer(inv -> new ArrayList<AppOpsManager.PackageOps>()); + )).thenAnswer(inv -> new ArrayList<AppOpsManager.PackageOps>()); + + mMockContentResolver = new MockContentResolver(); + mFakeSettingsProvider = new FakeSettingsProvider(); + when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver); + mMockContentResolver.addProvider(Settings.AUTHORITY, mFakeSettingsProvider); // Call start. instance.start(); @@ -208,7 +224,6 @@ public class ForceAppStandbyTrackerTest { verify(mMockPowerManagerInternal).registerLowPowerModeObserver( eq(ServiceType.FORCE_ALL_APPS_STANDBY), powerSaveObserverCaptor.capture()); - verify(mMockContext).registerReceiver( receiverCaptor.capture(), any(IntentFilter.class)); @@ -221,6 +236,7 @@ public class ForceAppStandbyTrackerTest { assertNotNull(mAppOpsCallback); assertNotNull(mPowerSaveObserver); assertNotNull(mReceiver); + assertNotNull(instance.mFlagsObserver); } private void setAppOps(int uid, String packageName, boolean restrict) throws RemoteException { @@ -822,6 +838,33 @@ public class ForceAppStandbyTrackerTest { assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2)); } + @Test + public void testSmallBatteryAndCharging() throws Exception { + // This is a small battery device + mIsSmallBatteryDevice = true; + + final ForceAppStandbyTrackerTestable instance = newInstance(); + callStart(instance); + assertFalse(instance.isForceAllAppsStandbyEnabled()); + + // Setting/experiment for all app standby for small battery is enabled + Global.putInt(mMockContentResolver, Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED, 1); + instance.mFlagsObserver.onChange(true, + Global.getUriFor(Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED)); + assertTrue(instance.isForceAllAppsStandbyEnabled()); + + // When battery is charging, force app standby is disabled + Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED); + intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING); + mReceiver.onReceive(mMockContext, intent); + assertFalse(instance.isForceAllAppsStandbyEnabled()); + + // When battery stops charging, force app standby is enabled + intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_DISCHARGING); + mReceiver.onReceive(mMockContext, intent); + assertTrue(instance.isForceAllAppsStandbyEnabled()); + } + static int[] array(int... appIds) { Arrays.sort(appIds); return appIds; diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java index b38a4136c94a..9923fa86758b 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java @@ -22,6 +22,7 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.view.Display.DEFAULT_DISPLAY; +import static com.android.server.am.ActivityStack.ActivityState.INITIALIZING; import static com.android.server.am.ActivityStack.ActivityState.PAUSING; import static com.android.server.am.ActivityStack.ActivityState.STOPPED; import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING; @@ -122,7 +123,15 @@ public class ActivityRecordTests extends ActivityTestsBase { mActivity.makeVisibleIfNeeded(null /* starting */); assertEquals(mActivity.state, PAUSING); + assertTrue(pauseFound.value); + + // Make sure that the state does not change for current non-stopping states. + mActivity.state = INITIALIZING; + + mActivity.makeVisibleIfNeeded(null /* starting */); + + assertEquals(mActivity.state, INITIALIZING); } @Test diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java index 766d30d9f9a5..7b4441ae30e6 100644 --- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java @@ -96,7 +96,7 @@ public class BackupManagerServiceTest { createBackupManagerService(); verify(mTransportManager) - .setTransportBoundListener(any(TransportManager.TransportBoundListener.class)); + .setOnTransportRegisteredListener(any()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java index ccf2aaffb9b7..272b5d899d4b 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.when; import android.app.IActivityManager; import android.app.NotificationManager; import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyManagerInternal; import android.app.trust.TrustManager; import android.content.ComponentName; import android.content.pm.UserInfo; @@ -41,6 +42,7 @@ import android.test.AndroidTestCase; import com.android.internal.widget.ILockSettings; import com.android.internal.widget.LockPatternUtils; +import com.android.server.LocalServices; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -75,6 +77,7 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { FakeStorageManager mStorageManager; IActivityManager mActivityManager; DevicePolicyManager mDevicePolicyManager; + DevicePolicyManagerInternal mDevicePolicyManagerInternal; KeyStore mKeyStore; MockSyntheticPasswordManager mSpManager; @@ -88,6 +91,10 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { mStorageManager = new FakeStorageManager(); mActivityManager = mock(IActivityManager.class); mDevicePolicyManager = mock(DevicePolicyManager.class); + mDevicePolicyManagerInternal = mock(DevicePolicyManagerInternal.class); + + LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); + LocalServices.addService(DevicePolicyManagerInternal.class, mDevicePolicyManagerInternal); mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager, mDevicePolicyManager, mock(StorageManager.class), mock(TrustManager.class)); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/CachedSyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/CachedSyntheticPasswordTests.java new file mode 100644 index 000000000000..4ad9f19735c4 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/CachedSyntheticPasswordTests.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.locksettings; + +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; + +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; +import static com.android.server.testutils.TestUtils.assertExpectException; + +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verify; + +import android.os.RemoteException; + +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.VerifyCredentialResponse; +import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult; + +/** + * Run the synthetic password tests with caching enabled. + * + * By default, those tests run without caching. Untrusted credential reset depends on caching so + * this class included those tests. + */ +public class CachedSyntheticPasswordTests extends SyntheticPasswordTests { + + @Override + protected void setUp() throws Exception { + super.setUp(); + enableSpCaching(true); + } + + private void enableSpCaching(boolean enable) { + when(mDevicePolicyManagerInternal + .canUserHaveUntrustedCredentialReset(anyInt())).thenReturn(enable); + } + + public void testSyntheticPasswordClearCredentialUntrusted() throws RemoteException { + final String PASSWORD = "testSyntheticPasswordClearCredential-password"; + final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword"; + + initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); + long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID); + // clear password + mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, null, + PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID); + assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); + + // set a new password + mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, + PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID); + assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( + NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID) + .getResponseCode()); + assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); + } + + public void testSyntheticPasswordChangeCredentialUntrusted() throws RemoteException { + final String PASSWORD = "testSyntheticPasswordClearCredential-password"; + final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword"; + + initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); + long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID); + // Untrusted change password + mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, + PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID); + assertNotEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); + assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); + + // Verify the password + assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( + NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID) + .getResponseCode()); + } + + public void testUntrustedCredentialChangeBlockedIfSpNotCached() throws RemoteException { + final String PASSWORD = "testUntrustedCredentialChangeBlockedIfSpNotCached-password"; + final String NEWPASSWORD = "testUntrustedCredentialChangeBlockedIfSpNotCached-newpassword"; + + // Disable caching for this test + enableSpCaching(false); + + initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); + long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID); + // Untrusted change password + assertExpectException(IllegalStateException.class, /* messageRegex= */ null, + () -> mService.setLockCredential( + NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, + null, PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID)); + assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); + + // Verify the new password doesn't work but the old one still does + assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, mService.verifyCredential( + NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID) + .getResponseCode()); + assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( + PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID) + .getResponseCode()); + } + +} diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java index 2e4c74f19f9c..b07d6ac5dc04 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java @@ -112,7 +112,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { mStorageManager.getUserUnlockToken(PRIMARY_USER_ID)); } - private void initializeCredentialUnderSP(String password, int userId) throws RemoteException { + protected void initializeCredentialUnderSP(String password, int userId) throws RemoteException { enableSyntheticPassword(); int quality = password != null ? PASSWORD_QUALITY_ALPHABETIC : PASSWORD_QUALITY_UNSPECIFIED; @@ -129,7 +129,6 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID); mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, PASSWORD, PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID); - mGateKeeperService.clearSecureUserId(PRIMARY_USER_ID); assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID) .getResponseCode()); @@ -170,44 +169,6 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); } - public void testSyntheticPasswordClearCredentialUntrusted() throws RemoteException { - final String PASSWORD = "testSyntheticPasswordClearCredential-password"; - final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword"; - - initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); - long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID); - // clear password - mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, null, - PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID); - assertEquals(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); - - // set a new password - mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, - PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID); - assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( - NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID) - .getResponseCode()); - assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); - } - - public void testSyntheticPasswordChangeCredentialUntrusted() throws RemoteException { - final String PASSWORD = "testSyntheticPasswordClearCredential-password"; - final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword"; - - initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); - long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID); - // Untrusted change password - mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, - PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID); - assertNotEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); - assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); - - // Verify the password - assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( - NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID) - .getResponseCode()); - } - public void testManagedProfileUnifiedChallengeMigration() throws RemoteException { final String UnifiedPassword = "testManagedProfileUnifiedChallengeMigration-pwd"; disableSyntheticPassword(); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java index 9eb42e9db425..c1789ba0b15a 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java @@ -16,11 +16,11 @@ package com.android.server.locksettings.recoverablekeystore; -import static android.security.keystore.RecoveryMetadata.TYPE_LOCKSCREEN; +import static android.security.keystore.KeychainProtectionParameter.TYPE_LOCKSCREEN; -import static android.security.keystore.RecoveryMetadata.TYPE_PASSWORD; -import static android.security.keystore.RecoveryMetadata.TYPE_PATTERN; -import static android.security.keystore.RecoveryMetadata.TYPE_PIN; +import static android.security.keystore.KeychainProtectionParameter.TYPE_PASSWORD; +import static android.security.keystore.KeychainProtectionParameter.TYPE_PATTERN; +import static android.security.keystore.KeychainProtectionParameter.TYPE_PIN; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; @@ -41,8 +41,8 @@ import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.security.keystore.KeyDerivationParams; -import android.security.keystore.EntryRecoveryData; -import android.security.keystore.RecoveryData; +import android.security.keystore.KeychainSnapshot; +import android.security.keystore.WrappedApplicationKey; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -283,9 +283,9 @@ public class KeySyncTaskTest { addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS); mKeySyncTask.run(); - RecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID); + KeychainSnapshot keychainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID); KeyDerivationParams KeyDerivationParams = - recoveryData.getRecoveryMetadata().get(0).getKeyDerivationParams(); + keychainSnapshot.getKeychainProtectionParams().get(0).getKeyDerivationParams(); assertThat(KeyDerivationParams.getAlgorithm()).isEqualTo( KeyDerivationParams.ALGORITHM_SHA256); verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID); @@ -296,15 +296,15 @@ public class KeySyncTaskTest { assertThat(counterId).isNotNull(); byte[] recoveryKey = decryptThmEncryptedKey( lockScreenHash, - recoveryData.getEncryptedRecoveryKeyBlob(), + keychainSnapshot.getEncryptedRecoveryKeyBlob(), /*vaultParams=*/ KeySyncUtils.packVaultParams( mKeyPair.getPublic(), counterId, TEST_DEVICE_ID, /*maxAttempts=*/ 10)); - List<EntryRecoveryData> applicationKeys = recoveryData.getEntryRecoveryData(); + List<WrappedApplicationKey> applicationKeys = keychainSnapshot.getWrappedApplicationKeys(); assertThat(applicationKeys).hasSize(1); - EntryRecoveryData keyData = applicationKeys.get(0); + WrappedApplicationKey keyData = applicationKeys.get(0); assertEquals(TEST_APP_KEY_ALIAS, keyData.getAlias()); assertThat(keyData.getAlias()).isEqualTo(keyData.getAlias()); byte[] appKey = KeySyncUtils.decryptApplicationKey( @@ -322,14 +322,14 @@ public class KeySyncTaskTest { mKeySyncTask.run(); - RecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID); - assertThat(recoveryData.getSnapshotVersion()).isEqualTo(1); // default value; + KeychainSnapshot keychainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID); + assertThat(keychainSnapshot.getSnapshotVersion()).isEqualTo(1); // default value; mRecoverableKeyStoreDb.setShouldCreateSnapshot(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, true); mKeySyncTask.run(); - recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID); - assertThat(recoveryData.getSnapshotVersion()).isEqualTo(2); // Updated + keychainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID); + assertThat(keychainSnapshot.getSnapshotVersion()).isEqualTo(2); // Updated } @Test @@ -352,9 +352,9 @@ public class KeySyncTaskTest { mKeySyncTask.run(); - RecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID); - assertThat(recoveryData.getRecoveryMetadata()).hasSize(1); - assertThat(recoveryData.getRecoveryMetadata().get(0).getLockScreenUiFormat()). + KeychainSnapshot keychainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID); + assertThat(keychainSnapshot.getKeychainProtectionParams()).hasSize(1); + assertThat(keychainSnapshot.getKeychainProtectionParams().get(0).getLockScreenUiFormat()). isEqualTo(TYPE_PASSWORD); } @@ -378,10 +378,10 @@ public class KeySyncTaskTest { mKeySyncTask.run(); - RecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID); - assertThat(recoveryData.getRecoveryMetadata()).hasSize(1); + KeychainSnapshot keychainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID); + assertThat(keychainSnapshot.getKeychainProtectionParams()).hasSize(1); // Password with only digits is changed to pin. - assertThat(recoveryData.getRecoveryMetadata().get(0).getLockScreenUiFormat()). + assertThat(keychainSnapshot.getKeychainProtectionParams().get(0).getLockScreenUiFormat()). isEqualTo(TYPE_PIN); } @@ -405,9 +405,9 @@ public class KeySyncTaskTest { mKeySyncTask.run(); - RecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID); - assertThat(recoveryData.getRecoveryMetadata()).hasSize(1); - assertThat(recoveryData.getRecoveryMetadata().get(0).getLockScreenUiFormat()). + KeychainSnapshot keychainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID); + assertThat(keychainSnapshot.getKeychainProtectionParams()).hasSize(1); + assertThat(keychainSnapshot.getKeychainProtectionParams().get(0).getLockScreenUiFormat()). isEqualTo(TYPE_PATTERN); } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index 1bdcf478ce68..37157428683a 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -16,8 +16,8 @@ package com.android.server.locksettings.recoverablekeystore; -import static android.security.keystore.RecoveryMetadata.TYPE_LOCKSCREEN; -import static android.security.keystore.RecoveryMetadata.TYPE_PASSWORD; +import static android.security.keystore.KeychainProtectionParameter.TYPE_LOCKSCREEN; +import static android.security.keystore.KeychainProtectionParameter.TYPE_PASSWORD; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertArrayEquals; @@ -43,9 +43,8 @@ import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.security.keystore.KeyDerivationParams; -import android.security.keystore.EntryRecoveryData; -import android.security.keystore.RecoveryMetadata; -import android.security.keystore.RecoveryManager; +import android.security.keystore.KeychainProtectionParameter; +import android.security.keystore.WrappedApplicationKey; import android.support.test.filters.SmallTest; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; @@ -251,7 +250,7 @@ public class RecoverableKeyStoreManagerTest { TEST_VAULT_PARAMS, TEST_VAULT_CHALLENGE, ImmutableList.of( - new RecoveryMetadata( + new KeychainProtectionParameter( TYPE_LOCKSCREEN, TYPE_PASSWORD, KeyDerivationParams.createSha256Params(TEST_SALT), @@ -270,7 +269,7 @@ public class RecoverableKeyStoreManagerTest { TEST_VAULT_PARAMS, TEST_VAULT_CHALLENGE, ImmutableList.of( - new RecoveryMetadata( + new KeychainProtectionParameter( TYPE_LOCKSCREEN, TYPE_PASSWORD, KeyDerivationParams.createSha256Params(TEST_SALT), @@ -295,7 +294,7 @@ public class RecoverableKeyStoreManagerTest { fail("should have thrown"); } catch (ServiceSpecificException e) { assertThat(e.getMessage()).startsWith( - "Only a single RecoveryMetadata is supported"); + "Only a single KeychainProtectionParameter is supported"); } } @@ -308,7 +307,7 @@ public class RecoverableKeyStoreManagerTest { TEST_VAULT_PARAMS, TEST_VAULT_CHALLENGE, ImmutableList.of( - new RecoveryMetadata( + new KeychainProtectionParameter( TYPE_LOCKSCREEN, TYPE_PASSWORD, KeyDerivationParams.createSha256Params(TEST_SALT), @@ -330,7 +329,7 @@ public class RecoverableKeyStoreManagerTest { vaultParams, TEST_VAULT_CHALLENGE, ImmutableList.of( - new RecoveryMetadata( + new KeychainProtectionParameter( TYPE_LOCKSCREEN, TYPE_PASSWORD, KeyDerivationParams.createSha256Params(TEST_SALT), @@ -348,7 +347,7 @@ public class RecoverableKeyStoreManagerTest { TEST_SESSION_ID, /*recoveryKeyBlob=*/ randomBytes(32), /*applicationKeys=*/ ImmutableList.of( - new EntryRecoveryData("alias", randomBytes(32)) + new WrappedApplicationKey("alias", randomBytes(32)) )); fail("should have thrown"); } catch (ServiceSpecificException e) { @@ -363,7 +362,7 @@ public class RecoverableKeyStoreManagerTest { TEST_PUBLIC_KEY, TEST_VAULT_PARAMS, TEST_VAULT_CHALLENGE, - ImmutableList.of(new RecoveryMetadata( + ImmutableList.of(new KeychainProtectionParameter( TYPE_LOCKSCREEN, TYPE_PASSWORD, KeyDerivationParams.createSha256Params(TEST_SALT), @@ -387,7 +386,7 @@ public class RecoverableKeyStoreManagerTest { TEST_PUBLIC_KEY, TEST_VAULT_PARAMS, TEST_VAULT_CHALLENGE, - ImmutableList.of(new RecoveryMetadata( + ImmutableList.of(new KeychainProtectionParameter( TYPE_LOCKSCREEN, TYPE_PASSWORD, KeyDerivationParams.createSha256Params(TEST_SALT), @@ -397,7 +396,7 @@ public class RecoverableKeyStoreManagerTest { SecretKey recoveryKey = randomRecoveryKey(); byte[] encryptedClaimResponse = encryptClaimResponse( keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey); - EntryRecoveryData badApplicationKey = new EntryRecoveryData( + WrappedApplicationKey badApplicationKey = new WrappedApplicationKey( TEST_ALIAS, randomBytes(32)); @@ -419,7 +418,7 @@ public class RecoverableKeyStoreManagerTest { TEST_PUBLIC_KEY, TEST_VAULT_PARAMS, TEST_VAULT_CHALLENGE, - ImmutableList.of(new RecoveryMetadata( + ImmutableList.of(new KeychainProtectionParameter( TYPE_LOCKSCREEN, TYPE_PASSWORD, KeyDerivationParams.createSha256Params(TEST_SALT), @@ -430,7 +429,7 @@ public class RecoverableKeyStoreManagerTest { byte[] encryptedClaimResponse = encryptClaimResponse( keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey); byte[] applicationKeyBytes = randomBytes(32); - EntryRecoveryData applicationKey = new EntryRecoveryData( + WrappedApplicationKey applicationKey = new WrappedApplicationKey( TEST_ALIAS, encryptedApplicationKey(recoveryKey, applicationKeyBytes)); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java index 6308f74ff907..56b44e2fa17c 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java @@ -3,7 +3,7 @@ package com.android.server.locksettings.recoverablekeystore.storage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import android.security.keystore.RecoveryData; +import android.security.keystore.KeychainSnapshot; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -26,25 +26,25 @@ public class RecoverySnapshotStorageTest { @Test public void get_returnsSetSnapshot() { int userId = 1000; - RecoveryData recoveryData = new RecoveryData( + KeychainSnapshot keychainSnapshot = new KeychainSnapshot( /*snapshotVersion=*/ 1, new ArrayList<>(), new ArrayList<>(), new byte[0]); - mRecoverySnapshotStorage.put(userId, recoveryData); + mRecoverySnapshotStorage.put(userId, keychainSnapshot); - assertEquals(recoveryData, mRecoverySnapshotStorage.get(userId)); + assertEquals(keychainSnapshot, mRecoverySnapshotStorage.get(userId)); } @Test public void remove_removesSnapshots() { int userId = 1000; - RecoveryData recoveryData = new RecoveryData( + KeychainSnapshot keychainSnapshot = new KeychainSnapshot( /*snapshotVersion=*/ 1, new ArrayList<>(), new ArrayList<>(), new byte[0]); - mRecoverySnapshotStorage.put(userId, recoveryData); + mRecoverySnapshotStorage.put(userId, keychainSnapshot); mRecoverySnapshotStorage.remove(userId); diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java index 9a6da0e791b5..b6c370eb6b08 100644 --- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java +++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java @@ -16,15 +16,16 @@ package com.android.server.policy; -import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; +import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; @@ -34,7 +35,6 @@ import android.graphics.PixelFormat; import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.Surface; import android.view.WindowManager; import org.junit.Before; @@ -128,7 +128,23 @@ public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase { } @Test - public void layoutWindowLw_withDisplayCutout_fullscreen() { + public void layoutWindowLw_withhDisplayCutout_never() { + addDisplayCutout(); + + mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetByTopBottom(mAppWindow.parentFrame, STATUS_BAR_HEIGHT, 0); + assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); + } + + @Test + public void layoutWindowLw_withDisplayCutout_layoutFullscreen() { addDisplayCutout(); mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; @@ -137,6 +153,22 @@ public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase { mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0); + assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); + } + + @Test + public void layoutWindowLw_withDisplayCutout_fullscreen() { + addDisplayCutout(); + + mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + assertInsetByTopBottom(mAppWindow.parentFrame, STATUS_BAR_HEIGHT, 0); assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); @@ -147,8 +179,8 @@ public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase { public void layoutWindowLw_withDisplayCutout_fullscreenInCutout() { addDisplayCutout(); - mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; - mAppWindow.attrs.flags2 |= FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; + mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; + mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; mPolicy.addWindow(mAppWindow); mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); @@ -217,7 +249,7 @@ public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase { setRotation(ROTATION_90); mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; - mAppWindow.attrs.flags2 |= FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; + mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; mPolicy.addWindow(mAppWindow); mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); diff --git a/services/tests/servicestests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/servicestests/src/com/android/server/wm/AppTransitionTests.java index 77f96ca37e36..e7e55cd4404e 100644 --- a/services/tests/servicestests/src/com/android/server/wm/AppTransitionTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/AppTransitionTests.java @@ -16,9 +16,9 @@ package com.android.server.wm; -import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN; -import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_GOING_AWAY; -import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_UNOCCLUDE; +import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN; +import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; import static org.junit.Assert.assertEquals; import android.content.Context; diff --git a/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java index ce76a223ef20..ac291632c877 100644 --- a/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java @@ -16,27 +16,38 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import android.content.ClipData; import android.os.IBinder; +import android.os.Looper; +import android.os.UserHandle; +import android.os.UserManagerInternal; import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.view.InputChannel; import android.view.Surface; import android.view.SurfaceSession; +import android.view.View; +import com.android.internal.annotations.GuardedBy; +import com.android.server.LocalServices; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; + /** * Tests for the {@link DragDropController} class. * @@ -46,36 +57,92 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @Presubmit public class DragDropControllerTests extends WindowTestsBase { - private static final int TIMEOUT_MS = 1000; - private DragDropController mTarget; + private static final int TIMEOUT_MS = 3000; + private TestDragDropController mTarget; private WindowState mWindow; private IBinder mToken; + static class TestDragDropController extends DragDropController { + @GuardedBy("sWm.mWindowMap") + private Runnable mCloseCallback; + + TestDragDropController(WindowManagerService service, Looper looper) { + super(service, looper); + } + + void setOnClosedCallbackLocked(Runnable runnable) { + assertTrue(dragDropActiveLocked()); + mCloseCallback = runnable; + } + + @Override + void onDragStateClosedLocked(DragState dragState) { + super.onDragStateClosedLocked(dragState); + if (mCloseCallback != null) { + mCloseCallback.run(); + mCloseCallback = null; + } + } + } + + /** + * Creates a window state which can be used as a drop target. + */ + private WindowState createDropTargetWindow(String name, int ownerId) { + final WindowTestUtils.TestAppWindowToken token = new WindowTestUtils.TestAppWindowToken( + mDisplayContent); + final TaskStack stack = createStackControllerOnStackOnDisplay( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent).mContainer; + final Task task = createTaskInStack(stack, ownerId); + task.addChild(token, 0); + + final WindowState window = createWindow( + null, TYPE_BASE_APPLICATION, token, name, ownerId, false); + window.mInputChannel = new InputChannel(); + window.mHasSurface = true; + return window; + } + @Before public void setUp() throws Exception { + final UserManagerInternal userManager = mock(UserManagerInternal.class); + LocalServices.addService(UserManagerInternal.class, userManager); + super.setUp(); - assertNotNull(sWm.mDragDropController); - mTarget = sWm.mDragDropController; - mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window"); + + mTarget = new TestDragDropController(sWm, sWm.mH.getLooper()); + mDisplayContent = spy(mDisplayContent); + mWindow = createDropTargetWindow("Drag test window", 0); + when(mDisplayContent.getTouchableWinAtPointLocked(0, 0)).thenReturn(mWindow); + when(sWm.mInputManager.transferTouchFocus(any(), any())).thenReturn(true); + synchronized (sWm.mWindowMap) { - // Because sWm is a static object, the previous operation may remain. - assertFalse(mTarget.dragDropActiveLocked()); + sWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow); } } @After - public void tearDown() { - if (mToken != null) { - mTarget.cancelDragAndDrop(mToken); + public void tearDown() throws Exception { + LocalServices.removeServiceForTest(UserManagerInternal.class); + final CountDownLatch latch; + synchronized (sWm.mWindowMap) { + if (!mTarget.dragDropActiveLocked()) { + return; + } + if (mToken != null) { + mTarget.cancelDragAndDrop(mToken); + } + latch = new CountDownLatch(1); + mTarget.setOnClosedCallbackLocked(() -> { + latch.countDown(); + }); } + assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); } @Test - public void testPrepareDrag() throws Exception { - final Surface surface = new Surface(); - mToken = mTarget.prepareDrag( - new SurfaceSession(), 0, 0, mWindow.mClient, 0, 100, 100, surface); - assertNotNull(mToken); + public void testDragFlow() throws Exception { + dragFlow(0, ClipData.newPlainText("label", "Test"), 0, 0); } @Test @@ -85,4 +152,33 @@ public class DragDropControllerTests extends WindowTestsBase { new SurfaceSession(), 0, 0, mWindow.mClient, 0, 0, 0, surface); assertNull(mToken); } + + @Test + public void testPerformDrag_NullDataWithGrantUri() throws Exception { + dragFlow(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 0, 0); + } + + @Test + public void testPerformDrag_NullDataToOtherUser() throws Exception { + final WindowState otherUsersWindow = + createDropTargetWindow("Other user's window", 1 * UserHandle.PER_USER_RANGE); + when(mDisplayContent.getTouchableWinAtPointLocked(10, 10)) + .thenReturn(otherUsersWindow); + + dragFlow(0, null, 10, 10); + } + + private void dragFlow(int flag, ClipData data, float dropX, float dropY) { + final Surface surface = new Surface(); + mToken = mTarget.prepareDrag( + new SurfaceSession(), 0, 0, mWindow.mClient, flag, 100, 100, surface); + assertNotNull(mToken); + + assertTrue(sWm.mInputManager.transferTouchFocus(null, null)); + assertTrue(mTarget.performDrag( + mWindow.mClient, mToken, 0, 0, 0, 0, 0, data)); + + mTarget.handleMotionEvent(false, dropX, dropY); + mToken = mWindow.mClient.asBinder(); + } } diff --git a/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java new file mode 100644 index 000000000000..897be34e36fe --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import android.graphics.Point; +import android.graphics.Rect; +import android.platform.test.annotations.Postsubmit; +import android.support.test.filters.FlakyTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.IRemoteAnimationFinishedCallback; +import android.view.IRemoteAnimationRunner; +import android.view.RemoteAnimationAdapter; +import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; + +import com.android.server.testutils.OffsettableClock; +import com.android.server.testutils.TestHandler; +import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * atest FrameworksServicesTests:com.android.server.wm.RemoteAnimationControllerTest + */ +@SmallTest +@FlakyTest(detail = "Promote to presubmit if non-flakyness is established") +@RunWith(AndroidJUnit4.class) +public class RemoteAnimationControllerTest extends WindowTestsBase { + + @Mock SurfaceControl mMockLeash; + @Mock Transaction mMockTransaction; + @Mock OnAnimationFinishedCallback mFinishedCallback; + @Mock IRemoteAnimationRunner mMockRunner; + private RemoteAnimationAdapter mAdapter; + private RemoteAnimationController mController; + private final OffsettableClock mClock = new OffsettableClock.Stopped(); + private TestHandler mHandler; + + @Before + public void setUp() throws Exception { + super.setUp(); + MockitoAnnotations.initMocks(this); + mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50); + sWm.mH.runWithScissors(() -> { + mHandler = new TestHandler(null, mClock); + }, 0); + mController = new RemoteAnimationController(sWm, mAdapter, mHandler); + } + + @Test + public void testRun() throws Exception { + final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); + sWm.mOpeningApps.add(win.mAppToken); + try { + final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150)); + adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + mController.goodToGo(); + + final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = + ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); + verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture()); + assertEquals(1, appsCaptor.getValue().length); + final RemoteAnimationTarget app = appsCaptor.getValue()[0]; + assertEquals(new Point(50, 100), app.position); + assertEquals(new Rect(50, 100, 150, 150), app.sourceContainerBounds); + assertEquals(win.mAppToken.getPrefixOrderIndex(), app.prefixOrderIndex); + assertEquals(win.mAppToken.getTask().mTaskId, app.taskId); + assertEquals(mMockLeash, app.leash); + assertEquals(win.mWinAnimator.mLastClipRect, app.clipRect); + assertEquals(false, app.isTranslucent); + verify(mMockTransaction).setLayer(mMockLeash, app.prefixOrderIndex); + verify(mMockTransaction).setPosition(mMockLeash, app.position.x, app.position.y); + verify(mMockTransaction).setWindowCrop(mMockLeash, new Rect(0, 0, 100, 50)); + + finishedCaptor.getValue().onAnimationFinished(); + verify(mFinishedCallback).onAnimationFinished(eq(adapter)); + } finally { + sWm.mOpeningApps.clear(); + } + } + + @Test + public void testCancel() throws Exception { + final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); + final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150)); + adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + mController.goodToGo(); + + adapter.onAnimationCancelled(mMockLeash); + verify(mMockRunner).onAnimationCancelled(); + } + + @Test + public void testTimeout() throws Exception { + final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); + final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150)); + adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + mController.goodToGo(); + + mClock.fastForward(2500); + mHandler.timeAdvance(); + + verify(mMockRunner).onAnimationCancelled(); + verify(mFinishedCallback).onAnimationFinished(eq(adapter)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotControllerTest.java index f253632a1765..920796ed6a30 100644 --- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotControllerTest.java @@ -18,7 +18,7 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; -import static com.android.server.wm.AppTransition.TRANSIT_UNSET; +import static android.view.WindowManager.TRANSIT_UNSET; import static com.android.server.wm.TaskSnapshotController.*; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java index 7be203a99391..6a4710bb06a4 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java @@ -223,6 +223,19 @@ public class WindowStateTests extends WindowTestsBase { assertFalse(app.canAffectSystemUiFlags()); } + @Test + public void testIsSelfOrAncestorWindowAnimating() throws Exception { + final WindowState root = createWindow(null, TYPE_APPLICATION, "root"); + final WindowState child1 = createWindow(root, FIRST_SUB_WINDOW, "child1"); + final WindowState child2 = createWindow(child1, FIRST_SUB_WINDOW, "child2"); + assertFalse(child2.isSelfOrAncestorWindowAnimatingExit()); + child2.mAnimatingExit = true; + assertTrue(child2.isSelfOrAncestorWindowAnimatingExit()); + child2.mAnimatingExit = false; + root.mAnimatingExit = true; + assertTrue(child2.isSelfOrAncestorWindowAnimatingExit()); + } + private void testPrepareWindowToDisplayDuringRelayout(boolean wasVisible) { final WindowState root = createWindow(null, TYPE_APPLICATION, "root"); root.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java index c699a94db279..69b13787ef93 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java @@ -230,20 +230,22 @@ class WindowTestsBase { boolean ownerCanAddInternalSystemWindow) { final WindowToken token = createWindowToken( dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type); - return createWindow(parent, type, token, name, ownerCanAddInternalSystemWindow); + return createWindow(parent, type, token, name, 0 /* ownerId */, + ownerCanAddInternalSystemWindow); } static WindowState createWindow(WindowState parent, int type, WindowToken token, String name) { - return createWindow(parent, type, token, name, false /* ownerCanAddInternalSystemWindow */); + return createWindow(parent, type, token, name, 0 /* ownerId */, + false /* ownerCanAddInternalSystemWindow */); } static WindowState createWindow(WindowState parent, int type, WindowToken token, String name, - boolean ownerCanAddInternalSystemWindow) { + int ownerId, boolean ownerCanAddInternalSystemWindow) { final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type); attrs.setTitle(name); final WindowState w = new WindowState(sWm, sMockSession, sIWindow, token, parent, OP_NONE, - 0, attrs, VISIBLE, 0, ownerCanAddInternalSystemWindow); + 0, attrs, VISIBLE, ownerId, ownerCanAddInternalSystemWindow); // TODO: Probably better to make this call in the WindowState ctor to avoid errors with // adding it to the token... token.addWindow(w); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java new file mode 100644 index 000000000000..689c2ce4c873 --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.notification; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.UserInfo; +import android.os.UserManager; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.util.FastXmlSerializer; +import com.android.server.UiServiceTestCase; +import com.android.server.notification.NotificationManagerService.NotificationAssistants; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlSerializer; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.List; + +public class NotificationAssistantsTest extends UiServiceTestCase { + + @Mock + private PackageManager mPm; + @Mock + private IPackageManager miPm; + @Mock + private UserManager mUm; + @Mock + NotificationManagerService mNm; + + NotificationAssistants mAssistants; + + @Mock + private ManagedServices.UserProfiles mUserProfiles; + + Object mLock = new Object(); + + UserInfo mZero = new UserInfo(0, "zero", 0); + UserInfo mTen = new UserInfo(10, "ten", 0); + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + getContext().setMockPackageManager(mPm); + getContext().addMockSystemService(Context.USER_SERVICE, mUm); + mAssistants = spy(mNm.new NotificationAssistants(getContext(), mLock, mUserProfiles, miPm)); + + List<ResolveInfo> approved = new ArrayList<>(); + ResolveInfo resolve = new ResolveInfo(); + approved.add(resolve); + ServiceInfo info = new ServiceInfo(); + info.packageName = "a"; + info.name="a"; + resolve.serviceInfo = info; + when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())) + .thenReturn(approved); + + List<UserInfo> users = new ArrayList<>(); + users.add(mZero); + users.add(mTen); + users.add(new UserInfo(11, "11", 0)); + users.add(new UserInfo(12, "12", 0)); + for (UserInfo user : users) { + when(mUm.getUserInfo(eq(user.id))).thenReturn(user); + } + when(mUm.getUsers()).thenReturn(users); + when(mUm.getUsers(anyBoolean())).thenReturn(users); + when(mUserProfiles.getCurrentProfileIds()).thenReturn(new int[] {0, 10, 11, 12}); + } + + @Test + public void testXmlUpgrade() throws Exception { + String xml = "<enabled_assistants/>"; + + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(xml.toString().getBytes())), null); + parser.nextTag(); + mAssistants.readXml(parser); + + //once per user + verify(mNm, times(mUm.getUsers().size())).readDefaultAssistant(anyInt()); + } + + @Test + public void testXmlUpgradeExistingApprovedComponents() throws Exception { + String xml = "<enabled_assistants>" + + "<service_listing approved=\"b/b\" user=\"10\" primary=\"true\" />" + + "</enabled_assistants>"; + + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(xml.toString().getBytes())), null); + parser.nextTag(); + mAssistants.readXml(parser); + + // once per user + verify(mNm, times(mUm.getUsers().size())).readDefaultAssistant(anyInt()); + verify(mAssistants, times(1)).addApprovedList( + new ComponentName("b", "b").flattenToString(),10, true); + } + + @Test + public void testXmlUpgradeOnce() throws Exception { + String xml = "<enabled_assistants/>"; + + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(xml.toString().getBytes())), null); + parser.nextTag(); + mAssistants.readXml(parser); + + XmlSerializer serializer = new FastXmlSerializer(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + serializer.setOutput(new BufferedOutputStream(baos), "utf-8"); + serializer.startDocument(null, true); + mAssistants.writeXml(serializer, true); + serializer.endDocument(); + serializer.flush(); + + //once per user + verify(mNm, times(mUm.getUsers().size())).readDefaultAssistant(anyInt()); + + Mockito.reset(mNm); + + parser = Xml.newPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(baos.toByteArray())), null); + parser.nextTag(); + mAssistants.readXml(parser); + + //once per user + verify(mNm, never()).readDefaultAssistant(anyInt()); + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java index 9564ab9bdfee..36136a8932c9 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java @@ -48,7 +48,6 @@ public class ScheduleCalendarTest extends UiServiceTestCase { mScheduleCalendar = new ScheduleCalendar(); mScheduleInfo = new ZenModeConfig.ScheduleInfo(); mScheduleInfo.days = new int[] {1, 2, 3, 4, 5}; - mScheduleCalendar.setSchedule(mScheduleInfo); } @Test @@ -100,6 +99,7 @@ public class ScheduleCalendarTest extends UiServiceTestCase { mScheduleInfo.startMinute = 15; mScheduleInfo.endMinute = 15; mScheduleInfo.exitAtAlarm = false; + mScheduleCalendar.setSchedule(mScheduleInfo); Calendar expected = new GregorianCalendar(); expected.setTimeInMillis(cal.getTimeInMillis()); @@ -126,6 +126,7 @@ public class ScheduleCalendarTest extends UiServiceTestCase { mScheduleInfo.startMinute = 15; mScheduleInfo.endMinute = 15; mScheduleInfo.exitAtAlarm = false; + mScheduleCalendar.setSchedule(mScheduleInfo); Calendar expected = new GregorianCalendar(); expected.setTimeInMillis(cal.getTimeInMillis()); @@ -153,6 +154,7 @@ public class ScheduleCalendarTest extends UiServiceTestCase { mScheduleInfo.startMinute = 15; mScheduleInfo.endMinute = 15; mScheduleInfo.exitAtAlarm = false; + mScheduleCalendar.setSchedule(mScheduleInfo); Calendar expected = new GregorianCalendar(); expected.setTimeInMillis(cal.getTimeInMillis()); @@ -171,6 +173,7 @@ public class ScheduleCalendarTest extends UiServiceTestCase { public void testShouldExitForAlarm_settingOff() { mScheduleInfo.exitAtAlarm = false; mScheduleInfo.nextAlarm = 1000; + mScheduleCalendar.setSchedule(mScheduleInfo); assertFalse(mScheduleCalendar.shouldExitForAlarm(1000)); } @@ -179,6 +182,7 @@ public class ScheduleCalendarTest extends UiServiceTestCase { public void testShouldExitForAlarm_beforeAlarm() { mScheduleInfo.exitAtAlarm = true; mScheduleInfo.nextAlarm = 1000; + mScheduleCalendar.setSchedule(mScheduleInfo); assertFalse(mScheduleCalendar.shouldExitForAlarm(999)); } @@ -187,6 +191,7 @@ public class ScheduleCalendarTest extends UiServiceTestCase { public void testShouldExitForAlarm_noAlarm() { mScheduleInfo.exitAtAlarm = true; mScheduleInfo.nextAlarm = 0; + mScheduleCalendar.setSchedule(mScheduleInfo); assertFalse(mScheduleCalendar.shouldExitForAlarm(999)); } @@ -195,6 +200,7 @@ public class ScheduleCalendarTest extends UiServiceTestCase { public void testShouldExitForAlarm() { mScheduleInfo.exitAtAlarm = true; mScheduleInfo.nextAlarm = 1000; + mScheduleCalendar.setSchedule(mScheduleInfo); assertTrue(mScheduleCalendar.shouldExitForAlarm(1000)); } @@ -203,6 +209,7 @@ public class ScheduleCalendarTest extends UiServiceTestCase { public void testMaybeSetNextAlarm_settingOff() { mScheduleInfo.exitAtAlarm = false; mScheduleInfo.nextAlarm = 0; + mScheduleCalendar.setSchedule(mScheduleInfo); mScheduleCalendar.maybeSetNextAlarm(1000, 2000); @@ -213,6 +220,7 @@ public class ScheduleCalendarTest extends UiServiceTestCase { public void testMaybeSetNextAlarm_settingOn() { mScheduleInfo.exitAtAlarm = true; mScheduleInfo.nextAlarm = 0; + mScheduleCalendar.setSchedule(mScheduleInfo); mScheduleCalendar.maybeSetNextAlarm(1000, 2000); @@ -223,6 +231,7 @@ public class ScheduleCalendarTest extends UiServiceTestCase { public void testMaybeSetNextAlarm_alarmCanceled() { mScheduleInfo.exitAtAlarm = true; mScheduleInfo.nextAlarm = 10000; + mScheduleCalendar.setSchedule(mScheduleInfo); mScheduleCalendar.maybeSetNextAlarm(1000, 0); @@ -233,6 +242,7 @@ public class ScheduleCalendarTest extends UiServiceTestCase { public void testMaybeSetNextAlarm_earlierAlarm() { mScheduleInfo.exitAtAlarm = true; mScheduleInfo.nextAlarm = 2000; + mScheduleCalendar.setSchedule(mScheduleInfo); mScheduleCalendar.maybeSetNextAlarm(1000, 1500); @@ -242,6 +252,7 @@ public class ScheduleCalendarTest extends UiServiceTestCase { @Test public void testMaybeSetNextAlarm_laterAlarm() { mScheduleInfo.exitAtAlarm = true; + mScheduleCalendar.setSchedule(mScheduleInfo); mScheduleInfo.nextAlarm = 2000; mScheduleCalendar.maybeSetNextAlarm(1000, 3000); @@ -253,6 +264,7 @@ public class ScheduleCalendarTest extends UiServiceTestCase { public void testMaybeSetNextAlarm_expiredAlarm() { mScheduleInfo.exitAtAlarm = true; mScheduleInfo.nextAlarm = 998; + mScheduleCalendar.setSchedule(mScheduleInfo); mScheduleCalendar.maybeSetNextAlarm(1000, 999); @@ -272,6 +284,7 @@ public class ScheduleCalendarTest extends UiServiceTestCase { mScheduleInfo.endHour = 3; mScheduleInfo.startMinute = 15; mScheduleInfo.endMinute = 15; + mScheduleCalendar.setSchedule(mScheduleInfo); assertTrue(mScheduleCalendar.isInSchedule(cal.getTimeInMillis())); } @@ -289,6 +302,7 @@ public class ScheduleCalendarTest extends UiServiceTestCase { mScheduleInfo.endHour = 3; mScheduleInfo.startMinute = 16; mScheduleInfo.endMinute = 15; + mScheduleCalendar.setSchedule(mScheduleInfo); assertTrue(mScheduleCalendar.isInSchedule(cal.getTimeInMillis())); } @@ -306,6 +320,7 @@ public class ScheduleCalendarTest extends UiServiceTestCase { mScheduleInfo.startMinute = 16; mScheduleInfo.endHour = 15; mScheduleInfo.endMinute = 15; + mScheduleCalendar.setSchedule(mScheduleInfo); assertFalse(mScheduleCalendar.isInSchedule(cal.getTimeInMillis())); } @@ -322,6 +337,7 @@ public class ScheduleCalendarTest extends UiServiceTestCase { mScheduleInfo.endHour = 3; mScheduleInfo.startMinute = 16; mScheduleInfo.endMinute = 15; + mScheduleCalendar.setSchedule(mScheduleInfo); assertFalse(mScheduleCalendar.isInSchedule(cal.getTimeInMillis())); } diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java index e0b6f610ab55..e633053800bc 100644 --- a/telephony/java/android/provider/Telephony.java +++ b/telephony/java/android/provider/Telephony.java @@ -2564,6 +2564,35 @@ public final class Telephony { public static final Uri CONTENT_URI = Uri.parse("content://telephony/carriers"); /** + * The {@code content://} style URL to be called from DevicePolicyManagerService, + * can manage DPC-owned APNs. + * @hide + */ + public static final Uri DPC_URI = Uri.parse("content://telephony/carriers/dpc"); + + /** + * The {@code content://} style URL to be called from Telephony to query APNs. + * When DPC-owned APNs are enforced, only DPC-owned APNs are returned, otherwise only + * non-DPC-owned APNs are returned. + * @hide + */ + public static final Uri FILTERED_URI = Uri.parse("content://telephony/carriers/filtered"); + + /** + * The {@code content://} style URL to be called from DevicePolicyManagerService + * or Telephony to manage whether DPC-owned APNs are enforced. + * @hide + */ + public static final Uri ENFORCE_MANAGED_URI = Uri.parse( + "content://telephony/carriers/enforce_managed"); + + /** + * The column name for ENFORCE_MANAGED_URI, indicates whether DPC-owned APNs are enforced. + * @hide + */ + public static final String ENFORCE_KEY = "enforced"; + + /** * The default sort order for this table. */ public static final String DEFAULT_SORT_ORDER = "name ASC"; diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 5a1a3e353d70..ce0b551311b0 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -960,8 +960,9 @@ public class CarrierConfigManager { public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool"; /** - * String to identify carrier name in CarrierConfig app. This string is used only if - * #KEY_CARRIER_NAME_OVERRIDE_BOOL is true + * String to identify carrier name in CarrierConfig app. This string overrides SPN if + * #KEY_CARRIER_NAME_OVERRIDE_BOOL is true; otherwise, it will be used if its value is provided + * and SPN is unavailable * @hide */ public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string"; diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index f411ef71c94b..8edc8b17c9e0 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -53,6 +53,7 @@ import android.util.Log; import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsRcsFeature; +import com.android.ims.internal.IImsRegistration; import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telecom.ITelecomService; @@ -2514,6 +2515,33 @@ public class TelephonyManager { } } + /** + * Resets the Carrier Keys in the database. This involves 2 steps: + * 1. Delete the keys from the database. + * 2. Send an intent to download new Certificates. + * <p> + * Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * @hide + */ + public void resetCarrierKeysForImsiEncryption() { + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) { + throw new RuntimeException("IMSI error: Subscriber Info is null"); + } + int subId = getSubId(SubscriptionManager.getDefaultDataSubscriptionId()); + info.resetCarrierKeysForImsiEncryption(subId, mContext.getOpPackageName()); + } catch (RemoteException ex) { + Rlog.e(TAG, "getCarrierInfoForImsiEncryption RemoteException" + ex); + throw new RuntimeException("IMSI error: Remote Exception"); + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + Rlog.e(TAG, "getCarrierInfoForImsiEncryption NullPointerException" + ex); + throw new RuntimeException("IMSI error: Null Pointer exception"); + } + } + /** * @param keyAvailability bitmask that defines the availabilty of keys for a type. * @param keyType the key type which is being checked. (WLAN, EPDG) @@ -2549,7 +2577,7 @@ public class TelephonyManager { * device keystore. * <p> * Requires Permission: - * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} * @param imsiEncryptionInfo which includes the Key Type, the Public Key * (java.security.PublicKey) and the Key Identifier.and the Key Identifier. * The keyIdentifier Attribute value pair that helps a server locate @@ -4932,6 +4960,25 @@ public class TelephonyManager { } /** + * @return the {@IImsRegistration} interface that corresponds with the slot index and feature. + * @param slotIndex The SIM slot corresponding to the ImsService ImsRegistration is active for. + * @param feature An integer indicating the feature that we wish to get the ImsRegistration for. + * Corresponds to features defined in ImsFeature. + * @hide + */ + public @Nullable IImsRegistration getImsRegistration(int slotIndex, int feature) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getImsRegistration(slotIndex, feature); + } + } catch (RemoteException e) { + Rlog.e(TAG, "getImsRegistration, RemoteException: " + e.getMessage()); + } + return null; + } + + /** * Set IMS registration state * * @param Registration state diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java new file mode 100644 index 000000000000..ea0817551369 --- /dev/null +++ b/telephony/java/android/telephony/data/DataService.java @@ -0,0 +1,540 @@ +/* + * Copyright 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.telephony.data; + +import android.annotation.CallSuper; +import android.annotation.SystemApi; +import android.app.Service; +import android.content.Intent; +import android.net.LinkProperties; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.telephony.AccessNetworkConstants; +import android.telephony.Rlog; +import android.telephony.SubscriptionManager; +import android.util.SparseArray; + +import java.util.ArrayList; +import java.util.List; + +/** + * Base class of data service. Services that extend DataService must register the service in + * their AndroidManifest to be detected by the framework. They must be protected by the permission + * "android.permission.BIND_DATA_SERVICE". The data service definition in the manifest must follow + * the following format: + * ... + * <service android:name=".xxxDataService" + * android:permission="android.permission.BIND_DATA_SERVICE" > + * <intent-filter> + * <action android:name="android.telephony.data.DataService" /> + * </intent-filter> + * </service> + * @hide + */ +@SystemApi +public abstract class DataService extends Service { + private static final String TAG = DataService.class.getSimpleName(); + + public static final String DATA_SERVICE_INTERFACE = "android.telephony.data.DataService"; + public static final String DATA_SERVICE_EXTRA_SLOT_ID = "android.telephony.data.extra.SLOT_ID"; + + private static final int DATA_SERVICE_INTERNAL_REQUEST_INITIALIZE_SERVICE = 1; + private static final int DATA_SERVICE_REQUEST_SETUP_DATA_CALL = 2; + private static final int DATA_SERVICE_REQUEST_DEACTIVATE_DATA_CALL = 3; + private static final int DATA_SERVICE_REQUEST_SET_INITIAL_ATTACH_APN = 4; + private static final int DATA_SERVICE_REQUEST_SET_DATA_PROFILE = 5; + private static final int DATA_SERVICE_REQUEST_GET_DATA_CALL_LIST = 6; + private static final int DATA_SERVICE_REQUEST_REGISTER_DATA_CALL_LIST_CHANGED = 7; + private static final int DATA_SERVICE_REQUEST_UNREGISTER_DATA_CALL_LIST_CHANGED = 8; + private static final int DATA_SERVICE_INDICATION_DATA_CALL_LIST_CHANGED = 9; + + private final HandlerThread mHandlerThread; + + private final DataServiceHandler mHandler; + + private final SparseArray<DataServiceProvider> mServiceMap = new SparseArray<>(); + + private final SparseArray<IDataServiceWrapper> mBinderMap = new SparseArray<>(); + + /** + * The abstract class of the actual data service implementation. The data service provider + * must extend this class to support data connection. Note that each instance of data service + * provider is associated with one physical SIM slot. + */ + public class DataServiceProvider { + + private final int mSlotId; + + private final List<IDataServiceCallback> mDataCallListChangedCallbacks = new ArrayList<>(); + + /** + * Constructor + * @param slotId SIM slot id the data service provider associated with. + */ + public DataServiceProvider(int slotId) { + mSlotId = slotId; + } + + /** + * @return SIM slot id the data service provider associated with. + */ + public final int getSlotId() { + return mSlotId; + } + + /** + * Setup a data connection. The data service provider must implement this method to support + * establishing a packet data connection. When completed or error, the service must invoke + * the provided callback to notify the platform. + * + * @param accessNetworkType Access network type that the data call will be established on. + * Must be one of {@link AccessNetworkConstants.AccessNetworkType}. + * @param dataProfile Data profile used for data call setup. See {@link DataProfile} + * @param isRoaming True if the device is data roaming. + * @param allowRoaming True if data roaming is allowed by the user. + * @param isHandover True if the request is for IWLAN handover. + * @param linkProperties If {@code isHandover} is true, this is the link properties of the + * existing data connection, otherwise null. + * @param callback The result callback for this request. + */ + public void setupDataCall(int accessNetworkType, DataProfile dataProfile, boolean isRoaming, + boolean allowRoaming, boolean isHandover, + LinkProperties linkProperties, DataServiceCallback callback) { + // The default implementation is to return unsupported. + callback.onSetupDataCallComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED, null); + } + + /** + * Deactivate a data connection. The data service provider must implement this method to + * support data connection tear down. When completed or error, the service must invoke the + * provided callback to notify the platform. + * + * @param cid Call id returned in the callback of {@link DataServiceProvider#setupDataCall( + * int, DataProfile, boolean, boolean, boolean, LinkProperties, DataServiceCallback)}. + * @param reasonRadioShutDown True if the deactivate request reason is device shut down. + * @param isHandover True if the request is for IWLAN handover. + * @param callback The result callback for this request. + */ + public void deactivateDataCall(int cid, boolean reasonRadioShutDown, boolean isHandover, + DataServiceCallback callback) { + // The default implementation is to return unsupported. + callback.onDeactivateDataCallComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED); + } + + /** + * Set an APN to initial attach network. + * + * @param dataProfile Data profile used for data call setup. See {@link DataProfile}. + * @param isRoaming True if the device is data roaming. + * @param callback The result callback for this request. + */ + public void setInitialAttachApn(DataProfile dataProfile, boolean isRoaming, + DataServiceCallback callback) { + // The default implementation is to return unsupported. + callback.onSetInitialAttachApnComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED); + } + + /** + * Send current carrier's data profiles to the data service for data call setup. This is + * only for CDMA carrier that can change the profile through OTA. The data service should + * always uses the latest data profile sent by the framework. + * + * @param dps A list of data profiles. + * @param isRoaming True if the device is data roaming. + * @param callback The result callback for this request. + */ + public void setDataProfile(List<DataProfile> dps, boolean isRoaming, + DataServiceCallback callback) { + // The default implementation is to return unsupported. + callback.onSetDataProfileComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED); + } + + /** + * Get the active data call list. + * + * @param callback The result callback for this request. + */ + public void getDataCallList(DataServiceCallback callback) { + // The default implementation is to return unsupported. + callback.onGetDataCallListComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED, null); + } + + private void registerForDataCallListChanged(IDataServiceCallback callback) { + synchronized (mDataCallListChangedCallbacks) { + mDataCallListChangedCallbacks.add(callback); + } + } + + private void unregisterForDataCallListChanged(IDataServiceCallback callback) { + synchronized (mDataCallListChangedCallbacks) { + mDataCallListChangedCallbacks.remove(callback); + } + } + + /** + * Notify the system that current data call list changed. Data service must invoke this + * method whenever there is any data call status changed. + * + * @param dataCallList List of the current active data call. + */ + public final void notifyDataCallListChanged(List<DataCallResponse> dataCallList) { + synchronized (mDataCallListChangedCallbacks) { + for (IDataServiceCallback callback : mDataCallListChangedCallbacks) { + mHandler.obtainMessage(DATA_SERVICE_INDICATION_DATA_CALL_LIST_CHANGED, mSlotId, + 0, new DataCallListChangedIndication(dataCallList, callback)) + .sendToTarget(); + } + } + } + + /** + * Called when the instance of data service is destroyed (e.g. got unbind or binder died). + */ + @CallSuper + protected void onDestroy() { + mDataCallListChangedCallbacks.clear(); + } + } + + private static final class SetupDataCallRequest { + public final int accessNetworkType; + public final DataProfile dataProfile; + public final boolean isRoaming; + public final boolean allowRoaming; + public final boolean isHandover; + public final LinkProperties linkProperties; + public final IDataServiceCallback callback; + SetupDataCallRequest(int accessNetworkType, DataProfile dataProfile, boolean isRoaming, + boolean allowRoaming, boolean isHandover, + LinkProperties linkProperties, IDataServiceCallback callback) { + this.accessNetworkType = accessNetworkType; + this.dataProfile = dataProfile; + this.isRoaming = isRoaming; + this.allowRoaming = allowRoaming; + this.linkProperties = linkProperties; + this.isHandover = isHandover; + this.callback = callback; + } + } + + private static final class DeactivateDataCallRequest { + public final int cid; + public final boolean reasonRadioShutDown; + public final boolean isHandover; + public final IDataServiceCallback callback; + DeactivateDataCallRequest(int cid, boolean reasonRadioShutDown, boolean isHandover, + IDataServiceCallback callback) { + this.cid = cid; + this.reasonRadioShutDown = reasonRadioShutDown; + this.isHandover = isHandover; + this.callback = callback; + } + } + + private static final class SetInitialAttachApnRequest { + public final DataProfile dataProfile; + public final boolean isRoaming; + public final IDataServiceCallback callback; + SetInitialAttachApnRequest(DataProfile dataProfile, boolean isRoaming, + IDataServiceCallback callback) { + this.dataProfile = dataProfile; + this.isRoaming = isRoaming; + this.callback = callback; + } + } + + private static final class SetDataProfileRequest { + public final List<DataProfile> dps; + public final boolean isRoaming; + public final IDataServiceCallback callback; + SetDataProfileRequest(List<DataProfile> dps, boolean isRoaming, + IDataServiceCallback callback) { + this.dps = dps; + this.isRoaming = isRoaming; + this.callback = callback; + } + } + + private static final class DataCallListChangedIndication { + public final List<DataCallResponse> dataCallList; + public final IDataServiceCallback callback; + DataCallListChangedIndication(List<DataCallResponse> dataCallList, + IDataServiceCallback callback) { + this.dataCallList = dataCallList; + this.callback = callback; + } + } + + private class DataServiceHandler extends Handler { + + DataServiceHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message message) { + IDataServiceCallback callback; + final int slotId = message.arg1; + DataServiceProvider service; + + synchronized (mServiceMap) { + service = mServiceMap.get(slotId); + } + + switch (message.what) { + case DATA_SERVICE_INTERNAL_REQUEST_INITIALIZE_SERVICE: + service = createDataServiceProvider(message.arg1); + if (service != null) { + mServiceMap.put(slotId, service); + } + break; + case DATA_SERVICE_REQUEST_SETUP_DATA_CALL: + if (service == null) break; + SetupDataCallRequest setupDataCallRequest = (SetupDataCallRequest) message.obj; + service.setupDataCall(setupDataCallRequest.accessNetworkType, + setupDataCallRequest.dataProfile, setupDataCallRequest.isRoaming, + setupDataCallRequest.allowRoaming, setupDataCallRequest.isHandover, + setupDataCallRequest.linkProperties, + new DataServiceCallback(setupDataCallRequest.callback)); + + break; + case DATA_SERVICE_REQUEST_DEACTIVATE_DATA_CALL: + if (service == null) break; + DeactivateDataCallRequest deactivateDataCallRequest = + (DeactivateDataCallRequest) message.obj; + service.deactivateDataCall(deactivateDataCallRequest.cid, + deactivateDataCallRequest.reasonRadioShutDown, + deactivateDataCallRequest.isHandover, + new DataServiceCallback(deactivateDataCallRequest.callback)); + break; + case DATA_SERVICE_REQUEST_SET_INITIAL_ATTACH_APN: + if (service == null) break; + SetInitialAttachApnRequest setInitialAttachApnRequest = + (SetInitialAttachApnRequest) message.obj; + service.setInitialAttachApn(setInitialAttachApnRequest.dataProfile, + setInitialAttachApnRequest.isRoaming, + new DataServiceCallback(setInitialAttachApnRequest.callback)); + break; + case DATA_SERVICE_REQUEST_SET_DATA_PROFILE: + if (service == null) break; + SetDataProfileRequest setDataProfileRequest = + (SetDataProfileRequest) message.obj; + service.setDataProfile(setDataProfileRequest.dps, + setDataProfileRequest.isRoaming, + new DataServiceCallback(setDataProfileRequest.callback)); + break; + case DATA_SERVICE_REQUEST_GET_DATA_CALL_LIST: + if (service == null) break; + + service.getDataCallList(new DataServiceCallback( + (IDataServiceCallback) message.obj)); + break; + case DATA_SERVICE_REQUEST_REGISTER_DATA_CALL_LIST_CHANGED: + if (service == null) break; + service.registerForDataCallListChanged((IDataServiceCallback) message.obj); + break; + case DATA_SERVICE_REQUEST_UNREGISTER_DATA_CALL_LIST_CHANGED: + if (service == null) break; + callback = (IDataServiceCallback) message.obj; + service.unregisterForDataCallListChanged(callback); + break; + case DATA_SERVICE_INDICATION_DATA_CALL_LIST_CHANGED: + if (service == null) break; + DataCallListChangedIndication indication = + (DataCallListChangedIndication) message.obj; + try { + indication.callback.onDataCallListChanged(indication.dataCallList); + } catch (RemoteException e) { + loge("Failed to call onDataCallListChanged. " + e); + } + break; + } + } + } + + private DataService() { + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + + mHandler = new DataServiceHandler(mHandlerThread.getLooper()); + log("Data service created"); + } + + /** + * Create the instance of {@link DataServiceProvider}. Data service provider must override + * this method to facilitate the creation of {@link DataServiceProvider} instances. The system + * will call this method after binding the data service for each active SIM slot id. + * + * @param slotId SIM slot id the data service associated with. + * @return Data service object + */ + public abstract DataServiceProvider createDataServiceProvider(int slotId); + + /** @hide */ + @Override + public IBinder onBind(Intent intent) { + if (intent == null || !DATA_SERVICE_INTERFACE.equals(intent.getAction())) { + loge("Unexpected intent " + intent); + return null; + } + + int slotId = intent.getIntExtra( + DATA_SERVICE_EXTRA_SLOT_ID, SubscriptionManager.INVALID_SIM_SLOT_INDEX); + + if (!SubscriptionManager.isValidSlotIndex(slotId)) { + loge("Invalid slot id " + slotId); + return null; + } + + log("onBind: slot id=" + slotId); + + IDataServiceWrapper binder = mBinderMap.get(slotId); + if (binder == null) { + Message msg = mHandler.obtainMessage(DATA_SERVICE_INTERNAL_REQUEST_INITIALIZE_SERVICE); + msg.arg1 = slotId; + msg.sendToTarget(); + + binder = new IDataServiceWrapper(slotId); + mBinderMap.put(slotId, binder); + } + + return binder; + } + + /** @hide */ + @Override + public boolean onUnbind(Intent intent) { + int slotId = intent.getIntExtra(DATA_SERVICE_EXTRA_SLOT_ID, + SubscriptionManager.INVALID_SIM_SLOT_INDEX); + if (mBinderMap.get(slotId) != null) { + DataServiceProvider serviceImpl; + synchronized (mServiceMap) { + serviceImpl = mServiceMap.get(slotId); + } + if (serviceImpl != null) { + serviceImpl.onDestroy(); + } + mBinderMap.remove(slotId); + } + + // If all clients unbinds, quit the handler thread + if (mBinderMap.size() == 0) { + mHandlerThread.quit(); + } + + return false; + } + + /** @hide */ + @Override + public void onDestroy() { + synchronized (mServiceMap) { + for (int i = 0; i < mServiceMap.size(); i++) { + DataServiceProvider serviceImpl = mServiceMap.get(i); + if (serviceImpl != null) { + serviceImpl.onDestroy(); + } + } + mServiceMap.clear(); + } + + mHandlerThread.quit(); + } + + /** + * A wrapper around IDataService that forwards calls to implementations of {@link DataService}. + */ + private class IDataServiceWrapper extends IDataService.Stub { + + private final int mSlotId; + + IDataServiceWrapper(int slotId) { + mSlotId = slotId; + } + + @Override + public void setupDataCall(int accessNetworkType, DataProfile dataProfile, + boolean isRoaming, boolean allowRoaming, boolean isHandover, + LinkProperties linkProperties, IDataServiceCallback callback) { + mHandler.obtainMessage(DATA_SERVICE_REQUEST_SETUP_DATA_CALL, mSlotId, 0, + new SetupDataCallRequest(accessNetworkType, dataProfile, isRoaming, + allowRoaming, isHandover, linkProperties, callback)) + .sendToTarget(); + } + + @Override + public void deactivateDataCall(int cid, boolean reasonRadioShutDown, boolean isHandover, + IDataServiceCallback callback) { + mHandler.obtainMessage(DATA_SERVICE_REQUEST_DEACTIVATE_DATA_CALL, mSlotId, 0, + new DeactivateDataCallRequest(cid, reasonRadioShutDown, isHandover, callback)) + .sendToTarget(); + } + + @Override + public void setInitialAttachApn(DataProfile dataProfile, boolean isRoaming, + IDataServiceCallback callback) { + mHandler.obtainMessage(DATA_SERVICE_REQUEST_SET_INITIAL_ATTACH_APN, mSlotId, 0, + new SetInitialAttachApnRequest(dataProfile, isRoaming, callback)) + .sendToTarget(); + } + + @Override + public void setDataProfile(List<DataProfile> dps, boolean isRoaming, + IDataServiceCallback callback) { + mHandler.obtainMessage(DATA_SERVICE_REQUEST_SET_DATA_PROFILE, mSlotId, 0, + new SetDataProfileRequest(dps, isRoaming, callback)).sendToTarget(); + } + + @Override + public void getDataCallList(IDataServiceCallback callback) { + mHandler.obtainMessage(DATA_SERVICE_REQUEST_GET_DATA_CALL_LIST, mSlotId, 0, + callback).sendToTarget(); + } + + @Override + public void registerForDataCallListChanged(IDataServiceCallback callback) { + if (callback == null) { + loge("Callback is null"); + return; + } + mHandler.obtainMessage(DATA_SERVICE_REQUEST_REGISTER_DATA_CALL_LIST_CHANGED, mSlotId, + 0, callback).sendToTarget(); + } + + @Override + public void unregisterForDataCallListChanged(IDataServiceCallback callback) { + if (callback == null) { + loge("Callback is null"); + return; + } + mHandler.obtainMessage(DATA_SERVICE_REQUEST_UNREGISTER_DATA_CALL_LIST_CHANGED, mSlotId, + 0, callback).sendToTarget(); + } + } + + private void log(String s) { + Rlog.d(TAG, s); + } + + private void loge(String s) { + Rlog.e(TAG, s); + } +} diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java new file mode 100644 index 000000000000..b6a81f94028b --- /dev/null +++ b/telephony/java/android/telephony/data/DataServiceCallback.java @@ -0,0 +1,172 @@ +/* + * Copyright 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.telephony.data; + +import android.annotation.IntDef; +import android.annotation.SystemApi; +import android.os.RemoteException; +import android.telephony.Rlog; +import android.telephony.data.DataService.DataServiceProvider; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; +import java.util.List; + +/** + * Data service callback, which is for bound data service to invoke for solicited and unsolicited + * response. The caller is responsible to create a callback object for each single asynchronous + * request. + * + * @hide + */ +@SystemApi +public class DataServiceCallback { + + private static final String mTag = DataServiceCallback.class.getSimpleName(); + + /** + * Result of data requests + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({RESULT_SUCCESS, RESULT_ERROR_UNSUPPORTED, RESULT_ERROR_INVALID_ARG, RESULT_ERROR_BUSY, + RESULT_ERROR_ILLEGAL_STATE}) + public @interface Result {} + + /** Request is completed successfully */ + public static final int RESULT_SUCCESS = 0; + /** Request is not support */ + public static final int RESULT_ERROR_UNSUPPORTED = 1; + /** Request contains invalid arguments */ + public static final int RESULT_ERROR_INVALID_ARG = 2; + /** Service is busy */ + public static final int RESULT_ERROR_BUSY = 3; + /** Request sent in illegal state */ + public static final int RESULT_ERROR_ILLEGAL_STATE = 4; + + private final WeakReference<IDataServiceCallback> mCallback; + + /** @hide */ + public DataServiceCallback(IDataServiceCallback callback) { + mCallback = new WeakReference<>(callback); + } + + /** + * Called to indicate result for the request {@link DataServiceProvider#setupDataCall(int, + * DataProfile, boolean, boolean, boolean, DataServiceCallback)}. + * + * @param result The result code. Must be one of the {@link Result}. + * @param response Setup data call response. + */ + public void onSetupDataCallComplete(@Result int result, DataCallResponse response) { + IDataServiceCallback callback = mCallback.get(); + if (callback != null) { + try { + callback.onSetupDataCallComplete(result, response); + } catch (RemoteException e) { + Rlog.e(mTag, "Failed to onSetupDataCallComplete on the remote"); + } + } + } + + /** + * Called to indicate result for the request {@link DataServiceProvider#deactivateDataCall(int, + * boolean, boolean, DataServiceCallback)}. + * + * @param result The result code. Must be one of the {@link Result}. + */ + public void onDeactivateDataCallComplete(@Result int result) { + IDataServiceCallback callback = mCallback.get(); + if (callback != null) { + try { + callback.onDeactivateDataCallComplete(result); + } catch (RemoteException e) { + Rlog.e(mTag, "Failed to onDeactivateDataCallComplete on the remote"); + } + } + } + + /** + * Called to indicate result for the request {@link DataServiceProvider#setInitialAttachApn( + * DataProfile, boolean, DataServiceCallback)}. + * + * @param result The result code. Must be one of the {@link Result}. + */ + public void onSetInitialAttachApnComplete(@Result int result) { + IDataServiceCallback callback = mCallback.get(); + if (callback != null) { + try { + callback.onSetInitialAttachApnComplete(result); + } catch (RemoteException e) { + Rlog.e(mTag, "Failed to onSetInitialAttachApnComplete on the remote"); + } + } + } + + /** + * Called to indicate result for the request {@link DataServiceProvider#setDataProfile(List, + * boolean, DataServiceCallback)}. + * + * @param result The result code. Must be one of the {@link Result}. + */ + @SystemApi + public void onSetDataProfileComplete(@Result int result) { + IDataServiceCallback callback = mCallback.get(); + if (callback != null) { + try { + callback.onSetDataProfileComplete(result); + } catch (RemoteException e) { + Rlog.e(mTag, "Failed to onSetDataProfileComplete on the remote"); + } + } + } + + /** + * Called to indicate result for the request {@link DataServiceProvider#getDataCallList( + * DataServiceCallback)}. + * + * @param result The result code. Must be one of the {@link Result}. + * @param dataCallList List of the current active data connection. + */ + public void onGetDataCallListComplete(@Result int result, List<DataCallResponse> dataCallList) { + IDataServiceCallback callback = mCallback.get(); + if (callback != null) { + try { + callback.onGetDataCallListComplete(result, dataCallList); + } catch (RemoteException e) { + Rlog.e(mTag, "Failed to onGetDataCallListComplete on the remote"); + } + } + } + + /** + * Called to indicate that data connection list changed. + * + * @param dataCallList List of the current active data connection. + */ + public void onDataCallListChanged(List<DataCallResponse> dataCallList) { + IDataServiceCallback callback = mCallback.get(); + if (callback != null) { + try { + callback.onDataCallListChanged(dataCallList); + } catch (RemoteException e) { + Rlog.e(mTag, "Failed to onDataCallListChanged on the remote"); + } + } + } +} diff --git a/telephony/java/android/telephony/data/IDataService.aidl b/telephony/java/android/telephony/data/IDataService.aidl new file mode 100644 index 000000000000..4eaaa252da02 --- /dev/null +++ b/telephony/java/android/telephony/data/IDataService.aidl @@ -0,0 +1,39 @@ +/* + * Copyright 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.telephony.data; + +import android.net.LinkProperties; +import android.telephony.data.DataProfile; +import android.telephony.data.IDataServiceCallback; + +/** + * {@hide} + */ +oneway interface IDataService +{ + void setupDataCall(int accessNetwork, in DataProfile dataProfile, boolean isRoaming, + boolean allowRoaming, boolean isHandover, in LinkProperties linkProperties, + IDataServiceCallback callback); + void deactivateDataCall(int cid, boolean reasonRadioShutDown, boolean isHandover, + IDataServiceCallback callback); + void setInitialAttachApn(in DataProfile dataProfile, boolean isRoaming, + IDataServiceCallback callback); + void setDataProfile(in List<DataProfile> dps, boolean isRoaming, IDataServiceCallback callback); + void getDataCallList(IDataServiceCallback callback); + void registerForDataCallListChanged(IDataServiceCallback callback); + void unregisterForDataCallListChanged(IDataServiceCallback callback); +} diff --git a/telephony/java/android/telephony/data/IDataServiceCallback.aidl b/telephony/java/android/telephony/data/IDataServiceCallback.aidl new file mode 100644 index 000000000000..856185b2974f --- /dev/null +++ b/telephony/java/android/telephony/data/IDataServiceCallback.aidl @@ -0,0 +1,33 @@ +/* + * Copyright 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.telephony.data; + +import android.telephony.data.DataCallResponse; + +/** + * The call back interface + * @hide + */ +oneway interface IDataServiceCallback +{ + void onSetupDataCallComplete(int result, in DataCallResponse dataCallResponse); + void onDeactivateDataCallComplete(int result); + void onSetInitialAttachApnComplete(int result); + void onSetDataProfileComplete(int result); + void onGetDataCallListComplete(int result, in List<DataCallResponse> dataCallList); + void onDataCallListChanged(in List<DataCallResponse> dataCallList); +} diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java index 29849c1f9ccd..6975354c8af1 100644 --- a/telephony/java/android/telephony/euicc/EuiccCardManager.java +++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java @@ -15,14 +15,31 @@ */ package android.telephony.euicc; +import android.annotation.IntDef; +import android.annotation.Nullable; import android.content.Context; import android.os.RemoteException; import android.os.ServiceManager; import android.service.euicc.EuiccProfileInfo; import android.util.Log; +import com.android.internal.telephony.euicc.IAuthenticateServerCallback; +import com.android.internal.telephony.euicc.ICancelSessionCallback; import com.android.internal.telephony.euicc.IEuiccCardController; import com.android.internal.telephony.euicc.IGetAllProfilesCallback; +import com.android.internal.telephony.euicc.IGetEuiccChallengeCallback; +import com.android.internal.telephony.euicc.IGetEuiccInfo1Callback; +import com.android.internal.telephony.euicc.IGetEuiccInfo2Callback; +import com.android.internal.telephony.euicc.IGetRulesAuthTableCallback; +import com.android.internal.telephony.euicc.IListNotificationsCallback; +import com.android.internal.telephony.euicc.ILoadBoundProfilePackageCallback; +import com.android.internal.telephony.euicc.IPrepareDownloadCallback; +import com.android.internal.telephony.euicc.IRemoveNotificationFromListCallback; +import com.android.internal.telephony.euicc.IRetrieveNotificationCallback; +import com.android.internal.telephony.euicc.IRetrieveNotificationListCallback; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * EuiccCardManager is the application interface to an eSIM card. @@ -34,6 +51,35 @@ import com.android.internal.telephony.euicc.IGetAllProfilesCallback; public class EuiccCardManager { private static final String TAG = "EuiccCardManager"; + /** Reason for canceling a profile download session */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "CANCEL_REASON_" }, value = { + CANCEL_REASON_END_USER_REJECTED, + CANCEL_REASON_POSTPONED, + CANCEL_REASON_TIMEOUT, + CANCEL_REASON_PPR_NOT_ALLOWED + }) + public @interface CancelReason {} + + /** + * The end user has rejected the download. The profile will be put into the error state and + * cannot be downloaded again without the operator's change. + */ + public static final int CANCEL_REASON_END_USER_REJECTED = 0; + + /** The download has been postponed and can be restarted later. */ + public static final int CANCEL_REASON_POSTPONED = 1; + + /** The download has been timed out and can be restarted later. */ + public static final int CANCEL_REASON_TIMEOUT = 2; + + /** + * The profile to be downloaded cannot be installed due to its policy rule is not allowed by + * the RAT (Rules Authorisation Table) on the eUICC or by other installed profiles. The + * download can be restarted later. + */ + public static final int CANCEL_REASON_PPR_NOT_ALLOWED = 3; + /** Result code of execution with no error. */ public static final int RESULT_OK = 0; @@ -85,4 +131,298 @@ public class EuiccCardManager { throw e.rethrowFromSystemServer(); } } + + /** + * Gets Rules Authorisation Table. + * + * @param callback the callback to get the result code and the rule authorisation table. + */ + public void getRulesAuthTable(ResultCallback<EuiccRulesAuthTable> callback) { + try { + getIEuiccCardController().getRulesAuthTable(mContext.getOpPackageName(), + new IGetRulesAuthTableCallback.Stub() { + @Override + public void onComplete(int resultCode, EuiccRulesAuthTable rat) { + callback.onComplete(resultCode, rat); + } + }); + } catch (RemoteException e) { + Log.e(TAG, "Error calling getRulesAuthTable", e); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Gets the eUICC challenge for new profile downloading. + * + * @param callback the callback to get the result code and the challenge. + */ + public void getEuiccChallenge(ResultCallback<byte[]> callback) { + try { + getIEuiccCardController().getEuiccChallenge(mContext.getOpPackageName(), + new IGetEuiccChallengeCallback.Stub() { + @Override + public void onComplete(int resultCode, byte[] challenge) { + callback.onComplete(resultCode, challenge); + } + }); + } catch (RemoteException e) { + Log.e(TAG, "Error calling getEuiccChallenge", e); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Gets the eUICC info1 defined in GSMA RSP v2.0+ for new profile downloading. + * + * @param callback the callback to get the result code and the info1. + */ + public void getEuiccInfo1(ResultCallback<byte[]> callback) { + try { + getIEuiccCardController().getEuiccInfo1(mContext.getOpPackageName(), + new IGetEuiccInfo1Callback.Stub() { + @Override + public void onComplete(int resultCode, byte[] info) { + callback.onComplete(resultCode, info); + } + }); + } catch (RemoteException e) { + Log.e(TAG, "Error calling getEuiccInfo1", e); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Gets the eUICC info2 defined in GSMA RSP v2.0+ for new profile downloading. + * + * @param callback the callback to get the result code and the info2. + */ + public void getEuiccInfo2(ResultCallback<byte[]> callback) { + try { + getIEuiccCardController().getEuiccInfo2(mContext.getOpPackageName(), + new IGetEuiccInfo2Callback.Stub() { + @Override + public void onComplete(int resultCode, byte[] info) { + callback.onComplete(resultCode, info); + } + }); + } catch (RemoteException e) { + Log.e(TAG, "Error calling getEuiccInfo2", e); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Authenticates the SM-DP+ server by the eUICC. + * + * @param matchingId the activation code token defined in GSMA RSP v2.0+ or empty when it is not + * required. + * @param serverSigned1 ASN.1 data in byte array signed and returned by the SM-DP+ server. + * @param serverSignature1 ASN.1 data in byte array indicating a SM-DP+ signature which is + * returned by SM-DP+ server. + * @param euiccCiPkIdToBeUsed ASN.1 data in byte array indicating CI Public Key Identifier to be + * used by the eUICC for signature which is returned by SM-DP+ server. This is defined in + * GSMA RSP v2.0+. + * @param serverCertificate ASN.1 data in byte array indicating SM-DP+ Certificate returned by + * SM-DP+ server. + * @param callback the callback to get the result code and a byte array which represents a + * {@code AuthenticateServerResponse} defined in GSMA RSP v2.0+. + */ + public void authenticateServer(String matchingId, byte[] serverSigned1, + byte[] serverSignature1, byte[] euiccCiPkIdToBeUsed, byte[] serverCertificate, + ResultCallback<byte[]> callback) { + try { + getIEuiccCardController().authenticateServer( + mContext.getOpPackageName(), + matchingId, + serverSigned1, + serverSignature1, + euiccCiPkIdToBeUsed, + serverCertificate, + new IAuthenticateServerCallback.Stub() { + @Override + public void onComplete(int resultCode, byte[] response) { + callback.onComplete(resultCode, response); + } + }); + } catch (RemoteException e) { + Log.e(TAG, "Error calling authenticateServer", e); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Prepares the profile download request sent to SM-DP+. + * + * @param hashCc the hash of confirmation code. It can be null if there is no confirmation code + * required. + * @param smdpSigned2 ASN.1 data in byte array indicating the data to be signed by the SM-DP+ + * returned by SM-DP+ server. + * @param smdpSignature2 ASN.1 data in byte array indicating the SM-DP+ signature returned by + * SM-DP+ server. + * @param smdpCertificate ASN.1 data in byte array indicating the SM-DP+ Certificate returned + * by SM-DP+ server. + * @param callback the callback to get the result code and a byte array which represents a + * {@code PrepareDownloadResponse} defined in GSMA RSP v2.0+ + */ + public void prepareDownload(@Nullable byte[] hashCc, byte[] smdpSigned2, + byte[] smdpSignature2, byte[] smdpCertificate, ResultCallback<byte[]> callback) { + try { + getIEuiccCardController().prepareDownload( + mContext.getOpPackageName(), + hashCc, + smdpSigned2, + smdpSignature2, + smdpCertificate, + new IPrepareDownloadCallback.Stub() { + @Override + public void onComplete(int resultCode, byte[] response) { + callback.onComplete(resultCode, response); + } + }); + } catch (RemoteException e) { + Log.e(TAG, "Error calling prepareDownload", e); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Loads a downloaded bound profile package onto the eUICC. + * + * @param boundProfilePackage the Bound Profile Package data returned by SM-DP+ server. + * @param callback the callback to get the result code and a byte array which represents a + * {@code LoadBoundProfilePackageResponse} defined in GSMA RSP v2.0+. + */ + public void loadBoundProfilePackage(byte[] boundProfilePackage, + ResultCallback<byte[]> callback) { + try { + getIEuiccCardController().loadBoundProfilePackage( + mContext.getOpPackageName(), + boundProfilePackage, + new ILoadBoundProfilePackageCallback.Stub() { + @Override + public void onComplete(int resultCode, byte[] response) { + callback.onComplete(resultCode, response); + } + }); + } catch (RemoteException e) { + Log.e(TAG, "Error calling loadBoundProfilePackage", e); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Cancels the current profile download session. + * + * @param transactionId the transaction ID returned by SM-DP+ server. + * @param reason the cancel reason. + * @param callback the callback to get the result code and an byte[] which represents a + * {@code CancelSessionResponse} defined in GSMA RSP v2.0+. + */ + public void cancelSession(byte[] transactionId, @CancelReason int reason, + ResultCallback<byte[]> callback) { + try { + getIEuiccCardController().cancelSession( + mContext.getOpPackageName(), + transactionId, + reason, + new ICancelSessionCallback.Stub() { + @Override + public void onComplete(int resultCode, byte[] response) { + callback.onComplete(resultCode, response); + } + }); + } catch (RemoteException e) { + Log.e(TAG, "Error calling cancelSession", e); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Lists all notifications of the given {@code notificationEvents}. + * + * @param events bits of the event types ({@link EuiccNotification.Event}) to list. + * @param callback the callback to get the result code and the list of notifications. + */ + public void listNotifications(@EuiccNotification.Event int events, + ResultCallback<EuiccNotification[]> callback) { + try { + getIEuiccCardController().listNotifications(mContext.getOpPackageName(), events, + new IListNotificationsCallback.Stub() { + @Override + public void onComplete(int resultCode, EuiccNotification[] notifications) { + callback.onComplete(resultCode, notifications); + } + }); + } catch (RemoteException e) { + Log.e(TAG, "Error calling listNotifications", e); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Retrieves contents of all notification of the given {@code events}. + * + * @param events bits of the event types ({@link EuiccNotification.Event}) to list. + * @param callback the callback to get the result code and the list of notifications. + */ + public void retrieveNotificationList(@EuiccNotification.Event int events, + ResultCallback<EuiccNotification[]> callback) { + try { + getIEuiccCardController().retrieveNotificationList(mContext.getOpPackageName(), events, + new IRetrieveNotificationListCallback.Stub() { + @Override + public void onComplete(int resultCode, EuiccNotification[] notifications) { + callback.onComplete(resultCode, notifications); + } + }); + } catch (RemoteException e) { + Log.e(TAG, "Error calling retrieveNotificationList", e); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Retrieves the content of a notification of the given {@code seqNumber}. + * + * @param seqNumber the sequence number of the notification. + * @param callback the callback to get the result code and the notification. + */ + public void retrieveNotification(int seqNumber, ResultCallback<EuiccNotification> callback) { + try { + getIEuiccCardController().retrieveNotification(mContext.getOpPackageName(), seqNumber, + new IRetrieveNotificationCallback.Stub() { + @Override + public void onComplete(int resultCode, EuiccNotification notification) { + callback.onComplete(resultCode, notification); + } + }); + } catch (RemoteException e) { + Log.e(TAG, "Error calling retrieveNotification", e); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes a notification from eUICC. + * + * @param seqNumber the sequence number of the notification. + * @param callback the callback to get the result code. + */ + public void removeNotificationFromList(int seqNumber, ResultCallback<Void> callback) { + try { + getIEuiccCardController().removeNotificationFromList( + mContext.getOpPackageName(), + seqNumber, + new IRemoveNotificationFromListCallback.Stub() { + @Override + public void onComplete(int resultCode) { + callback.onComplete(resultCode, null); + } + }); + } catch (RemoteException e) { + Log.e(TAG, "Error calling removeNotificationFromList", e); + throw e.rethrowFromSystemServer(); + } + } } diff --git a/telephony/java/android/telephony/euicc/EuiccNotification.aidl b/telephony/java/android/telephony/euicc/EuiccNotification.aidl new file mode 100644 index 000000000000..dad770da260d --- /dev/null +++ b/telephony/java/android/telephony/euicc/EuiccNotification.aidl @@ -0,0 +1,19 @@ +/* + * 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 android.telephony.euicc; + +parcelable EuiccNotification; diff --git a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.aidl b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.aidl new file mode 100644 index 000000000000..9785a4533c5b --- /dev/null +++ b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.aidl @@ -0,0 +1,19 @@ +/* + * 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.telephony.euicc; + +parcelable EuiccRulesAuthTable;
\ No newline at end of file diff --git a/telephony/java/android/telephony/euicc/EuiccRat.java b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java index 6a56503ac380..7efe04364280 100644 --- a/telephony/java/android/telephony/euicc/EuiccRat.java +++ b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java @@ -35,7 +35,7 @@ import java.util.Arrays; * * TODO(b/35851809): Make this a @SystemApi. */ -public final class EuiccRat implements Parcelable { +public final class EuiccRulesAuthTable implements Parcelable { /** Profile policy rule flags */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "POLICY_RULE_FLAG_" }, value = { @@ -50,7 +50,7 @@ public final class EuiccRat implements Parcelable { private final CarrierIdentifier[][] mCarrierIds; private final int[] mPolicyRuleFlags; - /** This is used to build new {@link EuiccRat} instance. */ + /** This is used to build new {@link EuiccRulesAuthTable} instance. */ public static final class Builder { private int[] mPolicyRules; private CarrierIdentifier[][] mCarrierIds; @@ -72,7 +72,7 @@ public final class EuiccRat implements Parcelable { * Builds the RAT instance. This builder should not be used anymore after this method is * called, otherwise {@link NullPointerException} will be thrown. */ - public EuiccRat build() { + public EuiccRulesAuthTable build() { if (mPosition != mPolicyRules.length) { throw new IllegalStateException( "Not enough rules are added, expected: " @@ -80,7 +80,7 @@ public final class EuiccRat implements Parcelable { + ", added: " + mPosition); } - return new EuiccRat(mPolicyRules, mCarrierIds, mPolicyRuleFlags); + return new EuiccRulesAuthTable(mPolicyRules, mCarrierIds, mPolicyRuleFlags); } /** @@ -125,7 +125,8 @@ public final class EuiccRat implements Parcelable { return true; } - private EuiccRat(int[] policyRules, CarrierIdentifier[][] carrierIds, int[] policyRuleFlags) { + private EuiccRulesAuthTable(int[] policyRules, CarrierIdentifier[][] carrierIds, + int[] policyRuleFlags) { mPolicyRules = policyRules; mCarrierIds = carrierIds; mPolicyRuleFlags = policyRuleFlags; @@ -207,7 +208,7 @@ public final class EuiccRat implements Parcelable { return false; } - EuiccRat that = (EuiccRat) obj; + EuiccRulesAuthTable that = (EuiccRulesAuthTable) obj; if (mCarrierIds.length != that.mCarrierIds.length) { return false; } @@ -234,7 +235,7 @@ public final class EuiccRat implements Parcelable { && Arrays.equals(mPolicyRuleFlags, that.mPolicyRuleFlags); } - private EuiccRat(Parcel source) { + private EuiccRulesAuthTable(Parcel source) { mPolicyRules = source.createIntArray(); int len = mPolicyRules.length; mCarrierIds = new CarrierIdentifier[len][]; @@ -244,16 +245,16 @@ public final class EuiccRat implements Parcelable { mPolicyRuleFlags = source.createIntArray(); } - public static final Creator<EuiccRat> CREATOR = - new Creator<EuiccRat>() { + public static final Creator<EuiccRulesAuthTable> CREATOR = + new Creator<EuiccRulesAuthTable>() { @Override - public EuiccRat createFromParcel(Parcel source) { - return new EuiccRat(source); + public EuiccRulesAuthTable createFromParcel(Parcel source) { + return new EuiccRulesAuthTable(source); } @Override - public EuiccRat[] newArray(int size) { - return new EuiccRat[size]; + public EuiccRulesAuthTable[] newArray(int size) { + return new EuiccRulesAuthTable[size]; } }; } diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java index 8230eafc2e4d..aaa0f08594d1 100644 --- a/telephony/java/android/telephony/ims/ImsService.java +++ b/telephony/java/android/telephony/ims/ImsService.java @@ -26,12 +26,14 @@ import android.telephony.CarrierConfigManager; import android.telephony.ims.feature.ImsFeature; import android.telephony.ims.feature.MMTelFeature; import android.telephony.ims.feature.RcsFeature; +import android.telephony.ims.stub.ImsRegistrationImplBase; import android.util.Log; import android.util.SparseArray; import com.android.ims.internal.IImsFeatureStatusCallback; import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsRcsFeature; +import com.android.ims.internal.IImsRegistration; import com.android.ims.internal.IImsServiceController; import com.android.internal.annotations.VisibleForTesting; @@ -113,6 +115,12 @@ public class ImsService extends Service { throws RemoteException { ImsService.this.removeImsFeature(slotId, featureType, c); } + + @Override + public IImsRegistration getRegistration(int slotId) throws RemoteException { + ImsRegistrationImplBase r = ImsService.this.getRegistration(slotId); + return r != null ? r.getBinder() : null; + } }; /** @@ -174,6 +182,8 @@ public class ImsService extends Service { f.setSlotId(slotId); f.addImsFeatureStatusCallback(c); addImsFeature(slotId, featureType, f); + // TODO: Remove once new onFeatureReady AIDL is merged in. + f.onFeatureReady(); } private void addImsFeature(int slotId, int featureType, ImsFeature f) { @@ -236,4 +246,13 @@ public class ImsService extends Service { public @Nullable RcsFeature onCreateRcsFeature(int slotId) { return null; } + + /** + * @param slotId The slot that is associated with the IMS Registration. + * @return the ImsRegistration implementation associated with the slot. + * @hide + */ + public ImsRegistrationImplBase getRegistration(int slotId) { + return new ImsRegistrationImplBase(); + } } diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java index ca4a210e30cc..d47cea3097f3 100644 --- a/telephony/java/android/telephony/ims/feature/ImsFeature.java +++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java @@ -96,7 +96,7 @@ public abstract class ImsFeature { new WeakHashMap<IImsFeatureStatusCallback, Boolean>()); private @ImsState int mState = STATE_NOT_AVAILABLE; private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX; - private Context mContext; + protected Context mContext; public void setContext(Context context) { mContext = context; diff --git a/telephony/java/android/telephony/ims/internal/ImsService.java b/telephony/java/android/telephony/ims/internal/ImsService.java index b7c8ca0f9799..afaf33294d8a 100644 --- a/telephony/java/android/telephony/ims/internal/ImsService.java +++ b/telephony/java/android/telephony/ims/internal/ImsService.java @@ -24,7 +24,6 @@ import android.telephony.CarrierConfigManager; import android.telephony.ims.internal.aidl.IImsConfig; import android.telephony.ims.internal.aidl.IImsMmTelFeature; import android.telephony.ims.internal.aidl.IImsRcsFeature; -import android.telephony.ims.internal.aidl.IImsRegistration; import android.telephony.ims.internal.aidl.IImsServiceController; import android.telephony.ims.internal.aidl.IImsServiceControllerListener; import android.telephony.ims.internal.feature.ImsFeature; @@ -32,11 +31,12 @@ import android.telephony.ims.internal.feature.MmTelFeature; import android.telephony.ims.internal.feature.RcsFeature; import android.telephony.ims.internal.stub.ImsConfigImplBase; import android.telephony.ims.internal.stub.ImsFeatureConfiguration; -import android.telephony.ims.internal.stub.ImsRegistrationImplBase; +import android.telephony.ims.stub.ImsRegistrationImplBase; import android.util.Log; import android.util.SparseArray; import com.android.ims.internal.IImsFeatureStatusCallback; +import com.android.ims.internal.IImsRegistration; import com.android.internal.annotations.VisibleForTesting; /** diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl index 8332bc024e37..43f5098af3ca 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl +++ b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl @@ -24,4 +24,5 @@ import com.android.ims.internal.IImsCallSession; */ oneway interface IImsMmTelListener { void onIncomingCall(IImsCallSession c); + void onVoiceMessageCountUpdate(int count); }
\ No newline at end of file diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl index 8afb95588b01..82a85254bbca 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl +++ b/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl @@ -18,12 +18,12 @@ package android.telephony.ims.internal.aidl; import android.telephony.ims.internal.aidl.IImsMmTelFeature; import android.telephony.ims.internal.aidl.IImsRcsFeature; -import android.telephony.ims.internal.aidl.IImsRegistration; import android.telephony.ims.internal.aidl.IImsConfig; import android.telephony.ims.internal.aidl.IImsServiceControllerListener; import android.telephony.ims.internal.stub.ImsFeatureConfiguration; import com.android.ims.internal.IImsFeatureStatusCallback; +import com.android.ims.internal.IImsRegistration; /** * See ImsService and MmTelFeature for more information. diff --git a/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java b/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java index 4d188734c10e..5dbf077ee7c5 100644 --- a/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java +++ b/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java @@ -18,7 +18,7 @@ package android.telephony.ims.internal.feature; import android.os.Parcel; import android.os.Parcelable; -import android.telephony.ims.internal.stub.ImsRegistrationImplBase; +import android.telephony.ims.stub.ImsRegistrationImplBase; import android.util.ArraySet; import java.util.ArrayList; diff --git a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java index 238411ee4f8f..8d888c2bcb28 100644 --- a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java +++ b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java @@ -28,8 +28,8 @@ import android.telephony.ims.internal.aidl.IImsCallSessionListener; import android.telephony.ims.internal.aidl.IImsCapabilityCallback; import android.telephony.ims.internal.aidl.IImsMmTelFeature; import android.telephony.ims.internal.aidl.IImsMmTelListener; -import android.telephony.ims.internal.stub.ImsRegistrationImplBase; import android.telephony.ims.internal.aidl.IImsSmsListener; +import android.telephony.ims.stub.ImsRegistrationImplBase; import android.telephony.ims.stub.ImsEcbmImplBase; import android.telephony.ims.stub.ImsMultiEndpointImplBase; import android.telephony.ims.stub.ImsUtImplBase; @@ -262,6 +262,15 @@ public class MmTelFeature extends ImsFeature { } /** + * Updates the Listener when the voice message count for IMS has changed. + * @param count an integer representing the new message count. + */ + @Override + public void onVoiceMessageCountUpdate(int count) { + + } + + /** * Called when the IMS provider receives an incoming call. * @param c The {@link ImsCallSession} associated with the new call. */ diff --git a/telephony/java/android/telephony/ims/internal/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java index 558b009ab4c2..42af08365f61 100644 --- a/telephony/java/android/telephony/ims/internal/stub/ImsRegistrationImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java @@ -14,16 +14,19 @@ * limitations under the License */ -package android.telephony.ims.internal.stub; +package android.telephony.ims.stub; import android.annotation.IntDef; +import android.net.Uri; +import android.os.IBinder; import android.os.RemoteCallbackList; import android.os.RemoteException; -import android.telephony.ims.internal.aidl.IImsRegistration; -import android.telephony.ims.internal.aidl.IImsRegistrationCallback; import android.util.Log; import com.android.ims.ImsReasonInfo; +import com.android.ims.internal.IImsRegistration; +import com.android.ims.internal.IImsRegistrationCallback; +import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -62,23 +65,25 @@ public class ImsRegistrationImplBase { // Registration states, used to notify new ImsRegistrationImplBase#Callbacks of the current // state. + // The unknown state is set as the initialization state. This is so that we do not call back + // with NOT_REGISTERED in the case where the ImsService has not updated the registration state + // yet. + private static final int REGISTRATION_STATE_UNKNOWN = -1; private static final int REGISTRATION_STATE_NOT_REGISTERED = 0; private static final int REGISTRATION_STATE_REGISTERING = 1; private static final int REGISTRATION_STATE_REGISTERED = 2; - /** * Callback class for receiving Registration callback events. + * @hide */ - public static class Callback extends IImsRegistrationCallback.Stub { - + public static class Callback { /** * Notifies the framework when the IMS Provider is connected to the IMS network. * * @param imsRadioTech the radio access technology. Valid values are defined in * {@link ImsRegistrationTech}. */ - @Override public void onRegistered(@ImsRegistrationTech int imsRadioTech) { } @@ -88,7 +93,6 @@ public class ImsRegistrationImplBase { * @param imsRadioTech the radio access technology. Valid values are defined in * {@link ImsRegistrationTech}. */ - @Override public void onRegistering(@ImsRegistrationTech int imsRadioTech) { } @@ -97,7 +101,6 @@ public class ImsRegistrationImplBase { * * @param info the {@link ImsReasonInfo} associated with why registration was disconnected. */ - @Override public void onDeregistered(ImsReasonInfo info) { } @@ -108,10 +111,19 @@ public class ImsRegistrationImplBase { * @param imsRadioTech The {@link ImsRegistrationTech} type that has failed * @param info A {@link ImsReasonInfo} that identifies the reason for failure. */ - @Override public void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech, ImsReasonInfo info) { } + + /** + * Returns a list of subscriber {@link Uri}s associated with this IMS subscription when + * it changes. + * @param uris new array of subscriber {@link Uri}s that are associated with this IMS + * subscription. + */ + public void onSubscriberAssociatedUriChanged(Uri[] uris) { + + } } private final IImsRegistration mBinder = new IImsRegistration.Stub() { @@ -139,9 +151,9 @@ public class ImsRegistrationImplBase { private @ImsRegistrationTech int mConnectionType = REGISTRATION_TECH_NONE; // Locked on mLock - private int mRegistrationState = REGISTRATION_STATE_NOT_REGISTERED; - // Locked on mLock - private ImsReasonInfo mLastDisconnectCause; + private int mRegistrationState = REGISTRATION_STATE_UNKNOWN; + // Locked on mLock, create unspecified disconnect cause. + private ImsReasonInfo mLastDisconnectCause = new ImsReasonInfo(); public final IImsRegistration getBinder() { return mBinder; @@ -221,6 +233,17 @@ public class ImsRegistrationImplBase { }); } + public final void onSubscriberAssociatedUriChanged(Uri[] uris) { + mCallbacks.broadcast((c) -> { + try { + c.onSubscriberAssociatedUriChanged(uris); + } catch (RemoteException e) { + Log.w(LOG_TAG, e + " " + "onSubscriberAssociatedUriChanged() - Skipping " + + "callback."); + } + }); + } + private void updateToState(@ImsRegistrationTech int connType, int newState) { synchronized (mLock) { mConnectionType = connType; @@ -241,7 +264,8 @@ public class ImsRegistrationImplBase { } } - private @ImsRegistrationTech int getConnectionType() { + @VisibleForTesting + public final @ImsRegistrationTech int getConnectionType() { synchronized (mLock) { return mConnectionType; } @@ -271,6 +295,10 @@ public class ImsRegistrationImplBase { c.onRegistered(getConnectionType()); break; } + case REGISTRATION_STATE_UNKNOWN: { + // Do not callback if the state has not been updated yet by the ImsService. + break; + } } } } diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl b/telephony/java/com/android/ims/internal/IImsRegistration.aidl index 687b7ca408d5..6de264ec90fb 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl +++ b/telephony/java/com/android/ims/internal/IImsRegistration.aidl @@ -15,10 +15,9 @@ */ -package android.telephony.ims.internal.aidl; +package com.android.ims.internal; -import android.telephony.ims.internal.aidl.IImsRegistrationCallback; -import android.telephony.ims.internal.stub.ImsFeatureConfiguration; +import com.android.ims.internal.IImsRegistrationCallback; /** * See ImsRegistration for more information. diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl b/telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl index a50575b96865..5f21167422dc 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl +++ b/telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl @@ -15,8 +15,9 @@ */ -package android.telephony.ims.internal.aidl; +package com.android.ims.internal; +import android.net.Uri; import android.telephony.ims.internal.stub.ImsFeatureConfiguration; import com.android.ims.ImsReasonInfo; @@ -31,4 +32,5 @@ oneway interface IImsRegistrationCallback { void onRegistering(int imsRadioTech); void onDeregistered(in ImsReasonInfo info); void onTechnologyChangeFailed(int imsRadioTech, in ImsReasonInfo info); + void onSubscriberAssociatedUriChanged(in Uri[] uris); }
\ No newline at end of file diff --git a/telephony/java/com/android/ims/internal/IImsServiceController.aidl b/telephony/java/com/android/ims/internal/IImsServiceController.aidl index 857089fac33a..7ac25ac13fbe 100644 --- a/telephony/java/com/android/ims/internal/IImsServiceController.aidl +++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl @@ -18,6 +18,7 @@ package com.android.ims.internal; import com.android.ims.internal.IImsFeatureStatusCallback; import com.android.ims.internal.IImsMMTelFeature; +import com.android.ims.internal.IImsRegistration; import com.android.ims.internal.IImsRcsFeature; /** @@ -29,4 +30,5 @@ interface IImsServiceController { IImsMMTelFeature createMMTelFeature(int slotId, in IImsFeatureStatusCallback c); IImsRcsFeature createRcsFeature(int slotId, in IImsFeatureStatusCallback c); void removeImsFeature(int slotId, int featureType, in IImsFeatureStatusCallback c); + IImsRegistration getRegistration(int slotId); } diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl index 0f3182136997..f8a040da30ba 100644 --- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl +++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl @@ -152,6 +152,13 @@ interface IPhoneSubInfo { in ImsiEncryptionInfo imsiEncryptionInfo); /** + * Resets the Carrier Keys in the database. This involves 2 steps: + * 1. Delete the keys from the database. + * 2. Send an intent to download new Certificates. + */ + void resetCarrierKeysForImsiEncryption(int subId, String callingPackage); + + /** * Retrieves the alpha identifier associated with the voice mail number. */ String getVoiceMailAlphaTag(String callingPackage); diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 416146fcb98e..fba82ee17f77 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -40,6 +40,7 @@ import android.telephony.TelephonyHistogram; import android.telephony.VisualVoicemailSmsFilterSettings; import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsRcsFeature; +import com.android.ims.internal.IImsRegistration; import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.telephony.CellNetworkScanResult; import com.android.internal.telephony.OperatorInfo; @@ -808,6 +809,11 @@ interface ITelephony { IImsRcsFeature getRcsFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback); /** + * Returns the IImsRegistration associated with the slot and feature specified. + */ + IImsRegistration getImsRegistration(int slotId, int feature); + + /** * Set the network selection mode to automatic. * * @param subId the id of the subscription to update. diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java index f29d993c55da..51369d06bf46 100644 --- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java +++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java @@ -486,4 +486,10 @@ public class TelephonyIntents { */ public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE = "com.android.omadm.service.CONFIGURATION_UPDATE"; + + /** + * Broadcast action to trigger the Carrier Certificate download. + */ + public static final String ACTION_CARRIER_CERTIFICATE_DOWNLOAD = + "com.android.internal.telephony.ACTION_CARRIER_CERTIFICATE_DOWNLOAD"; } diff --git a/telephony/java/com/android/internal/telephony/euicc/IAuthenticateServerCallback.aidl b/telephony/java/com/android/internal/telephony/euicc/IAuthenticateServerCallback.aidl new file mode 100644 index 000000000000..8a77bf127f9e --- /dev/null +++ b/telephony/java/com/android/internal/telephony/euicc/IAuthenticateServerCallback.aidl @@ -0,0 +1,21 @@ +/* + * 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.internal.telephony.euicc; + +/** @hide */ +oneway interface IAuthenticateServerCallback { + void onComplete(int resultCode, in byte[] response); +} diff --git a/telephony/java/com/android/internal/telephony/euicc/ICancelSessionCallback.aidl b/telephony/java/com/android/internal/telephony/euicc/ICancelSessionCallback.aidl new file mode 100644 index 000000000000..f6b99e2b9801 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/euicc/ICancelSessionCallback.aidl @@ -0,0 +1,21 @@ +/* + * 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.internal.telephony.euicc; + +/** @hide */ +oneway interface ICancelSessionCallback { + void onComplete(int resultCode, in byte[] response); +} diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl index 2846a1ad1f9f..ba9b05e2654f 100644 --- a/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl +++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl @@ -17,8 +17,41 @@ package com.android.internal.telephony.euicc; import com.android.internal.telephony.euicc.IGetAllProfilesCallback; +import com.android.internal.telephony.euicc.IAuthenticateServerCallback; +import com.android.internal.telephony.euicc.ICancelSessionCallback; +import com.android.internal.telephony.euicc.IGetEuiccChallengeCallback; +import com.android.internal.telephony.euicc.IGetEuiccInfo1Callback; +import com.android.internal.telephony.euicc.IGetEuiccInfo2Callback; +import com.android.internal.telephony.euicc.IGetRulesAuthTableCallback; +import com.android.internal.telephony.euicc.IListNotificationsCallback; +import com.android.internal.telephony.euicc.ILoadBoundProfilePackageCallback; +import com.android.internal.telephony.euicc.IPrepareDownloadCallback; +import com.android.internal.telephony.euicc.IRemoveNotificationFromListCallback; +import com.android.internal.telephony.euicc.IRetrieveNotificationCallback; +import com.android.internal.telephony.euicc.IRetrieveNotificationListCallback; /** @hide */ interface IEuiccCardController { oneway void getAllProfiles(String callingPackage, in IGetAllProfilesCallback callback); + oneway void getRulesAuthTable(String callingPackage, in IGetRulesAuthTableCallback callback); + oneway void getEuiccChallenge(String callingPackage, in IGetEuiccChallengeCallback callback); + oneway void getEuiccInfo1(String callingPackage, in IGetEuiccInfo1Callback callback); + oneway void getEuiccInfo2(String callingPackage, in IGetEuiccInfo2Callback callback); + oneway void authenticateServer(String callingPackage, String matchingId, + in byte[] serverSigned1, in byte[] serverSignature1, in byte[] euiccCiPkIdToBeUsed, + in byte[] serverCertificatein, in IAuthenticateServerCallback callback); + oneway void prepareDownload(String callingPackage, in byte[] hashCc, in byte[] smdpSigned2, + in byte[] smdpSignature2, in byte[] smdpCertificate, in IPrepareDownloadCallback callback); + oneway void loadBoundProfilePackage(String callingPackage, in byte[] boundProfilePackage, + in ILoadBoundProfilePackageCallback callback); + oneway void cancelSession(String callingPackage, in byte[] transactionId, int reason, + in ICancelSessionCallback callback); + oneway void listNotifications(String callingPackage, int events, + in IListNotificationsCallback callback); + oneway void retrieveNotificationList(String callingPackage, int events, + in IRetrieveNotificationListCallback callback); + oneway void retrieveNotification(String callingPackage, int seqNumber, + in IRetrieveNotificationCallback callback); + oneway void removeNotificationFromList(String callingPackage, int seqNumber, + in IRemoveNotificationFromListCallback callback); } diff --git a/telephony/java/com/android/internal/telephony/euicc/IGetEuiccChallengeCallback.aidl b/telephony/java/com/android/internal/telephony/euicc/IGetEuiccChallengeCallback.aidl new file mode 100644 index 000000000000..5ffb3400912a --- /dev/null +++ b/telephony/java/com/android/internal/telephony/euicc/IGetEuiccChallengeCallback.aidl @@ -0,0 +1,21 @@ +/* + * 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.internal.telephony.euicc; + +/** @hide */ +oneway interface IGetEuiccChallengeCallback { + void onComplete(int resultCode, in byte[] challenge); +} diff --git a/telephony/java/com/android/internal/telephony/euicc/IGetEuiccInfo1Callback.aidl b/telephony/java/com/android/internal/telephony/euicc/IGetEuiccInfo1Callback.aidl new file mode 100644 index 000000000000..9592acb6d330 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/euicc/IGetEuiccInfo1Callback.aidl @@ -0,0 +1,21 @@ +/* + * 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.internal.telephony.euicc; + +/** @hide */ +oneway interface IGetEuiccInfo1Callback { + void onComplete(int resultCode, in byte[] info); +} diff --git a/telephony/java/com/android/internal/telephony/euicc/IGetEuiccInfo2Callback.aidl b/telephony/java/com/android/internal/telephony/euicc/IGetEuiccInfo2Callback.aidl new file mode 100644 index 000000000000..5256b35c7516 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/euicc/IGetEuiccInfo2Callback.aidl @@ -0,0 +1,21 @@ +/* + * 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.internal.telephony.euicc; + +/** @hide */ +oneway interface IGetEuiccInfo2Callback { + void onComplete(int resultCode, in byte[] info); +} diff --git a/telephony/java/com/android/internal/telephony/euicc/IGetRulesAuthTableCallback.aidl b/telephony/java/com/android/internal/telephony/euicc/IGetRulesAuthTableCallback.aidl new file mode 100644 index 000000000000..58f0bde65b86 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/euicc/IGetRulesAuthTableCallback.aidl @@ -0,0 +1,23 @@ +/* + * 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.internal.telephony.euicc; + +import android.telephony.euicc.EuiccRulesAuthTable; + +/** @hide */ +oneway interface IGetRulesAuthTableCallback { + void onComplete(int resultCode, in EuiccRulesAuthTable rat); +} diff --git a/telephony/java/com/android/internal/telephony/euicc/IListNotificationsCallback.aidl b/telephony/java/com/android/internal/telephony/euicc/IListNotificationsCallback.aidl new file mode 100644 index 000000000000..65aa302a6a64 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/euicc/IListNotificationsCallback.aidl @@ -0,0 +1,23 @@ +/* + * 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.internal.telephony.euicc; + +import android.telephony.euicc.EuiccNotification; + +/** @hide */ +oneway interface IListNotificationsCallback { + void onComplete(int resultCode, in EuiccNotification[] notifications); +} diff --git a/telephony/java/com/android/internal/telephony/euicc/ILoadBoundProfilePackageCallback.aidl b/telephony/java/com/android/internal/telephony/euicc/ILoadBoundProfilePackageCallback.aidl new file mode 100644 index 000000000000..4ad7081c2e67 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/euicc/ILoadBoundProfilePackageCallback.aidl @@ -0,0 +1,21 @@ +/* + * 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.internal.telephony.euicc; + +/** @hide */ +oneway interface ILoadBoundProfilePackageCallback { + void onComplete(int resultCode, in byte[] response); +} diff --git a/telephony/java/com/android/internal/telephony/euicc/IPrepareDownloadCallback.aidl b/telephony/java/com/android/internal/telephony/euicc/IPrepareDownloadCallback.aidl new file mode 100644 index 000000000000..c0351841084e --- /dev/null +++ b/telephony/java/com/android/internal/telephony/euicc/IPrepareDownloadCallback.aidl @@ -0,0 +1,21 @@ +/* + * 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.internal.telephony.euicc; + +/** @hide */ +oneway interface IPrepareDownloadCallback { + void onComplete(int resultCode, in byte[] response); +} diff --git a/telephony/java/com/android/internal/telephony/euicc/IRemoveNotificationFromListCallback.aidl b/telephony/java/com/android/internal/telephony/euicc/IRemoveNotificationFromListCallback.aidl new file mode 100644 index 000000000000..b22d0da5dc5f --- /dev/null +++ b/telephony/java/com/android/internal/telephony/euicc/IRemoveNotificationFromListCallback.aidl @@ -0,0 +1,23 @@ +/* + * 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.internal.telephony.euicc; + +import android.telephony.euicc.EuiccNotification; + +/** @hide */ +oneway interface IRemoveNotificationFromListCallback { + void onComplete(int resultCode); +} diff --git a/telephony/java/com/android/internal/telephony/euicc/IRetrieveNotificationCallback.aidl b/telephony/java/com/android/internal/telephony/euicc/IRetrieveNotificationCallback.aidl new file mode 100644 index 000000000000..dd8889a94143 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/euicc/IRetrieveNotificationCallback.aidl @@ -0,0 +1,23 @@ +/* + * 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.internal.telephony.euicc; + +import android.telephony.euicc.EuiccNotification; + +/** @hide */ +oneway interface IRetrieveNotificationCallback { + void onComplete(int resultCode, in EuiccNotification notification); +} diff --git a/telephony/java/com/android/internal/telephony/euicc/IRetrieveNotificationListCallback.aidl b/telephony/java/com/android/internal/telephony/euicc/IRetrieveNotificationListCallback.aidl new file mode 100644 index 000000000000..bc4e451329af --- /dev/null +++ b/telephony/java/com/android/internal/telephony/euicc/IRetrieveNotificationListCallback.aidl @@ -0,0 +1,23 @@ +/* + * 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.internal.telephony.euicc; + +import android.telephony.euicc.EuiccNotification; + +/** @hide */ +oneway interface IRetrieveNotificationListCallback { + void onComplete(int resultCode, in EuiccNotification[] notifications); +} diff --git a/test-base/Android.bp b/test-base/Android.bp index d2da613c3832..ccf57b00a379 100644 --- a/test-base/Android.bp +++ b/test-base/Android.bp @@ -49,7 +49,8 @@ java_library { // Build the repackaged.android.test.base library // ============================================== -// This contains repackaged versions of the classes from legacy-test. +// This contains repackaged versions of the classes from +// android.test.base. java_library_static { name: "repackaged.android.test.base", diff --git a/test-mock/Android.bp b/test-mock/Android.bp index 8eddec48611b..b1ae40e17b9d 100644 --- a/test-mock/Android.bp +++ b/test-mock/Android.bp @@ -24,7 +24,6 @@ java_library { no_framework_libs: true, libs: [ "framework", - "legacy-test", ], } diff --git a/test-runner/Android.bp b/test-runner/Android.bp index d495e90ac1d5..dfaeed5e271e 100644 --- a/test-runner/Android.bp +++ b/test-runner/Android.bp @@ -40,7 +40,7 @@ java_library { no_framework_libs: true, libs: [ "framework", - "legacy-test", + "android.test.base", "android.test.mock", "junit", ], diff --git a/tests/net/java/android/net/IpSecConfigTest.java b/tests/net/java/android/net/IpSecConfigTest.java index efc01f2ace6f..f6c5532363e8 100644 --- a/tests/net/java/android/net/IpSecConfigTest.java +++ b/tests/net/java/android/net/IpSecConfigTest.java @@ -36,19 +36,16 @@ public class IpSecConfigTest { public void testDefaults() throws Exception { IpSecConfig c = new IpSecConfig(); assertEquals(IpSecTransform.MODE_TRANSPORT, c.getMode()); - assertEquals("", c.getLocalAddress()); - assertEquals("", c.getRemoteAddress()); + assertEquals("", c.getSourceAddress()); + assertEquals("", c.getDestinationAddress()); assertNull(c.getNetwork()); assertEquals(IpSecTransform.ENCAP_NONE, c.getEncapType()); assertEquals(IpSecManager.INVALID_RESOURCE_ID, c.getEncapSocketResourceId()); assertEquals(0, c.getEncapRemotePort()); assertEquals(0, c.getNattKeepaliveInterval()); - for (int direction : - new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN}) { - assertNull(c.getEncryption(direction)); - assertNull(c.getAuthentication(direction)); - assertEquals(IpSecManager.INVALID_RESOURCE_ID, c.getSpiResourceId(direction)); - } + assertNull(c.getEncryption()); + assertNull(c.getAuthentication()); + assertEquals(IpSecManager.INVALID_RESOURCE_ID, c.getSpiResourceId()); } @Test @@ -57,34 +54,21 @@ public class IpSecConfigTest { IpSecConfig c = new IpSecConfig(); c.setMode(IpSecTransform.MODE_TUNNEL); - c.setLocalAddress("0.0.0.0"); - c.setRemoteAddress("1.2.3.4"); + c.setSourceAddress("0.0.0.0"); + c.setDestinationAddress("1.2.3.4"); c.setEncapType(android.system.OsConstants.UDP_ENCAP_ESPINUDP); c.setEncapSocketResourceId(7); c.setEncapRemotePort(22); c.setNattKeepaliveInterval(42); c.setEncryption( - IpSecTransform.DIRECTION_OUT, new IpSecAlgorithm( IpSecAlgorithm.CRYPT_AES_CBC, new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF})); c.setAuthentication( - IpSecTransform.DIRECTION_OUT, new IpSecAlgorithm( IpSecAlgorithm.AUTH_HMAC_MD5, new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0})); - c.setSpiResourceId(IpSecTransform.DIRECTION_OUT, 1984); - c.setEncryption( - IpSecTransform.DIRECTION_IN, - new IpSecAlgorithm( - IpSecAlgorithm.CRYPT_AES_CBC, - new byte[] {2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF})); - c.setAuthentication( - IpSecTransform.DIRECTION_IN, - new IpSecAlgorithm( - IpSecAlgorithm.AUTH_HMAC_MD5, - new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 1})); - c.setSpiResourceId(IpSecTransform.DIRECTION_IN, 99); + c.setSpiResourceId(1984); assertParcelingIsLossless(c); } diff --git a/tests/net/java/android/net/IpSecManagerTest.java b/tests/net/java/android/net/IpSecManagerTest.java index 0f40b4562b0d..cc3366fbc832 100644 --- a/tests/net/java/android/net/IpSecManagerTest.java +++ b/tests/net/java/android/net/IpSecManagerTest.java @@ -81,15 +81,13 @@ public class IpSecManagerTest { IpSecSpiResponse spiResp = new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, DROID_SPI); when(mMockIpSecService.allocateSecurityParameterIndex( - eq(IpSecTransform.DIRECTION_IN), eq(GOOGLE_DNS_4.getHostAddress()), eq(DROID_SPI), anyObject())) .thenReturn(spiResp); IpSecManager.SecurityParameterIndex droidSpi = - mIpSecManager.allocateSecurityParameterIndex( - IpSecTransform.DIRECTION_IN, GOOGLE_DNS_4, DROID_SPI); + mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4, DROID_SPI); assertEquals(DROID_SPI, droidSpi.getSpi()); droidSpi.close(); @@ -103,15 +101,13 @@ public class IpSecManagerTest { IpSecSpiResponse spiResp = new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, DROID_SPI); when(mMockIpSecService.allocateSecurityParameterIndex( - eq(IpSecTransform.DIRECTION_OUT), eq(GOOGLE_DNS_4.getHostAddress()), eq(IpSecManager.INVALID_SECURITY_PARAMETER_INDEX), anyObject())) .thenReturn(spiResp); IpSecManager.SecurityParameterIndex randomSpi = - mIpSecManager.allocateSecurityParameterIndex( - IpSecTransform.DIRECTION_OUT, GOOGLE_DNS_4); + mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4); assertEquals(DROID_SPI, randomSpi.getSpi()); @@ -124,16 +120,15 @@ public class IpSecManagerTest { * Throws resource unavailable exception */ @Test - public void testAllocSpiResUnavaiableExeption() throws Exception { + public void testAllocSpiResUnavailableException() throws Exception { IpSecSpiResponse spiResp = new IpSecSpiResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE, 0, 0); when(mMockIpSecService.allocateSecurityParameterIndex( - anyInt(), anyString(), anyInt(), anyObject())) + anyString(), anyInt(), anyObject())) .thenReturn(spiResp); try { - mIpSecManager.allocateSecurityParameterIndex( - IpSecTransform.DIRECTION_OUT, GOOGLE_DNS_4); + mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4); fail("ResourceUnavailableException was not thrown"); } catch (IpSecManager.ResourceUnavailableException e) { } @@ -143,15 +138,14 @@ public class IpSecManagerTest { * Throws spi unavailable exception */ @Test - public void testAllocSpiSpiUnavaiableExeption() throws Exception { + public void testAllocSpiSpiUnavailableException() throws Exception { IpSecSpiResponse spiResp = new IpSecSpiResponse(IpSecManager.Status.SPI_UNAVAILABLE, 0, 0); when(mMockIpSecService.allocateSecurityParameterIndex( - anyInt(), anyString(), anyInt(), anyObject())) + anyString(), anyInt(), anyObject())) .thenReturn(spiResp); try { - mIpSecManager.allocateSecurityParameterIndex( - IpSecTransform.DIRECTION_OUT, GOOGLE_DNS_4); + mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4); fail("ResourceUnavailableException was not thrown"); } catch (IpSecManager.ResourceUnavailableException e) { } @@ -163,8 +157,7 @@ public class IpSecManagerTest { @Test public void testRequestAllocInvalidSpi() throws Exception { try { - mIpSecManager.allocateSecurityParameterIndex( - IpSecTransform.DIRECTION_OUT, GOOGLE_DNS_4, 0); + mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4, 0); fail("Able to allocate invalid spi"); } catch (IllegalArgumentException e) { } diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java index 473dc538f09d..9aad413c354b 100644 --- a/tests/net/java/android/net/MacAddressTest.java +++ b/tests/net/java/android/net/MacAddressTest.java @@ -67,7 +67,7 @@ public class MacAddressTest { assertEquals(msg, t.expectedType, got); if (got != MacAddress.TYPE_UNKNOWN) { - assertEquals(got, MacAddress.fromBytes(t.addr).addressType()); + assertEquals(got, MacAddress.fromBytes(t.addr).getAddressType()); } } } @@ -191,7 +191,7 @@ public class MacAddressTest { assertTrue(stringRepr + " expected to be a locally assigned address", mac.isLocallyAssigned()); - assertEquals(MacAddress.TYPE_UNICAST, mac.addressType()); + assertEquals(MacAddress.TYPE_UNICAST, mac.getAddressType()); assertTrue(stringRepr + " expected to begin with " + expectedLocalOui, stringRepr.startsWith(expectedLocalOui)); } diff --git a/tests/net/java/android/net/NetworkTest.java b/tests/net/java/android/net/NetworkTest.java index bacf986b3627..94d01e91d03b 100644 --- a/tests/net/java/android/net/NetworkTest.java +++ b/tests/net/java/android/net/NetworkTest.java @@ -147,9 +147,9 @@ public class NetworkTest { // Adjust as necessary to test an implementation's specific constants. // When running with runtest, "adb logcat -s TestRunner" can be useful. - assertEquals(4311403230L, one.getNetworkHandle()); - assertEquals(8606370526L, two.getNetworkHandle()); - assertEquals(12901337822L, three.getNetworkHandle()); + assertEquals(7700664333L, one.getNetworkHandle()); + assertEquals(11995631629L, two.getNetworkHandle()); + assertEquals(16290598925L, three.getNetworkHandle()); } private static <T> void assertNotEqual(T t1, T t2) { diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 2b0349c6fa83..b8e37f3a10ea 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -1392,39 +1392,75 @@ public class ConnectivityServiceTest { return null; } - void expectAvailableCallbacks( - MockNetworkAgent agent, boolean expectSuspended, int timeoutMs) { + // Expects onAvailable and the callbacks that follow it. These are: + // - onSuspended, iff the network was suspended when the callbacks fire. + // - onCapabilitiesChanged. + // - onLinkPropertiesChanged. + // + // @param agent the network to expect the callbacks on. + // @param expectSuspended whether to expect a SUSPENDED callback. + // @param expectValidated the expected value of the VALIDATED capability in the + // onCapabilitiesChanged callback. + // @param timeoutMs how long to wait for the callbacks. + void expectAvailableCallbacks(MockNetworkAgent agent, boolean expectSuspended, + boolean expectValidated, int timeoutMs) { expectCallback(CallbackState.AVAILABLE, agent, timeoutMs); if (expectSuspended) { expectCallback(CallbackState.SUSPENDED, agent, timeoutMs); } - expectCallback(CallbackState.NETWORK_CAPABILITIES, agent, timeoutMs); + if (expectValidated) { + expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent); + } else { + expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, agent); + } expectCallback(CallbackState.LINK_PROPERTIES, agent, timeoutMs); } - void expectAvailableCallbacks(MockNetworkAgent agent) { - expectAvailableCallbacks(agent, false, TIMEOUT_MS); + // Expects the available callbacks (validated), plus onSuspended. + void expectAvailableAndSuspendedCallbacks(MockNetworkAgent agent, boolean expectValidated) { + expectAvailableCallbacks(agent, true, expectValidated, TIMEOUT_MS); + } + + void expectAvailableCallbacksValidated(MockNetworkAgent agent) { + expectAvailableCallbacks(agent, false, true, TIMEOUT_MS); + } + + void expectAvailableCallbacksUnvalidated(MockNetworkAgent agent) { + expectAvailableCallbacks(agent, false, false, TIMEOUT_MS); } - void expectAvailableAndSuspendedCallbacks(MockNetworkAgent agent) { - expectAvailableCallbacks(agent, true, TIMEOUT_MS); + // Expects the available callbacks (where the onCapabilitiesChanged must contain the + // VALIDATED capability), plus another onCapabilitiesChanged which is identical to the + // one we just sent. + // TODO: this is likely a bug. Fix it and remove this method. + void expectAvailableDoubleValidatedCallbacks(MockNetworkAgent agent) { + expectCallback(CallbackState.AVAILABLE, agent, TIMEOUT_MS); + NetworkCapabilities nc1 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent); + expectCallback(CallbackState.LINK_PROPERTIES, agent, TIMEOUT_MS); + NetworkCapabilities nc2 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent); + assertEquals(nc1, nc2); } - void expectAvailableAndValidatedCallbacks(MockNetworkAgent agent) { - expectAvailableCallbacks(agent, false, TIMEOUT_MS); + // Expects the available callbacks where the onCapabilitiesChanged must not have validated, + // then expects another onCapabilitiesChanged that has the validated bit set. This is used + // when a network connects and satisfies a callback, and then immediately validates. + void expectAvailableThenValidatedCallbacks(MockNetworkAgent agent) { + expectAvailableCallbacksUnvalidated(agent); expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent); } - void expectCapabilitiesWith(int capability, MockNetworkAgent agent) { + NetworkCapabilities expectCapabilitiesWith(int capability, MockNetworkAgent agent) { CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent); NetworkCapabilities nc = (NetworkCapabilities) cbi.arg; assertTrue(nc.hasCapability(capability)); + return nc; } - void expectCapabilitiesWithout(int capability, MockNetworkAgent agent) { + NetworkCapabilities expectCapabilitiesWithout(int capability, MockNetworkAgent agent) { CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent); NetworkCapabilities nc = (NetworkCapabilities) cbi.arg; assertFalse(nc.hasCapability(capability)); + return nc; } void assertNoCallback() { @@ -1461,8 +1497,8 @@ public class ConnectivityServiceTest { ConditionVariable cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false); - genericNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent); - cellNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent); + genericNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + cellNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); waitFor(cv); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); @@ -1476,8 +1512,8 @@ public class ConnectivityServiceTest { cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - genericNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent); - wifiNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); waitFor(cv); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); @@ -1500,8 +1536,8 @@ public class ConnectivityServiceTest { // Test validated networks mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - genericNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); - cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + genericNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); @@ -1513,10 +1549,10 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - genericNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); genericNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); genericNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); - wifiNetworkCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + wifiNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); cellNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); @@ -1552,32 +1588,32 @@ public class ConnectivityServiceTest { mEthernetNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); mCellNetworkAgent.connect(true); - callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); mWiFiNetworkAgent.connect(true); // We get AVAILABLE on wifi when wifi connects and satisfies our unmetered request. // We then get LOSING when wifi validates and cell is outscored. - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // TODO: Investigate sending validated before losing. callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); mEthernetNetworkAgent.connect(true); - callback.expectAvailableCallbacks(mEthernetNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent); // TODO: Investigate sending validated before losing. callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent); assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork()); mEthernetNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent); - defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); for (int i = 0; i < 4; i++) { MockNetworkAgent oldNetwork, newNetwork; @@ -1594,7 +1630,7 @@ public class ConnectivityServiceTest { callback.expectCallback(CallbackState.LOSING, oldNetwork); // TODO: should we send an AVAILABLE callback to newNetwork, to indicate that it is no // longer lingering? - defaultCallback.expectAvailableCallbacks(newNetwork); + defaultCallback.expectAvailableCallbacksValidated(newNetwork); assertEquals(newNetwork.getNetwork(), mCm.getActiveNetwork()); } assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); @@ -1614,7 +1650,7 @@ public class ConnectivityServiceTest { // Disconnect our test networks. mWiFiNetworkAgent.disconnect(); defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); mCellNetworkAgent.disconnect(); defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); @@ -1630,22 +1666,22 @@ public class ConnectivityServiceTest { mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false); // Score: 10 - callback.expectAvailableCallbacks(mCellNetworkAgent); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); // Bring up wifi with a score of 20. // Cell stays up because it would satisfy the default request if it validated. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); // Score: 20 - callback.expectAvailableCallbacks(mWiFiNetworkAgent); - defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); // Bring up wifi with a score of 70. @@ -1653,33 +1689,33 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.adjustScore(50); mWiFiNetworkAgent.connect(false); // Score: 70 - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); - defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); // Tear down wifi. mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); // Bring up wifi, then validate it. Previous versions would immediately tear down cell, but // it's arguably correct to linger it, since it was the default network before it validated. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // TODO: Investigate sending validated before losing. callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); mCellNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mCellNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); @@ -1687,12 +1723,12 @@ public class ConnectivityServiceTest { // If a network is lingering, and we add and remove a request from it, resume lingering. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // TODO: Investigate sending validated before losing. callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); @@ -1711,7 +1747,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); // Cell is now the default network. Pin it with a cell-specific request. noopCallback = new NetworkCallback(); // Can't reuse NetworkCallbacks. http://b/20701525 @@ -1720,8 +1756,8 @@ public class ConnectivityServiceTest { // Now connect wifi, and expect it to become the default network. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - callback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + callback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); // The default request is lingering on cell, but nothing happens to cell, and we send no // callbacks for it, because it's kept up by cellRequest. callback.assertNoCallback(); @@ -1737,14 +1773,14 @@ public class ConnectivityServiceTest { // Register a TRACK_DEFAULT request and check that it does not affect lingering. TestNetworkCallback trackDefaultCallback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(trackDefaultCallback); - trackDefaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + trackDefaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET); mEthernetNetworkAgent.connect(true); - callback.expectAvailableCallbacks(mEthernetNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent); callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent); - trackDefaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent); + trackDefaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent); // Let linger run its course. callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent, lingerTimeoutMs); @@ -1771,13 +1807,13 @@ public class ConnectivityServiceTest { // Bring up validated cell. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); // Bring up unvalidated wifi with explicitlySelected=true. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.explicitlySelected(false); mWiFiNetworkAgent.connect(false); - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // Cell Remains the default. assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); @@ -1800,7 +1836,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.explicitlySelected(false); mWiFiNetworkAgent.connect(false); - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // If the user chooses no on the "No Internet access, stay connected?" dialog, we ask the // network to disconnect. @@ -1811,7 +1847,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.explicitlySelected(false); mWiFiNetworkAgent.connect(true); - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); @@ -1820,7 +1856,7 @@ public class ConnectivityServiceTest { // TODO: fix this. mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET); mEthernetNetworkAgent.connect(true); - callback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent); + callback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork()); callback.assertNoCallback(); @@ -1993,7 +2029,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS); mCellNetworkAgent.connectWithoutInternet(); - networkCallback.expectAvailableCallbacks(mCellNetworkAgent); + networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); verifyActiveNetwork(TRANSPORT_WIFI); // Test releasing NetworkRequest disconnects cellular with MMS @@ -2022,7 +2058,7 @@ public class ConnectivityServiceTest { MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS); mmsNetworkAgent.connectWithoutInternet(); - networkCallback.expectAvailableCallbacks(mmsNetworkAgent); + networkCallback.expectAvailableCallbacksUnvalidated(mmsNetworkAgent); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent @@ -2049,7 +2085,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); String firstRedirectUrl = "http://example.com/firstPath"; mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl); - captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl); // Take down network. @@ -2062,7 +2098,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); String secondRedirectUrl = "http://example.com/secondPath"; mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl); - captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), secondRedirectUrl); // Make captive portal disappear then revalidate. @@ -2072,9 +2108,7 @@ public class ConnectivityServiceTest { captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); // Expect NET_CAPABILITY_VALIDATED onAvailable callback. - validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent); - // TODO: Investigate only sending available callbacks. - validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); // Break network connectivity. // Expect NET_CAPABILITY_VALIDATED onLost callback. @@ -2098,7 +2132,7 @@ public class ConnectivityServiceTest { // Bring up wifi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - validatedCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); // Check that calling startCaptivePortalApp does nothing. @@ -2109,7 +2143,7 @@ public class ConnectivityServiceTest { // Turn into a captive portal. mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 302; mCm.reportNetworkConnectivity(wifiNetwork, false); - captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); // Check that startCaptivePortalApp sends the expected intent. @@ -2122,7 +2156,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204; CaptivePortal c = (CaptivePortal) intent.getExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL); c.reportCaptivePortalDismissed(); - validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); mCm.unregisterNetworkCallback(validatedCallback); @@ -2165,7 +2199,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl); // Expect NET_CAPABILITY_VALIDATED onAvailable callback. - validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); // But there should be no CaptivePortal callback. captivePortalCallback.assertNoCallback(); } @@ -2203,14 +2237,14 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - cEmpty1.expectAvailableCallbacks(mWiFiNetworkAgent); - cEmpty2.expectAvailableCallbacks(mWiFiNetworkAgent); - cEmpty3.expectAvailableCallbacks(mWiFiNetworkAgent); - cEmpty4.expectAvailableCallbacks(mWiFiNetworkAgent); + cEmpty1.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + cEmpty2.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + cEmpty3.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + cEmpty4.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertNoCallbacks(cFoo, cBar); mWiFiNetworkAgent.setNetworkSpecifier(new StringNetworkSpecifier("foo")); - cFoo.expectAvailableCallbacks(mWiFiNetworkAgent); + cFoo.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); for (TestNetworkCallback c: emptyCallbacks) { c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent); } @@ -2219,7 +2253,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.setNetworkSpecifier(new StringNetworkSpecifier("bar")); cFoo.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - cBar.expectAvailableCallbacks(mWiFiNetworkAgent); + cBar.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); for (TestNetworkCallback c: emptyCallbacks) { c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent); } @@ -2348,14 +2382,14 @@ public class ConnectivityServiceTest { // Bring up cell and expect CALLBACK_AVAILABLE. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); - defaultNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); // Bring up wifi and expect CALLBACK_AVAILABLE. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); cellNetworkCallback.assertNoCallback(); - defaultNetworkCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + defaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); // Bring down cell. Expect no default network callback, since it wasn't the default. mCellNetworkAgent.disconnect(); @@ -2365,7 +2399,7 @@ public class ConnectivityServiceTest { // Bring up cell. Expect no default network callback, since it won't be the default. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); defaultNetworkCallback.assertNoCallback(); // Bring down wifi. Expect the default network callback to notified of LOST wifi @@ -2373,7 +2407,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.disconnect(); cellNetworkCallback.assertNoCallback(); defaultNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); mCellNetworkAgent.disconnect(); cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); @@ -2394,7 +2428,7 @@ public class ConnectivityServiceTest { // We should get onAvailable(), onCapabilitiesChanged(), and // onLinkPropertiesChanged() in rapid succession. Additionally, we // should get onCapabilitiesChanged() when the mobile network validates. - cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); // Update LinkProperties. @@ -2415,7 +2449,7 @@ public class ConnectivityServiceTest { mCm.registerDefaultNetworkCallback(dfltNetworkCallback); // We should get onAvailable(), onCapabilitiesChanged(), onLinkPropertiesChanged(), // as well as onNetworkSuspended() in rapid succession. - dfltNetworkCallback.expectAvailableAndSuspendedCallbacks(mCellNetworkAgent); + dfltNetworkCallback.expectAvailableAndSuspendedCallbacks(mCellNetworkAgent, true); dfltNetworkCallback.assertNoCallback(); mCm.unregisterNetworkCallback(dfltNetworkCallback); @@ -2455,18 +2489,18 @@ public class ConnectivityServiceTest { mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); - fgCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + fgCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); assertTrue(isForegroundNetwork(mCellNetworkAgent)); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); // When wifi connects, cell lingers. - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); - fgCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + fgCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); fgCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); fgCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); assertTrue(isForegroundNetwork(mCellNetworkAgent)); @@ -2490,8 +2524,8 @@ public class ConnectivityServiceTest { // is currently delivered before the onAvailable() callbacks. // TODO: Fix this. cellCallback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); - cellCallback.expectAvailableCallbacks(mCellNetworkAgent); - fgCallback.expectAvailableCallbacks(mCellNetworkAgent); + cellCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); // Expect a network capabilities update with FOREGROUND, because the most recent // request causes its state to change. callback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); @@ -2511,7 +2545,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); fgCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - fgCallback.expectAvailableCallbacks(mCellNetworkAgent); + fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); assertTrue(isForegroundNetwork(mCellNetworkAgent)); mCm.unregisterNetworkCallback(callback); @@ -2651,7 +2685,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); testFactory.expectAddRequests(2); // Because the cell request changes score twice. mCellNetworkAgent.connect(true); - cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); testFactory.waitForNetworkRequests(2); assertFalse(testFactory.getMyStartRequested()); // Because the cell network outscores us. @@ -2742,16 +2776,15 @@ public class ConnectivityServiceTest { // Bring up validated cell. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); Network cellNetwork = mCellNetworkAgent.getNetwork(); // Bring up validated wifi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); - validatedWifiCallback.expectAvailableCallbacks(mWiFiNetworkAgent); - validatedWifiCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); // Fail validation on wifi. @@ -2772,18 +2805,18 @@ public class ConnectivityServiceTest { // that we switch back to cell. tracker.configRestrictsAvoidBadWifi = false; tracker.reevaluate(); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); assertEquals(mCm.getActiveNetwork(), cellNetwork); // Switch back to a restrictive carrier. tracker.configRestrictsAvoidBadWifi = true; tracker.reevaluate(); - defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mCm.getActiveNetwork(), wifiNetwork); // Simulate the user selecting "switch" on the dialog, and check that we switch to cell. mCm.setAvoidUnvalidated(wifiNetwork); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability( NET_CAPABILITY_VALIDATED)); assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability( @@ -2794,9 +2827,8 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.disconnect(); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); - validatedWifiCallback.expectAvailableCallbacks(mWiFiNetworkAgent); - validatedWifiCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); wifiNetwork = mWiFiNetworkAgent.getNetwork(); // Fail validation on wifi and expect the dialog to appear. @@ -2810,7 +2842,7 @@ public class ConnectivityServiceTest { tracker.reevaluate(); // We now switch to cell. - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability( NET_CAPABILITY_VALIDATED)); assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability( @@ -2821,17 +2853,17 @@ public class ConnectivityServiceTest { // We switch to wifi and then to cell. Settings.Global.putString(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, null); tracker.reevaluate(); - defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mCm.getActiveNetwork(), wifiNetwork); Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1); tracker.reevaluate(); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); assertEquals(mCm.getActiveNetwork(), cellNetwork); // If cell goes down, we switch to wifi. mCellNetworkAgent.disconnect(); defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); - defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); validatedWifiCallback.assertNoCallback(); mCm.unregisterNetworkCallback(cellNetworkCallback); @@ -2873,7 +2905,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, timeoutMs); + networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, timeoutMs); // pass timeout and validate that UNAVAILABLE is not called networkCallback.assertNoCallback(); @@ -2894,7 +2926,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); final int assertTimeoutMs = 100; - networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, assertTimeoutMs); + networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, assertTimeoutMs); mWiFiNetworkAgent.disconnect(); networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); @@ -3381,7 +3413,7 @@ public class ConnectivityServiceTest { // Bring up wifi aware network. wifiAware.connect(false, false); - callback.expectAvailableCallbacks(wifiAware); + callback.expectAvailableCallbacksUnvalidated(wifiAware); assertNull(mCm.getActiveNetworkInfo()); assertNull(mCm.getActiveNetwork()); diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java index 2282c1319a9a..4fbb228e6e53 100644 --- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java +++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java @@ -32,7 +32,6 @@ import android.net.IpSecAlgorithm; import android.net.IpSecConfig; import android.net.IpSecManager; import android.net.IpSecSpiResponse; -import android.net.IpSecTransform; import android.net.IpSecTransformResponse; import android.net.NetworkUtils; import android.os.Binder; @@ -54,14 +53,14 @@ import org.junit.runners.Parameterized; @RunWith(Parameterized.class) public class IpSecServiceParameterizedTest { - private static final int TEST_SPI_OUT = 0xD1201D; - private static final int TEST_SPI_IN = TEST_SPI_OUT + 1; + private static final int TEST_SPI = 0xD1201D; - private final String mRemoteAddr; + private final String mDestinationAddr; + private final String mSourceAddr; @Parameterized.Parameters public static Collection ipSecConfigs() { - return Arrays.asList(new Object[][] {{"8.8.4.4"}, {"2601::10"}}); + return Arrays.asList(new Object[][] {{"1.2.3.4", "8.8.4.4"}, {"2601::2", "2601::10"}}); } private static final byte[] AEAD_KEY = { @@ -96,11 +95,9 @@ public class IpSecServiceParameterizedTest { private static final IpSecAlgorithm AEAD_ALGO = new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128); - private static final int[] DIRECTIONS = - new int[] {IpSecTransform.DIRECTION_IN, IpSecTransform.DIRECTION_OUT}; - - public IpSecServiceParameterizedTest(String remoteAddr) { - mRemoteAddr = remoteAddr; + public IpSecServiceParameterizedTest(String sourceAddr, String destAddr) { + mSourceAddr = sourceAddr; + mDestinationAddr = destAddr; } @Before @@ -116,44 +113,30 @@ public class IpSecServiceParameterizedTest { @Test public void testIpSecServiceReserveSpi() throws Exception { - when(mMockNetd.ipSecAllocateSpi( - anyInt(), - eq(IpSecTransform.DIRECTION_OUT), - anyString(), - eq(mRemoteAddr), - eq(TEST_SPI_OUT))) - .thenReturn(TEST_SPI_OUT); + when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI))) + .thenReturn(TEST_SPI); IpSecSpiResponse spiResp = mIpSecService.allocateSecurityParameterIndex( - IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT, new Binder()); + mDestinationAddr, TEST_SPI, new Binder()); assertEquals(IpSecManager.Status.OK, spiResp.status); - assertEquals(TEST_SPI_OUT, spiResp.spi); + assertEquals(TEST_SPI, spiResp.spi); } @Test public void testReleaseSecurityParameterIndex() throws Exception { - when(mMockNetd.ipSecAllocateSpi( - anyInt(), - eq(IpSecTransform.DIRECTION_OUT), - anyString(), - eq(mRemoteAddr), - eq(TEST_SPI_OUT))) - .thenReturn(TEST_SPI_OUT); + when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI))) + .thenReturn(TEST_SPI); IpSecSpiResponse spiResp = mIpSecService.allocateSecurityParameterIndex( - IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT, new Binder()); + mDestinationAddr, TEST_SPI, new Binder()); mIpSecService.releaseSecurityParameterIndex(spiResp.resourceId); verify(mMockNetd) .ipSecDeleteSecurityAssociation( - eq(spiResp.resourceId), - anyInt(), - anyString(), - anyString(), - eq(TEST_SPI_OUT)); + eq(spiResp.resourceId), anyString(), anyString(), eq(TEST_SPI)); // Verify quota and RefcountedResource objects cleaned up IpSecService.UserRecord userRecord = @@ -169,17 +152,12 @@ public class IpSecServiceParameterizedTest { @Test public void testSecurityParameterIndexBinderDeath() throws Exception { - when(mMockNetd.ipSecAllocateSpi( - anyInt(), - eq(IpSecTransform.DIRECTION_OUT), - anyString(), - eq(mRemoteAddr), - eq(TEST_SPI_OUT))) - .thenReturn(TEST_SPI_OUT); + when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI))) + .thenReturn(TEST_SPI); IpSecSpiResponse spiResp = mIpSecService.allocateSecurityParameterIndex( - IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT, new Binder()); + mDestinationAddr, TEST_SPI, new Binder()); IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid()); @@ -190,11 +168,7 @@ public class IpSecServiceParameterizedTest { verify(mMockNetd) .ipSecDeleteSecurityAssociation( - eq(spiResp.resourceId), - anyInt(), - anyString(), - anyString(), - eq(TEST_SPI_OUT)); + eq(spiResp.resourceId), anyString(), anyString(), eq(TEST_SPI)); // Verify quota and RefcountedResource objects cleaned up assertEquals(0, userRecord.mSpiQuotaTracker.mCurrent); @@ -206,14 +180,12 @@ public class IpSecServiceParameterizedTest { } } - private int getNewSpiResourceId(int direction, String remoteAddress, int returnSpi) - throws Exception { - when(mMockNetd.ipSecAllocateSpi(anyInt(), anyInt(), anyString(), anyString(), anyInt())) + private int getNewSpiResourceId(String remoteAddress, int returnSpi) throws Exception { + when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), anyString(), anyInt())) .thenReturn(returnSpi); IpSecSpiResponse spi = mIpSecService.allocateSecurityParameterIndex( - direction, NetworkUtils.numericToInetAddress(remoteAddress).getHostAddress(), IpSecManager.INVALID_SECURITY_PARAMETER_INDEX, new Binder()); @@ -221,20 +193,14 @@ public class IpSecServiceParameterizedTest { } private void addDefaultSpisAndRemoteAddrToIpSecConfig(IpSecConfig config) throws Exception { - config.setSpiResourceId( - IpSecTransform.DIRECTION_OUT, - getNewSpiResourceId(IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT)); - config.setSpiResourceId( - IpSecTransform.DIRECTION_IN, - getNewSpiResourceId(IpSecTransform.DIRECTION_IN, mRemoteAddr, TEST_SPI_IN)); - config.setRemoteAddress(mRemoteAddr); + config.setSpiResourceId(getNewSpiResourceId(mDestinationAddr, TEST_SPI)); + config.setSourceAddress(mSourceAddr); + config.setDestinationAddress(mDestinationAddr); } private void addAuthAndCryptToIpSecConfig(IpSecConfig config) throws Exception { - for (int direction : DIRECTIONS) { - config.setEncryption(direction, CRYPT_ALGO); - config.setAuthentication(direction, AUTH_ALGO); - } + config.setEncryption(CRYPT_ALGO); + config.setAuthentication(AUTH_ALGO); } @Test @@ -251,32 +217,10 @@ public class IpSecServiceParameterizedTest { .ipSecAddSecurityAssociation( eq(createTransformResp.resourceId), anyInt(), - eq(IpSecTransform.DIRECTION_OUT), anyString(), anyString(), anyLong(), - eq(TEST_SPI_OUT), - eq(IpSecAlgorithm.AUTH_HMAC_SHA256), - eq(AUTH_KEY), - anyInt(), - eq(IpSecAlgorithm.CRYPT_AES_CBC), - eq(CRYPT_KEY), - anyInt(), - eq(""), - eq(new byte[] {}), - eq(0), - anyInt(), - anyInt(), - anyInt()); - verify(mMockNetd) - .ipSecAddSecurityAssociation( - eq(createTransformResp.resourceId), - anyInt(), - eq(IpSecTransform.DIRECTION_IN), - anyString(), - anyString(), - anyLong(), - eq(TEST_SPI_IN), + eq(TEST_SPI), eq(IpSecAlgorithm.AUTH_HMAC_SHA256), eq(AUTH_KEY), anyInt(), @@ -296,8 +240,7 @@ public class IpSecServiceParameterizedTest { IpSecConfig ipSecConfig = new IpSecConfig(); addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); - ipSecConfig.setAuthenticatedEncryption(IpSecTransform.DIRECTION_OUT, AEAD_ALGO); - ipSecConfig.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO); + ipSecConfig.setAuthenticatedEncryption(AEAD_ALGO); IpSecTransformResponse createTransformResp = mIpSecService.createTransportModeTransform(ipSecConfig, new Binder()); @@ -307,32 +250,10 @@ public class IpSecServiceParameterizedTest { .ipSecAddSecurityAssociation( eq(createTransformResp.resourceId), anyInt(), - eq(IpSecTransform.DIRECTION_OUT), - anyString(), - anyString(), - anyLong(), - eq(TEST_SPI_OUT), - eq(""), - eq(new byte[] {}), - eq(0), - eq(""), - eq(new byte[] {}), - eq(0), - eq(IpSecAlgorithm.AUTH_CRYPT_AES_GCM), - eq(AEAD_KEY), - anyInt(), - anyInt(), - anyInt(), - anyInt()); - verify(mMockNetd) - .ipSecAddSecurityAssociation( - eq(createTransformResp.resourceId), - anyInt(), - eq(IpSecTransform.DIRECTION_IN), anyString(), anyString(), anyLong(), - eq(TEST_SPI_IN), + eq(TEST_SPI), eq(""), eq(new byte[] {}), eq(0), @@ -359,18 +280,7 @@ public class IpSecServiceParameterizedTest { verify(mMockNetd) .ipSecDeleteSecurityAssociation( - eq(createTransformResp.resourceId), - eq(IpSecTransform.DIRECTION_OUT), - anyString(), - anyString(), - eq(TEST_SPI_OUT)); - verify(mMockNetd) - .ipSecDeleteSecurityAssociation( - eq(createTransformResp.resourceId), - eq(IpSecTransform.DIRECTION_IN), - anyString(), - anyString(), - eq(TEST_SPI_IN)); + eq(createTransformResp.resourceId), anyString(), anyString(), eq(TEST_SPI)); // Verify quota and RefcountedResource objects cleaned up IpSecService.UserRecord userRecord = @@ -404,18 +314,7 @@ public class IpSecServiceParameterizedTest { verify(mMockNetd) .ipSecDeleteSecurityAssociation( - eq(createTransformResp.resourceId), - eq(IpSecTransform.DIRECTION_OUT), - anyString(), - anyString(), - eq(TEST_SPI_OUT)); - verify(mMockNetd) - .ipSecDeleteSecurityAssociation( - eq(createTransformResp.resourceId), - eq(IpSecTransform.DIRECTION_IN), - anyString(), - anyString(), - eq(TEST_SPI_IN)); + eq(createTransformResp.resourceId), anyString(), anyString(), eq(TEST_SPI)); // Verify quota and RefcountedResource objects cleaned up assertEquals(0, userRecord.mTransformQuotaTracker.mCurrent); @@ -439,30 +338,22 @@ public class IpSecServiceParameterizedTest { ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket()); int resourceId = createTransformResp.resourceId; - mIpSecService.applyTransportModeTransform(pfd, resourceId); + mIpSecService.applyTransportModeTransform(pfd, IpSecManager.DIRECTION_OUT, resourceId); verify(mMockNetd) .ipSecApplyTransportModeTransform( eq(pfd.getFileDescriptor()), eq(resourceId), - eq(IpSecTransform.DIRECTION_OUT), - anyString(), - anyString(), - eq(TEST_SPI_OUT)); - verify(mMockNetd) - .ipSecApplyTransportModeTransform( - eq(pfd.getFileDescriptor()), - eq(resourceId), - eq(IpSecTransform.DIRECTION_IN), + eq(IpSecManager.DIRECTION_OUT), anyString(), anyString(), - eq(TEST_SPI_IN)); + eq(TEST_SPI)); } @Test public void testRemoveTransportModeTransform() throws Exception { ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket()); - mIpSecService.removeTransportModeTransform(pfd, 1); + mIpSecService.removeTransportModeTransforms(pfd); verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd.getFileDescriptor()); } diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java index 0467989d8984..3eba881df427 100644 --- a/tests/net/java/com/android/server/IpSecServiceTest.java +++ b/tests/net/java/com/android/server/IpSecServiceTest.java @@ -105,9 +105,6 @@ public class IpSecServiceTest { private static final IpSecAlgorithm AEAD_ALGO = new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128); - private static final int[] DIRECTIONS = - new int[] {IpSecTransform.DIRECTION_IN, IpSecTransform.DIRECTION_OUT}; - static { try { INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0}); @@ -303,83 +300,75 @@ public class IpSecServiceTest { @Test public void testValidateAlgorithmsAuth() { - for (int direction : DIRECTIONS) { - // Validate that correct algorithm type succeeds - IpSecConfig config = new IpSecConfig(); - config.setAuthentication(direction, AUTH_ALGO); - mIpSecService.validateAlgorithms(config, direction); - - // Validate that incorrect algorithm types fails - for (IpSecAlgorithm algo : new IpSecAlgorithm[] {CRYPT_ALGO, AEAD_ALGO}) { - try { - config = new IpSecConfig(); - config.setAuthentication(direction, algo); - mIpSecService.validateAlgorithms(config, direction); - fail("Did not throw exception on invalid algorithm type"); - } catch (IllegalArgumentException expected) { - } + // Validate that correct algorithm type succeeds + IpSecConfig config = new IpSecConfig(); + config.setAuthentication(AUTH_ALGO); + mIpSecService.validateAlgorithms(config); + + // Validate that incorrect algorithm types fails + for (IpSecAlgorithm algo : new IpSecAlgorithm[] {CRYPT_ALGO, AEAD_ALGO}) { + try { + config = new IpSecConfig(); + config.setAuthentication(algo); + mIpSecService.validateAlgorithms(config); + fail("Did not throw exception on invalid algorithm type"); + } catch (IllegalArgumentException expected) { } } } @Test public void testValidateAlgorithmsCrypt() { - for (int direction : DIRECTIONS) { - // Validate that correct algorithm type succeeds - IpSecConfig config = new IpSecConfig(); - config.setEncryption(direction, CRYPT_ALGO); - mIpSecService.validateAlgorithms(config, direction); - - // Validate that incorrect algorithm types fails - for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, AEAD_ALGO}) { - try { - config = new IpSecConfig(); - config.setEncryption(direction, algo); - mIpSecService.validateAlgorithms(config, direction); - fail("Did not throw exception on invalid algorithm type"); - } catch (IllegalArgumentException expected) { - } + // Validate that correct algorithm type succeeds + IpSecConfig config = new IpSecConfig(); + config.setEncryption(CRYPT_ALGO); + mIpSecService.validateAlgorithms(config); + + // Validate that incorrect algorithm types fails + for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, AEAD_ALGO}) { + try { + config = new IpSecConfig(); + config.setEncryption(algo); + mIpSecService.validateAlgorithms(config); + fail("Did not throw exception on invalid algorithm type"); + } catch (IllegalArgumentException expected) { } } } @Test public void testValidateAlgorithmsAead() { - for (int direction : DIRECTIONS) { - // Validate that correct algorithm type succeeds - IpSecConfig config = new IpSecConfig(); - config.setAuthenticatedEncryption(direction, AEAD_ALGO); - mIpSecService.validateAlgorithms(config, direction); - - // Validate that incorrect algorithm types fails - for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, CRYPT_ALGO}) { - try { - config = new IpSecConfig(); - config.setAuthenticatedEncryption(direction, algo); - mIpSecService.validateAlgorithms(config, direction); - fail("Did not throw exception on invalid algorithm type"); - } catch (IllegalArgumentException expected) { - } + // Validate that correct algorithm type succeeds + IpSecConfig config = new IpSecConfig(); + config.setAuthenticatedEncryption(AEAD_ALGO); + mIpSecService.validateAlgorithms(config); + + // Validate that incorrect algorithm types fails + for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, CRYPT_ALGO}) { + try { + config = new IpSecConfig(); + config.setAuthenticatedEncryption(algo); + mIpSecService.validateAlgorithms(config); + fail("Did not throw exception on invalid algorithm type"); + } catch (IllegalArgumentException expected) { } } } @Test public void testValidateAlgorithmsAuthCrypt() { - for (int direction : DIRECTIONS) { - // Validate that correct algorithm type succeeds - IpSecConfig config = new IpSecConfig(); - config.setAuthentication(direction, AUTH_ALGO); - config.setEncryption(direction, CRYPT_ALGO); - mIpSecService.validateAlgorithms(config, direction); - } + // Validate that correct algorithm type succeeds + IpSecConfig config = new IpSecConfig(); + config.setAuthentication(AUTH_ALGO); + config.setEncryption(CRYPT_ALGO); + mIpSecService.validateAlgorithms(config); } @Test public void testValidateAlgorithmsNoAlgorithms() { IpSecConfig config = new IpSecConfig(); try { - mIpSecService.validateAlgorithms(config, IpSecTransform.DIRECTION_IN); + mIpSecService.validateAlgorithms(config); fail("Expected exception; no algorithms specified"); } catch (IllegalArgumentException expected) { } @@ -388,10 +377,10 @@ public class IpSecServiceTest { @Test public void testValidateAlgorithmsAeadWithAuth() { IpSecConfig config = new IpSecConfig(); - config.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO); - config.setAuthentication(IpSecTransform.DIRECTION_IN, AUTH_ALGO); + config.setAuthenticatedEncryption(AEAD_ALGO); + config.setAuthentication(AUTH_ALGO); try { - mIpSecService.validateAlgorithms(config, IpSecTransform.DIRECTION_IN); + mIpSecService.validateAlgorithms(config); fail("Expected exception; both AEAD and auth algorithm specified"); } catch (IllegalArgumentException expected) { } @@ -400,10 +389,10 @@ public class IpSecServiceTest { @Test public void testValidateAlgorithmsAeadWithCrypt() { IpSecConfig config = new IpSecConfig(); - config.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO); - config.setEncryption(IpSecTransform.DIRECTION_IN, CRYPT_ALGO); + config.setAuthenticatedEncryption(AEAD_ALGO); + config.setEncryption(CRYPT_ALGO); try { - mIpSecService.validateAlgorithms(config, IpSecTransform.DIRECTION_IN); + mIpSecService.validateAlgorithms(config); fail("Expected exception; both AEAD and crypt algorithm specified"); } catch (IllegalArgumentException expected) { } @@ -412,11 +401,11 @@ public class IpSecServiceTest { @Test public void testValidateAlgorithmsAeadWithAuthAndCrypt() { IpSecConfig config = new IpSecConfig(); - config.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO); - config.setAuthentication(IpSecTransform.DIRECTION_IN, AUTH_ALGO); - config.setEncryption(IpSecTransform.DIRECTION_IN, CRYPT_ALGO); + config.setAuthenticatedEncryption(AEAD_ALGO); + config.setAuthentication(AUTH_ALGO); + config.setEncryption(CRYPT_ALGO); try { - mIpSecService.validateAlgorithms(config, IpSecTransform.DIRECTION_IN); + mIpSecService.validateAlgorithms(config); fail("Expected exception; AEAD, auth and crypt algorithm specified"); } catch (IllegalArgumentException expected) { } @@ -434,7 +423,7 @@ public class IpSecServiceTest { @Test public void testRemoveTransportModeTransform() throws Exception { ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket()); - mIpSecService.removeTransportModeTransform(pfd, 1); + mIpSecService.removeTransportModeTransforms(pfd); verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd.getFileDescriptor()); } @@ -447,7 +436,7 @@ public class IpSecServiceTest { try { IpSecSpiResponse spiResp = mIpSecService.allocateSecurityParameterIndex( - IpSecTransform.DIRECTION_OUT, address, DROID_SPI, new Binder()); + address, DROID_SPI, new Binder()); fail("Invalid address was passed through IpSecService validation: " + address); } catch (IllegalArgumentException e) { } catch (Exception e) { @@ -519,7 +508,6 @@ public class IpSecServiceTest { // tracks the resource ID. when(mMockNetd.ipSecAllocateSpi( anyInt(), - eq(IpSecTransform.DIRECTION_OUT), anyString(), eq(InetAddress.getLoopbackAddress().getHostAddress()), anyInt())) @@ -528,7 +516,6 @@ public class IpSecServiceTest { for (int i = 0; i < MAX_NUM_SPIS; i++) { IpSecSpiResponse newSpi = mIpSecService.allocateSecurityParameterIndex( - 0x1, InetAddress.getLoopbackAddress().getHostAddress(), DROID_SPI + i, new Binder()); @@ -544,7 +531,6 @@ public class IpSecServiceTest { // Try to reserve one more SPI, and should fail. IpSecSpiResponse extraSpi = mIpSecService.allocateSecurityParameterIndex( - 0x1, InetAddress.getLoopbackAddress().getHostAddress(), DROID_SPI + MAX_NUM_SPIS, new Binder()); @@ -558,7 +544,6 @@ public class IpSecServiceTest { // Should successfully reserve one more spi. extraSpi = mIpSecService.allocateSecurityParameterIndex( - 0x1, InetAddress.getLoopbackAddress().getHostAddress(), DROID_SPI + MAX_NUM_SPIS, new Binder()); diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp index b692ccf7e52d..f5f5b05491bb 100644 --- a/tools/aapt2/java/ClassDefinition.cpp +++ b/tools/aapt2/java/ClassDefinition.cpp @@ -45,8 +45,18 @@ ClassDefinition::Result ClassDefinition::AddMember(std::unique_ptr<ClassMember> Result result = Result::kAdded; auto iter = indexed_members_.find(member->GetName()); if (iter != indexed_members_.end()) { - // Overwrite the entry. - ordered_members_[iter->second].reset(); + // Overwrite the entry. Be careful, as the key in indexed_members_ is actually memory owned + // by the value at ordered_members_[index]. Since overwriting a value for a key doesn't replace + // the key (the initial key inserted into the unordered_map is kept), we must erase and then + // insert a new key, whose memory is being kept around. We do all this to avoid using more + // memory for each key. + size_t index = iter->second; + + // Erase the key + value from the map. + indexed_members_.erase(iter); + + // Now clear the memory that was backing the key (now erased). + ordered_members_[index].reset(); result = Result::kOverridden; } diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp index c324238d3ecb..f4e10ab2e584 100644 --- a/tools/aapt2/java/ManifestClassGenerator_test.cpp +++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp @@ -129,13 +129,16 @@ TEST(ManifestClassGeneratorTest, LastSeenPermissionWithSameLeafNameTakesPreceden std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <permission android:name="android.permission.ACCESS_INTERNET" /> - <permission android:name="com.android.aapt.test.ACCESS_INTERNET" /> + <permission android:name="com.android.sample.ACCESS_INTERNET" /> + <permission android:name="com.android.permission.UNRELATED_PERMISSION" /> + <permission android:name="com.android.aapt.test.ACCESS_INTERNET" /> --> </manifest>)"); std::string actual; ASSERT_TRUE(GetManifestClassText(context.get(), manifest.get(), &actual)); EXPECT_THAT(actual, HasSubstr("ACCESS_INTERNET=\"com.android.aapt.test.ACCESS_INTERNET\";")); EXPECT_THAT(actual, Not(HasSubstr("ACCESS_INTERNET=\"android.permission.ACCESS_INTERNET\";"))); + EXPECT_THAT(actual, Not(HasSubstr("ACCESS_INTERNET=\"com.android.sample.ACCESS_INTERNET\";"))); } static ::testing::AssertionResult GetManifestClassText(IAaptContext* context, xml::XmlResource* res, diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp index 80853b13a7cc..0e57f7ff0ed2 100644 --- a/tools/stats_log_api_gen/Collation.cpp +++ b/tools/stats_log_api_gen/Collation.cpp @@ -15,6 +15,7 @@ */ #include "Collation.h" +#include "frameworks/base/cmds/statsd/src/atoms.pb.h" #include <stdio.h> #include <map> @@ -137,6 +138,16 @@ java_type(const FieldDescriptor* field) } /** + * Gather the enums info. + */ +void collate_enums(const EnumDescriptor &enumDescriptor, AtomField *atomField) { + for (int i = 0; i < enumDescriptor.value_count(); i++) { + atomField->enumValues[enumDescriptor.value(i)->number()] = + enumDescriptor.value(i)->name().c_str(); + } +} + +/** * Gather the info about an atom proto. */ int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, @@ -221,11 +232,7 @@ int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, if (javaType == JAVA_TYPE_ENUM) { // All enums are treated as ints when it comes to function signatures. signature->push_back(JAVA_TYPE_INT); - const EnumDescriptor *enumDescriptor = field->enum_type(); - for (int i = 0; i < enumDescriptor->value_count(); i++) { - atField.enumValues[enumDescriptor->value(i)->number()] = - enumDescriptor->value(i)->name().c_str(); - } + collate_enums(*field->enum_type(), &atField); } else { signature->push_back(javaType); } @@ -235,6 +242,53 @@ int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, return errorCount; } +// This function flattens the fields of the AttributionNode proto in an Atom proto and generates +// the corresponding atom decl and signature. +bool get_non_chained_node(const Descriptor *atom, AtomDecl *atomDecl, + vector<java_type_t> *signature) { + // Build a sorted list of the fields. Descriptor has them in source file + // order. + map<int, const FieldDescriptor *> fields; + for (int j = 0; j < atom->field_count(); j++) { + const FieldDescriptor *field = atom->field(j); + fields[field->number()] = field; + } + + AtomDecl attributionDecl; + vector<java_type_t> attributionSignature; + collate_atom(android::os::statsd::AttributionNode::descriptor(), + &attributionDecl, &attributionSignature); + + // Build the type signature and the atom data. + bool has_attribution_node = false; + for (map<int, const FieldDescriptor *>::const_iterator it = fields.begin(); + it != fields.end(); it++) { + const FieldDescriptor *field = it->second; + java_type_t javaType = java_type(field); + if (javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) { + atomDecl->fields.insert( + atomDecl->fields.end(), + attributionDecl.fields.begin(), attributionDecl.fields.end()); + signature->insert( + signature->end(), + attributionSignature.begin(), attributionSignature.end()); + has_attribution_node = true; + + } else { + AtomField atField(field->name(), javaType); + if (javaType == JAVA_TYPE_ENUM) { + // All enums are treated as ints when it comes to function signatures. + signature->push_back(JAVA_TYPE_INT); + collate_enums(*field->enum_type(), &atField); + } else { + signature->push_back(javaType); + } + atomDecl->fields.push_back(atField); + } + } + return has_attribution_node; +} + /** * Gather the info about the atoms. */ @@ -266,6 +320,13 @@ int collate_atoms(const Descriptor *descriptor, Atoms *atoms) { errorCount += collate_atom(atom, &atomDecl, &signature); atoms->signatures.insert(signature); atoms->decls.insert(atomDecl); + + AtomDecl nonChainedAtomDecl(atomField->number(), atomField->name(), atom->name()); + vector<java_type_t> nonChainedSignature; + if (get_non_chained_node(atom, &nonChainedAtomDecl, &nonChainedSignature)) { + atoms->non_chained_signatures.insert(nonChainedSignature); + atoms->non_chained_decls.insert(nonChainedAtomDecl); + } } if (dbg) { diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h index cd0625c3a61d..0455eca62da8 100644 --- a/tools/stats_log_api_gen/Collation.h +++ b/tools/stats_log_api_gen/Collation.h @@ -32,6 +32,7 @@ using std::set; using std::string; using std::vector; using google::protobuf::Descriptor; +using google::protobuf::FieldDescriptor; /** * The types for atom parameters. @@ -93,14 +94,15 @@ struct AtomDecl { struct Atoms { set<vector<java_type_t>> signatures; set<AtomDecl> decls; + set<AtomDecl> non_chained_decls; + set<vector<java_type_t>> non_chained_signatures; }; /** * Gather the information about the atoms. Returns the number of errors. */ int collate_atoms(const Descriptor* descriptor, Atoms* atoms); -int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, - vector<java_type_t> *signature); +int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, vector<java_type_t> *signature); } // namespace stats_log_api_gen } // namespace android diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp index bbe6d63073c1..e0e6b5883e32 100644 --- a/tools/stats_log_api_gen/main.cpp +++ b/tools/stats_log_api_gen/main.cpp @@ -195,6 +195,47 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, fprintf(out, "\n"); } + for (set<vector<java_type_t>>::const_iterator signature = atoms.non_chained_signatures.begin(); + signature != atoms.non_chained_signatures.end(); signature++) { + int argIndex; + + fprintf(out, "void\n"); + fprintf(out, "stats_write_non_chained(int32_t code"); + argIndex = 1; + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex); + argIndex++; + } + fprintf(out, ")\n"); + + fprintf(out, "{\n"); + argIndex = 1; + fprintf(out, " android_log_event_list event(kStatsEventTag);\n"); + fprintf(out, " event << code;\n\n"); + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + if (argIndex == 1) { + fprintf(out, " event.begin();\n\n"); + fprintf(out, " event.begin();\n"); + } + if (*arg == JAVA_TYPE_STRING) { + fprintf(out, " if (arg%d == NULL) {\n", argIndex); + fprintf(out, " arg%d = \"\";\n", argIndex); + fprintf(out, " }\n"); + } + fprintf(out, " event << arg%d;\n", argIndex); + if (argIndex == 2) { + fprintf(out, " event.end();\n\n"); + fprintf(out, " event.end();\n\n"); + } + argIndex++; + } + + fprintf(out, " event.write(LOG_ID_STATS);\n"); + fprintf(out, "}\n"); + fprintf(out, "\n"); + } // Print footer fprintf(out, "\n"); fprintf(out, "} // namespace util\n"); @@ -203,6 +244,68 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, return 0; } +void build_non_chained_decl_map(const Atoms& atoms, + std::map<int, set<AtomDecl>::const_iterator>* decl_map){ + for (set<AtomDecl>::const_iterator atom = atoms.non_chained_decls.begin(); + atom != atoms.non_chained_decls.end(); atom++) { + decl_map->insert(std::make_pair(atom->code, atom)); + } +} + +static void write_cpp_usage( + FILE* out, const string& method_name, const string& atom_code_name, + const AtomDecl& atom, const AtomDecl &attributionDecl) { + fprintf(out, " * Usage: %s(StatsLog.%s", method_name.c_str(), atom_code_name.c_str()); + for (vector<AtomField>::const_iterator field = atom.fields.begin(); + field != atom.fields.end(); field++) { + if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) { + for (auto chainField : attributionDecl.fields) { + if (chainField.javaType == JAVA_TYPE_STRING) { + fprintf(out, ", const std::vector<%s>& %s", + cpp_type_name(chainField.javaType), + chainField.name.c_str()); + } else { + fprintf(out, ", const %s* %s, size_t %s_length", + cpp_type_name(chainField.javaType), + chainField.name.c_str(), chainField.name.c_str()); + } + } + } else { + fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str()); + } + } + fprintf(out, ");\n"); +} + +static void write_cpp_method_header( + FILE* out, const string& method_name, const set<vector<java_type_t>>& signatures, + const AtomDecl &attributionDecl) { + for (set<vector<java_type_t>>::const_iterator signature = signatures.begin(); + signature != signatures.end(); signature++) { + fprintf(out, "void %s(int32_t code ", method_name.c_str()); + int argIndex = 1; + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) { + for (auto chainField : attributionDecl.fields) { + if (chainField.javaType == JAVA_TYPE_STRING) { + fprintf(out, ", const std::vector<%s>& %s", + cpp_type_name(chainField.javaType), chainField.name.c_str()); + } else { + fprintf(out, ", const %s* %s, size_t %s_length", + cpp_type_name(chainField.javaType), + chainField.name.c_str(), chainField.name.c_str()); + } + } + } else { + fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex); + } + argIndex++; + } + fprintf(out, ");\n"); + + } +} static int write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl) @@ -228,6 +331,9 @@ write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributio fprintf(out, " */\n"); fprintf(out, "enum {\n"); + std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map; + build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map); + size_t i = 0; // Print constants for (set<AtomDecl>::const_iterator atom = atoms.decls.begin(); @@ -236,26 +342,13 @@ write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributio fprintf(out, "\n"); fprintf(out, " /**\n"); fprintf(out, " * %s %s\n", atom->message.c_str(), atom->name.c_str()); - fprintf(out, " * Usage: stats_write(StatsLog.%s", constant.c_str()); - for (vector<AtomField>::const_iterator field = atom->fields.begin(); - field != atom->fields.end(); field++) { - if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) { - for (auto chainField : attributionDecl.fields) { - if (chainField.javaType == JAVA_TYPE_STRING) { - fprintf(out, ", const std::vector<%s>& %s", - cpp_type_name(chainField.javaType), - chainField.name.c_str()); - } else { - fprintf(out, ", const %s* %s, size_t %s_length", - cpp_type_name(chainField.javaType), - chainField.name.c_str(), chainField.name.c_str()); - } - } - } else { - fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str()); - } + write_cpp_usage(out, "stats_write", constant, *atom, attributionDecl); + + auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code); + if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) { + write_cpp_usage(out, "stats_write_non_chained", constant, *non_chained_decl->second, + attributionDecl); } - fprintf(out, ");\n"); fprintf(out, " */\n"); char const* const comma = (i == atoms.decls.size() - 1) ? "" : ","; fprintf(out, " %s = %d%s\n", constant.c_str(), atom->code, comma); @@ -274,38 +367,64 @@ write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributio fprintf(out, "//\n"); fprintf(out, "// Write methods\n"); fprintf(out, "//\n"); - for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); - signature != atoms.signatures.end(); signature++) { - fprintf(out, "void stats_write(int32_t code "); + write_cpp_method_header(out, "stats_write", atoms.signatures, attributionDecl); + + fprintf(out, "//\n"); + fprintf(out, "// Write flattened methods\n"); + fprintf(out, "//\n"); + write_cpp_method_header(out, "stats_write_non_chained", atoms.non_chained_signatures, + attributionDecl); + + fprintf(out, "\n"); + fprintf(out, "} // namespace util\n"); + fprintf(out, "} // namespace android\n"); + + return 0; +} + +static void write_java_usage( + FILE* out, const string& method_name, const string& atom_code_name, + const AtomDecl& atom, const AtomDecl &attributionDecl) { + fprintf(out, " * Usage: StatsLog.%s(StatsLog.%s", + method_name.c_str(), atom_code_name.c_str()); + for (vector<AtomField>::const_iterator field = atom.fields.begin(); + field != atom.fields.end(); field++) { + if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) { + for (auto chainField : attributionDecl.fields) { + fprintf(out, ", %s[] %s", + java_type_name(chainField.javaType), chainField.name.c_str()); + } + } else { + fprintf(out, ", %s %s", java_type_name(field->javaType), field->name.c_str()); + } + } + fprintf(out, ");\n"); +} + +static void write_java_method( + FILE* out, const string& method_name, const set<vector<java_type_t>>& signatures, + const AtomDecl &attributionDecl) { + for (set<vector<java_type_t>>::const_iterator signature = signatures.begin(); + signature != signatures.end(); signature++) { + fprintf(out, " public static native void %s(int code", method_name.c_str()); int argIndex = 1; for (vector<java_type_t>::const_iterator arg = signature->begin(); arg != signature->end(); arg++) { if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) { for (auto chainField : attributionDecl.fields) { - if (chainField.javaType == JAVA_TYPE_STRING) { - fprintf(out, ", const std::vector<%s>& %s", - cpp_type_name(chainField.javaType), chainField.name.c_str()); - } else { - fprintf(out, ", const %s* %s, size_t %s_length", - cpp_type_name(chainField.javaType), - chainField.name.c_str(), chainField.name.c_str()); - } + fprintf(out, ", %s[] %s", + java_type_name(chainField.javaType), chainField.name.c_str()); } } else { - fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex); + fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex); } argIndex++; } fprintf(out, ");\n"); } - - fprintf(out, "\n"); - fprintf(out, "} // namespace util\n"); - fprintf(out, "} // namespace android\n"); - - return 0; } + static int write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl) { @@ -322,6 +441,9 @@ write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl &attributionD fprintf(out, "public class StatsLogInternal {\n"); fprintf(out, " // Constants for atom codes.\n"); + std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map; + build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map); + // Print constants for the atom codes. for (set<AtomDecl>::const_iterator atom = atoms.decls.begin(); atom != atoms.decls.end(); atom++) { @@ -329,19 +451,12 @@ write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl &attributionD fprintf(out, "\n"); fprintf(out, " /**\n"); fprintf(out, " * %s %s\n", atom->message.c_str(), atom->name.c_str()); - fprintf(out, " * Usage: StatsLog.write(StatsLog.%s", constant.c_str()); - for (vector<AtomField>::const_iterator field = atom->fields.begin(); - field != atom->fields.end(); field++) { - if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) { - for (auto chainField : attributionDecl.fields) { - fprintf(out, ", %s[] %s", - java_type_name(chainField.javaType), chainField.name.c_str()); - } - } else { - fprintf(out, ", %s %s", java_type_name(field->javaType), field->name.c_str()); - } + write_java_usage(out, "write", constant, *atom, attributionDecl); + auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code); + if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) { + write_java_usage(out, "write_non_chained", constant, *non_chained_decl->second, + attributionDecl); } - fprintf(out, ");\n"); fprintf(out, " */\n"); fprintf(out, " public static final int %s = %d;\n", constant.c_str(), atom->code); } @@ -371,24 +486,8 @@ write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl &attributionD // Print write methods fprintf(out, " // Write methods\n"); - for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); - signature != atoms.signatures.end(); signature++) { - fprintf(out, " public static native void write(int code"); - int argIndex = 1; - for (vector<java_type_t>::const_iterator arg = signature->begin(); - arg != signature->end(); arg++) { - if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) { - for (auto chainField : attributionDecl.fields) { - fprintf(out, ", %s[] %s", - java_type_name(chainField.javaType), chainField.name.c_str()); - } - } else { - fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex); - } - argIndex++; - } - fprintf(out, ");\n"); - } + write_java_method(out, "write", atoms.signatures, attributionDecl); + write_java_method(out, "write_non_chained", atoms.non_chained_signatures, attributionDecl); fprintf(out, "}\n"); @@ -431,9 +530,9 @@ jni_array_type_name(java_type_t type) } static string -jni_function_name(const vector<java_type_t>& signature) +jni_function_name(const string& method_name, const vector<java_type_t>& signature) { - string result("StatsLog_write"); + string result("StatsLog_" + method_name); for (vector<java_type_t>::const_iterator arg = signature.begin(); arg != signature.end(); arg++) { switch (*arg) { @@ -509,34 +608,17 @@ jni_function_signature(const vector<java_type_t>& signature, const AtomDecl &att } static int -write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl) +write_stats_log_jni(FILE* out, const string& java_method_name, const string& cpp_method_name, + const set<vector<java_type_t>>& signatures, const AtomDecl &attributionDecl) { - // Print prelude - fprintf(out, "// This file is autogenerated\n"); - fprintf(out, "\n"); - - fprintf(out, "#include <statslog.h>\n"); - fprintf(out, "\n"); - fprintf(out, "#include <nativehelper/JNIHelp.h>\n"); - fprintf(out, "#include <nativehelper/ScopedUtfChars.h>\n"); - fprintf(out, "#include <utils/Vector.h>\n"); - fprintf(out, "#include \"core_jni_helpers.h\"\n"); - fprintf(out, "#include \"jni.h\"\n"); - fprintf(out, "\n"); - fprintf(out, "#define UNUSED __attribute__((__unused__))\n"); - fprintf(out, "\n"); - - fprintf(out, "namespace android {\n"); - fprintf(out, "\n"); - // Print write methods - for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); - signature != atoms.signatures.end(); signature++) { + for (set<vector<java_type_t>>::const_iterator signature = signatures.begin(); + signature != signatures.end(); signature++) { int argIndex; fprintf(out, "static void\n"); fprintf(out, "%s(JNIEnv* env, jobject clazz UNUSED, jint code", - jni_function_name(*signature).c_str()); + jni_function_name(java_method_name, *signature).c_str()); argIndex = 1; for (vector<java_type_t>::const_iterator arg = signature->begin(); arg != signature->end(); arg++) { @@ -624,7 +706,7 @@ write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDe // stats_write call argIndex = 1; - fprintf(out, " android::util::stats_write(code"); + fprintf(out, " android::util::%s(code", cpp_method_name.c_str()); for (vector<java_type_t>::const_iterator arg = signature->begin(); arg != signature->end(); arg++) { if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) { @@ -675,17 +757,53 @@ write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDe fprintf(out, "\n"); } + + return 0; +} + +void write_jni_registration(FILE* out, const string& java_method_name, + const set<vector<java_type_t>>& signatures, const AtomDecl &attributionDecl) { + for (set<vector<java_type_t>>::const_iterator signature = signatures.begin(); + signature != signatures.end(); signature++) { + fprintf(out, " { \"%s\", \"%s\", (void*)%s },\n", + java_method_name.c_str(), + jni_function_signature(*signature, attributionDecl).c_str(), + jni_function_name(java_method_name, *signature).c_str()); + } +} + +static int +write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl) +{ + // Print prelude + fprintf(out, "// This file is autogenerated\n"); + fprintf(out, "\n"); + + fprintf(out, "#include <statslog.h>\n"); + fprintf(out, "\n"); + fprintf(out, "#include <nativehelper/JNIHelp.h>\n"); + fprintf(out, "#include <nativehelper/ScopedUtfChars.h>\n"); + fprintf(out, "#include <utils/Vector.h>\n"); + fprintf(out, "#include \"core_jni_helpers.h\"\n"); + fprintf(out, "#include \"jni.h\"\n"); + fprintf(out, "\n"); + fprintf(out, "#define UNUSED __attribute__((__unused__))\n"); + fprintf(out, "\n"); + + fprintf(out, "namespace android {\n"); + fprintf(out, "\n"); + + write_stats_log_jni(out, "write", "stats_write", atoms.signatures, attributionDecl); + write_stats_log_jni(out, "write_non_chained", "stats_write_non_chained", + atoms.non_chained_signatures, attributionDecl); + // Print registration function table fprintf(out, "/*\n"); fprintf(out, " * JNI registration.\n"); fprintf(out, " */\n"); fprintf(out, "static const JNINativeMethod gRegisterMethods[] = {\n"); - for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); - signature != atoms.signatures.end(); signature++) { - fprintf(out, " { \"write\", \"%s\", (void*)%s },\n", - jni_function_signature(*signature, attributionDecl).c_str(), - jni_function_name(*signature).c_str()); - } + write_jni_registration(out, "write", atoms.signatures, attributionDecl); + write_jni_registration(out, "write_non_chained", atoms.non_chained_signatures, attributionDecl); fprintf(out, "};\n"); fprintf(out, "\n"); @@ -699,11 +817,9 @@ write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDe fprintf(out, "\n"); fprintf(out, "} // namespace android\n"); - return 0; } - static void print_usage() { |