diff options
423 files changed, 12548 insertions, 4905 deletions
diff --git a/Android.mk b/Android.mk index e219661b19f1..d7d9c900e3bf 100644 --- a/Android.mk +++ b/Android.mk @@ -252,6 +252,11 @@ framework_docs_LOCAL_DROIDDOC_OPTIONS += \ -federate SupportLib https://developer.android.com \ -federationapi SupportLib prebuilts/sdk/current/support-api.txt +# Federate AndroidX references against local API file. +framework_docs_LOCAL_DROIDDOC_OPTIONS += \ + -federate AndroidX https://developer.android.com \ + -federationapi AndroidX prebuilts/sdk/current/androidx-api.txt + # ==== Public API diff =========================== include $(CLEAR_VARS) diff --git a/apct-tests/perftests/core/AndroidTest.xml b/apct-tests/perftests/core/AndroidTest.xml new file mode 100644 index 000000000000..6aa34a627940 --- /dev/null +++ b/apct-tests/perftests/core/AndroidTest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Runs CorePerfTests metric instrumentation."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-metric-instrumentation" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="CorePerfTests.apk" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.perftests.core" /> + </test> +</configuration> diff --git a/apct-tests/perftests/multiuser/Android.mk b/apct-tests/perftests/multiuser/Android.mk index a803369d1e03..9bc7d051121a 100644 --- a/apct-tests/perftests/multiuser/Android.mk +++ b/apct-tests/perftests/multiuser/Android.mk @@ -26,6 +26,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ LOCAL_PACKAGE_NAME := MultiUserPerfTests LOCAL_PRIVATE_PLATFORM_APIS := true +LOCAL_COMPATIBILITY_SUITE += device-tests + LOCAL_CERTIFICATE := platform include $(BUILD_PACKAGE) diff --git a/apct-tests/perftests/multiuser/AndroidTest.xml b/apct-tests/perftests/multiuser/AndroidTest.xml new file mode 100644 index 000000000000..6ede8275afcb --- /dev/null +++ b/apct-tests/perftests/multiuser/AndroidTest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Runs MultiUserPerfTests metric instrumentation."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-metric-instrumentation" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="MultiUserPerfTests.apk" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.perftests.multiuser" /> + </test> +</configuration> diff --git a/api/current.txt b/api/current.txt index 85f238c1113d..cbe00c9a9e23 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1605,17 +1605,17 @@ package android { field public static final int holo_purple = 17170458; // 0x106001a field public static final int holo_red_dark = 17170455; // 0x1060017 field public static final int holo_red_light = 17170454; // 0x1060016 - field public static final int primary_text_dark = 17170433; // 0x1060001 - field public static final int primary_text_dark_nodisable = 17170434; // 0x1060002 - field public static final int primary_text_light = 17170435; // 0x1060003 - field public static final int primary_text_light_nodisable = 17170436; // 0x1060004 - field public static final int secondary_text_dark = 17170437; // 0x1060005 - field public static final int secondary_text_dark_nodisable = 17170438; // 0x1060006 - field public static final int secondary_text_light = 17170439; // 0x1060007 - field public static final int secondary_text_light_nodisable = 17170440; // 0x1060008 + field public static final deprecated int primary_text_dark = 17170433; // 0x1060001 + field public static final deprecated int primary_text_dark_nodisable = 17170434; // 0x1060002 + field public static final deprecated int primary_text_light = 17170435; // 0x1060003 + field public static final deprecated int primary_text_light_nodisable = 17170436; // 0x1060004 + field public static final deprecated int secondary_text_dark = 17170437; // 0x1060005 + field public static final deprecated int secondary_text_dark_nodisable = 17170438; // 0x1060006 + field public static final deprecated int secondary_text_light = 17170439; // 0x1060007 + field public static final deprecated int secondary_text_light_nodisable = 17170440; // 0x1060008 field public static final int tab_indicator_text = 17170441; // 0x1060009 - field public static final int tertiary_text_dark = 17170448; // 0x1060010 - field public static final int tertiary_text_light = 17170449; // 0x1060011 + field public static final deprecated int tertiary_text_dark = 17170448; // 0x1060010 + field public static final deprecated int tertiary_text_light = 17170449; // 0x1060011 field public static final int transparent = 17170445; // 0x106000d field public static final int white = 17170443; // 0x106000b field public static final int widget_edittext_dark = 17170442; // 0x106000a @@ -6115,8 +6115,12 @@ package android.app { method public android.view.WindowAnimationFrameStats getWindowAnimationFrameStats(); method public android.view.WindowContentFrameStats getWindowContentFrameStats(int); method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows(); + method public void grantRuntimePermission(java.lang.String, java.lang.String); + method public void grantRuntimePermissionAsUser(java.lang.String, java.lang.String, android.os.UserHandle); method public boolean injectInputEvent(android.view.InputEvent, boolean); method public boolean performGlobalAction(int); + method public void revokeRuntimePermission(java.lang.String, java.lang.String); + method public void revokeRuntimePermissionAsUser(java.lang.String, java.lang.String, android.os.UserHandle); method public void setOnAccessibilityEventListener(android.app.UiAutomation.OnAccessibilityEventListener); method public boolean setRotation(int); method public void setRunAsMonkey(boolean); @@ -6464,7 +6468,6 @@ package android.app.admin { method public java.lang.CharSequence getOrganizationName(android.content.ComponentName); method public java.util.List<android.telephony.data.ApnSetting> getOverrideApns(android.content.ComponentName); method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName); - method public java.lang.String getPasswordBlacklistName(android.content.ComponentName); method public long getPasswordExpiration(android.content.ComponentName); method public long getPasswordExpirationTimeout(android.content.ComponentName); method public int getPasswordHistoryLength(android.content.ComponentName); @@ -6501,7 +6504,7 @@ package android.app.admin { method public boolean installExistingPackage(android.content.ComponentName, java.lang.String); method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String); method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean); - method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean, boolean); + method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, int); method public boolean isActivePasswordSufficient(); method public boolean isAdminActive(android.content.ComponentName); method public boolean isAffiliatedUser(); @@ -6573,7 +6576,6 @@ package android.app.admin { method public void setOrganizationName(android.content.ComponentName, java.lang.CharSequence); method public void setOverrideApnsEnabled(android.content.ComponentName, boolean); method public java.lang.String[] setPackagesSuspended(android.content.ComponentName, java.lang.String[], boolean); - method public boolean setPasswordBlacklist(android.content.ComponentName, java.lang.String, java.util.List<java.lang.String>); method public void setPasswordExpirationTimeout(android.content.ComponentName, long); method public void setPasswordHistoryLength(android.content.ComponentName, int); method public void setPasswordMinimumLength(android.content.ComponentName, int); @@ -6685,6 +6687,8 @@ package android.app.admin { field public static final int ID_TYPE_IMEI = 4; // 0x4 field public static final int ID_TYPE_MEID = 8; // 0x8 field public static final int ID_TYPE_SERIAL = 2; // 0x2 + field public static final int INSTALLKEY_REQUEST_CREDENTIALS_ACCESS = 1; // 0x1 + field public static final int INSTALLKEY_SET_USER_SELECTABLE = 2; // 0x2 field public static final int KEYGUARD_DISABLE_BIOMETRICS = 416; // 0x1a0 field public static final int KEYGUARD_DISABLE_FACE = 128; // 0x80 field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff @@ -7213,6 +7217,7 @@ package android.app.slice { field public static final java.lang.String HINT_LIST_ITEM = "list_item"; field public static final java.lang.String HINT_NO_TINT = "no_tint"; field public static final java.lang.String HINT_PARTIAL = "partial"; + field public static final java.lang.String HINT_PERMISSION_REQUEST = "permission_request"; field public static final java.lang.String HINT_SEE_MORE = "see_more"; field public static final java.lang.String HINT_SELECTED = "selected"; field public static final java.lang.String HINT_SHORTCUT = "shortcut"; @@ -7281,11 +7286,14 @@ 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 int checkSlicePermission(android.net.Uri, int, int); method public java.util.List<android.net.Uri> getPinnedSlices(); 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 grantSlicePermission(java.lang.String, android.net.Uri); method public android.net.Uri mapIntentToUri(android.content.Intent); method public void pinSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>); + method public void revokeSlicePermission(java.lang.String, android.net.Uri); method public void unpinSlice(android.net.Uri); field public static final java.lang.String CATEGORY_SLICE = "android.app.slice.category.SLICE"; field public static final java.lang.String SLICE_METADATA_KEY = "android.metadata.SLICE_URI"; @@ -7299,6 +7307,7 @@ package android.app.slice { } public abstract class SliceProvider extends android.content.ContentProvider { + ctor public SliceProvider(java.lang.String...); ctor public SliceProvider(); method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]); method public final java.lang.String getType(android.net.Uri); @@ -10904,6 +10913,7 @@ package android.content.pm { method public java.util.List<android.content.pm.LauncherActivityInfo> getShortcutConfigActivityList(java.lang.String, android.os.UserHandle); method public android.graphics.drawable.Drawable getShortcutIconDrawable(android.content.pm.ShortcutInfo, int); method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle); + method public android.os.Bundle getSuspendedPackageLauncherExtras(java.lang.String, android.os.UserHandle); method public boolean hasShortcutHostPermission(); method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle); method public boolean isPackageEnabled(java.lang.String, android.os.UserHandle); @@ -10928,6 +10938,7 @@ package android.content.pm { method public abstract void onPackageRemoved(java.lang.String, android.os.UserHandle); method public abstract void onPackagesAvailable(java.lang.String[], android.os.UserHandle, boolean); method public void onPackagesSuspended(java.lang.String[], android.os.UserHandle); + method public void onPackagesSuspended(java.lang.String[], android.os.Bundle, android.os.UserHandle); method public abstract void onPackagesUnavailable(java.lang.String[], android.os.UserHandle, boolean); method public void onPackagesUnsuspended(java.lang.String[], android.os.UserHandle); method public void onShortcutsChanged(java.lang.String, java.util.List<android.content.pm.ShortcutInfo>, android.os.UserHandle); @@ -13639,22 +13650,22 @@ package android.graphics { method public int getAllocator(); method public boolean getConserveMemory(); method public android.graphics.Rect getCrop(); - method public boolean getDecodeAsAlphaMask(); - method public boolean getMutable(); method public android.graphics.ImageDecoder.OnPartialImageListener getOnPartialImageListener(); method public android.graphics.PostProcessor getPostProcessor(); - method public boolean getRequireUnpremultiplied(); - method public android.util.Size getSampledSize(int); - method public android.graphics.ImageDecoder setAllocator(int); - method public android.graphics.ImageDecoder setConserveMemory(boolean); - method public android.graphics.ImageDecoder setCrop(android.graphics.Rect); - method public android.graphics.ImageDecoder setDecodeAsAlphaMask(boolean); - method public android.graphics.ImageDecoder setMutable(boolean); - method public android.graphics.ImageDecoder setOnPartialImageListener(android.graphics.ImageDecoder.OnPartialImageListener); - method public android.graphics.ImageDecoder setPostProcessor(android.graphics.PostProcessor); - method public android.graphics.ImageDecoder setRequireUnpremultiplied(boolean); - method public android.graphics.ImageDecoder setResize(int, int); - method public android.graphics.ImageDecoder setResize(int); + method public boolean isDecodeAsAlphaMaskEnabled(); + method public boolean isMutableRequired(); + method public boolean isUnpremultipliedRequired(); + method public void setAllocator(int); + method public void setConserveMemory(boolean); + method public void setCrop(android.graphics.Rect); + method public void setDecodeAsAlphaMaskEnabled(boolean); + method public void setMutableRequired(boolean); + method public void setOnPartialImageListener(android.graphics.ImageDecoder.OnPartialImageListener); + method public void setPostProcessor(android.graphics.PostProcessor); + method public void setTargetColorSpace(android.graphics.ColorSpace); + method public void setTargetSampleSize(int); + method public void setTargetSize(int, int); + method public void setUnpremultipliedRequired(boolean); field public static final int ALLOCATOR_DEFAULT = 0; // 0x0 field public static final int ALLOCATOR_HARDWARE = 3; // 0x3 field public static final int ALLOCATOR_SHARED_MEMORY = 2; // 0x2 @@ -13670,6 +13681,7 @@ package android.graphics { } public static class ImageDecoder.ImageInfo { + method public android.graphics.ColorSpace getColorSpace(); method public java.lang.String getMimeType(); method public android.util.Size getSize(); method public boolean isAnimated(); @@ -21957,6 +21969,7 @@ package android.media { } public final class AudioDeviceInfo { + method public java.lang.String getAddress(); method public int[] getChannelCounts(); method public int[] getChannelIndexMasks(); method public int[] getChannelMasks(); @@ -23454,9 +23467,6 @@ package android.media { field public static final int HDCP_V2 = 3; // 0x3 field public static final int HDCP_V2_1 = 4; // 0x4 field public static final int HDCP_V2_2 = 5; // 0x5 - field public static final int HW_SECURE_ALL = 5; // 0x5 - field public static final int HW_SECURE_CRYPTO = 3; // 0x3 - field public static final int HW_SECURE_DECODE = 4; // 0x4 field public static final int KEY_TYPE_OFFLINE = 2; // 0x2 field public static final int KEY_TYPE_RELEASE = 3; // 0x3 field public static final int KEY_TYPE_STREAMING = 1; // 0x1 @@ -23465,9 +23475,12 @@ package android.media { field public static final java.lang.String PROPERTY_DEVICE_UNIQUE_ID = "deviceUniqueId"; field public static final java.lang.String PROPERTY_VENDOR = "vendor"; field public static final java.lang.String PROPERTY_VERSION = "version"; + field public static final int SECURITY_LEVEL_HW_SECURE_ALL = 5; // 0x5 + field public static final int SECURITY_LEVEL_HW_SECURE_CRYPTO = 3; // 0x3 + field public static final int SECURITY_LEVEL_HW_SECURE_DECODE = 4; // 0x4 + field public static final int SECURITY_LEVEL_SW_SECURE_CRYPTO = 1; // 0x1 + field public static final int SECURITY_LEVEL_SW_SECURE_DECODE = 2; // 0x2 field public static final int SECURITY_LEVEL_UNKNOWN = 0; // 0x0 - field public static final int SW_SECURE_CRYPTO = 1; // 0x1 - field public static final int SW_SECURE_DECODE = 2; // 0x2 } public final class MediaDrm.CryptoSession { @@ -24004,6 +24017,7 @@ package android.media { field public static final int MEDIA_INFO_BUFFERING_START = 701; // 0x2bd field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322 field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321 + field public static final int MEDIA_INFO_STARTED_AS_NEXT = 2; // 0x2 field public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; // 0x386 field public static final int MEDIA_INFO_UNKNOWN = 1; // 0x1 field public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901; // 0x385 @@ -24447,6 +24461,7 @@ package android.media { } public final class MicrophoneInfo { + method public java.lang.String getAddress(); method public java.util.List<android.util.Pair<java.lang.Integer, java.lang.Integer>> getChannelMapping(); method public java.lang.String getDescription(); method public int getDirectionality(); @@ -25758,6 +25773,7 @@ package android.media.session { public final class MediaSession { ctor public MediaSession(android.content.Context, java.lang.String); method public android.media.session.MediaController getController(); + method public android.media.session.MediaSessionManager.RemoteUserInfo getCurrentControllerInfo(); method public android.media.session.MediaSession.Token getSessionToken(); method public boolean isActive(); method public void release(); @@ -25824,6 +25840,7 @@ package android.media.session { method public void addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName); method public void addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName, android.os.Handler); method public java.util.List<android.media.session.MediaController> getActiveSessions(android.content.ComponentName); + method public boolean isTrustedForMediaControl(android.media.session.MediaSessionManager.RemoteUserInfo); method public void removeOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener); } @@ -25831,6 +25848,13 @@ package android.media.session { method public abstract void onActiveSessionsChanged(java.util.List<android.media.session.MediaController>); } + public static final class MediaSessionManager.RemoteUserInfo { + ctor public MediaSessionManager.RemoteUserInfo(java.lang.String, int, int); + method public java.lang.String getPackageName(); + method public int getPid(); + method public int getUid(); + } + public final class PlaybackState implements android.os.Parcelable { method public int describeContents(); method public long getActions(); @@ -26994,8 +27018,8 @@ package android.net { public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable { method public void close() throws java.io.IOException; + method public java.io.FileDescriptor getFileDescriptor(); method public int getPort(); - method public java.io.FileDescriptor getSocket(); } public final class IpSecTransform implements java.lang.AutoCloseable { @@ -27124,6 +27148,7 @@ package android.net { method public void bindSocket(java.net.Socket) throws java.io.IOException; method public void bindSocket(java.io.FileDescriptor) throws java.io.IOException; method public int describeContents(); + method public static android.net.Network fromNetworkHandle(long); method public java.net.InetAddress[] getAllByName(java.lang.String) throws java.net.UnknownHostException; method public java.net.InetAddress getByName(java.lang.String) throws java.net.UnknownHostException; method public long getNetworkHandle(); @@ -27223,6 +27248,8 @@ package android.net { public class NetworkRequest implements android.os.Parcelable { method public int describeContents(); + method public boolean hasCapability(int); + method public boolean hasTransport(int); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.NetworkRequest> CREATOR; } @@ -38545,6 +38572,7 @@ package android.security.keystore { method public boolean isRandomizedEncryptionRequired(); method public boolean isStrongBoxBacked(); method public boolean isTrustedUserPresenceRequired(); + method public boolean isUnlockedDeviceRequired(); method public boolean isUserAuthenticationRequired(); method public boolean isUserAuthenticationValidWhileOnBody(); method public boolean isUserConfirmationRequired(); @@ -38572,6 +38600,7 @@ package android.security.keystore { method public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean); method public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...); method public android.security.keystore.KeyGenParameterSpec.Builder setTrustedUserPresenceRequired(boolean); + method public android.security.keystore.KeyGenParameterSpec.Builder setUnlockedDeviceRequired(boolean); method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationRequired(boolean); method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidWhileOnBody(boolean); method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(int); @@ -38664,6 +38693,7 @@ package android.security.keystore { method public boolean isInvalidatedByBiometricEnrollment(); method public boolean isRandomizedEncryptionRequired(); method public boolean isTrustedUserPresenceRequired(); + method public boolean isUnlockedDeviceRequired(); method public boolean isUserAuthenticationRequired(); method public boolean isUserAuthenticationValidWhileOnBody(); method public boolean isUserConfirmationRequired(); @@ -38683,6 +38713,7 @@ package android.security.keystore { method public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean); method public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...); method public android.security.keystore.KeyProtection.Builder setTrustedUserPresenceRequired(boolean); + method public android.security.keystore.KeyProtection.Builder setUnlockedDeviceRequired(boolean); method public android.security.keystore.KeyProtection.Builder setUserAuthenticationRequired(boolean); method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidWhileOnBody(boolean); method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(int); @@ -39168,6 +39199,7 @@ package android.service.media { ctor public MediaBrowserService(); method public void dump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]); method public final android.os.Bundle getBrowserRootHints(); + method public final android.media.session.MediaSessionManager.RemoteUserInfo getCurrentBrowserInfo(); method public android.media.session.MediaSession.Token getSessionToken(); method public void notifyChildrenChanged(java.lang.String); method public void notifyChildrenChanged(java.lang.String, android.os.Bundle); @@ -50402,9 +50434,9 @@ package android.view.textclassifier { ctor public TextClassification.Options(); method public int describeContents(); method public android.os.LocaleList getDefaultLocales(); - method public java.util.Calendar getReferenceTime(); + method public java.time.ZonedDateTime getReferenceTime(); method public android.view.textclassifier.TextClassification.Options setDefaultLocales(android.os.LocaleList); - method public android.view.textclassifier.TextClassification.Options setReferenceTime(java.util.Calendar); + method public android.view.textclassifier.TextClassification.Options setReferenceTime(java.time.ZonedDateTime); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassification.Options> CREATOR; } diff --git a/api/removed.txt b/api/removed.txt index 9fe25c90872b..2d76c5a575a0 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -183,7 +183,15 @@ package android.graphics { public final class ImageDecoder implements java.lang.AutoCloseable { method public deprecated boolean getAsAlphaMask(); + method public deprecated boolean getDecodeAsAlphaMask(); + method public deprecated boolean getMutable(); + method public deprecated boolean getRequireUnpremultiplied(); method public deprecated android.graphics.ImageDecoder setAsAlphaMask(boolean); + method public deprecated android.graphics.ImageDecoder setDecodeAsAlphaMask(boolean); + method public deprecated android.graphics.ImageDecoder setMutable(boolean); + method public deprecated android.graphics.ImageDecoder setRequireUnpremultiplied(boolean); + method public deprecated android.graphics.ImageDecoder setResize(int, int); + method public deprecated android.graphics.ImageDecoder setResize(int); field public static final deprecated int ERROR_SOURCE_ERROR = 3; // 0x3 field public static final deprecated int ERROR_SOURCE_EXCEPTION = 1; // 0x1 field public static final deprecated int ERROR_SOURCE_INCOMPLETE = 2; // 0x2 diff --git a/api/system-current.txt b/api/system-current.txt index 5568dbad0e8c..165c6ae1e628 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -57,6 +57,7 @@ package android { field public static final java.lang.String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"; field public static final java.lang.String CONNECTIVITY_INTERNAL = "android.permission.CONNECTIVITY_INTERNAL"; field public static final java.lang.String CONNECTIVITY_USE_RESTRICTED_NETWORKS = "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"; + field public static final java.lang.String CONTROL_DISPLAY_SATURATION = "android.permission.CONTROL_DISPLAY_SATURATION"; field public static final java.lang.String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE"; field public static final java.lang.String CONTROL_LOCATION_UPDATES = "android.permission.CONTROL_LOCATION_UPDATES"; field public static final java.lang.String CONTROL_VPN = "android.permission.CONTROL_VPN"; @@ -1252,6 +1253,7 @@ package android.hardware.display { method public android.hardware.display.BrightnessConfiguration getDefaultBrightnessConfiguration(); method public android.graphics.Point getStableDisplaySize(); method public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration); + method public void setSaturationLevel(float); } } @@ -2101,6 +2103,7 @@ package android.hardware.radio { method public java.lang.String getVersion(); method public boolean isBackgroundScanningSupported(); method public boolean isCaptureSupported(); + method public boolean isInitializationRequired(); method public boolean isProgramIdentifierSupported(int); method public boolean isProgramTypeSupported(int); method public void writeToParcel(android.os.Parcel, int); @@ -3102,6 +3105,10 @@ package android.net { field public static final int ERROR_INVALID_NETWORK = 1; // 0x1 } + public final class NetworkCapabilities implements android.os.Parcelable { + field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16 + } + public class NetworkKey implements android.os.Parcelable { ctor public NetworkKey(android.net.WifiKey); method public int describeContents(); @@ -5549,6 +5556,7 @@ package android.telephony.euicc { package android.telephony.ims { public final class ImsCallForwardInfo implements android.os.Parcelable { + ctor public ImsCallForwardInfo(int, int, int, int, java.lang.String, int); method public int describeContents(); method public int getCondition(); method public java.lang.String getNumber(); @@ -5863,7 +5871,7 @@ package android.telephony.ims { } public final class ImsSsData implements android.os.Parcelable { - ctor public ImsSsData(); + ctor public ImsSsData(int, int, int, int, int); method public int describeContents(); method public boolean isTypeBarring(); method public boolean isTypeCf(); @@ -5914,7 +5922,7 @@ package android.telephony.ims { } public final class ImsSsInfo implements android.os.Parcelable { - ctor public ImsSsInfo(); + ctor public ImsSsInfo(int, java.lang.String); method public int describeContents(); method public java.lang.String getIcbNum(); method public int getStatus(); diff --git a/api/test-current.txt b/api/test-current.txt index d32372517431..b520dfb441d0 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1,8 +1,10 @@ package android { public static final class Manifest.permission { + field public static final java.lang.String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING"; field public static final java.lang.String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE"; field public static final java.lang.String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"; + field public static final java.lang.String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS"; } } @@ -127,8 +129,8 @@ package android.app { public final class UiAutomation { method public void destroy(); method public android.os.ParcelFileDescriptor[] executeShellCommandRw(java.lang.String); - method public boolean grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); - method public boolean revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); + method public deprecated boolean grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); + method public deprecated boolean revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); } public class UiModeManager { diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp index 2b85ec08f9a6..c6e561f84052 100644 --- a/cmds/incidentd/src/FdBuffer.cpp +++ b/cmds/incidentd/src/FdBuffer.cpp @@ -34,11 +34,11 @@ FdBuffer::FdBuffer() FdBuffer::~FdBuffer() {} -status_t FdBuffer::read(unique_fd* fd, int64_t timeout) { - struct pollfd pfds = {.fd = fd->get(), .events = POLLIN}; +status_t FdBuffer::read(int fd, int64_t timeout) { + struct pollfd pfds = {.fd = fd, .events = POLLIN}; mStartTime = uptimeMillis(); - fcntl(fd->get(), F_SETFL, fcntl(fd->get(), F_GETFL, 0) | O_NONBLOCK); + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); while (true) { if (mBuffer.size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) { @@ -67,16 +67,16 @@ status_t FdBuffer::read(unique_fd* fd, int64_t timeout) { VLOG("return event has error %s", strerror(errno)); return errno != 0 ? -errno : UNKNOWN_ERROR; } else { - ssize_t amt = ::read(fd->get(), mBuffer.writeBuffer(), mBuffer.currentToWrite()); + ssize_t amt = ::read(fd, mBuffer.writeBuffer(), mBuffer.currentToWrite()); if (amt < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { continue; } else { - VLOG("Fail to read %d: %s", fd->get(), strerror(errno)); + VLOG("Fail to read %d: %s", fd, strerror(errno)); return -errno; } } else if (amt == 0) { - VLOG("Reached EOF of fd=%d", fd->get()); + VLOG("Reached EOF of fd=%d", fd); break; } mBuffer.wp()->move(amt); @@ -87,7 +87,7 @@ status_t FdBuffer::read(unique_fd* fd, int64_t timeout) { return NO_ERROR; } -status_t FdBuffer::readFully(unique_fd* fd) { +status_t FdBuffer::readFully(int fd) { mStartTime = uptimeMillis(); while (true) { @@ -99,10 +99,10 @@ status_t FdBuffer::readFully(unique_fd* fd) { } if (mBuffer.writeBuffer() == NULL) return NO_MEMORY; - ssize_t amt = TEMP_FAILURE_RETRY( - ::read(fd->get(), mBuffer.writeBuffer(), mBuffer.currentToWrite())); + ssize_t amt = + TEMP_FAILURE_RETRY(::read(fd, mBuffer.writeBuffer(), mBuffer.currentToWrite())); if (amt < 0) { - VLOG("Fail to read %d: %s", fd->get(), strerror(errno)); + VLOG("Fail to read %d: %s", fd, strerror(errno)); return -errno; } else if (amt == 0) { VLOG("Done reading %zu bytes", mBuffer.size()); @@ -116,20 +116,20 @@ status_t FdBuffer::readFully(unique_fd* fd) { return NO_ERROR; } -status_t FdBuffer::readProcessedDataInStream(unique_fd* fd, unique_fd* toFd, unique_fd* fromFd, +status_t FdBuffer::readProcessedDataInStream(int fd, unique_fd toFd, unique_fd fromFd, int64_t timeoutMs, const bool isSysfs) { struct pollfd pfds[] = { - {.fd = fd->get(), .events = POLLIN}, - {.fd = toFd->get(), .events = POLLOUT}, - {.fd = fromFd->get(), .events = POLLIN}, + {.fd = fd, .events = POLLIN}, + {.fd = toFd.get(), .events = POLLOUT}, + {.fd = fromFd.get(), .events = POLLIN}, }; mStartTime = uptimeMillis(); // mark all fds non blocking - fcntl(fd->get(), F_SETFL, fcntl(fd->get(), F_GETFL, 0) | O_NONBLOCK); - fcntl(toFd->get(), F_SETFL, fcntl(toFd->get(), F_GETFL, 0) | O_NONBLOCK); - fcntl(fromFd->get(), F_SETFL, fcntl(fromFd->get(), F_GETFL, 0) | O_NONBLOCK); + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); + fcntl(toFd.get(), F_SETFL, fcntl(toFd.get(), F_GETFL, 0) | O_NONBLOCK); + fcntl(fromFd.get(), F_SETFL, fcntl(fromFd.get(), F_GETFL, 0) | O_NONBLOCK); // A circular buffer holds data read from fd and writes to parsing process uint8_t cirBuf[BUFFER_SIZE]; @@ -166,10 +166,10 @@ status_t FdBuffer::readProcessedDataInStream(unique_fd* fd, unique_fd* toFd, uni for (int i = 0; i < 3; ++i) { if ((pfds[i].revents & POLLERR) != 0) { if (i == 0 && isSysfs) { - VLOG("fd %d is sysfs, ignore its POLLERR return value", fd->get()); + VLOG("fd %d is sysfs, ignore its POLLERR return value", fd); continue; } - VLOG("fd[%d]=%d returns error events: %s", i, fd->get(), strerror(errno)); + VLOG("fd[%d]=%d returns error events: %s", i, fd, strerror(errno)); return errno != 0 ? -errno : UNKNOWN_ERROR; } } @@ -178,17 +178,17 @@ status_t FdBuffer::readProcessedDataInStream(unique_fd* fd, unique_fd* toFd, uni if (cirSize != BUFFER_SIZE && pfds[0].fd != -1) { ssize_t amt; if (rpos >= wpos) { - amt = ::read(fd->get(), cirBuf + rpos, BUFFER_SIZE - rpos); + amt = ::read(fd, cirBuf + rpos, BUFFER_SIZE - rpos); } else { - amt = ::read(fd->get(), cirBuf + rpos, wpos - rpos); + amt = ::read(fd, cirBuf + rpos, wpos - rpos); } if (amt < 0) { if (!(errno == EAGAIN || errno == EWOULDBLOCK)) { - VLOG("Fail to read fd %d: %s", fd->get(), strerror(errno)); + VLOG("Fail to read fd %d: %s", fd, strerror(errno)); return -errno; } // otherwise just continue } else if (amt == 0) { - VLOG("Reached EOF of input file %d", fd->get()); + VLOG("Reached EOF of input file %d", fd); pfds[0].fd = -1; // reach EOF so don't have to poll pfds[0]. } else { rpos += amt; @@ -200,13 +200,13 @@ status_t FdBuffer::readProcessedDataInStream(unique_fd* fd, unique_fd* toFd, uni if (cirSize > 0 && pfds[1].fd != -1) { ssize_t amt; if (rpos > wpos) { - amt = ::write(toFd->get(), cirBuf + wpos, rpos - wpos); + amt = ::write(toFd.get(), cirBuf + wpos, rpos - wpos); } else { - amt = ::write(toFd->get(), cirBuf + wpos, BUFFER_SIZE - wpos); + amt = ::write(toFd.get(), cirBuf + wpos, BUFFER_SIZE - wpos); } if (amt < 0) { if (!(errno == EAGAIN || errno == EWOULDBLOCK)) { - VLOG("Fail to write toFd %d: %s", toFd->get(), strerror(errno)); + VLOG("Fail to write toFd.get() %d: %s", toFd.get(), strerror(errno)); return -errno; } // otherwise just continue } else { @@ -217,8 +217,8 @@ status_t FdBuffer::readProcessedDataInStream(unique_fd* fd, unique_fd* toFd, uni // if buffer is empty and fd is closed, close write fd. if (cirSize == 0 && pfds[0].fd == -1 && pfds[1].fd != -1) { - VLOG("Close write pipe %d", toFd->get()); - toFd->reset(); + VLOG("Close write pipe %d", toFd.get()); + toFd.reset(); pfds[1].fd = -1; } @@ -231,14 +231,14 @@ status_t FdBuffer::readProcessedDataInStream(unique_fd* fd, unique_fd* toFd, uni } // read from parsing process - ssize_t amt = ::read(fromFd->get(), mBuffer.writeBuffer(), mBuffer.currentToWrite()); + ssize_t amt = ::read(fromFd.get(), mBuffer.writeBuffer(), mBuffer.currentToWrite()); if (amt < 0) { if (!(errno == EAGAIN || errno == EWOULDBLOCK)) { - VLOG("Fail to read fromFd %d: %s", fromFd->get(), strerror(errno)); + VLOG("Fail to read fromFd.get() %d: %s", fromFd.get(), strerror(errno)); return -errno; } // otherwise just continue } else if (amt == 0) { - VLOG("Reached EOF of fromFd %d", fromFd->get()); + VLOG("Reached EOF of fromFd.get() %d", fromFd.get()); break; } else { mBuffer.wp()->move(amt); diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h index db3a74b78178..f467da866024 100644 --- a/cmds/incidentd/src/FdBuffer.h +++ b/cmds/incidentd/src/FdBuffer.h @@ -40,13 +40,13 @@ public: * Returns NO_ERROR if there were no errors or if we timed out. * Will mark the file O_NONBLOCK. */ - status_t read(unique_fd* fd, int64_t timeoutMs); + status_t read(int fd, int64_t timeoutMs); /** * Read the data until we hit eof. * Returns NO_ERROR if there were no errors. */ - status_t readFully(unique_fd* fd); + status_t readFully(int fd); /** * Read processed results by streaming data to a parsing process, e.g. incident helper. @@ -58,8 +58,8 @@ public: * * Poll will return POLLERR if fd is from sysfs, handle this edge case. */ - status_t readProcessedDataInStream(unique_fd* fd, unique_fd* toFd, unique_fd* fromFd, - int64_t timeoutMs, const bool isSysfs = false); + status_t readProcessedDataInStream(int fd, unique_fd toFd, unique_fd fromFd, int64_t timeoutMs, + const bool isSysfs = false); /** * Whether we timed out. diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp index aeccefdd15c0..d02b4dd99067 100644 --- a/cmds/incidentd/src/IncidentService.cpp +++ b/cmds/incidentd/src/IncidentService.cpp @@ -352,8 +352,7 @@ status_t IncidentService::cmd_privacy(FILE* in, FILE* out, FILE* err, Vector<Str printPrivacy(p, out, String8("")); } else if (opt == "parse") { FdBuffer buf; - unique_fd infd(fileno(in)); - status_t error = buf.read(&infd, 60000); + status_t error = buf.read(fileno(in), 60000); if (error != NO_ERROR) { fprintf(err, "Error reading from stdin\n"); return error; diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp index 3b57d34c1ea3..3f693fad4d46 100644 --- a/cmds/incidentd/src/Section.cpp +++ b/cmds/incidentd/src/Section.cpp @@ -264,8 +264,9 @@ status_t FileSection::Execute(ReportRequestSet* requests) const { } // parent process - status_t readStatus = buffer.readProcessedDataInStream( - &fd, &p2cPipe.writeFd(), &c2pPipe.readFd(), this->timeoutMs, mIsSysfs); + status_t readStatus = buffer.readProcessedDataInStream(fd.get(), std::move(p2cPipe.writeFd()), + std::move(c2pPipe.readFd()), + this->timeoutMs, mIsSysfs); if (readStatus != NO_ERROR || buffer.timedOut()) { ALOGW("FileSection '%s' failed to read data from incident helper: %s, timedout: %s", @@ -319,8 +320,10 @@ status_t GZipSection::Execute(ReportRequestSet* requests) const { index++; // look at the next file. } VLOG("GZipSection is using file %s, fd=%d", mFilenames[index], fd.get()); - if (fd.get() == -1) return -1; - + if (fd.get() == -1) { + ALOGW("GZipSection %s can't open all the files", this->name.string()); + return NO_ERROR; // e.g. LAST_KMSG will reach here in user build. + } FdBuffer buffer; Fpipe p2cPipe; Fpipe c2pPipe; @@ -354,9 +357,9 @@ status_t GZipSection::Execute(ReportRequestSet* requests) const { VLOG("GZipSection '%s' editPos=%zd, dataBeginAt=%zd", this->name.string(), editPos, dataBeginAt); - status_t readStatus = - buffer.readProcessedDataInStream(&fd, &p2cPipe.writeFd(), &c2pPipe.readFd(), - this->timeoutMs, isSysfs(mFilenames[index])); + status_t readStatus = buffer.readProcessedDataInStream( + fd.get(), std::move(p2cPipe.writeFd()), std::move(c2pPipe.readFd()), this->timeoutMs, + isSysfs(mFilenames[index])); if (readStatus != NO_ERROR || buffer.timedOut()) { ALOGW("GZipSection '%s' failed to read data from gzip: %s, timedout: %s", @@ -466,7 +469,7 @@ status_t WorkerThreadSection::Execute(ReportRequestSet* requests) const { pthread_attr_destroy(&attr); // Loop reading until either the timeout or the worker side is done (i.e. eof). - err = buffer.read(&data->pipe.readFd(), this->timeoutMs); + err = buffer.read(data->pipe.readFd().get(), this->timeoutMs); if (err != NO_ERROR) { // TODO: Log this error into the incident report. ALOGW("WorkerThreadSection '%s' reader failed with error '%s'", this->name.string(), @@ -573,7 +576,7 @@ status_t CommandSection::Execute(ReportRequestSet* requests) const { } cmdPipe.writeFd().reset(); - status_t readStatus = buffer.read(&ihPipe.readFd(), this->timeoutMs); + status_t readStatus = buffer.read(ihPipe.readFd().get(), this->timeoutMs); if (readStatus != NO_ERROR || buffer.timedOut()) { ALOGW("CommandSection '%s' failed to read data from incident helper: %s, timedout: %s", this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false"); @@ -892,7 +895,7 @@ status_t TombstoneSection::BlockingCall(int pipeWriteFd) const { // Parent process. // Read from the pipe concurrently to avoid blocking the child. FdBuffer buffer; - err = buffer.readFully(&dumpPipe.readFd()); + err = buffer.readFully(dumpPipe.readFd().get()); if (err != NO_ERROR) { ALOGW("TombstoneSection '%s' failed to read stack dump: %d", this->name.string(), err); dumpPipe.readFd().reset(); diff --git a/cmds/incidentd/tests/FdBuffer_test.cpp b/cmds/incidentd/tests/FdBuffer_test.cpp index bf770173793f..7a05d7e071d0 100644 --- a/cmds/incidentd/tests/FdBuffer_test.cpp +++ b/cmds/incidentd/tests/FdBuffer_test.cpp @@ -37,7 +37,6 @@ class FdBufferTest : public Test { public: virtual void SetUp() override { ASSERT_NE(tf.fd, -1); - tffd.reset(tf.fd); ASSERT_NE(p2cPipe.init(), -1); ASSERT_NE(c2pPipe.init(), -1); } @@ -57,13 +56,13 @@ public: EXPECT_EQ(expected[i], '\0'); } - bool DoDataStream(unique_fd* rFd, unique_fd* wFd) { + bool DoDataStream(const unique_fd& rFd, const unique_fd& wFd) { char buf[BUFFER_SIZE]; ssize_t nRead; - while ((nRead = read(rFd->get(), buf, BUFFER_SIZE)) > 0) { + while ((nRead = read(rFd.get(), buf, BUFFER_SIZE)) > 0) { ssize_t nWritten = 0; while (nWritten < nRead) { - ssize_t amt = write(wFd->get(), buf + nWritten, nRead - nWritten); + ssize_t amt = write(wFd.get(), buf + nWritten, nRead - nWritten); if (amt < 0) { return false; } @@ -76,7 +75,6 @@ public: protected: FdBuffer buffer; TemporaryFile tf; - unique_fd tffd; Fpipe p2cPipe; Fpipe c2pPipe; @@ -87,7 +85,7 @@ protected: TEST_F(FdBufferTest, ReadAndWrite) { std::string testdata = "FdBuffer test string"; ASSERT_TRUE(WriteStringToFile(testdata, tf.path)); - ASSERT_EQ(NO_ERROR, buffer.read(&tffd, READ_TIMEOUT)); + ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, READ_TIMEOUT)); AssertBufferReadSuccessful(testdata.size()); AssertBufferContent(testdata.c_str()); } @@ -100,7 +98,7 @@ TEST_F(FdBufferTest, IterateEmpty) { TEST_F(FdBufferTest, ReadAndIterate) { std::string testdata = "FdBuffer test string"; ASSERT_TRUE(WriteStringToFile(testdata, tf.path)); - ASSERT_EQ(NO_ERROR, buffer.read(&tffd, READ_TIMEOUT)); + ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, READ_TIMEOUT)); int i = 0; EncodedBuffer::iterator it = buffer.data(); @@ -128,7 +126,7 @@ TEST_F(FdBufferTest, ReadTimeout) { } else { c2pPipe.writeFd().reset(); - status_t status = buffer.read(&c2pPipe.readFd(), QUICK_TIMEOUT_MS); + status_t status = buffer.read(c2pPipe.readFd().get(), QUICK_TIMEOUT_MS); ASSERT_EQ(NO_ERROR, status); EXPECT_TRUE(buffer.timedOut()); @@ -148,7 +146,7 @@ TEST_F(FdBufferTest, ReadInStreamAndWrite) { p2cPipe.writeFd().reset(); c2pPipe.readFd().reset(); ASSERT_TRUE(WriteStringToFd(HEAD, c2pPipe.writeFd())); - ASSERT_TRUE(DoDataStream(&p2cPipe.readFd(), &c2pPipe.writeFd())); + ASSERT_TRUE(DoDataStream(p2cPipe.readFd(), c2pPipe.writeFd())); p2cPipe.readFd().reset(); c2pPipe.writeFd().reset(); // Must exit here otherwise the child process will continue executing the test binary. @@ -157,8 +155,9 @@ TEST_F(FdBufferTest, ReadInStreamAndWrite) { p2cPipe.readFd().reset(); c2pPipe.writeFd().reset(); - ASSERT_EQ(NO_ERROR, buffer.readProcessedDataInStream(&tffd, &p2cPipe.writeFd(), - &c2pPipe.readFd(), READ_TIMEOUT)); + ASSERT_EQ(NO_ERROR, + buffer.readProcessedDataInStream(tf.fd, std::move(p2cPipe.writeFd()), + std::move(c2pPipe.readFd()), READ_TIMEOUT)); AssertBufferReadSuccessful(HEAD.size() + testdata.size()); AssertBufferContent(expected.c_str()); wait(&pid); @@ -189,8 +188,9 @@ TEST_F(FdBufferTest, ReadInStreamAndWriteAllAtOnce) { p2cPipe.readFd().reset(); c2pPipe.writeFd().reset(); - ASSERT_EQ(NO_ERROR, buffer.readProcessedDataInStream(&tffd, &p2cPipe.writeFd(), - &c2pPipe.readFd(), READ_TIMEOUT)); + ASSERT_EQ(NO_ERROR, + buffer.readProcessedDataInStream(tf.fd, std::move(p2cPipe.writeFd()), + std::move(c2pPipe.readFd()), READ_TIMEOUT)); AssertBufferReadSuccessful(HEAD.size() + testdata.size()); AssertBufferContent(expected.c_str()); wait(&pid); @@ -206,7 +206,7 @@ TEST_F(FdBufferTest, ReadInStreamEmpty) { if (pid == 0) { p2cPipe.writeFd().reset(); c2pPipe.readFd().reset(); - ASSERT_TRUE(DoDataStream(&p2cPipe.readFd(), &c2pPipe.writeFd())); + ASSERT_TRUE(DoDataStream(p2cPipe.readFd(), c2pPipe.writeFd())); p2cPipe.readFd().reset(); c2pPipe.writeFd().reset(); _exit(EXIT_SUCCESS); @@ -214,8 +214,9 @@ TEST_F(FdBufferTest, ReadInStreamEmpty) { p2cPipe.readFd().reset(); c2pPipe.writeFd().reset(); - ASSERT_EQ(NO_ERROR, buffer.readProcessedDataInStream(&tffd, &p2cPipe.writeFd(), - &c2pPipe.readFd(), READ_TIMEOUT)); + ASSERT_EQ(NO_ERROR, + buffer.readProcessedDataInStream(tf.fd, std::move(p2cPipe.writeFd()), + std::move(c2pPipe.readFd()), READ_TIMEOUT)); AssertBufferReadSuccessful(0); AssertBufferContent(""); wait(&pid); @@ -233,7 +234,7 @@ TEST_F(FdBufferTest, ReadInStreamMoreThan4MB) { if (pid == 0) { p2cPipe.writeFd().reset(); c2pPipe.readFd().reset(); - ASSERT_TRUE(DoDataStream(&p2cPipe.readFd(), &c2pPipe.writeFd())); + ASSERT_TRUE(DoDataStream(p2cPipe.readFd(), c2pPipe.writeFd())); p2cPipe.readFd().reset(); c2pPipe.writeFd().reset(); _exit(EXIT_SUCCESS); @@ -241,8 +242,9 @@ TEST_F(FdBufferTest, ReadInStreamMoreThan4MB) { p2cPipe.readFd().reset(); c2pPipe.writeFd().reset(); - ASSERT_EQ(NO_ERROR, buffer.readProcessedDataInStream(&fd, &p2cPipe.writeFd(), - &c2pPipe.readFd(), READ_TIMEOUT)); + ASSERT_EQ(NO_ERROR, + buffer.readProcessedDataInStream(fd, std::move(p2cPipe.writeFd()), + std::move(c2pPipe.readFd()), READ_TIMEOUT)); EXPECT_EQ(buffer.size(), fourMB); EXPECT_FALSE(buffer.timedOut()); EXPECT_TRUE(buffer.truncated()); @@ -278,8 +280,9 @@ TEST_F(FdBufferTest, ReadInStreamTimeOut) { p2cPipe.readFd().reset(); c2pPipe.writeFd().reset(); - ASSERT_EQ(NO_ERROR, buffer.readProcessedDataInStream(&tffd, &p2cPipe.writeFd(), - &c2pPipe.readFd(), QUICK_TIMEOUT_MS)); + ASSERT_EQ(NO_ERROR, + buffer.readProcessedDataInStream(tf.fd, std::move(p2cPipe.writeFd()), + std::move(c2pPipe.readFd()), QUICK_TIMEOUT_MS)); EXPECT_TRUE(buffer.timedOut()); kill(pid, SIGKILL); // reap the child process } diff --git a/cmds/incidentd/tests/PrivacyBuffer_test.cpp b/cmds/incidentd/tests/PrivacyBuffer_test.cpp index 5edc0c79785b..10c2981d6403 100644 --- a/cmds/incidentd/tests/PrivacyBuffer_test.cpp +++ b/cmds/incidentd/tests/PrivacyBuffer_test.cpp @@ -38,7 +38,7 @@ const uint8_t STRING_TYPE = 9; const uint8_t MESSAGE_TYPE = 11; const string STRING_FIELD_0 = "\x02\viamtestdata"; const string VARINT_FIELD_1 = "\x08\x96\x01"; // 150 -const string STRING_FIELD_2 = "\x12\vwhatthefuck"; +const string STRING_FIELD_2 = "\x12\vandroidwins"; const string FIX64_FIELD_3 = "\x19\xff\xff\xff\xff\xff\xff\xff\xff"; // -1 const string FIX32_FIELD_4 = "\x25\xff\xff\xff\xff"; // -1 const string MESSAGE_FIELD_5 = "\x2a\x10" + VARINT_FIELD_1 + STRING_FIELD_2; @@ -58,8 +58,7 @@ public: void writeToFdBuffer(string str) { ASSERT_TRUE(WriteStringToFile(str, tf.path)); - unique_fd tffd(tf.fd); - ASSERT_EQ(NO_ERROR, buffer.read(&tffd, 10000)); + ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, 10000)); ASSERT_EQ(str.size(), buffer.size()); } @@ -274,4 +273,4 @@ TEST_F(PrivacyBufferTest, AutoMessage) { autoMsg->children = list; string expected = "\x2a\xd" + STRING_FIELD_2; assertStripByFields(DEST_AUTOMATIC, expected, 1, autoMsg); -}
\ No newline at end of file +} diff --git a/cmds/incidentd/tests/Section_test.cpp b/cmds/incidentd/tests/Section_test.cpp index f93839b62dcf..2f6698bc3116 100644 --- a/cmds/incidentd/tests/Section_test.cpp +++ b/cmds/incidentd/tests/Section_test.cpp @@ -19,6 +19,7 @@ #include <android-base/file.h> #include <android-base/test_utils.h> #include <android/os/IncidentReportArgs.h> +#include <android/util/protobuf.h> #include <frameworks/base/libs/incident/proto/android/os/header.pb.h> #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -31,12 +32,13 @@ const int REVERSE_PARSER = 1; const int QUICK_TIMEOUT_MS = 100; const string VARINT_FIELD_1 = "\x08\x96\x01"; // 150 -const string STRING_FIELD_2 = "\x12\vwhatthefuck"; +const string STRING_FIELD_2 = "\x12\vandroidwins"; const string FIX64_FIELD_3 = "\x19\xff\xff\xff\xff\xff\xff\xff\xff"; // -1 using namespace android::base; using namespace android::binder; using namespace android::os; +using namespace android::util; using namespace std; using ::testing::StrEq; using ::testing::Test; @@ -154,17 +156,26 @@ TEST_F(SectionTest, GZipSection) { requests.setMainDest(android::os::DEST_LOCAL); ASSERT_EQ(NO_ERROR, gs.Execute(&requests)); - std::string expect, gzFile, actual; + std::string expected, gzFile, actual; ASSERT_TRUE(ReadFileToString(testGzFile, &gzFile)); ASSERT_TRUE(ReadFileToString(tf.path, &actual)); - expect = "\x2\xC6\x6\n\"" + testFile + "\x12\x9F\x6" + gzFile; - EXPECT_THAT(actual, StrEq(expect)); + // generates the expected protobuf result. + size_t fileLen = testFile.size(); + size_t totalLen = 1 + get_varint_size(fileLen) + fileLen + 3 + gzFile.size(); + uint8_t header[20]; + header[0] = '\x2'; // header 0 << 3 + 2 + uint8_t* ptr = write_raw_varint(header + 1, totalLen); + *ptr = '\n'; // header 1 << 3 + 2 + ptr = write_raw_varint(++ptr, fileLen); + expected.assign((const char*)header, ptr - header); + expected += testFile + "\x12\x9F\x6" + gzFile; + EXPECT_THAT(actual, StrEq(expected)); } TEST_F(SectionTest, GZipSectionNoFileFound) { GZipSection gs(NOOP_PARSER, "/tmp/nonexist1", "/tmp/nonexist2", NULL); requests.setMainFd(STDOUT_FILENO); - ASSERT_EQ(-1, gs.Execute(&requests)); + ASSERT_EQ(NO_ERROR, gs.Execute(&requests)); } TEST_F(SectionTest, CommandSectionConstructor) { diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java index 9df229cbf490..66761960e0ae 100644 --- a/cmds/media/src/com/android/commands/media/Media.java +++ b/cmds/media/src/com/android/commands/media/Media.java @@ -264,13 +264,13 @@ public class Media extends BaseCommand { } else if ("q".equals(line) || "quit".equals(line)) { break; } else if ("play".equals(line)) { - mController.play(); + mController.play(""); } else if ("pause".equals(line)) { - mController.pause(); + mController.pause(""); } else if ("next".equals(line)) { - mController.next(); + mController.next(""); } else if ("previous".equals(line)) { - mController.previous(); + mController.previous(""); } else { System.out.println("Invalid command: " + line); } diff --git a/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java b/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java index b76d6694ca2d..37b7acfaf5e6 100644 --- a/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java +++ b/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java @@ -29,23 +29,21 @@ public class RequestSync { private String[] mArgs; private int mNextArg; private String mCurArgData; - private boolean mIsForegroundRequest; + + private int mExemptionFlag = ContentResolver.SYNC_EXEMPTION_NONE; enum Operation { REQUEST_SYNC { @Override void invoke(RequestSync caller) { - if (caller.mIsForegroundRequest) { - caller.mExtras.putBoolean( - ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC, true); - } else { - caller.mExtras.putBoolean( - ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_BG_SYNC, true); + final int flag = caller.mExemptionFlag; + caller.mExtras.putInt(ContentResolver.SYNC_VIRTUAL_EXTRAS_EXEMPTION_FLAG, flag); + if (flag == ContentResolver.SYNC_EXEMPTION_NONE) { System.out.println( "Making a sync request as a background app.\n" + "Note: request may be throttled by App Standby.\n" + "To override this behavior and run a sync immediately," - + " pass a -f option.\n"); + + " pass a -f or -F option (use -h for help).\n"); } final SyncRequest request = new SyncRequest.Builder() @@ -213,7 +211,10 @@ public class RequestSync { mExtras.putBoolean(key, Boolean.valueOf(value)); } else if (opt.equals("-f") || opt.equals("--foreground")) { - mIsForegroundRequest = true; + mExemptionFlag = ContentResolver.SYNC_EXEMPTION_ACTIVE; + + } else if (opt.equals("-F") || opt.equals("--top")) { + mExemptionFlag = ContentResolver.SYNC_EXEMPTION_ACTIVE_WITH_TEMP; } else { System.err.println("Error: Unknown option: " + opt); @@ -293,7 +294,9 @@ public class RequestSync { " -a|--authority <AUTHORITY>\n" + " App-standby related options\n" + "\n" + - " -f|--foreground (Exempt a sync from app standby)\n" + + " -f|--foreground (cause WORKING_SET, FREQUENT sync adapters" + + " to run immediately)\n" + + " -F|--top (cause even RARE sync adapters to run immediately)\n" + " ContentResolver extra options:\n" + " --is|--ignore-settings: Add SYNC_EXTRAS_IGNORE_SETTINGS\n" + " --ib|--ignore-backoff: Add SYNC_EXTRAS_IGNORE_BACKOFF\n" + diff --git a/cmds/statsd/benchmark/metric_util.cpp b/cmds/statsd/benchmark/metric_util.cpp index b67764bd5130..50b05cd5b1d8 100644 --- a/cmds/statsd/benchmark/metric_util.cpp +++ b/cmds/statsd/benchmark/metric_util.cpp @@ -127,25 +127,25 @@ AtomMatcher CreateSyncEndAtomMatcher() { } AtomMatcher CreateActivityForegroundStateChangedAtomMatcher( - const string& name, ActivityForegroundStateChanged::Activity activity) { + const string& name, ActivityForegroundStateChanged::State state) { 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(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); field_value_matcher->set_field(4); // Activity field. - field_value_matcher->set_eq_int(activity); + field_value_matcher->set_eq_int(state); return atom_matcher; } AtomMatcher CreateMoveToBackgroundAtomMatcher() { return CreateActivityForegroundStateChangedAtomMatcher( - "MoveToBackground", ActivityForegroundStateChanged::MOVE_TO_BACKGROUND); + "MoveToBackground", ActivityForegroundStateChanged::BACKGROUND); } AtomMatcher CreateMoveToForegroundAtomMatcher() { return CreateActivityForegroundStateChangedAtomMatcher( - "MoveToForeground", ActivityForegroundStateChanged::MOVE_TO_FOREGROUND); + "MoveToForeground", ActivityForegroundStateChanged::FOREGROUND); } Predicate CreateScheduledJobPredicate() { @@ -315,25 +315,25 @@ std::unique_ptr<LogEvent> CreateReleaseWakelockEvent( } std::unique_ptr<LogEvent> CreateActivityForegroundStateChangedEvent( - const int uid, const ActivityForegroundStateChanged::Activity activity, uint64_t timestampNs) { + const int uid, const ActivityForegroundStateChanged::State state, uint64_t timestampNs) { auto event = std::make_unique<LogEvent>( android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, timestampNs); event->write(uid); event->write("pkg_name"); event->write("class_name"); - event->write(activity); + event->write(state); event->init(); return event; } std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs) { return CreateActivityForegroundStateChangedEvent( - uid, ActivityForegroundStateChanged::MOVE_TO_BACKGROUND, timestampNs); + uid, ActivityForegroundStateChanged::BACKGROUND, timestampNs); } std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs) { return CreateActivityForegroundStateChangedEvent( - uid, ActivityForegroundStateChanged::MOVE_TO_FOREGROUND, timestampNs); + uid, ActivityForegroundStateChanged::FOREGROUND, timestampNs); } std::unique_ptr<LogEvent> CreateSyncStateChangedEvent( diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index a458c07394a7..c1ff27545401 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -79,8 +79,6 @@ StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap, mSendBroadcast(sendBroadcast), mTimeBaseSec(timeBaseSec), mLastLogTimestamp(0) { - StatsPullerManager statsPullerManager; - statsPullerManager.SetTimeBaseSec(mTimeBaseSec); } StatsLogProcessor::~StatsLogProcessor() { @@ -177,7 +175,7 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event) { uint64_t curTimeSec = getElapsedRealtimeSec(); if (curTimeSec - mLastPullerCacheClearTimeSec > StatsdStats::kPullerCacheClearIntervalSec) { - mStatsPullerManager.ClearPullerCacheIfNecessary(curTimeSec); + mStatsPullerManager.ClearPullerCacheIfNecessary(curTimeSec * NS_PER_SEC); mLastPullerCacheClearTimeSec = curTimeSec; } diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index b03b4b4a942c..9f70c75d66e9 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -595,7 +595,7 @@ status_t StatsService::cmd_log_app_breadcrumb(FILE* out, const Vector<String8>& status_t StatsService::cmd_print_pulled_metrics(FILE* out, const Vector<String8>& args) { int s = atoi(args[1].c_str()); vector<shared_ptr<LogEvent> > stats; - if (mStatsPullerManager.Pull(s, &stats)) { + if (mStatsPullerManager.Pull(s, getElapsedRealtimeNs(), &stats)) { for (const auto& it : stats) { fprintf(out, "Pull from %d: %s\n", s, it->ToString().c_str()); } diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 5e75359f111e..7fe8e6208e1e 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -16,7 +16,6 @@ syntax = "proto2"; -// TODO: Not the right package and class name package android.os.statsd; option java_package = "com.android.os"; option java_outer_classname = "AtomsProto"; @@ -49,7 +48,7 @@ message Atom { oneof pushed { // For StatsLog reasons, 1 is illegal and will not work. Must start at 2. BleScanStateChanged ble_scan_state_changed = 2; - // TODO: 3 is blank, but need not be + // 3 is available for use BleScanResultReceived ble_scan_result_received = 4; SensorStateChanged sensor_state_changed = 5; GpsScanStateChanged gps_scan_state_changed = 6; @@ -60,12 +59,12 @@ message Atom { LongPartialWakelockStateChanged long_partial_wakelock_state_changed = 11; MobileRadioPowerStateChanged mobile_radio_power_state_changed = 12; WifiRadioPowerStateChanged wifi_radio_power_state_changed = 13; - // TODO: 14-19 are blank, but need not be + // 14 - 19 are available BatterySaverModeStateChanged battery_saver_mode_state_changed = 20; DeviceIdleModeStateChanged device_idle_mode_state_changed = 21; DeviceIdlingModeStateChanged device_idling_mode_state_changed = 22; AudioStateChanged audio_state_changed = 23; - MediaCodecActivityChanged media_codec_activity_changed = 24; + MediaCodecStateChanged media_codec_state_changed = 24; CameraStateChanged camera_state_changed = 25; FlashlightStateChanged flashlight_state_changed = 26; UidProcessStateChanged uid_process_state_changed = 27; @@ -74,8 +73,7 @@ message Atom { BatteryLevelChanged battery_level_changed = 30; ChargingStateChanged charging_state_changed = 31; PluggedStateChanged plugged_state_changed = 32; - // TODO: 33 is blank, but is available for use. - DeviceOnStatusChanged device_on_status_changed = 34; + // 33 - 34 are available WakeupAlarmOccurred wakeup_alarm_occurred = 35; KernelWakeupReported kernel_wakeup_reported = 36; WifiLockStateChanged wifi_lock_state_changed = 37; @@ -86,12 +84,12 @@ message Atom { ActivityForegroundStateChanged activity_foreground_state_changed = 42; IsolatedUidChanged isolated_uid_changed = 43; PacketWakeupOccurred packet_wakeup_occurred = 44; - DropboxErrorChanged dropbox_error_changed = 45; + // 45 is available AnomalyDetected anomaly_detected = 46; AppBreadcrumbReported app_breadcrumb_reported = 47; - AppStartChanged app_start_changed = 48; - AppStartCancelChanged app_start_cancel_changed = 49; - AppStartFullyDrawnChanged app_start_fully_drawn_changed = 50; + AppStartOccurred app_start_occurred = 48; + AppStartCanceled app_start_canceled = 49; + AppStartFullyDrawn app_start_fully_drawn = 50; LmkKillOccurred lmk_kill_occurred = 51; PictureInPictureStateChanged picture_in_picture_state_changed = 52; WifiMulticastLockStateChanged wifi_multicast_lock_state_changed = 53; @@ -106,7 +104,7 @@ message Atom { KeyguardStateChanged keyguard_state_changed = 62; KeyguardBouncerStateChanged keyguard_bouncer_state_changed = 63; KeyguardBouncerPasswordEntered keyguard_bouncer_password_entered = 64; - AppDied app_died=65; + AppDied app_died = 65; ResourceConfigurationChanged resource_configuration_changed = 66; BluetoothEnabledStateChanged bluetooth_enabled_state_changed = 67; BluetoothConnectionStateChanged bluetooth_connection_state_changed = 68; @@ -119,6 +117,12 @@ message Atom { MobileConnectionStateChanged mobile_connection_state_changed = 75; MobileRadioTechnologyChanged mobile_radio_technology_changed = 76; UsbDeviceAttached usb_device_attached = 77; + AppCrashOccurred app_crash_occurred = 78; + ANROccurred anr_occurred = 79; + WTFOccurred wtf_occurred = 80; + LowMemReported low_mem_reported = 81; + + } // Pulled events will start at field 10000. @@ -134,7 +138,7 @@ message Atom { CpuTimePerFreq cpu_time_per_freq = 10008; CpuTimePerUid cpu_time_per_uid = 10009; CpuTimePerUidFreq cpu_time_per_uid_freq = 10010; - WifiActivityEnergyInfo wifi_activity_energy_info = 10011; + WifiActivityInfo wifi_activity_info = 10011; ModemActivityInfo modem_activity_info = 10012; BluetoothActivityInfo bluetooth_activity_info = 10007; ProcessMemoryState process_memory_state = 10013; @@ -224,30 +228,27 @@ message UidProcessStateChanged { * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java */ message ProcessLifeCycleStateChanged { - // TODO: should be a string tagged w/ uid annotation optional int32 uid = 1 [(is_uid) = true]; // The process name (usually same as the app name). - optional string name = 2; + optional string process_name = 2; // What lifecycle state the process changed to. // This enum is specific to atoms.proto. - enum Event { - PROCESS_FINISHED = 0; - PROCESS_STARTED = 1; - PROCESS_CRASHED = 2; - PROCESS_ANRED = 3; + enum State { + FINISHED = 0; + STARTED = 1; + CRASHED = 2; } - optional Event event = 3; + optional State state = 3; } /** * Logs when the ble scan state changes. * * Logged from: - * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + * packages/apps/Bluetooth/src/com/android/bluetooth/gatt/AppScanStats.java */ -// TODO: Consider changing to tracking per-scanner-id (log from AppScanStats). message BleScanStateChanged { repeated AttributionNode attribution_node = 1; @@ -278,7 +279,7 @@ message BleScanResultReceived { repeated AttributionNode attribution_node = 1; // Number of ble scan results returned. - optional int32 num_of_results = 2; + optional int32 num_results = 2; } /** @@ -290,7 +291,6 @@ message BleScanResultReceived { message SensorStateChanged { repeated AttributionNode attribution_node = 1; - // TODO: Is there a way to get the actual name of the sensor? // The id (int) of the sensor. optional int32 sensor_id = 2; @@ -329,7 +329,7 @@ message SyncStateChanged { repeated AttributionNode attribution_node = 1; // Name of the sync (as named in the app). Can be chosen at run-time. - optional string name = 2; + optional string sync_name = 2; enum State { OFF = 0; @@ -348,7 +348,7 @@ message ScheduledJobStateChanged { repeated AttributionNode attribution_node = 1; // Name of the job (as named in the app) - optional string name = 2; + optional string job_name = 2; enum State { FINISHED = 0; @@ -387,7 +387,7 @@ message AudioStateChanged { * Logged from: * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java */ -message MediaCodecActivityChanged { +message MediaCodecStateChanged { repeated AttributionNode attribution_node = 1; enum State { @@ -561,22 +561,6 @@ message PluggedStateChanged { optional android.os.BatteryPluggedStateEnum state = 1; } -// TODO: Define this more precisely. -// TODO: Log the ON state somewhere. It isn't currently logged anywhere. -/** - * Logs when the device turns off or on. - * - * Logged from: - * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java - */ -message DeviceOnStatusChanged { - enum State { - OFF = 0; - ON = 1; - } - optional State state = 1; -} - /** * Logs when an app's wakeup alarm fires. * @@ -598,8 +582,7 @@ message WakeupAlarmOccurred { * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java */ message MobileRadioPowerStateChanged { - // TODO: Add attribution instead of uid? - optional int32 uid = 1 [(is_uid) = true]; + repeated AttributionNode attribution_node = 1; // Power state, from frameworks/base/core/proto/android/telephony/enums.proto. optional android.telephony.DataConnectionPowerStateEnum state = 2; @@ -613,8 +596,7 @@ message MobileRadioPowerStateChanged { * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java */ message WifiRadioPowerStateChanged { - // TODO: Add attribution instead of uid? - optional int32 uid = 1 [(is_uid) = true]; + repeated AttributionNode attribution_node = 1; // Power state, from frameworks/base/core/proto/android/telephony/enums.proto. optional android.telephony.DataConnectionPowerStateEnum state = 2; @@ -1154,7 +1136,6 @@ message ChargeCyclesReported { message DaveyOccurred { // The UID that logged this atom. optional int32 uid = 1 [(is_uid) = true]; - ; // Amount of time it took to render the frame. Should be >=700ms. optional int64 jank_duration_millis = 2; @@ -1221,42 +1202,70 @@ message ActivityForegroundStateChanged { optional string pkg_name = 2; optional string class_name = 3; - enum Activity { - MOVE_TO_BACKGROUND = 0; - MOVE_TO_FOREGROUND = 1; + enum State { + BACKGROUND = 0; + FOREGROUND = 1; } - optional Activity activity = 4; + optional State state = 4; } /** - * Logs when an error is written to dropbox. + * Logs when an app crashes. * Logged from: * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java */ -message DropboxErrorChanged { - // The uid if available. -1 means not available. +message AppCrashOccurred { + optional int32 uid = 1 [(is_uid) = true]; + + optional string event_type = 2; + + // The name of the process. + // system_server if it is not by an app + optional string process_name = 3; + + // The pid if available. -1 means not available. + optional sint32 pid = 4; +} + +/** + * Logs when a WTF (What a Terrible Failure) happened. + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java + */ +message WTFOccurred { optional int32 uid = 1 [(is_uid) = true]; - // Tag used when recording this error to dropbox. Contains data_ or system_ prefix. optional string tag = 2; // The name of the process. + // system_server if it is not by an app optional string process_name = 3; // The pid if available. -1 means not available. optional sint32 pid = 4; +} - // 1 indicates is instant app. -1 indicates Not applicable. - optional sint32 is_instant_app = 5; +/** + * Logs when system server reports low memory. + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java + */ +message LowMemReported { +} + +/** + * Logs when an app ANR (App Not Responding) occurs. + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/AppErrors.java + */ +message ANROccurred { + optional int32 uid = 1 [(is_uid) = true]; - // The activity name if available. - optional string activity_name = 6; + optional string process_name = 2; - // The package name if available. - optional string package_name = 7; + optional string short_component_name = 3; - // 1 indicates in foreground. -1 indicates not available. - optional sint32 is_foreground = 8; + optional string reason = 4; } /* @@ -1299,7 +1308,7 @@ message AnomalyDetected { optional int64 alert_id = 3; } -message AppStartChanged { +message AppStartOccurred { // The uid if available. -1 means not available. optional int32 uid = 1 [(is_uid) = true]; @@ -1307,7 +1316,7 @@ message AppStartChanged { optional string pkg_name = 2; enum TransitionType { - APP_START_TRANSITION_TYPE_UNKNOWN = 0; + UNKNOWN = 0; WARM = 1; HOT = 2; COLD = 3; @@ -1346,7 +1355,7 @@ message AppStartChanged { optional int32 package_optimization_compilation_reason = 15; } -message AppStartCancelChanged { +message AppStartCanceled { // The uid if available. -1 means not available. optional int32 uid = 1 [(is_uid) = true]; @@ -1354,7 +1363,7 @@ message AppStartCancelChanged { optional string pkg_name = 2; enum TransitionType { - APP_START_TRANSITION_TYPE_UNKNOWN = 0; + UNKNOWN = 0; WARM = 1; HOT = 2; COLD = 3; @@ -1366,7 +1375,7 @@ message AppStartCancelChanged { optional string activity_name = 4; } -message AppStartFullyDrawnChanged { +message AppStartFullyDrawn { // The uid if available. -1 means not available. optional int32 uid = 1 [(is_uid) = true]; @@ -1374,7 +1383,7 @@ message AppStartFullyDrawnChanged { optional string pkg_name = 2; enum TransitionType { - APP_START_TRANSITION_TYPE_UNKNOWN = 0; + UNKNOWN = 0; WITH_BUNDLE = 1; WITHOUT_BUNDLE = 2; } @@ -1459,8 +1468,8 @@ message ForegroundServiceStateChanged { * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java */ message IsolatedUidChanged { - // NOTE: DO NOT annotate uid field in this atom. This atom is specially handled in statsd. // The host UID. Generally, we should attribute metrics from the isolated uid to the host uid. + // NOTE: DO NOT annotate uid field in this atom. This atom is specially handled in statsd. optional int32 parent_uid = 1; optional int32 isolated_uid = 2; @@ -1621,9 +1630,8 @@ message WifiBytesTransfer { message WifiBytesTransferByFgBg { optional int32 uid = 1 [(is_uid) = true]; - // 1 denotes foreground and 0 denotes background. This is called Set in - // NetworkStats. - optional int32 is_foreground = 2; + // 1 denotes foreground and 0 denotes background. This is called Set in NetworkStats. + optional bool is_foreground = 2; optional int64 rx_bytes = 3; @@ -1663,7 +1671,7 @@ message MobileBytesTransferByFgBg { // 1 denotes foreground and 0 denotes background. This is called Set in // NetworkStats. - optional int32 is_foreground = 2; + optional bool is_foreground = 2; optional int64 rx_bytes = 3; @@ -1760,7 +1768,7 @@ message CpuTimePerUidFreq { /** * Pulls Wifi Controller Activity Energy Info */ -message WifiActivityEnergyInfo { +message WifiActivityInfo { // timestamp(wall clock) of record creation optional uint64 timestamp_millis = 1; // stack reported state diff --git a/cmds/statsd/src/external/StatsPuller.cpp b/cmds/statsd/src/external/StatsPuller.cpp index 3b0cd349168d..b29e979b5236 100644 --- a/cmds/statsd/src/external/StatsPuller.cpp +++ b/cmds/statsd/src/external/StatsPuller.cpp @@ -35,27 +35,32 @@ void StatsPuller::SetUidMap(const sp<UidMap>& uidMap) { mUidMap = uidMap; } // ValueMetric has a minimum bucket size of 10min so that we don't pull too frequently StatsPuller::StatsPuller(const int tagId) : mTagId(tagId) { - mCoolDownSec = StatsPullerManagerImpl::kAllPullAtomInfo.find(tagId)->second.coolDownSec; - VLOG("Puller for tag %d created. Cooldown set to %ld", mTagId, mCoolDownSec); + mCoolDownNs = StatsPullerManagerImpl::kAllPullAtomInfo.find(tagId)->second.coolDownNs; + VLOG("Puller for tag %d created. Cooldown set to %lld", mTagId, (long long)mCoolDownNs); } -bool StatsPuller::Pull(std::vector<std::shared_ptr<LogEvent>>* data) { +bool StatsPuller::Pull(const int64_t elapsedTimeNs, std::vector<std::shared_ptr<LogEvent>>* data) { lock_guard<std::mutex> lock(mLock); + int64_t wallClockTimeNs = getWallClockNs(); StatsdStats::getInstance().notePull(mTagId); - long curTime = getElapsedRealtimeSec(); - if (curTime - mLastPullTimeSec < mCoolDownSec) { + if (elapsedTimeNs - mLastPullTimeNs < mCoolDownNs) { (*data) = mCachedData; StatsdStats::getInstance().notePullFromCache(mTagId); return true; } - if (mMinPullIntervalSec > curTime - mLastPullTimeSec) { - mMinPullIntervalSec = curTime - mLastPullTimeSec; - StatsdStats::getInstance().updateMinPullIntervalSec(mTagId, mMinPullIntervalSec); + if (mMinPullIntervalNs > elapsedTimeNs - mLastPullTimeNs) { + mMinPullIntervalNs = elapsedTimeNs - mLastPullTimeNs; + StatsdStats::getInstance().updateMinPullIntervalSec(mTagId, + mMinPullIntervalNs / NS_PER_SEC); } mCachedData.clear(); - mLastPullTimeSec = curTime; + mLastPullTimeNs = elapsedTimeNs; bool ret = PullInternal(&mCachedData); - if (ret) { + for (const shared_ptr<LogEvent>& data : mCachedData) { + data->setElapsedTimestampNs(elapsedTimeNs); + data->setLogdWallClockTimestampNs(wallClockTimeNs); + } + if (ret && mCachedData.size() > 0) { mergeIsolatedUidsToHostUid(mCachedData, mUidMap, mTagId); (*data) = mCachedData; } @@ -70,12 +75,12 @@ int StatsPuller::clearCache() { lock_guard<std::mutex> lock(mLock); int ret = mCachedData.size(); mCachedData.clear(); - mLastPullTimeSec = 0; + mLastPullTimeNs = 0; return ret; } -int StatsPuller::ClearCacheIfNecessary(long timestampSec) { - if (timestampSec - mLastPullTimeSec > mCoolDownSec) { +int StatsPuller::ClearCacheIfNecessary(int64_t timestampNs) { + if (timestampNs - mLastPullTimeNs > mCoolDownNs) { return clearCache(); } else { return 0; diff --git a/cmds/statsd/src/external/StatsPuller.h b/cmds/statsd/src/external/StatsPuller.h index 936c47e92f2c..caac677ee215 100644 --- a/cmds/statsd/src/external/StatsPuller.h +++ b/cmds/statsd/src/external/StatsPuller.h @@ -37,13 +37,13 @@ public: virtual ~StatsPuller() {} - bool Pull(std::vector<std::shared_ptr<LogEvent>>* data); + bool Pull(const int64_t timeNs, std::vector<std::shared_ptr<LogEvent>>* data); // Clear cache immediately int ForceClearCache(); // Clear cache if elapsed time is more than cooldown time - int ClearCacheIfNecessary(long timestampSec); + int ClearCacheIfNecessary(int64_t timestampNs); static void SetUidMap(const sp<UidMap>& uidMap); @@ -59,9 +59,9 @@ private: // If a pull request comes before cooldown, a cached version from purevious pull // will be returned. // The actual value should be determined by individual pullers. - long mCoolDownSec; + int64_t mCoolDownNs; // For puller stats - long mMinPullIntervalSec = LONG_MAX; + int64_t mMinPullIntervalNs = LONG_MAX; virtual bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) = 0; @@ -69,7 +69,7 @@ private: // cached data will be returned. std::vector<std::shared_ptr<LogEvent>> mCachedData; - long mLastPullTimeSec; + int64_t mLastPullTimeNs; int clearCache(); diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h index 2717d5c2de9c..83d59c0a5830 100644 --- a/cmds/statsd/src/external/StatsPullerManager.h +++ b/cmds/statsd/src/external/StatsPullerManager.h @@ -26,10 +26,9 @@ class StatsPullerManager { public: virtual ~StatsPullerManager() {} - virtual void RegisterReceiver(int tagId, - wp <PullDataReceiver> receiver, - long intervalMs) { - mPullerManager.RegisterReceiver(tagId, receiver, intervalMs); + virtual void RegisterReceiver(int tagId, wp<PullDataReceiver> receiver, int64_t nextPullTimeNs, + int64_t intervalNs) { + mPullerManager.RegisterReceiver(tagId, receiver, nextPullTimeNs, intervalNs); }; virtual void UnRegisterReceiver(int tagId, wp <PullDataReceiver> receiver) { @@ -45,13 +44,9 @@ class StatsPullerManager { mPullerManager.OnAlarmFired(); } - virtual bool - Pull(const int tagId, vector<std::shared_ptr<LogEvent>>* data) { - return mPullerManager.Pull(tagId, data); - } - - void SetTimeBaseSec(const long timeBaseSec) { - mPullerManager.SetTimeBaseSec(timeBaseSec); + virtual bool Pull(const int tagId, const int64_t timesNs, + vector<std::shared_ptr<LogEvent>>* data) { + return mPullerManager.Pull(tagId, timesNs, data); } int ForceClearPullerCache() { @@ -62,8 +57,8 @@ class StatsPullerManager { mPullerManager.SetStatsCompanionService(statsCompanionService); } - int ClearPullerCacheIfNecessary(long timestampSec) { - return mPullerManager.ClearPullerCacheIfNecessary(timestampSec); + int ClearPullerCacheIfNecessary(int64_t timestampNs) { + return mPullerManager.ClearPullerCacheIfNecessary(timestampNs); } private: diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp index dd6406bb90ca..2f0e88595d45 100644 --- a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp +++ b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp @@ -19,15 +19,17 @@ #include <android/os/IStatsCompanionService.h> #include <cutils/log.h> +#include <math.h> #include <algorithm> #include <climits> +#include "../StatsService.h" #include "../logd/LogEvent.h" #include "../stats_log_util.h" #include "../statscompanion_util.h" #include "ResourceHealthManagerPuller.h" #include "ResourceThermalManagerPuller.h" #include "StatsCompanionServicePuller.h" -#include "StatsService.h" +#include "StatsPullerManagerImpl.h" #include "SubsystemSleepStatePuller.h" #include "statslog.h" @@ -47,89 +49,136 @@ namespace statsd { const std::map<int, PullAtomInfo> StatsPullerManagerImpl::kAllPullAtomInfo = { // wifi_bytes_transfer {android::util::WIFI_BYTES_TRANSFER, - {{2, 3, 4, 5}, {}, 1, + {{2, 3, 4, 5}, + {}, + 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER)}}, // wifi_bytes_transfer_by_fg_bg {android::util::WIFI_BYTES_TRANSFER_BY_FG_BG, - {{3, 4, 5, 6}, {2}, 1, + {{3, 4, 5, 6}, + {2}, + 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER_BY_FG_BG)}}, // mobile_bytes_transfer {android::util::MOBILE_BYTES_TRANSFER, - {{2, 3, 4, 5}, {}, 1, + {{2, 3, 4, 5}, + {}, + 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER)}}, // mobile_bytes_transfer_by_fg_bg {android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG, - {{3, 4, 5, 6}, {2}, 1, + {{3, 4, 5, 6}, + {2}, + 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG)}}, // bluetooth_bytes_transfer {android::util::BLUETOOTH_BYTES_TRANSFER, - {{2, 3}, {}, 1, new StatsCompanionServicePuller(android::util::BLUETOOTH_BYTES_TRANSFER)}}, + {{2, 3}, + {}, + 1 * NS_PER_SEC, + new StatsCompanionServicePuller(android::util::BLUETOOTH_BYTES_TRANSFER)}}, // kernel_wakelock {android::util::KERNEL_WAKELOCK, - {{}, {}, 1, new StatsCompanionServicePuller(android::util::KERNEL_WAKELOCK)}}, + {{}, {}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::KERNEL_WAKELOCK)}}, // subsystem_sleep_state {android::util::SUBSYSTEM_SLEEP_STATE, - {{}, {}, 1, new SubsystemSleepStatePuller()}}, + {{}, {}, 1 * NS_PER_SEC, new SubsystemSleepStatePuller()}}, // cpu_time_per_freq {android::util::CPU_TIME_PER_FREQ, - {{3}, {2}, 1, new StatsCompanionServicePuller(android::util::CPU_TIME_PER_FREQ)}}, + {{3}, + {2}, + 1 * NS_PER_SEC, + new StatsCompanionServicePuller(android::util::CPU_TIME_PER_FREQ)}}, // cpu_time_per_uid {android::util::CPU_TIME_PER_UID, - {{2, 3}, {}, 1, new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID)}}, + {{2, 3}, + {}, + 1 * NS_PER_SEC, + new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID)}}, // cpu_time_per_uid_freq - // the throttling is 3sec, handled in frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader + // the throttling is 3sec, handled in + // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader {android::util::CPU_TIME_PER_UID_FREQ, - {{4}, {2,3}, 0, new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID_FREQ)}}, + {{4}, + {2, 3}, + 1 * NS_PER_SEC, + new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID_FREQ)}}, // cpu_active_time - // the throttling is 3sec, handled in frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader + // the throttling is 3sec, handled in + // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader {android::util::CPU_ACTIVE_TIME, - {{2}, {}, 0, new StatsCompanionServicePuller(android::util::CPU_ACTIVE_TIME)}}, + {{2}, + {}, + 1 * NS_PER_SEC, + new StatsCompanionServicePuller(android::util::CPU_ACTIVE_TIME)}}, // cpu_cluster_time - // the throttling is 3sec, handled in frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader + // the throttling is 3sec, handled in + // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader {android::util::CPU_CLUSTER_TIME, - {{3}, {2}, 0, new StatsCompanionServicePuller(android::util::CPU_CLUSTER_TIME)}}, + {{3}, + {2}, + 1 * NS_PER_SEC, + new StatsCompanionServicePuller(android::util::CPU_CLUSTER_TIME)}}, // wifi_activity_energy_info - {android::util::WIFI_ACTIVITY_ENERGY_INFO, - {{}, {}, 1, new StatsCompanionServicePuller(android::util::WIFI_ACTIVITY_ENERGY_INFO)}}, + {android::util::WIFI_ACTIVITY_INFO, + {{}, + {}, + 1 * NS_PER_SEC, + new StatsCompanionServicePuller(android::util::WIFI_ACTIVITY_INFO)}}, // modem_activity_info {android::util::MODEM_ACTIVITY_INFO, - {{}, {}, 1, new StatsCompanionServicePuller(android::util::MODEM_ACTIVITY_INFO)}}, + {{}, + {}, + 1 * NS_PER_SEC, + new StatsCompanionServicePuller(android::util::MODEM_ACTIVITY_INFO)}}, // bluetooth_activity_info {android::util::BLUETOOTH_ACTIVITY_INFO, - {{}, {}, 1, new StatsCompanionServicePuller(android::util::BLUETOOTH_ACTIVITY_INFO)}}, + {{}, + {}, + 1 * NS_PER_SEC, + new StatsCompanionServicePuller(android::util::BLUETOOTH_ACTIVITY_INFO)}}, // system_elapsed_realtime {android::util::SYSTEM_ELAPSED_REALTIME, - {{}, {}, 1, new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME)}}, + {{}, + {}, + 1 * NS_PER_SEC, + new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME)}}, // system_uptime {android::util::SYSTEM_UPTIME, - {{}, {}, 1, new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}}, + {{}, {}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}}, // disk_space {android::util::DISK_SPACE, - {{}, {}, 1, new StatsCompanionServicePuller(android::util::DISK_SPACE)}}, + {{}, {}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::DISK_SPACE)}}, // remaining_battery_capacity {android::util::REMAINING_BATTERY_CAPACITY, - {{}, {}, 1, new ResourceHealthManagerPuller(android::util::REMAINING_BATTERY_CAPACITY)}}, + {{}, + {}, + 1 * NS_PER_SEC, + new ResourceHealthManagerPuller(android::util::REMAINING_BATTERY_CAPACITY)}}, // full_battery_capacity {android::util::FULL_BATTERY_CAPACITY, - {{}, {}, 1, new ResourceHealthManagerPuller(android::util::FULL_BATTERY_CAPACITY)}}, + {{}, + {}, + 1 * NS_PER_SEC, + new ResourceHealthManagerPuller(android::util::FULL_BATTERY_CAPACITY)}}, // process_memory_state {android::util::PROCESS_MEMORY_STATE, - {{4,5,6,7,8}, - {2,3}, - 0, + {{4, 5, 6, 7, 8}, + {2, 3}, + 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_STATE)}}, // temperature {android::util::TEMPERATURE, {{}, {}, 1, new ResourceThermalManagerPuller()}}}; -StatsPullerManagerImpl::StatsPullerManagerImpl() - : mCurrentPullingInterval(LONG_MAX) { +StatsPullerManagerImpl::StatsPullerManagerImpl() : mNextPullTimeNs(LONG_MAX) { } -bool StatsPullerManagerImpl::Pull(int tagId, vector<shared_ptr<LogEvent>>* data) { +bool StatsPullerManagerImpl::Pull(const int tagId, const int64_t timeNs, + vector<shared_ptr<LogEvent>>* data) { VLOG("Initiating pulling %d", tagId); if (kAllPullAtomInfo.find(tagId) != kAllPullAtomInfo.end()) { - bool ret = kAllPullAtomInfo.find(tagId)->second.puller->Pull(data); + bool ret = kAllPullAtomInfo.find(tagId)->second.puller->Pull(timeNs, data); VLOG("pulled %d items", (int)data->size()); return ret; } else { @@ -148,12 +197,14 @@ bool StatsPullerManagerImpl::PullerForMatcherExists(int tagId) const { } void StatsPullerManagerImpl::updateAlarmLocked() { - long currentTimeMs = getElapsedRealtimeMillis(); - long nextAlarmTimeMs = currentTimeMs + mCurrentPullingInterval - - (currentTimeMs - mTimeBaseSec * 1000) % mCurrentPullingInterval; + if (mNextPullTimeNs == LONG_MAX) { + VLOG("No need to set alarms. Skipping"); + return; + } + sp<IStatsCompanionService> statsCompanionServiceCopy = mStatsCompanionService; if (statsCompanionServiceCopy != nullptr) { - statsCompanionServiceCopy->setPullingAlarms(nextAlarmTimeMs, mCurrentPullingInterval); + statsCompanionServiceCopy->setPullingAlarm(mNextPullTimeNs / 1000000); } else { VLOG("StatsCompanionService not available. Alarm not set."); } @@ -174,7 +225,7 @@ void StatsPullerManagerImpl::SetStatsCompanionService( } void StatsPullerManagerImpl::RegisterReceiver(int tagId, wp<PullDataReceiver> receiver, - long intervalMs) { + int64_t nextPullTimeNs, int64_t intervalNs) { AutoMutex _l(mLock); auto& receivers = mReceivers[tagId]; for (auto it = receivers.begin(); it != receivers.end(); it++) { @@ -185,21 +236,24 @@ void StatsPullerManagerImpl::RegisterReceiver(int tagId, wp<PullDataReceiver> re } ReceiverInfo receiverInfo; receiverInfo.receiver = receiver; - receiverInfo.timeInfo.first = intervalMs; - receivers.push_back(receiverInfo); // Round it to the nearest minutes. This is the limit of alarm manager. - // In practice, we should limit it higher. - long roundedIntervalMs = intervalMs/1000/60 * 1000 * 60; + // In practice, we should always have larger buckets. + int64_t roundedIntervalNs = intervalNs / NS_PER_SEC / 60 * NS_PER_SEC * 60; // Scheduled pulling should be at least 1 min apart. // This can be lower in cts tests, in which case we round it to 1 min. - if (roundedIntervalMs < 60 * 1000) { - roundedIntervalMs = 60 * 1000; + if (roundedIntervalNs < 60 * (int64_t)NS_PER_SEC) { + roundedIntervalNs = 60 * (int64_t)NS_PER_SEC; } + + receiverInfo.intervalNs = roundedIntervalNs; + receiverInfo.nextPullTimeNs = nextPullTimeNs; + receivers.push_back(receiverInfo); + // There is only one alarm for all pulled events. So only set it to the smallest denom. - if (roundedIntervalMs < mCurrentPullingInterval) { - VLOG("Updating pulling interval %ld", intervalMs); - mCurrentPullingInterval = roundedIntervalMs; + if (nextPullTimeNs < mNextPullTimeNs) { + VLOG("Updating next pull time %lld", (long long)mNextPullTimeNs); + mNextPullTimeNs = nextPullTimeNs; updateAlarmLocked(); } VLOG("Puller for tagId %d registered of %d", tagId, (int)receivers.size()); @@ -224,16 +278,22 @@ void StatsPullerManagerImpl::UnRegisterReceiver(int tagId, wp<PullDataReceiver> void StatsPullerManagerImpl::OnAlarmFired() { AutoMutex _l(mLock); - uint64_t currentTimeMs = getElapsedRealtimeMillis(); + int64_t currentTimeNs = getElapsedRealtimeNs(); + + int64_t minNextPullTimeNs = LONG_MAX; vector<pair<int, vector<ReceiverInfo*>>> needToPull = vector<pair<int, vector<ReceiverInfo*>>>(); for (auto& pair : mReceivers) { vector<ReceiverInfo*> receivers = vector<ReceiverInfo*>(); if (pair.second.size() != 0) { - for (auto& receiverInfo : pair.second) { - if (receiverInfo.timeInfo.first + receiverInfo.timeInfo.second > currentTimeMs) { + for (ReceiverInfo& receiverInfo : pair.second) { + if (receiverInfo.nextPullTimeNs < currentTimeNs) { receivers.push_back(&receiverInfo); + } else { + if (receiverInfo.nextPullTimeNs < minNextPullTimeNs) { + minNextPullTimeNs = receiverInfo.nextPullTimeNs; + } } } if (receivers.size() > 0) { @@ -244,18 +304,29 @@ void StatsPullerManagerImpl::OnAlarmFired() { for (const auto& pullInfo : needToPull) { vector<shared_ptr<LogEvent>> data; - if (Pull(pullInfo.first, &data)) { + if (Pull(pullInfo.first, currentTimeNs, &data)) { for (const auto& receiverInfo : pullInfo.second) { sp<PullDataReceiver> receiverPtr = receiverInfo->receiver.promote(); if (receiverPtr != nullptr) { receiverPtr->onDataPulled(data); - receiverInfo->timeInfo.second = currentTimeMs; + // we may have just come out of a coma, compute next pull time + receiverInfo->nextPullTimeNs = + ceil((double_t)(currentTimeNs - receiverInfo->nextPullTimeNs) / + receiverInfo->intervalNs) * + receiverInfo->intervalNs + + receiverInfo->nextPullTimeNs; + if (receiverInfo->nextPullTimeNs < minNextPullTimeNs) { + minNextPullTimeNs = receiverInfo->nextPullTimeNs; + } } else { VLOG("receiver already gone."); } } } } + + mNextPullTimeNs = minNextPullTimeNs; + updateAlarmLocked(); } int StatsPullerManagerImpl::ForceClearPullerCache() { @@ -266,10 +337,10 @@ int StatsPullerManagerImpl::ForceClearPullerCache() { return totalCleared; } -int StatsPullerManagerImpl::ClearPullerCacheIfNecessary(long timestampSec) { +int StatsPullerManagerImpl::ClearPullerCacheIfNecessary(int64_t timestampNs) { int totalCleared = 0; for (const auto& pulledAtom : kAllPullAtomInfo) { - totalCleared += pulledAtom.second.puller->ClearCacheIfNecessary(timestampSec); + totalCleared += pulledAtom.second.puller->ClearCacheIfNecessary(timestampNs); } return totalCleared; } diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.h b/cmds/statsd/src/external/StatsPullerManagerImpl.h index 682ad33a8749..8c771f31fdc5 100644 --- a/cmds/statsd/src/external/StatsPullerManagerImpl.h +++ b/cmds/statsd/src/external/StatsPullerManagerImpl.h @@ -41,7 +41,7 @@ typedef struct { std::vector<int> nonAdditiveFields; // How long should the puller wait before doing an actual pull again. Default // 1 sec. Set this to 0 if this is handled elsewhere. - long coolDownSec = 1; + int64_t coolDownNs = 1 * NS_PER_SEC; // The actual puller sp<StatsPuller> puller; } PullAtomInfo; @@ -50,7 +50,8 @@ class StatsPullerManagerImpl : public virtual RefBase { public: static StatsPullerManagerImpl& GetInstance(); - void RegisterReceiver(int tagId, wp<PullDataReceiver> receiver, long intervalMs); + void RegisterReceiver(int tagId, wp<PullDataReceiver> receiver, int64_t nextPullTimeNs, + int64_t intervalNs); void UnRegisterReceiver(int tagId, wp<PullDataReceiver> receiver); @@ -59,13 +60,11 @@ public: void OnAlarmFired(); - bool Pull(const int tagId, vector<std::shared_ptr<LogEvent>>* data); - - void SetTimeBaseSec(long timeBaseSec) {mTimeBaseSec = timeBaseSec;}; + bool Pull(const int tagId, const int64_t timeNs, vector<std::shared_ptr<LogEvent>>* data); int ForceClearPullerCache(); - int ClearPullerCacheIfNecessary(long timestampSec); + int ClearPullerCacheIfNecessary(int64_t timestampNs); void SetStatsCompanionService(sp<IStatsCompanionService> statsCompanionService); @@ -77,8 +76,8 @@ public: sp<IStatsCompanionService> mStatsCompanionService = nullptr; typedef struct { - // pull_interval_sec : last_pull_time_sec - std::pair<uint64_t, uint64_t> timeInfo; + int64_t nextPullTimeNs; + int64_t intervalNs; wp<PullDataReceiver> receiver; } ReceiverInfo; @@ -90,12 +89,7 @@ public: void updateAlarmLocked(); - long mCurrentPullingInterval; - - // for pulled metrics, it is important for the buckets to be aligned to multiple of smallest - // bucket size. All pulled metrics start pulling based on this time, so that they can be - // correctly attributed to the correct buckets. - long mTimeBaseSec; + int64_t mNextPullTimeNs; }; } // namespace statsd diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp index ef637df68ed3..22ff9428c83a 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -76,6 +76,7 @@ const int FIELD_ID_CONFIG_STATS_MATCHER_STATS = 13; const int FIELD_ID_CONFIG_STATS_CONDITION_STATS = 14; const int FIELD_ID_CONFIG_STATS_METRIC_STATS = 15; const int FIELD_ID_CONFIG_STATS_ALERT_STATS = 16; +const int FIELD_ID_CONFIG_STATS_METRIC_DIMENSION_IN_CONDITION_STATS = 17; const int FIELD_ID_MATCHER_STATS_ID = 1; const int FIELD_ID_MATCHER_STATS_COUNT = 2; @@ -255,6 +256,20 @@ void StatsdStats::noteMetricDimensionSize(const ConfigKey& key, const int64_t& i } } +void StatsdStats::noteMetricDimensionInConditionSize( + const ConfigKey& key, const int64_t& id, int size) { + lock_guard<std::mutex> lock(mLock); + // if name doesn't exist before, it will create the key with count 0. + auto statsIt = mConfigStats.find(key); + if (statsIt == mConfigStats.end()) { + return; + } + auto& metricsDimensionMap = statsIt->second->metric_dimension_in_condition_stats; + if (size > metricsDimensionMap[id]) { + metricsDimensionMap[id] = size; + } +} + void StatsdStats::noteMatcherMatched(const ConfigKey& key, const int64_t& id) { lock_guard<std::mutex> lock(mLock); @@ -339,6 +354,7 @@ void StatsdStats::resetInternalLocked() { config.second->matcher_stats.clear(); config.second->condition_stats.clear(); config.second->metric_stats.clear(); + config.second->metric_dimension_in_condition_stats.clear(); config.second->alert_stats.clear(); } } @@ -504,6 +520,13 @@ void addConfigStatsToProto(const ConfigStats& configStats, ProtoOutputStream* pr proto->write(FIELD_TYPE_INT32 | FIELD_ID_METRIC_STATS_COUNT, pair.second); proto->end(tmpToken); } + for (const auto& pair : configStats.metric_dimension_in_condition_stats) { + uint64_t tmpToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_CONFIG_STATS_METRIC_DIMENSION_IN_CONDITION_STATS); + proto->write(FIELD_TYPE_INT64 | FIELD_ID_METRIC_STATS_ID, (long long)pair.first); + proto->write(FIELD_TYPE_INT32 | FIELD_ID_METRIC_STATS_COUNT, pair.second); + proto->end(tmpToken); + } for (const auto& pair : configStats.alert_stats) { uint64_t tmpToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h index 7f8755b8c1e0..bd395c4c232b 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -57,6 +57,12 @@ struct ConfigStats { // it means some data has been dropped. The map size is capped by kMaxConfigCount. std::map<const int64_t, int> metric_stats; + // Stores the max number of output tuple of dimensions in condition across dimensions in what + // when it's bigger than kDimensionKeySizeSoftLimit. When you see the number is + // kDimensionKeySizeHardLimit +1, it means some data has been dropped. The map size is capped by + // kMaxConfigCount. + std::map<const int64_t, int> metric_dimension_in_condition_stats; + // Stores the number of times an anomaly detection alert has been declared. // The map size is capped by kMaxConfigCount. std::map<const int64_t, int> alert_stats; @@ -183,6 +189,19 @@ public: */ void noteMetricDimensionSize(const ConfigKey& key, const int64_t& id, int size); + + /** + * Report the max size of output tuple of dimension in condition across dimensions in what. + * + * Note: only report when the metric has an output dimension in condition, and the max tuple + * count > kDimensionKeySizeSoftLimit. + * + * [key]: The config key that this metric belongs to. + * [id]: The id of the metric. + * [size]: The output tuple size. + */ + void noteMetricDimensionInConditionSize(const ConfigKey& key, const int64_t& id, int size); + /** * Report a matcher has been matched. * diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index c28bb8800e20..f02f30756b2d 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -308,11 +308,14 @@ void DurationMetricProducer::onSlicedConditionMayChangeLocked_opt2(bool conditio if (mMetric2ConditionLinks.size() == 0 || trueDim.contains(linkedConditionDimensionKey)) { if (!whatIt.second.empty()) { + auto newEventKey = MetricDimensionKey(whatIt.first, trueDim); + if (hitGuardRailLocked(newEventKey)) { + continue; + } unique_ptr<DurationTracker> newTracker = whatIt.second.begin()->second->clone(eventTime); if (newTracker != nullptr) { - newTracker->setEventKey( - MetricDimensionKey(whatIt.first, trueDim)); + newTracker->setEventKey(newEventKey); newTracker->onConditionChanged(true, eventTime); whatIt.second[trueDim] = std::move(newTracker); } @@ -370,11 +373,14 @@ void DurationMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondit for (const auto& conditionDimension : conditionDimensionsKeySet) { for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { if (!whatIt.second.empty()) { + auto newEventKey = MetricDimensionKey(whatIt.first, conditionDimension); + if (hitGuardRailLocked(newEventKey)) { + continue; + } unique_ptr<DurationTracker> newTracker = whatIt.second.begin()->second->clone(eventTime); if (newTracker != nullptr) { - newTracker->setEventKey(MetricDimensionKey( - whatIt.first, conditionDimension)); + newTracker->setEventKey(MetricDimensionKey(newEventKey)); newTracker->onSlicedConditionMayChange(overallCondition, eventTime); whatIt.second[conditionDimension] = std::move(newTracker); } @@ -397,10 +403,13 @@ void DurationMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondit for (const auto& conditionDimension : conditionDimensionsKeys) { if (!whatIt.second.empty() && whatIt.second.find(conditionDimension) == whatIt.second.end()) { + auto newEventKey = MetricDimensionKey(whatIt.first, conditionDimension); + if (hitGuardRailLocked(newEventKey)) { + continue; + } auto newTracker = whatIt.second.begin()->second->clone(eventTime); if (newTracker != nullptr) { - newTracker->setEventKey( - MetricDimensionKey(whatIt.first, conditionDimension)); + newTracker->setEventKey(newEventKey); newTracker->onSlicedConditionMayChange(overallCondition, eventTime); whatIt.second[conditionDimension] = std::move(newTracker); } @@ -552,15 +561,35 @@ void DurationMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { } bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { - // 1. Report the tuple count if the tuple count > soft limit - if (mCurrentSlicedDurationTrackerMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { - size_t newTupleCount = mCurrentSlicedDurationTrackerMap.size() + 1; - StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount); - // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. - if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("DurationMetric %lld dropping data for dimension key %s", - (long long)mMetricId, newKey.toString().c_str()); - return true; + auto whatIt = mCurrentSlicedDurationTrackerMap.find(newKey.getDimensionKeyInWhat()); + if (whatIt != mCurrentSlicedDurationTrackerMap.end()) { + auto condIt = whatIt->second.find(newKey.getDimensionKeyInCondition()); + if (condIt != whatIt->second.end()) { + return false; + } + if (whatIt->second.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { + size_t newTupleCount = whatIt->second.size() + 1; + StatsdStats::getInstance().noteMetricDimensionInConditionSize( + mConfigKey, mMetricId, newTupleCount); + // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. + if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { + ALOGE("DurationMetric %lld dropping data for condition dimension key %s", + (long long)mMetricId, newKey.getDimensionKeyInCondition().toString().c_str()); + return true; + } + } + } else { + // 1. Report the tuple count if the tuple count > soft limit + if (mCurrentSlicedDurationTrackerMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { + size_t newTupleCount = mCurrentSlicedDurationTrackerMap.size() + 1; + StatsdStats::getInstance().noteMetricDimensionSize( + mConfigKey, mMetricId, newTupleCount); + // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. + if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { + ALOGE("DurationMetric %lld dropping data for what dimension key %s", + (long long)mMetricId, newKey.getDimensionKeyInWhat().toString().c_str()); + return true; + } } } return false; diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index f0e0df18693a..b13c3e7a0487 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -112,7 +112,8 @@ GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric // Kicks off the puller immediately. if (mPullTagId != -1 && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { - mStatsPullerManager->RegisterReceiver(mPullTagId, this, bucketSizeMills); + mStatsPullerManager->RegisterReceiver( + mPullTagId, this, mCurrentBucketStartTimeNs + mBucketSizeNs, mBucketSizeNs); } VLOG("Gauge metric %lld created. bucket size %lld start_time: %lld sliced %d", @@ -255,7 +256,7 @@ void GaugeMetricProducer::pullLocked() { } vector<std::shared_ptr<LogEvent>> allData; - if (!mStatsPullerManager->Pull(mPullTagId, &allData)) { + if (!mStatsPullerManager->Pull(mPullTagId, getElapsedRealtimeNs(), &allData)) { ALOGE("Gauge Stats puller failed for tag: %d", mPullTagId); return; } diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index e19e2368f751..d0f510df2aa2 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -110,10 +110,12 @@ ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, const ValueMetric } mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0); - if (!metric.has_condition() && mPullTagId != -1) { - VLOG("Setting up periodic pulling for %d", mPullTagId); - mStatsPullerManager->RegisterReceiver(mPullTagId, this, bucketSizeMills); + // Kicks off the puller immediately. + if (mPullTagId != -1) { + mStatsPullerManager->RegisterReceiver( + mPullTagId, this, mCurrentBucketStartTimeNs + mBucketSizeNs, mBucketSizeNs); } + VLOG("value metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), (long long)mBucketSizeNs, (long long)mStartTimeNs); } @@ -194,26 +196,21 @@ void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, // TODO: Clear mDimensionKeyMap once the report is dumped. } -void ValueMetricProducer::onConditionChangedLocked(const bool condition, const uint64_t eventTime) { +void ValueMetricProducer::onConditionChangedLocked(const bool condition, + const uint64_t eventTimeNs) { mCondition = condition; - if (eventTime < mCurrentBucketStartTimeNs) { - VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTime, + if (eventTimeNs < mCurrentBucketStartTimeNs) { + VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs, (long long)mCurrentBucketStartTimeNs); return; } - flushIfNeededLocked(eventTime); + flushIfNeededLocked(eventTimeNs); if (mPullTagId != -1) { - if (mCondition == true) { - mStatsPullerManager->RegisterReceiver(mPullTagId, this, mBucketSizeNs / 1000 / 1000); - } else if (mCondition == false) { - mStatsPullerManager->UnRegisterReceiver(mPullTagId, this); - } - vector<shared_ptr<LogEvent>> allData; - if (mStatsPullerManager->Pull(mPullTagId, &allData)) { + if (mStatsPullerManager->Pull(mPullTagId, eventTimeNs, &allData)) { if (allData.size() == 0) { return; } @@ -315,8 +312,13 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked( if (mPullTagId != -1) { // for pulled events if (mCondition == true) { - interval.start = value; - interval.startUpdated = true; + if (!interval.startUpdated) { + interval.start = value; + interval.startUpdated = true; + } else { + // skip it if there is already value recorded for the start + VLOG("Already recorded value for this dimension %s", eventKey.toString().c_str()); + } } else { // Generally we expect value to be monotonically increasing. // If not, there was a reset event. We take the absolute value as @@ -385,6 +387,7 @@ void ValueMetricProducer::flushCurrentBucketLocked(const uint64_t& eventTimeNs) int tainted = 0; for (const auto& slice : mCurrentSlicedBucket) { tainted += slice.second.tainted; + tainted += slice.second.startUpdated; info.mValue = slice.second.sum; // it will auto create new vector of ValuebucketInfo if the key is not found. auto& bucketList = mPastBuckets[slice.first]; diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index 796e83af4f1d..45d95316e8e1 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -53,7 +53,7 @@ public: if (mPullTagId != -1) { vector<shared_ptr<LogEvent>> allData; - mStatsPullerManager->Pull(mPullTagId, &allData); + mStatsPullerManager->Pull(mPullTagId, eventTimeNs, &allData); if (allData.size() == 0) { // This shouldn't happen since this valuemetric is not useful now. } @@ -159,6 +159,10 @@ private: FRIEND_TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade); FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition); FRIEND_TEST(ValueMetricProducerTest, TestAnomalyDetection); + FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition); + FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition); + FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2); + FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition3); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp index 15d9619f0220..5c1e9c0cd65e 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp @@ -327,8 +327,9 @@ int64_t MaxDurationTracker::predictAnomalyTimestampNs(const DurationAnomalyTrack } } } - int64_t threshold = anomalyTracker.getAnomalyThreshold(); - return currentTimestamp + threshold - maxElapsed; + int64_t anomalyTimeNs = currentTimestamp + anomalyTracker.getAnomalyThreshold() - maxElapsed; + int64_t refractoryEndNs = anomalyTracker.getRefractoryPeriodEndsSec(mEventKey) * NS_PER_SEC; + return std::max(anomalyTimeNs, refractoryEndNs); } void MaxDurationTracker::dumpStates(FILE* out, bool verbose) const { diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index dd3b37c8c2d7..a25df3fc4c09 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -241,6 +241,7 @@ message StatsdStatsReport { repeated ConditionStats condition_stats = 14; repeated MetricStats metric_stats = 15; repeated AlertStats alert_stats = 16; + repeated MetricStats metric_dimension_in_condition_stats = 17; } repeated ConfigStats config_stats = 3; diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp index cab61e9787c6..efd810f01140 100644 --- a/cmds/statsd/src/stats_log_util.cpp +++ b/cmds/statsd/src/stats_log_util.cpp @@ -221,7 +221,8 @@ void writeFieldValueTreeToStream(int tagId, const std::vector<FieldValue>& value int64_t TimeUnitToBucketSizeInMillisGuardrailed(int uid, TimeUnit unit) { int64_t bucketSizeMillis = TimeUnitToBucketSizeInMillis(unit); - if (bucketSizeMillis > 1000 && bucketSizeMillis < 5 * 60 * 1000LL && uid != AID_SHELL) { + if (bucketSizeMillis > 1000 && bucketSizeMillis < 5 * 60 * 1000LL && uid != AID_SHELL && + uid != AID_ROOT) { bucketSizeMillis = 5 * 60 * 1000LL; } return bucketSizeMillis; diff --git a/cmds/statsd/statsd.rc b/cmds/statsd/statsd.rc index 920273b5dfaa..3a0c22425e5c 100644 --- a/cmds/statsd/statsd.rc +++ b/cmds/statsd/statsd.rc @@ -20,5 +20,5 @@ service statsd /system/bin/statsd on post-fs-data # Create directory for statsd - mkdir /data/misc/stats-data/ 0770 statsd statsd - mkdir /data/misc/stats-service/ 0770 statsd statsd + mkdir /data/misc/stats-data/ 0770 statsd system + mkdir /data/misc/stats-service/ 0770 statsd system diff --git a/cmds/statsd/tests/FieldValue_test.cpp b/cmds/statsd/tests/FieldValue_test.cpp index 73e7c44dc3da..5a6aba624d7d 100644 --- a/cmds/statsd/tests/FieldValue_test.cpp +++ b/cmds/statsd/tests/FieldValue_test.cpp @@ -356,7 +356,7 @@ TEST(AtomMatcherTest, TestWriteAtomToProto) { EXPECT_EQ("location1", atom.attribution_node(0).tag()); EXPECT_EQ(2222, atom.attribution_node(1).uid()); EXPECT_EQ("location2", atom.attribution_node(1).tag()); - EXPECT_EQ(999, atom.num_of_results()); + EXPECT_EQ(999, atom.num_results()); } diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp index 2e6a0f05a341..2b91324a9d19 100644 --- a/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp +++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp @@ -34,7 +34,7 @@ StatsdConfig CreateStatsdConfigForPushedEvent(const GaugeMetric::SamplingType sa *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); - auto atomMatcher = CreateSimpleAtomMatcher("", android::util::APP_START_CHANGED); + auto atomMatcher = CreateSimpleAtomMatcher("", android::util::APP_START_OCCURRED); *config.add_atom_matcher() = atomMatcher; auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); @@ -49,18 +49,18 @@ StatsdConfig CreateStatsdConfigForPushedEvent(const GaugeMetric::SamplingType sa gaugeMetric->mutable_gauge_fields_filter()->set_include_all(false); gaugeMetric->set_sampling_type(sampling_type); auto fieldMatcher = gaugeMetric->mutable_gauge_fields_filter()->mutable_fields(); - fieldMatcher->set_field(android::util::APP_START_CHANGED); + fieldMatcher->set_field(android::util::APP_START_OCCURRED); 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_in_what() = - CreateDimensions(android::util::APP_START_CHANGED, {1 /* uid field */ }); + CreateDimensions(android::util::APP_START_OCCURRED, {1 /* uid field */ }); gaugeMetric->set_bucket(FIVE_MINUTES); auto links = gaugeMetric->add_links(); links->set_condition(isInBackgroundPredicate.id()); auto dimensionWhat = links->mutable_fields_in_what(); - dimensionWhat->set_field(android::util::APP_START_CHANGED); + dimensionWhat->set_field(android::util::APP_START_OCCURRED); dimensionWhat->add_child()->set_field(1); // uid field. auto dimensionCondition = links->mutable_fields_in_condition(); dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); @@ -68,12 +68,12 @@ StatsdConfig CreateStatsdConfigForPushedEvent(const GaugeMetric::SamplingType sa return config; } -std::unique_ptr<LogEvent> CreateAppStartChangedEvent( - const int uid, const string& pkg_name, AppStartChanged::TransitionType type, +std::unique_ptr<LogEvent> CreateAppStartOccurredEvent( + const int uid, const string& pkg_name, AppStartOccurred::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); + android::util::APP_START_OCCURRED, timestampNs); logEvent->write(uid); logEvent->write(pkg_name); logEvent->write(type); @@ -112,32 +112,32 @@ TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { appUid1, bucketStartTimeNs + 2 * bucketSizeNs + 100)); - events.push_back(CreateAppStartChangedEvent( - appUid1, "app1", AppStartChanged::WARM, "activity_name1", "calling_pkg_name1", + events.push_back(CreateAppStartOccurredEvent( + appUid1, "app1", AppStartOccurred::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", + events.push_back(CreateAppStartOccurredEvent( + appUid1, "app1", AppStartOccurred::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", + events.push_back(CreateAppStartOccurredEvent( + appUid1, "app1", AppStartOccurred::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", + events.push_back(CreateAppStartOccurredEvent( + appUid1, "app1", AppStartOccurred::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", + events.push_back(CreateAppStartOccurredEvent( + appUid1, "app1", AppStartOccurred::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", + events.push_back(CreateAppStartOccurredEvent( + appUid1, "app1", AppStartOccurred::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", + events.push_back(CreateAppStartOccurredEvent( + appUid2, "app2", AppStartOccurred::COLD, "activity_name7", "calling_pkg_name7", true /*is_instant_app*/, 201 /*activity_start_msec*/, bucketStartTimeNs + 2 * bucketSizeNs + 10)); @@ -159,7 +159,7 @@ TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { EXPECT_EQ(2, gaugeMetrics.data_size()); auto data = gaugeMetrics.data(0); - EXPECT_EQ(android::util::APP_START_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(android::util::APP_START_OCCURRED, data.dimensions_in_what().field()); EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); @@ -171,29 +171,29 @@ TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { EXPECT_EQ(2, data.bucket_info(0).wall_clock_timestamp_nanos_size()); EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_nanos()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_nanos()); - EXPECT_EQ(AppStartChanged::HOT, data.bucket_info(0).atom(0).app_start_changed().type()); + EXPECT_EQ(AppStartOccurred::HOT, data.bucket_info(0).atom(0).app_start_occurred().type()); EXPECT_EQ("activity_name2", - data.bucket_info(0).atom(0).app_start_changed().activity_name()); + data.bucket_info(0).atom(0).app_start_occurred().activity_name()); EXPECT_EQ(102L, - data.bucket_info(0).atom(0).app_start_changed().activity_start_millis()); - EXPECT_EQ(AppStartChanged::COLD, - data.bucket_info(0).atom(1).app_start_changed().type()); + data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis()); + EXPECT_EQ(AppStartOccurred::COLD, + data.bucket_info(0).atom(1).app_start_occurred().type()); EXPECT_EQ("activity_name3", - data.bucket_info(0).atom(1).app_start_changed().activity_name()); + data.bucket_info(0).atom(1).app_start_occurred().activity_name()); EXPECT_EQ(103L, - data.bucket_info(0).atom(1).app_start_changed().activity_start_millis()); + data.bucket_info(0).atom(1).app_start_occurred().activity_start_millis()); EXPECT_EQ(1, data.bucket_info(1).atom_size()); EXPECT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size()); EXPECT_EQ(1, data.bucket_info(1).wall_clock_timestamp_nanos_size()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(1).start_bucket_nanos()); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(1).end_bucket_nanos()); - EXPECT_EQ(AppStartChanged::WARM, - data.bucket_info(1).atom(0).app_start_changed().type()); + EXPECT_EQ(AppStartOccurred::WARM, + data.bucket_info(1).atom(0).app_start_occurred().type()); EXPECT_EQ("activity_name4", - data.bucket_info(1).atom(0).app_start_changed().activity_name()); + data.bucket_info(1).atom(0).app_start_occurred().activity_name()); EXPECT_EQ(104L, - data.bucket_info(1).atom(0).app_start_changed().activity_start_millis()); + data.bucket_info(1).atom(0).app_start_occurred().activity_start_millis()); EXPECT_EQ(2, data.bucket_info(2).atom_size()); EXPECT_EQ(2, data.bucket_info(2).elapsed_timestamp_nanos_size()); @@ -202,41 +202,41 @@ TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { data.bucket_info(2).start_bucket_nanos()); EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, data.bucket_info(2).end_bucket_nanos()); - EXPECT_EQ(AppStartChanged::COLD, - data.bucket_info(2).atom(0).app_start_changed().type()); + EXPECT_EQ(AppStartOccurred::COLD, + data.bucket_info(2).atom(0).app_start_occurred().type()); EXPECT_EQ("activity_name5", - data.bucket_info(2).atom(0).app_start_changed().activity_name()); + data.bucket_info(2).atom(0).app_start_occurred().activity_name()); EXPECT_EQ(105L, - data.bucket_info(2).atom(0).app_start_changed().activity_start_millis()); - EXPECT_EQ(AppStartChanged::HOT, - data.bucket_info(2).atom(1).app_start_changed().type()); + data.bucket_info(2).atom(0).app_start_occurred().activity_start_millis()); + EXPECT_EQ(AppStartOccurred::HOT, + data.bucket_info(2).atom(1).app_start_occurred().type()); EXPECT_EQ("activity_name6", - data.bucket_info(2).atom(1).app_start_changed().activity_name()); + data.bucket_info(2).atom(1).app_start_occurred().activity_name()); EXPECT_EQ(106L, - data.bucket_info(2).atom(1).app_start_changed().activity_start_millis()); + data.bucket_info(2).atom(1).app_start_occurred().activity_start_millis()); } else { EXPECT_EQ(1, data.bucket_info(0).atom_size()); EXPECT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); EXPECT_EQ(1, data.bucket_info(0).wall_clock_timestamp_nanos_size()); EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_nanos()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_nanos()); - EXPECT_EQ(AppStartChanged::HOT, data.bucket_info(0).atom(0).app_start_changed().type()); + EXPECT_EQ(AppStartOccurred::HOT, data.bucket_info(0).atom(0).app_start_occurred().type()); EXPECT_EQ("activity_name2", - data.bucket_info(0).atom(0).app_start_changed().activity_name()); + data.bucket_info(0).atom(0).app_start_occurred().activity_name()); EXPECT_EQ(102L, - data.bucket_info(0).atom(0).app_start_changed().activity_start_millis()); + data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis()); EXPECT_EQ(1, data.bucket_info(1).atom_size()); EXPECT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size()); EXPECT_EQ(1, data.bucket_info(1).wall_clock_timestamp_nanos_size()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(1).start_bucket_nanos()); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(1).end_bucket_nanos()); - EXPECT_EQ(AppStartChanged::WARM, - data.bucket_info(1).atom(0).app_start_changed().type()); + EXPECT_EQ(AppStartOccurred::WARM, + data.bucket_info(1).atom(0).app_start_occurred().type()); EXPECT_EQ("activity_name4", - data.bucket_info(1).atom(0).app_start_changed().activity_name()); + data.bucket_info(1).atom(0).app_start_occurred().activity_name()); EXPECT_EQ(104L, - data.bucket_info(1).atom(0).app_start_changed().activity_start_millis()); + data.bucket_info(1).atom(0).app_start_occurred().activity_start_millis()); EXPECT_EQ(1, data.bucket_info(2).atom_size()); EXPECT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size()); @@ -245,17 +245,17 @@ TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { data.bucket_info(2).start_bucket_nanos()); EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, data.bucket_info(2).end_bucket_nanos()); - EXPECT_EQ(AppStartChanged::COLD, - data.bucket_info(2).atom(0).app_start_changed().type()); + EXPECT_EQ(AppStartOccurred::COLD, + data.bucket_info(2).atom(0).app_start_occurred().type()); EXPECT_EQ("activity_name5", - data.bucket_info(2).atom(0).app_start_changed().activity_name()); + data.bucket_info(2).atom(0).app_start_occurred().activity_name()); EXPECT_EQ(105L, - data.bucket_info(2).atom(0).app_start_changed().activity_start_millis()); + data.bucket_info(2).atom(0).app_start_occurred().activity_start_millis()); } data = gaugeMetrics.data(1); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::APP_START_CHANGED); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::APP_START_OCCURRED); EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); @@ -266,10 +266,10 @@ TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { EXPECT_EQ(1, data.bucket_info(0).wall_clock_timestamp_nanos_size()); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_nanos()); EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_nanos()); - EXPECT_EQ(AppStartChanged::COLD, data.bucket_info(0).atom(0).app_start_changed().type()); + EXPECT_EQ(AppStartOccurred::COLD, data.bucket_info(0).atom(0).app_start_occurred().type()); EXPECT_EQ("activity_name7", - data.bucket_info(0).atom(0).app_start_changed().activity_name()); - EXPECT_EQ(201L, data.bucket_info(0).atom(0).app_start_changed().activity_start_millis()); + data.bucket_info(0).atom(0).app_start_occurred().activity_name()); + EXPECT_EQ(201L, data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis()); } } diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp index 5c4eda8348d1..04ce73a76d3b 100644 --- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp +++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp @@ -210,7 +210,7 @@ TEST(StatsdStatsTest, TestAtomLog) { stats.noteAtomLogged(android::util::SENSOR_STATE_CHANGED, now + 1); stats.noteAtomLogged(android::util::SENSOR_STATE_CHANGED, now + 2); - stats.noteAtomLogged(android::util::DROPBOX_ERROR_CHANGED, now + 3); + stats.noteAtomLogged(android::util::APP_CRASH_OCCURRED, now + 3); // pulled event, should ignore stats.noteAtomLogged(android::util::WIFI_BYTES_TRANSFER, now + 4); @@ -228,7 +228,7 @@ TEST(StatsdStatsTest, TestAtomLog) { if (atomStats.tag() == android::util::SENSOR_STATE_CHANGED && atomStats.count() == 3) { sensorAtomGood = true; } - if (atomStats.tag() == android::util::DROPBOX_ERROR_CHANGED && atomStats.count() == 1) { + if (atomStats.tag() == android::util::APP_CRASH_OCCURRED && atomStats.count() == 1) { dropboxAtomGood = true; } } diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp index 2583c95b2016..7ca66fd361c2 100644 --- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp @@ -63,7 +63,7 @@ TEST(GaugeMetricProducerTest, TestNoCondition) { // For now we still need this so that it doesn't do real pulling. shared_ptr<MockStatsPullerManager> pullerManager = make_shared<StrictMock<MockStatsPullerManager>>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, @@ -213,10 +213,11 @@ TEST(GaugeMetricProducerTest, TestPulledWithUpgrade) { shared_ptr<MockStatsPullerManager> pullerManager = make_shared<StrictMock<MockStatsPullerManager>>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, _, _)) + .WillOnce(Invoke([](int tagId, int64_t timeNs, + vector<std::shared_ptr<LogEvent>>* data) { data->clear(); shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, eventUpgradeTimeNs); event->write("some value"); @@ -281,10 +282,11 @@ TEST(GaugeMetricProducerTest, TestWithCondition) { shared_ptr<MockStatsPullerManager> pullerManager = make_shared<StrictMock<MockStatsPullerManager>>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, _, _)) + .WillOnce(Invoke([](int tagId, int64_t timeNs, + vector<std::shared_ptr<LogEvent>>* data) { data->clear(); shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); event->write("some value"); @@ -372,10 +374,11 @@ TEST(GaugeMetricProducerTest, TestWithSlicedCondition) { shared_ptr<MockStatsPullerManager> pullerManager = make_shared<StrictMock<MockStatsPullerManager>>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, _, _)) + .WillOnce(Invoke([](int tagId, int64_t timeNs, + vector<std::shared_ptr<LogEvent>>* data) { data->clear(); shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); event->write(1000); @@ -420,7 +423,7 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) { shared_ptr<MockStatsPullerManager> pullerManager = make_shared<StrictMock<MockStatsPullerManager>>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); GaugeMetric metric; diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp index 57a8925a122e..a0f1c00b7b48 100644 --- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp @@ -344,8 +344,37 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) { tracker.noteConditionChanged(key1, true, conditionStarts2); EXPECT_EQ(1U, anomalyTracker->mAlarms.size()); auto alarm = anomalyTracker->mAlarms.begin()->second; + uint64_t anomalyFireTimeSec = alarm->timestampSec; EXPECT_EQ(conditionStarts2 + 36 * NS_PER_SEC, - (unsigned long long)(alarm->timestampSec * NS_PER_SEC)); + (unsigned long long)anomalyFireTimeSec * NS_PER_SEC); + + // Now we test the calculation now that there's a refractory period. + // At the correct time, declare the anomaly. This will set a refractory period. Make sure it + // gets correctly taken into account in future predictAnomalyTimestampNs calculations. + std::unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> firedAlarms({alarm}); + anomalyTracker->informAlarmsFired(anomalyFireTimeSec * NS_PER_SEC, firedAlarms); + EXPECT_EQ(0u, anomalyTracker->mAlarms.size()); + uint64_t refractoryPeriodEndsSec = anomalyFireTimeSec + refPeriodSec; + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), refractoryPeriodEndsSec); + + // Now stop and start again. Make sure the new predictAnomalyTimestampNs takes into account + // the refractory period correctly. + uint64_t eventStopTimeNs = anomalyFireTimeSec * NS_PER_SEC + 10; + tracker.noteStop(key1, eventStopTimeNs, false); + tracker.noteStop(key2, eventStopTimeNs, false); + tracker.noteStart(key1, true, eventStopTimeNs + 1000000, conditionKey1); + // Anomaly is ongoing, but we're still in the refractory period. + EXPECT_EQ(1U, anomalyTracker->mAlarms.size()); + alarm = anomalyTracker->mAlarms.begin()->second; + EXPECT_EQ(refractoryPeriodEndsSec, (unsigned long long)(alarm->timestampSec)); + + // Makes sure it is correct after the refractory period is over. + tracker.noteStop(key1, eventStopTimeNs + 2000000, false); + uint64_t justBeforeRefPeriodNs = (refractoryPeriodEndsSec - 2) * NS_PER_SEC; + tracker.noteStart(key1, true, justBeforeRefPeriodNs, conditionKey1); + alarm = anomalyTracker->mAlarms.begin()->second; + EXPECT_EQ(justBeforeRefPeriodNs + 40 * NS_PER_SEC, + (unsigned long long)(alarm->timestampSec * NS_PER_SEC)); } // Suppose that within one tracker there are two dimensions A and B. diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index a8eb27037ebf..c650a06fdbe1 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -45,6 +45,8 @@ const int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000L const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs; const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs; +const int64_t bucket5StartTimeNs = bucketStartTimeNs + 4 * bucketSizeNs; +const int64_t bucket6StartTimeNs = bucketStartTimeNs + 5 * bucketSizeNs; const int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; /* @@ -62,7 +64,7 @@ TEST(ValueMetricProducerTest, TestNonDimensionalEvents) { // For now we still need this so that it doesn't do real pulling. shared_ptr<MockStatsPullerManager> pullerManager = make_shared<StrictMock<MockStatsPullerManager>>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, @@ -141,11 +143,12 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); shared_ptr<MockStatsPullerManager> pullerManager = make_shared<StrictMock<MockStatsPullerManager>>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, _, _)) + .WillOnce(Invoke([](int tagId, int64_t timeNs, + vector<std::shared_ptr<LogEvent>>* data) { data->clear(); shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); event->write(tagId); @@ -154,7 +157,8 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { data->push_back(event); return true; })) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, int64_t timeNs, + vector<std::shared_ptr<LogEvent>>* data) { data->clear(); shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 10); event->write(tagId); @@ -260,10 +264,11 @@ TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); shared_ptr<MockStatsPullerManager> pullerManager = make_shared<StrictMock<MockStatsPullerManager>>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, _, _)) + .WillOnce(Invoke([](int tagId, int64_t timeNs, + vector<std::shared_ptr<LogEvent>>* data) { data->clear(); shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); event->write(tagId); @@ -428,6 +433,376 @@ TEST(ValueMetricProducerTest, TestAnomalyDetection) { std::ceil(1.0 * event6->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); } +// Test value metric no condition, the pull on bucket boundary come in time and too late +TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { + ValueMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_value_field()->set_field(tagId); + metric.mutable_value_field()->add_child()->set_field(2); + + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + shared_ptr<MockStatsPullerManager> pullerManager = + make_shared<StrictMock<MockStatsPullerManager>>(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, + tagId, bucketStartTimeNs, pullerManager); + valueProducer.setBucketSize(60 * NS_PER_SEC); + + vector<shared_ptr<LogEvent>> allData; + // pull 1 + allData.clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); + event->write(tagId); + event->write(11); + event->init(); + allData.push_back(event); + + valueProducer.onDataPulled(allData); + // has one slice + EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second; + valueProducer.setBucketSize(60 * NS_PER_SEC); + + // startUpdated:true tainted:0 sum:0 start:11 + EXPECT_EQ(true, curInterval.startUpdated); + EXPECT_EQ(0, curInterval.tainted); + EXPECT_EQ(0, curInterval.sum); + EXPECT_EQ(11, curInterval.start); + EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); + EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second.back().mValue); + + // pull 2 at correct time + allData.clear(); + event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1); + event->write(tagId); + event->write(23); + event->init(); + allData.push_back(event); + valueProducer.onDataPulled(allData); + // has one slice + EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second; + // tartUpdated:false tainted:0 sum:12 + EXPECT_EQ(true, curInterval.startUpdated); + EXPECT_EQ(0, curInterval.tainted); + EXPECT_EQ(0, curInterval.sum); + EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); + EXPECT_EQ(2UL, valueProducer.mPastBuckets.begin()->second.size()); + EXPECT_EQ(12, valueProducer.mPastBuckets.begin()->second.back().mValue); + + // pull 3 come late. + // The previous bucket gets closed with error. (Has start value 23, no ending) + // Another bucket gets closed with error. (No start, but ending with 36) + // The new bucket is back to normal. + allData.clear(); + event = make_shared<LogEvent>(tagId, bucket6StartTimeNs + 1); + event->write(tagId); + event->write(36); + event->init(); + allData.push_back(event); + valueProducer.onDataPulled(allData); + EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second; + // startUpdated:false tainted:0 sum:12 + EXPECT_EQ(true, curInterval.startUpdated); + EXPECT_EQ(0, curInterval.tainted); + EXPECT_EQ(36, curInterval.start); + EXPECT_EQ(0, curInterval.sum); + EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); + EXPECT_EQ(4UL, valueProducer.mPastBuckets.begin()->second.size()); + EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[0].mValue); + EXPECT_EQ(12, valueProducer.mPastBuckets.begin()->second[1].mValue); + EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[2].mValue); + EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[3].mValue); +} + +/* + * Test pulled event with non sliced condition. The pull on boundary come late because the alarm + * was delivered late. + */ +TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) { + ValueMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_value_field()->set_field(tagId); + metric.mutable_value_field()->add_child()->set_field(2); + metric.set_condition(StringToId("SCREEN_ON")); + + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + shared_ptr<MockStatsPullerManager> pullerManager = + make_shared<StrictMock<MockStatsPullerManager>>(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); + + EXPECT_CALL(*pullerManager, Pull(tagId, _, _)) + // condition becomes true + .WillOnce(Invoke([](int tagId, int64_t timeNs, + vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); + event->write(tagId); + event->write(100); + event->init(); + data->push_back(event); + return true; + })) + // condition becomes false + .WillOnce(Invoke([](int tagId, int64_t timeNs, + vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 20); + event->write(tagId); + event->write(120); + event->init(); + data->push_back(event); + return true; + })); + + ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, tagId, bucketStartTimeNs, + pullerManager); + valueProducer.setBucketSize(60 * NS_PER_SEC); + valueProducer.onConditionChanged(true, bucketStartTimeNs + 8); + + // has one slice + EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second; + // startUpdated:false tainted:0 sum:0 start:100 + EXPECT_EQ(100, curInterval.start); + EXPECT_EQ(true, curInterval.startUpdated); + EXPECT_EQ(0, curInterval.tainted); + EXPECT_EQ(0, curInterval.sum); + EXPECT_EQ(0UL, valueProducer.mPastBuckets.size()); + + // pull on bucket boundary come late, condition change happens before it + valueProducer.onConditionChanged(false, bucket2StartTimeNs + 1); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second; + EXPECT_EQ(false, curInterval.startUpdated); + EXPECT_EQ(1, curInterval.tainted); + EXPECT_EQ(0, curInterval.sum); + EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); + EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); + EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[0].mValue); + + // Now the alarm is delivered. + // since the condition turned to off before this pull finish, it has no effect + vector<shared_ptr<LogEvent>> allData; + allData.clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 30); + event->write(1); + event->write(110); + event->init(); + allData.push_back(event); + valueProducer.onDataPulled(allData); + + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second; + EXPECT_EQ(false, curInterval.startUpdated); + EXPECT_EQ(1, curInterval.tainted); + EXPECT_EQ(0, curInterval.sum); + EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); + EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); + EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[0].mValue); +} + +/* + * Test pulled event with non sliced condition. The pull on boundary come late, after the condition + * change to false, and then true again. This is due to alarm delivered late. + */ +TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { + ValueMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_value_field()->set_field(tagId); + metric.mutable_value_field()->add_child()->set_field(2); + metric.set_condition(StringToId("SCREEN_ON")); + + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + shared_ptr<MockStatsPullerManager> pullerManager = + make_shared<StrictMock<MockStatsPullerManager>>(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillRepeatedly(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); + + EXPECT_CALL(*pullerManager, Pull(tagId, _, _)) + // condition becomes true + .WillOnce(Invoke([](int tagId, int64_t timeNs, + vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); + event->write(tagId); + event->write(100); + event->init(); + data->push_back(event); + return true; + })) + // condition becomes false + .WillOnce(Invoke([](int tagId, int64_t timeNs, + vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 20); + event->write(tagId); + event->write(120); + event->init(); + data->push_back(event); + return true; + })) + // condition becomes true again + .WillOnce(Invoke([](int tagId, int64_t timeNs, + vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 30); + event->write(tagId); + event->write(130); + event->init(); + data->push_back(event); + return true; + })); + + ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, tagId, bucketStartTimeNs, + pullerManager); + valueProducer.setBucketSize(60 * NS_PER_SEC); + valueProducer.onConditionChanged(true, bucketStartTimeNs + 8); + + // has one slice + EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second; + // startUpdated:false tainted:0 sum:0 start:100 + EXPECT_EQ(100, curInterval.start); + EXPECT_EQ(true, curInterval.startUpdated); + EXPECT_EQ(0, curInterval.tainted); + EXPECT_EQ(0, curInterval.sum); + EXPECT_EQ(0UL, valueProducer.mPastBuckets.size()); + + // pull on bucket boundary come late, condition change happens before it + valueProducer.onConditionChanged(false, bucket2StartTimeNs + 1); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second; + EXPECT_EQ(false, curInterval.startUpdated); + EXPECT_EQ(1, curInterval.tainted); + EXPECT_EQ(0, curInterval.sum); + EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); + EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); + EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[0].mValue); + + // condition changed to true again, before the pull alarm is delivered + valueProducer.onConditionChanged(true, bucket2StartTimeNs + 25); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second; + EXPECT_EQ(true, curInterval.startUpdated); + EXPECT_EQ(130, curInterval.start); + EXPECT_EQ(1, curInterval.tainted); + EXPECT_EQ(0, curInterval.sum); + EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); + EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); + EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[0].mValue); + + // Now the alarm is delivered, but it is considered late, it has no effect + vector<shared_ptr<LogEvent>> allData; + allData.clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 50); + event->write(1); + event->write(110); + event->init(); + allData.push_back(event); + valueProducer.onDataPulled(allData); + + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second; + EXPECT_EQ(true, curInterval.startUpdated); + EXPECT_EQ(130, curInterval.start); + EXPECT_EQ(1, curInterval.tainted); + EXPECT_EQ(0, curInterval.sum); + EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); + EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); + EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[0].mValue); +} + +/* + * Test pulled event with non sliced condition. The pull on boundary come late because the puller is + * very slow. + */ +TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition3) { + ValueMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_value_field()->set_field(tagId); + metric.mutable_value_field()->add_child()->set_field(2); + metric.set_condition(StringToId("SCREEN_ON")); + + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + shared_ptr<MockStatsPullerManager> pullerManager = + make_shared<StrictMock<MockStatsPullerManager>>(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); + + EXPECT_CALL(*pullerManager, Pull(tagId, _, _)) + // condition becomes true + .WillOnce(Invoke([](int tagId, int64_t timeNs, + vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); + event->write(tagId); + event->write(100); + event->init(); + data->push_back(event); + return true; + })) + // condition becomes false + .WillOnce(Invoke([](int tagId, int64_t timeNs, + vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 20); + event->write(tagId); + event->write(120); + event->init(); + data->push_back(event); + return true; + })); + + ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, tagId, bucketStartTimeNs, + pullerManager); + valueProducer.setBucketSize(60 * NS_PER_SEC); + valueProducer.onConditionChanged(true, bucketStartTimeNs + 8); + + // has one slice + EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second; + // startUpdated:false tainted:0 sum:0 start:100 + EXPECT_EQ(100, curInterval.start); + EXPECT_EQ(true, curInterval.startUpdated); + EXPECT_EQ(0, curInterval.tainted); + EXPECT_EQ(0, curInterval.sum); + EXPECT_EQ(0UL, valueProducer.mPastBuckets.size()); + + // pull on bucket boundary come late, condition change happens before it. + // But puller is very slow in this one, so the data come after bucket finish + valueProducer.onConditionChanged(false, bucket2StartTimeNs + 1); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second; + EXPECT_EQ(false, curInterval.startUpdated); + EXPECT_EQ(1, curInterval.tainted); + EXPECT_EQ(0, curInterval.sum); + EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); + EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); + EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[0].mValue); + + // Alarm is delivered in time, but the pull is very slow, and pullers are called in order, + // so this one comes even later + vector<shared_ptr<LogEvent>> allData; + allData.clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 30); + event->write(1); + event->write(110); + event->init(); + allData.push_back(event); + valueProducer.onDataPulled(allData); + + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second; + EXPECT_EQ(false, curInterval.startUpdated); + EXPECT_EQ(1, curInterval.tainted); + EXPECT_EQ(0, curInterval.sum); + EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); + EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); + EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[0].mValue); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h index f040bf9f37ae..5afaba6671fa 100644 --- a/cmds/statsd/tests/metrics/metrics_test_helper.h +++ b/cmds/statsd/tests/metrics/metrics_test_helper.h @@ -35,9 +35,11 @@ public: class MockStatsPullerManager : public StatsPullerManager { public: - MOCK_METHOD3(RegisterReceiver, void(int tagId, wp<PullDataReceiver> receiver, long intervalMs)); + MOCK_METHOD4(RegisterReceiver, void(int tagId, wp<PullDataReceiver> receiver, + int64_t nextPulltimeNs, int64_t intervalNs)); MOCK_METHOD2(UnRegisterReceiver, void(int tagId, wp<PullDataReceiver> receiver)); - MOCK_METHOD2(Pull, bool(const int pullCode, vector<std::shared_ptr<LogEvent>>* data)); + MOCK_METHOD3(Pull, bool(const int pullCode, const int64_t timeNs, + vector<std::shared_ptr<LogEvent>>* data)); }; class MockUidMap : public UidMap { diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index ce44a35cbf21..66215004db5f 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -152,42 +152,42 @@ AtomMatcher CreateSyncEndAtomMatcher() { } AtomMatcher CreateActivityForegroundStateChangedAtomMatcher( - const string& name, ActivityForegroundStateChanged::Activity activity) { + const string& name, ActivityForegroundStateChanged::State state) { 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(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); field_value_matcher->set_field(4); // Activity field. - field_value_matcher->set_eq_int(activity); + field_value_matcher->set_eq_int(state); return atom_matcher; } AtomMatcher CreateMoveToBackgroundAtomMatcher() { return CreateActivityForegroundStateChangedAtomMatcher( - "MoveToBackground", ActivityForegroundStateChanged::MOVE_TO_BACKGROUND); + "Background", ActivityForegroundStateChanged::BACKGROUND); } AtomMatcher CreateMoveToForegroundAtomMatcher() { return CreateActivityForegroundStateChangedAtomMatcher( - "MoveToForeground", ActivityForegroundStateChanged::MOVE_TO_FOREGROUND); + "Foreground", ActivityForegroundStateChanged::FOREGROUND); } AtomMatcher CreateProcessLifeCycleStateChangedAtomMatcher( - const string& name, ProcessLifeCycleStateChanged::Event event) { + const string& name, ProcessLifeCycleStateChanged::State state) { 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(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); field_value_matcher->set_field(3); // Process state field. - field_value_matcher->set_eq_int(event); + field_value_matcher->set_eq_int(state); return atom_matcher; } AtomMatcher CreateProcessCrashAtomMatcher() { return CreateProcessLifeCycleStateChangedAtomMatcher( - "ProcessCrashed", ProcessLifeCycleStateChanged::PROCESS_CRASHED); + "Crashed", ProcessLifeCycleStateChanged::CRASHED); } Predicate CreateScheduledJobPredicate() { @@ -241,8 +241,8 @@ Predicate CreateIsSyncingPredicate() { Predicate CreateIsInBackgroundPredicate() { Predicate predicate; predicate.set_id(StringToId("IsInBackground")); - predicate.mutable_simple_predicate()->set_start(StringToId("MoveToBackground")); - predicate.mutable_simple_predicate()->set_stop(StringToId("MoveToForeground")); + predicate.mutable_simple_predicate()->set_start(StringToId("Background")); + predicate.mutable_simple_predicate()->set_stop(StringToId("Foreground")); return predicate; } @@ -373,25 +373,25 @@ std::unique_ptr<LogEvent> CreateReleaseWakelockEvent( } std::unique_ptr<LogEvent> CreateActivityForegroundStateChangedEvent( - const int uid, const ActivityForegroundStateChanged::Activity activity, uint64_t timestampNs) { + const int uid, const ActivityForegroundStateChanged::State state, uint64_t timestampNs) { auto event = std::make_unique<LogEvent>( android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, timestampNs); event->write(uid); event->write("pkg_name"); event->write("class_name"); - event->write(activity); + event->write(state); event->init(); return event; } std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs) { return CreateActivityForegroundStateChangedEvent( - uid, ActivityForegroundStateChanged::MOVE_TO_BACKGROUND, timestampNs); + uid, ActivityForegroundStateChanged::BACKGROUND, timestampNs); } std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs) { return CreateActivityForegroundStateChangedEvent( - uid, ActivityForegroundStateChanged::MOVE_TO_FOREGROUND, timestampNs); + uid, ActivityForegroundStateChanged::FOREGROUND, timestampNs); } std::unique_ptr<LogEvent> CreateSyncStateChangedEvent( @@ -418,19 +418,19 @@ std::unique_ptr<LogEvent> CreateSyncEndEvent( } std::unique_ptr<LogEvent> CreateProcessLifeCycleStateChangedEvent( - const int uid, const ProcessLifeCycleStateChanged::Event event, uint64_t timestampNs) { + const int uid, const ProcessLifeCycleStateChanged::State state, uint64_t timestampNs) { auto logEvent = std::make_unique<LogEvent>( android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, timestampNs); logEvent->write(uid); logEvent->write(""); - logEvent->write(event); + logEvent->write(state); logEvent->init(); return logEvent; } std::unique_ptr<LogEvent> CreateAppCrashEvent(const int uid, uint64_t timestampNs) { return CreateProcessLifeCycleStateChangedEvent( - uid, ProcessLifeCycleStateChanged::PROCESS_CRASHED, timestampNs); + uid, ProcessLifeCycleStateChanged::CRASHED, timestampNs); } std::unique_ptr<LogEvent> CreateIsolatedUidChangedEvent( diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt index 3af610592732..015c4b664bc3 100644 --- a/config/hiddenapi-light-greylist.txt +++ b/config/hiddenapi-light-greylist.txt @@ -1,4 +1,5 @@ Landroid/accounts/AccountManager;->mContext:Landroid/content/Context; +Landroid/accounts/IAccountAuthenticatorResponse$Stub;-><init>()V Landroid/accounts/IAccountManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/accounts/IAccountManager; Landroid/accounts/IAccountManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/animation/LayoutTransition;->cancel()V @@ -6,6 +7,7 @@ Landroid/animation/ValueAnimator;->animateValue(F)V Landroid/animation/ValueAnimator;->sDurationScale:F Landroid/app/Activity;->getActivityOptions()Landroid/app/ActivityOptions; Landroid/app/Activity;->getActivityToken()Landroid/os/IBinder; +Landroid/app/ActivityGroup;->mLocalActivityManager:Landroid/app/LocalActivityManager; Landroid/app/Activity;->mActivityInfo:Landroid/content/pm/ActivityInfo; Landroid/app/ActivityManager;->clearApplicationUserData(Ljava/lang/String;Landroid/content/pm/IPackageDataObserver;)Z Landroid/app/ActivityManager;->getMaxRecentTasksStatic()I @@ -62,8 +64,10 @@ Landroid/app/ActivityThread$ActivityClientRecord;->token:Landroid/os/IBinder; Landroid/app/ActivityThread$AppBindData;->appInfo:Landroid/content/pm/ApplicationInfo; Landroid/app/ActivityThread$AppBindData;->info:Landroid/app/LoadedApk; Landroid/app/ActivityThread$AppBindData;->instrumentationArgs:Landroid/os/Bundle; +Landroid/app/ActivityThread$AppBindData;->persistent:Z Landroid/app/ActivityThread$AppBindData;->processName:Ljava/lang/String; Landroid/app/ActivityThread$AppBindData;->providers:Ljava/util/List; +Landroid/app/ActivityThread$AppBindData;->restrictedBackupMode:Z Landroid/app/ActivityThread$BindServiceData;->intent:Landroid/content/Intent; Landroid/app/ActivityThread$BindServiceData;->token:Landroid/os/IBinder; Landroid/app/ActivityThread$CreateServiceData;->compatInfo:Landroid/content/res/CompatibilityInfo; @@ -88,11 +92,13 @@ Landroid/app/ActivityThread;->handleBindApplication(Landroid/app/ActivityThread$ Landroid/app/ActivityThread$H;->BIND_SERVICE:I Landroid/app/ActivityThread$H;->CREATE_SERVICE:I Landroid/app/ActivityThread$H;->DUMP_PROVIDER:I +Landroid/app/ActivityThread$H;->ENTER_ANIMATION_COMPLETE:I Landroid/app/ActivityThread$H;->EXIT_APPLICATION:I Landroid/app/ActivityThread$H;->GC_WHEN_IDLE:I Landroid/app/ActivityThread$H;->INSTALL_PROVIDER:I Landroid/app/ActivityThread$H;->RECEIVER:I Landroid/app/ActivityThread$H;->REMOVE_PROVIDER:I +Landroid/app/ActivityThread$H;->SCHEDULE_CRASH:I Landroid/app/ActivityThread$H;->SERVICE_ARGS:I Landroid/app/ActivityThread$H;->STOP_SERVICE:I Landroid/app/ActivityThread$H;->UNBIND_SERVICE:I @@ -101,6 +107,7 @@ Landroid/app/ActivityThread;->installProvider(Landroid/content/Context;Landroid/ Landroid/app/ActivityThread;->mActivities:Landroid/util/ArrayMap; Landroid/app/ActivityThread;->mAllApplications:Ljava/util/ArrayList; Landroid/app/ActivityThread;->mBoundApplication:Landroid/app/ActivityThread$AppBindData; +Landroid/app/ActivityThread;->mConfiguration:Landroid/content/res/Configuration; Landroid/app/ActivityThread;->mDensityCompatMode:Z Landroid/app/ActivityThread;->mH:Landroid/app/ActivityThread$H; Landroid/app/ActivityThread;->mInitialApplication:Landroid/app/Application; @@ -109,8 +116,10 @@ Landroid/app/ActivityThread;->mLocalProvidersByName:Landroid/util/ArrayMap; Landroid/app/ActivityThread;->mLocalProviders:Landroid/util/ArrayMap; Landroid/app/ActivityThread;->mNumVisibleActivities:I Landroid/app/ActivityThread;->mPackages:Landroid/util/ArrayMap; +Landroid/app/ActivityThread;->mPendingConfiguration:Landroid/content/res/Configuration; Landroid/app/ActivityThread;->mProviderMap:Landroid/util/ArrayMap; Landroid/app/ActivityThread;->mResourcePackages:Landroid/util/ArrayMap; +Landroid/app/ActivityThread;->mResourcesManager:Landroid/app/ResourcesManager; Landroid/app/ActivityThread;->mServices:Landroid/util/ArrayMap; Landroid/app/ActivityThread;->performNewIntents(Landroid/os/IBinder;Ljava/util/List;Z)V Landroid/app/ActivityThread;->performStopActivity(Landroid/os/IBinder;ZLjava/lang/String;)V @@ -145,6 +154,7 @@ Landroid/app/AlarmManager;->WINDOW_HEURISTIC:J Landroid/app/AlertDialog$Builder;->P:Lcom/android/internal/app/AlertController$AlertParams; Landroid/app/AlertDialog;->mAlert:Lcom/android/internal/app/AlertController; Landroid/app/AppGlobals;->getInitialApplication()Landroid/app/Application; +Landroid/app/AppGlobals;->getInitialPackage()Ljava/lang/String; Landroid/app/AppGlobals;->getPackageManager()Landroid/content/pm/IPackageManager; Landroid/app/Application;->attach(Landroid/content/Context;)V Landroid/app/Application;->collectActivityLifecycleCallbacks()[Ljava/lang/Object; @@ -165,21 +175,26 @@ Landroid/app/ApplicationPackageManager;->getPackageCurrentVolume(Landroid/conten Landroid/app/ApplicationPackageManager;->getPackageSizeInfoAsUser(Ljava/lang/String;ILandroid/content/pm/IPackageStatsObserver;)V Landroid/app/ApplicationPackageManager;-><init>(Landroid/app/ContextImpl;Landroid/content/pm/IPackageManager;)V Landroid/app/ApplicationPackageManager;->mPM:Landroid/content/pm/IPackageManager; +Landroid/app/ApplicationPackageManager;->shouldShowRequestPermissionRationale(Ljava/lang/String;)Z Landroid/app/AppOpsManager;->checkOp(IILjava/lang/String;)I Landroid/app/AppOpsManager;->checkOpNoThrow(IILjava/lang/String;)I Landroid/app/AppOpsManager;->getOpsForPackage(ILjava/lang/String;[I)Ljava/util/List; Landroid/app/AppOpsManager;->mService:Lcom/android/internal/app/IAppOpsService; Landroid/app/AppOpsManager;->noteOp(I)I Landroid/app/AppOpsManager;->noteOp(IILjava/lang/String;)I +Landroid/app/AppOpsManager;->OP_AUDIO_NOTIFICATION_VOLUME:I Landroid/app/AppOpsManager;->OP_COARSE_LOCATION:I Landroid/app/AppOpsManager$OpEntry;->getDuration()I Landroid/app/AppOpsManager$OpEntry;->getRejectTime()J Landroid/app/AppOpsManager;->OP_FINE_LOCATION:I Landroid/app/AppOpsManager;->OP_GET_USAGE_STATS:I Landroid/app/AppOpsManager;->OP_POST_NOTIFICATION:I +Landroid/app/AppOpsManager;->OP_READ_CONTACTS:I Landroid/app/AppOpsManager;->OP_READ_PHONE_STATE:I Landroid/app/AppOpsManager;->OP_READ_SMS:I +Landroid/app/AppOpsManager;->OP_VIBRATE:I Landroid/app/AppOpsManager;->OP_WIFI_SCAN:I +Landroid/app/AppOpsManager;->OP_WRITE_CONTACTS:I Landroid/app/AppOpsManager;->OP_WRITE_SMS:I Landroid/app/AppOpsManager;->permissionToOpCode(Ljava/lang/String;)I Landroid/app/AppOpsManager;->strOpToOp(Ljava/lang/String;)I @@ -203,6 +218,7 @@ Landroid/app/ContextImpl;->getDisplay()Landroid/view/Display; Landroid/app/ContextImpl;->getPreferencesDir()Ljava/io/File; Landroid/app/ContextImpl;->getReceiverRestrictedContext()Landroid/content/Context; Landroid/app/ContextImpl;->mBasePackageName:Ljava/lang/String; +Landroid/app/ContextImpl;->mClassLoader:Ljava/lang/ClassLoader; Landroid/app/ContextImpl;->mContentResolver:Landroid/app/ContextImpl$ApplicationContentResolver; Landroid/app/ContextImpl;->mMainThread:Landroid/app/ActivityThread; Landroid/app/ContextImpl;->mOpPackageName:Ljava/lang/String; @@ -212,6 +228,7 @@ Landroid/app/ContextImpl;->mPackageManager:Landroid/content/pm/PackageManager; Landroid/app/ContextImpl;->mResources:Landroid/content/res/Resources; Landroid/app/ContextImpl;->mServiceCache:[Ljava/lang/Object; Landroid/app/ContextImpl;->mTheme:Landroid/content/res/Resources$Theme; +Landroid/app/ContextImpl;->mThemeResource:I Landroid/app/ContextImpl;->scheduleFinalCleanup(Ljava/lang/String;Ljava/lang/String;)V Landroid/app/ContextImpl;->setOuterContext(Landroid/content/Context;)V Landroid/app/ContextImpl;->sSharedPrefsCache:Landroid/util/ArrayMap; @@ -229,26 +246,37 @@ Landroid/app/FragmentManagerImpl;->noteStateNotSaved()V Landroid/app/Fragment;->mChildFragmentManager:Landroid/app/FragmentManagerImpl; Landroid/app/Fragment;->mWho:Ljava/lang/String; Landroid/app/IActivityManager;->bindService(Landroid/app/IApplicationThread;Landroid/os/IBinder;Landroid/content/Intent;Ljava/lang/String;Landroid/app/IServiceConnection;ILjava/lang/String;I)I +Landroid/app/IActivityManager;->broadcastIntent(Landroid/app/IApplicationThread;Landroid/content/Intent;Ljava/lang/String;Landroid/content/IIntentReceiver;ILjava/lang/String;Landroid/os/Bundle;[Ljava/lang/String;ILandroid/os/Bundle;ZZI)I Landroid/app/IActivityManager;->finishActivity(Landroid/os/IBinder;ILandroid/content/Intent;I)Z Landroid/app/IActivityManager;->finishReceiver(Landroid/os/IBinder;ILjava/lang/String;Landroid/os/Bundle;ZI)V Landroid/app/IActivityManager;->forceStopPackage(Ljava/lang/String;I)V +Landroid/app/IActivityManager;->getIntentSender(ILjava/lang/String;Landroid/os/IBinder;Ljava/lang/String;I[Landroid/content/Intent;[Ljava/lang/String;ILandroid/os/Bundle;I)Landroid/content/IIntentSender; Landroid/app/IActivityManager;->getLaunchedFromPackage(Landroid/os/IBinder;)Ljava/lang/String; +Landroid/app/IActivityManager;->getProviderMimeType(Landroid/net/Uri;I)Ljava/lang/String; Landroid/app/IActivityManager;->getTaskForActivity(Landroid/os/IBinder;Z)I Landroid/app/IActivityManager;->moveTaskToFront(IILandroid/os/Bundle;)V Landroid/app/IActivityManager;->publishContentProviders(Landroid/app/IApplicationThread;Ljava/util/List;)V +Landroid/app/IActivityManager;->requestBugReport(I)V Landroid/app/IActivityManager;->resumeAppSwitches()V Landroid/app/IActivityManager;->setRequestedOrientation(Landroid/os/IBinder;I)V Landroid/app/IActivityManager;->setTaskResizeable(II)V +Landroid/app/IActivityManager;->stopService(Landroid/app/IApplicationThread;Landroid/content/Intent;Ljava/lang/String;I)I Landroid/app/IActivityManager$Stub$Proxy;->getConfiguration()Landroid/content/res/Configuration; Landroid/app/IActivityManager$Stub$Proxy;->getLaunchedFromUid(Landroid/os/IBinder;)I +Landroid/app/IActivityManager$Stub$Proxy;->getProcessLimit()I Landroid/app/IActivityManager$Stub$Proxy;->getProcessPss([I)[J Landroid/app/IActivityManager$Stub$Proxy;->isAppForeground(I)Z Landroid/app/IActivityManager$Stub$Proxy;->mRemote:Landroid/os/IBinder; Landroid/app/IActivityManager;->unbindService(Landroid/app/IServiceConnection;)Z +Landroid/app/IActivityManager;->unstableProviderDied(Landroid/os/IBinder;)V Landroid/app/IAlarmManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/app/IAlarmManager$Stub;->TRANSACTION_remove:I Landroid/app/IAlarmManager$Stub;->TRANSACTION_set:I Landroid/app/IApplicationThread;->scheduleTrimMemory(I)V +Landroid/app/INotificationManager;->cancelAllNotifications(Ljava/lang/String;I)V +Landroid/app/INotificationManager;->cancelNotificationWithTag(Ljava/lang/String;Ljava/lang/String;II)V +Landroid/app/INotificationManager;->cancelToast(Ljava/lang/String;Landroid/app/ITransientNotification;)V +Landroid/app/INotificationManager;->enqueueToast(Ljava/lang/String;Landroid/app/ITransientNotification;I)V Landroid/app/INotificationManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/app/INotificationManager; Landroid/app/INotificationManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/app/Instrumentation;->execStartActivities(Landroid/content/Context;Landroid/os/IBinder;Landroid/os/IBinder;Landroid/app/Activity;[Landroid/content/Intent;Landroid/os/Bundle;)V @@ -256,9 +284,12 @@ Landroid/app/Instrumentation;->execStartActivity(Landroid/content/Context;Landro Landroid/app/Instrumentation;->execStartActivity(Landroid/content/Context;Landroid/os/IBinder;Landroid/os/IBinder;Ljava/lang/String;Landroid/content/Intent;ILandroid/os/Bundle;)Landroid/app/Instrumentation$ActivityResult; Landroid/app/Instrumentation;->execStartActivity(Landroid/content/Context;Landroid/os/IBinder;Landroid/os/IBinder;Ljava/lang/String;Landroid/content/Intent;ILandroid/os/Bundle;Landroid/os/UserHandle;)Landroid/app/Instrumentation$ActivityResult; Landroid/app/IntentService;->mServiceHandler:Landroid/app/IntentService$ServiceHandler; +Landroid/app/IProcessObserver$Stub;-><init>()V Landroid/app/ISearchManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/app/ISearchManager; Landroid/app/ISearchManager$Stub$Proxy;->getGlobalSearchActivity()Landroid/content/ComponentName; Landroid/app/ISearchManager$Stub$Proxy;->getWebSearchActivity()Landroid/content/ComponentName; +Landroid/app/IServiceConnection$Stub;->asInterface(Landroid/os/IBinder;)Landroid/app/IServiceConnection; +Landroid/app/IStopUserCallback;->userStopped(I)V Landroid/app/IUiModeManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/app/IWallpaperManager;->getWallpaper(Ljava/lang/String;Landroid/app/IWallpaperManagerCallback;ILandroid/os/Bundle;I)Landroid/os/ParcelFileDescriptor; Landroid/app/job/IJobScheduler$Stub;->asInterface(Landroid/os/IBinder;)Landroid/app/job/IJobScheduler; @@ -285,16 +316,21 @@ Landroid/app/LoadedApk;->mSplitResDirs:[Ljava/lang/String; Landroid/app/LoadedApk;->rewriteRValues(Ljava/lang/ClassLoader;Ljava/lang/String;I)V Landroid/app/LocalActivityManager;->mActivities:Ljava/util/Map; Landroid/app/LocalActivityManager;->mActivityArray:Ljava/util/ArrayList; +Landroid/app/LocalActivityManager;->mParent:Landroid/app/Activity; +Landroid/app/LocalActivityManager;->mResumed:Landroid/app/LocalActivityManager$LocalActivityRecord; +Landroid/app/LocalActivityManager;->mSingleMode:Z Landroid/app/NativeActivity;->hideIme(I)V Landroid/app/NativeActivity;->setWindowFlags(II)V Landroid/app/NativeActivity;->setWindowFormat(I)V Landroid/app/NativeActivity;->showIme(I)V Landroid/app/Notification$Builder;->mActions:Ljava/util/ArrayList; +Landroid/app/Notification$Builder;->makePublicContentView()Landroid/widget/RemoteViews; Landroid/app/Notification$Builder;->setChannel(Ljava/lang/String;)Landroid/app/Notification$Builder; Landroid/app/Notification;->isGroupSummary()Z Landroid/app/NotificationManager;->getService()Landroid/app/INotificationManager; Landroid/app/NotificationManager;->notifyAsUser(Ljava/lang/String;ILandroid/app/Notification;Landroid/os/UserHandle;)V Landroid/app/NotificationManager;->sService:Landroid/app/INotificationManager; +Landroid/app/Notification;->mChannelId:Ljava/lang/String; Landroid/app/Notification;->mGroupKey:Ljava/lang/String; Landroid/app/Notification;->mLargeIcon:Landroid/graphics/drawable/Icon; Landroid/app/Notification;->mSmallIcon:Landroid/graphics/drawable/Icon; @@ -307,6 +343,7 @@ Landroid/app/Presentation;->createPresentationContext(Landroid/content/Context;L Landroid/app/ProgressDialog;->mProgressNumber:Landroid/widget/TextView; Landroid/app/QueuedWork;->addFinisher(Ljava/lang/Runnable;)V Landroid/app/QueuedWork;->removeFinisher(Ljava/lang/Runnable;)V +Landroid/app/QueuedWork;->sFinishers:Ljava/util/LinkedList; Landroid/app/ResourcesManager;->appendLibAssetForMainAssetPath(Ljava/lang/String;Ljava/lang/String;)V Landroid/app/ResourcesManager;->getInstance()Landroid/app/ResourcesManager; Landroid/app/ResourcesManager;->mActivityResourceReferences:Ljava/util/WeakHashMap; @@ -320,7 +357,9 @@ Landroid/app/Service;->mStartCompatibility:Z Landroid/app/Service;->mThread:Landroid/app/ActivityThread; Landroid/app/Service;->mToken:Landroid/os/IBinder; Landroid/app/Service;->setForeground(Z)V +Landroid/app/SharedPreferencesImpl;-><init>(Ljava/io/File;I)V Landroid/app/SharedPreferencesImpl;->mFile:Ljava/io/File; +Landroid/app/SharedPreferencesImpl;->startReloadIfChangedUnexpectedly()V Landroid/app/StatusBarManager;->collapsePanels()V Landroid/app/StatusBarManager;->disable(I)V Landroid/app/StatusBarManager;->expandNotificationsPanel()V @@ -427,6 +466,7 @@ Landroid/content/IContentService$Stub;->asInterface(Landroid/os/IBinder;)Landroi Landroid/content/IContentService$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/content/Intent;->ACTION_ALARM_CHANGED:Ljava/lang/String; Landroid/content/IntentFilter;->mActions:Ljava/util/ArrayList; +Landroid/content/Intent;->mExtras:Landroid/os/Bundle; Landroid/content/Intent;->putExtra(Ljava/lang/String;Landroid/os/IBinder;)Landroid/content/Intent; Landroid/content/pm/ActivityInfo;->resizeMode:I Landroid/content/pm/ApplicationInfo;->enabledSetting:I @@ -437,18 +477,33 @@ Landroid/content/pm/ApplicationInfo;->primaryCpuAbi:Ljava/lang/String; Landroid/content/pm/ApplicationInfo;->privateFlags:I Landroid/content/pm/ApplicationInfo;->scanPublicSourceDir:Ljava/lang/String; Landroid/content/pm/ApplicationInfo;->scanSourceDir:Ljava/lang/String; +Landroid/content/pm/ApplicationInfo;->secondaryCpuAbi:Ljava/lang/String; Landroid/content/pm/ApplicationInfo;->secondaryNativeLibraryDir:Ljava/lang/String; Landroid/content/pm/ComponentInfo;->getComponentName()Landroid/content/ComponentName; +Landroid/content/pm/IPackageDataObserver$Stub;->asInterface(Landroid/os/IBinder;)Landroid/content/pm/IPackageDataObserver; +Landroid/content/pm/IPackageManager;->addPermissionAsync(Landroid/content/pm/PermissionInfo;)Z +Landroid/content/pm/IPackageManager;->addPermission(Landroid/content/pm/PermissionInfo;)Z +Landroid/content/pm/IPackageManager;->getComponentEnabledSetting(Landroid/content/ComponentName;I)I +Landroid/content/pm/IPackageManager;->getInstalledPackages(II)Landroid/content/pm/ParceledListSlice; +Landroid/content/pm/IPackageManager;->getInstallerPackageName(Ljava/lang/String;)Ljava/lang/String; Landroid/content/pm/IPackageManager;->getInstallLocation()I Landroid/content/pm/IPackageManager;->getLastChosenActivity(Landroid/content/Intent;Ljava/lang/String;I)Landroid/content/pm/ResolveInfo; +Landroid/content/pm/IPackageManager;->getProviderInfo(Landroid/content/ComponentName;II)Landroid/content/pm/ProviderInfo; +Landroid/content/pm/IPackageManager;->getReceiverInfo(Landroid/content/ComponentName;II)Landroid/content/pm/ActivityInfo; +Landroid/content/pm/IPackageManager;->getServiceInfo(Landroid/content/ComponentName;II)Landroid/content/pm/ServiceInfo; Landroid/content/pm/IPackageManager;->setApplicationEnabledSetting(Ljava/lang/String;IIILjava/lang/String;)V Landroid/content/pm/IPackageManager;->setComponentEnabledSetting(Landroid/content/ComponentName;III)V +Landroid/content/pm/IPackageManager;->setInstallerPackageName(Ljava/lang/String;Ljava/lang/String;)V Landroid/content/pm/IPackageManager;->setLastChosenActivity(Landroid/content/Intent;Ljava/lang/String;ILandroid/content/IntentFilter;ILandroid/content/ComponentName;)V Landroid/content/pm/IPackageManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/content/pm/IPackageManager; +Landroid/content/pm/IPackageManager$Stub$Proxy;->getInstalledPackages(II)Landroid/content/pm/ParceledListSlice; Landroid/content/pm/IPackageManager$Stub$Proxy;->getPackageInfo(Ljava/lang/String;II)Landroid/content/pm/PackageInfo; Landroid/content/pm/IPackageManager$Stub$Proxy;->getPackagesForUid(I)[Ljava/lang/String; Landroid/content/pm/IPackageManager$Stub$Proxy;->getSystemSharedLibraryNames()[Ljava/lang/String; +Landroid/content/pm/IPackageMoveObserver$Stub;->asInterface(Landroid/os/IBinder;)Landroid/content/pm/IPackageMoveObserver; +Landroid/content/pm/IPackageMoveObserver$Stub;-><init>()V Landroid/content/pm/IPackageStatsObserver$Stub;-><init>()V +Landroid/content/pm/IShortcutService$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/content/pm/LauncherActivityInfo;->mActivityInfo:Landroid/content/pm/ActivityInfo; Landroid/content/pm/LauncherApps;->mPm:Landroid/content/pm/PackageManager; Landroid/content/pm/LauncherApps;->startShortcut(Ljava/lang/String;Ljava/lang/String;Landroid/graphics/Rect;Landroid/os/Bundle;I)V @@ -473,14 +528,29 @@ Landroid/content/pm/PackageParser;->collectCertificates(Landroid/content/pm/Pack Landroid/content/pm/PackageParser$Component;->className:Ljava/lang/String; Landroid/content/pm/PackageParser$Component;->getComponentName()Landroid/content/ComponentName; Landroid/content/pm/PackageParser$Component;->intents:Ljava/util/ArrayList; +Landroid/content/pm/PackageParser;->generateActivityInfo(Landroid/content/pm/PackageParser$Activity;ILandroid/content/pm/PackageUserState;I)Landroid/content/pm/ActivityInfo; Landroid/content/pm/PackageParser;->generatePackageInfo(Landroid/content/pm/PackageParser$Package;[IIJJLjava/util/Set;Landroid/content/pm/PackageUserState;I)Landroid/content/pm/PackageInfo; Landroid/content/pm/PackageParser;->generatePackageInfo(Landroid/content/pm/PackageParser$Package;[IIJJLjava/util/Set;Landroid/content/pm/PackageUserState;)Landroid/content/pm/PackageInfo; +Landroid/content/pm/PackageParser;->generateProviderInfo(Landroid/content/pm/PackageParser$Provider;ILandroid/content/pm/PackageUserState;I)Landroid/content/pm/ProviderInfo; +Landroid/content/pm/PackageParser;->generateServiceInfo(Landroid/content/pm/PackageParser$Service;ILandroid/content/pm/PackageUserState;I)Landroid/content/pm/ServiceInfo; Landroid/content/pm/PackageParser;-><init>()V +Landroid/content/pm/PackageParser$Instrumentation;->info:Landroid/content/pm/InstrumentationInfo; +Landroid/content/pm/PackageParser$IntentInfo;->banner:I +Landroid/content/pm/PackageParser$IntentInfo;->hasDefault:Z +Landroid/content/pm/PackageParser$IntentInfo;->icon:I +Landroid/content/pm/PackageParser$IntentInfo;-><init>()V +Landroid/content/pm/PackageParser$IntentInfo;->labelRes:I +Landroid/content/pm/PackageParser$IntentInfo;->logo:I +Landroid/content/pm/PackageParser$IntentInfo;->nonLocalizedLabel:Ljava/lang/CharSequence; Landroid/content/pm/PackageParser$Package;->activities:Ljava/util/ArrayList; Landroid/content/pm/PackageParser$Package;->applicationInfo:Landroid/content/pm/ApplicationInfo; +Landroid/content/pm/PackageParser$Package;->instrumentation:Ljava/util/ArrayList; +Landroid/content/pm/PackageParser$Package;->mAppMetaData:Landroid/os/Bundle; Landroid/content/pm/PackageParser$Package;->mVersionCode:I Landroid/content/pm/PackageParser$Package;->mVersionName:Ljava/lang/String; Landroid/content/pm/PackageParser$Package;->packageName:Ljava/lang/String; +Landroid/content/pm/PackageParser$Package;->permissionGroups:Ljava/util/ArrayList; +Landroid/content/pm/PackageParser$Package;->permissions:Ljava/util/ArrayList; Landroid/content/pm/PackageParser$Package;->providers:Ljava/util/ArrayList; Landroid/content/pm/PackageParser$Package;->receivers:Ljava/util/ArrayList; Landroid/content/pm/PackageParser$Package;->requestedPermissions:Ljava/util/ArrayList; @@ -491,6 +561,7 @@ Landroid/content/pm/PackageParser;->parsePackage(Ljava/io/File;I)Landroid/conten Landroid/content/pm/PackageParser;->parsePackage(Ljava/io/File;IZ)Landroid/content/pm/PackageParser$Package; Landroid/content/pm/PackageParser$Provider;->info:Landroid/content/pm/ProviderInfo; Landroid/content/pm/PackageParser$ProviderIntentInfo;->provider:Landroid/content/pm/PackageParser$Provider; +Landroid/content/pm/PackageParser$Service;->info:Landroid/content/pm/ServiceInfo; Landroid/content/pm/PackageParser$ServiceIntentInfo;->service:Landroid/content/pm/PackageParser$Service; Landroid/content/pm/PackageUserState;-><init>()V Landroid/content/pm/ParceledListSlice;-><init>(Ljava/util/List;)V @@ -529,14 +600,18 @@ Landroid/content/res/ColorStateList;->mFactory:Landroid/content/res/ColorStateLi Landroid/content/res/CompatibilityInfo;->applicationScale:F Landroid/content/res/CompatibilityInfo;->DEFAULT_COMPATIBILITY_INFO:Landroid/content/res/CompatibilityInfo; Landroid/content/res/DrawableCache;->getInstance(JLandroid/content/res/Resources;Landroid/content/res/Resources$Theme;)Landroid/graphics/drawable/Drawable; +Landroid/content/res/DrawableCache;-><init>()V Landroid/content/res/ObbInfo;->salt:[B Landroid/content/res/Resources;->getCompatibilityInfo()Landroid/content/res/CompatibilityInfo; +Landroid/content/res/ResourcesImpl;->getAssets()Landroid/content/res/AssetManager; Landroid/content/res/ResourcesImpl;->mAccessLock:Ljava/lang/Object; +Landroid/content/res/ResourcesImpl;->mAnimatorCache:Landroid/content/res/ConfigurationBoundResourceCache; Landroid/content/res/ResourcesImpl;->mAssets:Landroid/content/res/AssetManager; Landroid/content/res/ResourcesImpl;->mColorDrawableCache:Landroid/content/res/DrawableCache; Landroid/content/res/ResourcesImpl;->mConfiguration:Landroid/content/res/Configuration; Landroid/content/res/ResourcesImpl;->mDrawableCache:Landroid/content/res/DrawableCache; Landroid/content/res/ResourcesImpl;->mPreloading:Z +Landroid/content/res/ResourcesImpl;->mStateListAnimatorCache:Landroid/content/res/ConfigurationBoundResourceCache; Landroid/content/res/ResourcesImpl;->sPreloadedColorDrawables:Landroid/util/LongSparseArray; Landroid/content/res/ResourcesImpl;->sPreloadedComplexColors:Landroid/util/LongSparseArray; Landroid/content/res/ResourcesImpl;->sPreloadedDrawables:[Landroid/util/LongSparseArray; @@ -552,6 +627,7 @@ Landroid/content/res/Resources;->mTypedArrayPool:Landroid/util/Pools$Synchronize Landroid/content/res/Resources;->setCompatibilityInfo(Landroid/content/res/CompatibilityInfo;)V Landroid/content/res/Resources;->updateSystemConfiguration(Landroid/content/res/Configuration;Landroid/util/DisplayMetrics;Landroid/content/res/CompatibilityInfo;)V Landroid/content/res/StringBlock;-><init>(JZ)V +Landroid/content/res/ThemedResourceCache;->onConfigurationChange(I)V Landroid/content/res/TypedArray;->extractThemeAttrs()[I Landroid/content/res/TypedArray;->getNonConfigurationString(II)Ljava/lang/String; Landroid/content/res/TypedArray;->getValueAt(ILandroid/util/TypedValue;)Z @@ -570,7 +646,10 @@ Landroid/content/res/XmlBlock;->newParser()Landroid/content/res/XmlResourceParse Landroid/content/res/XmlBlock$Parser;->mBlock:Landroid/content/res/XmlBlock; Landroid/content/res/XmlBlock$Parser;->mParseState:J Landroid/content/SearchRecentSuggestionsProvider;->mSuggestionProjection:[Ljava/lang/String; +Landroid/content/SyncContext;->setStatusText(Ljava/lang/String;)V Landroid/content/SyncStatusInfo;->lastSuccessTime:J +Landroid/content/UriMatcher;->mChildren:Ljava/util/ArrayList; +Landroid/content/UriMatcher;->mText:Ljava/lang/String; Landroid/database/AbstractCursor;->mExtras:Landroid/os/Bundle; Landroid/database/AbstractCursor;->mNotifyUri:Landroid/net/Uri; Landroid/database/AbstractCursor;->mRowIdColumnIndex:I @@ -589,6 +668,7 @@ Landroid/database/sqlite/SQLiteDebug$PagerStats;->memoryUsed:I Landroid/database/sqlite/SQLiteDebug$PagerStats;->pageCacheOverflow:I Landroid/database/sqlite/SQLiteOpenHelper;->mName:Ljava/lang/String; Landroid/ddm/DdmHandleAppName;->getAppName()Ljava/lang/String; +Landroid/graphics/AvoidXfermode$Mode;->AVOID:Landroid/graphics/AvoidXfermode$Mode; Landroid/graphics/AvoidXfermode$Mode;->TARGET:Landroid/graphics/AvoidXfermode$Mode; Landroid/graphics/BaseCanvas;->mNativeCanvasWrapper:J Landroid/graphics/Bitmap$Config;->nativeInt:I @@ -609,6 +689,11 @@ Landroid/graphics/Bitmap;->reinit(IIZ)V Landroid/graphics/Camera;->native_instance:J Landroid/graphics/Canvas;-><init>(J)V Landroid/graphics/Canvas;->release()V +Landroid/graphics/Canvas;->save(I)I +Landroid/graphics/Canvas;->saveLayerAlpha(FFFFII)I +Landroid/graphics/Canvas;->saveLayerAlpha(Landroid/graphics/RectF;II)I +Landroid/graphics/Canvas;->saveLayer(FFFFLandroid/graphics/Paint;I)I +Landroid/graphics/Canvas;->saveLayer(Landroid/graphics/RectF;Landroid/graphics/Paint;I)I Landroid/graphics/ColorMatrixColorFilter;->setColorMatrix(Landroid/graphics/ColorMatrix;)V Landroid/graphics/drawable/AnimatedImageDrawable;->onAnimationEnd()V Landroid/graphics/drawable/AnimatedStateListDrawable$AnimatedStateListState;->mStateIds:Landroid/util/SparseIntArray; @@ -621,6 +706,7 @@ Landroid/graphics/drawable/BitmapDrawable;->getTint()Landroid/content/res/ColorS Landroid/graphics/drawable/BitmapDrawable;->getTintMode()Landroid/graphics/PorterDuff$Mode; Landroid/graphics/drawable/BitmapDrawable;->mTargetDensity:I Landroid/graphics/drawable/BitmapDrawable;->setBitmap(Landroid/graphics/Bitmap;)V +Landroid/graphics/drawable/ColorDrawable$ColorState;->mUseColor:I Landroid/graphics/drawable/DrawableContainer$DrawableContainerState;->mConstantPadding:Landroid/graphics/Rect; Landroid/graphics/drawable/DrawableContainer;->getOpticalInsets()Landroid/graphics/Insets; Landroid/graphics/drawable/DrawableContainer;->mDrawableContainerState:Landroid/graphics/drawable/DrawableContainer$DrawableContainerState; @@ -764,6 +850,7 @@ Landroid/hardware/Camera;->openLegacy(II)Landroid/hardware/Camera; Landroid/hardware/Camera;->postEventFromNative(Ljava/lang/Object;IIILjava/lang/Object;)V Landroid/hardware/display/WifiDisplayStatus;->mActiveDisplay:Landroid/hardware/display/WifiDisplay; Landroid/hardware/display/WifiDisplayStatus;->mDisplays:[Landroid/hardware/display/WifiDisplay; +Landroid/hardware/fingerprint/IFingerprintService$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/hardware/HardwareBuffer;-><init>(J)V Landroid/hardware/HardwareBuffer;->mNativeObject:J Landroid/hardware/input/IInputManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/input/IInputManager; @@ -842,6 +929,9 @@ Landroid/icu/text/Transliterator;->transliterate(Ljava/lang/String;)Ljava/lang/S Landroid/icu/text/UFormat;->getLocale(Landroid/icu/util/ULocale$Type;)Landroid/icu/util/ULocale; Landroid/icu/util/Calendar;->getLocale(Landroid/icu/util/ULocale$Type;)Landroid/icu/util/ULocale; Landroid/inputmethodservice/InputMethodService;->mExtractEditText:Landroid/inputmethodservice/ExtractEditText; +Landroid/inputmethodservice/InputMethodService;->mRootView:Landroid/view/View; +Landroid/inputmethodservice/InputMethodService;->mSettingsObserver:Landroid/inputmethodservice/InputMethodService$SettingsObserver; +Landroid/inputmethodservice/InputMethodService$SettingsObserver;->shouldShowImeWithHardKeyboard()Z Landroid/location/CountryDetector;->detectCountry()Landroid/location/Country; Landroid/location/Country;->getCountryIso()Ljava/lang/String; Landroid/location/Country;->getSource()I @@ -938,11 +1028,15 @@ Landroid/media/AudioTrack;->mNativeTrackInJavaObj:J Landroid/media/AudioTrack;->mStreamType:I Landroid/media/AudioTrack;->native_release()V Landroid/media/AudioTrack;->postEventFromNative(Ljava/lang/Object;IIILjava/lang/Object;)V +Landroid/media/ExifInterface;->getDateTime()J Landroid/media/IAudioService;->getStreamMaxVolume(I)I Landroid/media/IAudioService;->getStreamVolume(I)I Landroid/media/IAudioService;->setStreamVolume(IIILjava/lang/String;)V Landroid/media/IAudioService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/media/IAudioService; Landroid/media/IAudioService$Stub$Proxy;-><init>(Landroid/os/IBinder;)V +Landroid/media/IMediaScannerService;->scanFile(Ljava/lang/String;Ljava/lang/String;)V +Landroid/media/IMediaScannerService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/media/IMediaScannerService; +Landroid/media/IRemoteDisplayCallback;->onStateChanged(Landroid/media/RemoteDisplayState;)V Landroid/media/IVolumeController$Stub;->asInterface(Landroid/os/IBinder;)Landroid/media/IVolumeController; Landroid/media/JetPlayer;->mNativePlayerInJavaObj:J Landroid/media/JetPlayer;->postEventFromNative(Ljava/lang/Object;III)V @@ -995,6 +1089,8 @@ Landroid/media/PlaybackParams;->SET_SPEED:I Landroid/media/RemoteDisplay;->notifyDisplayConnected(Landroid/view/Surface;IIII)V Landroid/media/RemoteDisplay;->notifyDisplayDisconnected()V Landroid/media/RemoteDisplay;->notifyDisplayError(I)V +Landroid/media/RemoteDisplayState;->displays:Ljava/util/ArrayList; +Landroid/media/RemoteDisplayState;-><init>()V Landroid/media/RingtoneManager;->getRingtone(Landroid/content/Context;Landroid/net/Uri;I)Landroid/media/Ringtone; Landroid/media/session/MediaSessionLegacyHelper;->getHelper(Landroid/content/Context;)Landroid/media/session/MediaSessionLegacyHelper; Landroid/media/session/MediaSession;->mCallback:Landroid/media/session/MediaSession$CallbackMessageHandler; @@ -1006,6 +1102,11 @@ Landroid/media/soundtrigger/SoundTriggerManager;->startRecognition(Ljava/util/UU Landroid/media/soundtrigger/SoundTriggerManager;->stopRecognition(Ljava/util/UUID;)I Landroid/media/soundtrigger/SoundTriggerManager;->unloadSoundModel(Ljava/util/UUID;)I Landroid/media/SubtitleController;->mHandler:Landroid/os/Handler; +Landroid/media/SubtitleTrack$RenderingWidget;->draw(Landroid/graphics/Canvas;)V +Landroid/media/SubtitleTrack$RenderingWidget;->onAttachedToWindow()V +Landroid/media/SubtitleTrack$RenderingWidget;->onDetachedFromWindow()V +Landroid/media/SubtitleTrack$RenderingWidget;->setOnChangedListener(Landroid/media/SubtitleTrack$RenderingWidget$OnChangedListener;)V +Landroid/media/SubtitleTrack$RenderingWidget;->setSize(II)V Landroid/media/ThumbnailUtils;->createImageThumbnail(Ljava/lang/String;I)Landroid/graphics/Bitmap; Landroid/media/ToneGenerator;->mNativeContext:J Landroid/media/VolumeShaper$Configuration;-><init>(IIIDI[F[F)V @@ -1045,11 +1146,16 @@ Landroid/net/ConnectivityManager;->TYPE_NONE:I Landroid/net/ConnectivityManager;->TYPE_PROXY:I Landroid/net/ConnectivityManager;->TYPE_WIFI_P2P:I Landroid/net/IConnectivityManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/IConnectivityManager; +Landroid/net/IConnectivityManager$Stub$Proxy;->getActiveLinkProperties()Landroid/net/LinkProperties; +Landroid/net/IConnectivityManager$Stub$Proxy;->getActiveNetworkInfo()Landroid/net/NetworkInfo; +Landroid/net/IConnectivityManager$Stub$Proxy;->getAllNetworkInfo()[Landroid/net/NetworkInfo; Landroid/net/IConnectivityManager$Stub$Proxy;->getAllNetworks()[Landroid/net/Network; Landroid/net/IConnectivityManager$Stub$Proxy;->getTetherableIfaces()[Ljava/lang/String; Landroid/net/IConnectivityManager$Stub$Proxy;->getTetherableUsbRegexs()[Ljava/lang/String; +Landroid/net/IConnectivityManager$Stub$Proxy;->getTetheredIfaces()[Ljava/lang/String; Landroid/net/IConnectivityManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/net/INetworkStatsService$Stub$Proxy;->getMobileIfaces()[Ljava/lang/String; +Landroid/net/INetworkStatsService$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/net/IpConfiguration;->httpProxy:Landroid/net/ProxyInfo; Landroid/net/LinkProperties;->setHttpProxy(Landroid/net/ProxyInfo;)V Landroid/net/LocalSocketImpl;->inboundFileDescriptors:[Ljava/io/FileDescriptor; @@ -1073,6 +1179,7 @@ Landroid/net/NetworkStats;->txBytes:[J Landroid/net/NetworkStats;->txPackets:[J Landroid/net/NetworkStats;->uid:[I Landroid/net/NetworkTemplate;->buildTemplateWifi()Landroid/net/NetworkTemplate; +Landroid/net/Proxy;->getProxy(Landroid/content/Context;Ljava/lang/String;)Ljava/net/Proxy; Landroid/net/ProxyInfo;-><init>(Ljava/lang/String;ILjava/lang/String;)V Landroid/net/SntpClient;-><init>()V Landroid/net/SSLCertificateSocketFactory;->castToOpenSSLSocket(Ljava/net/Socket;)Lcom/android/org/conscrypt/OpenSSLSocketImpl; @@ -1099,6 +1206,7 @@ Landroid/net/SSLCertificateSocketFactory;->setSoWriteTimeout(Ljava/net/Socket;I) Landroid/net/SSLCertificateSocketFactory;->TAG:Ljava/lang/String; Landroid/net/SSLCertificateSocketFactory;->verifyHostname(Ljava/net/Socket;Ljava/lang/String;)V Landroid/net/SSLSessionCache;->mSessionCache:Lcom/android/org/conscrypt/SSLClientSessionCache; +Landroid/net/TrafficStats;->getMobileIfaces()[Ljava/lang/String; Landroid/net/TrafficStats;->getRxBytes(Ljava/lang/String;)J Landroid/net/TrafficStats;->getStatsService()Landroid/net/INetworkStatsService; Landroid/net/TrafficStats;->getTxBytes(Ljava/lang/String;)J @@ -1107,6 +1215,8 @@ Landroid/net/wifi/IWifiManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/ Landroid/net/wifi/IWifiManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/net/wifi/p2p/WifiP2pGroup;->getNetworkId()I Landroid/net/wifi/p2p/WifiP2pGroupList;->getGroupList()Ljava/util/Collection; +Landroid/net/wifi/p2p/WifiP2pManager$Channel;->mAsyncChannel:Lcom/android/internal/util/AsyncChannel; +Landroid/net/wifi/p2p/WifiP2pManager$Channel;->putListener(Ljava/lang/Object;)I Landroid/net/wifi/p2p/WifiP2pManager;->deletePersistentGroup(Landroid/net/wifi/p2p/WifiP2pManager$Channel;ILandroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V Landroid/net/wifi/p2p/WifiP2pManager;->requestPersistentGroupInfo(Landroid/net/wifi/p2p/WifiP2pManager$Channel;Landroid/net/wifi/p2p/WifiP2pManager$PersistentGroupInfoListener;)V Landroid/net/wifi/p2p/WifiP2pManager;->setDeviceName(Landroid/net/wifi/p2p/WifiP2pManager$Channel;Ljava/lang/String;Landroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V @@ -1140,10 +1250,12 @@ Landroid/net/wifi/ScanResult;->wifiSsid:Landroid/net/wifi/WifiSsid; Landroid/net/wifi/WifiConfiguration;->apBand:I Landroid/net/wifi/WifiConfiguration;->apChannel:I Landroid/net/wifi/WifiConfiguration;->defaultGwMacAddress:Ljava/lang/String; +Landroid/net/wifi/WifiConfiguration;->lastConnectUid:I Landroid/net/wifi/WifiConfiguration;->mIpConfiguration:Landroid/net/IpConfiguration; Landroid/net/wifi/WifiConfiguration;->validatedInternetAccess:Z Landroid/net/wifi/WifiEnterpriseConfig;->getCaCertificateAlias()Ljava/lang/String; Landroid/net/wifi/WifiEnterpriseConfig;->getClientCertificateAlias()Ljava/lang/String; +Landroid/net/wifi/WifiInfo;->DEFAULT_MAC_ADDRESS:Ljava/lang/String; Landroid/net/wifi/WifiInfo;->getMeteredHint()Z Landroid/net/wifi/WifiInfo;->mMacAddress:Ljava/lang/String; Landroid/net/wifi/WifiInfo;->removeDoubleQuotes(Ljava/lang/String;)Ljava/lang/String; @@ -1165,17 +1277,30 @@ Landroid/os/AsyncTask;->mTaskInvoked:Ljava/util/concurrent/atomic/AtomicBoolean; Landroid/os/AsyncTask;->mWorker:Landroid/os/AsyncTask$WorkerRunnable; Landroid/os/AsyncTask;->sDefaultExecutor:Ljava/util/concurrent/Executor; Landroid/os/AsyncTask;->setDefaultExecutor(Ljava/util/concurrent/Executor;)V +Landroid/os/BatteryStats$Counter;->getCountLocked(I)I Landroid/os/BatteryStats;->getUidStats()Landroid/util/SparseArray; Landroid/os/BatteryStats$HistoryItem;->states2:I Landroid/os/BatteryStats;->NUM_DATA_CONNECTION_TYPES:I Landroid/os/BatteryStats;->startIteratingHistoryLocked()Z Landroid/os/BatteryStats$Timer;->getTotalTimeLocked(JI)J +Landroid/os/BatteryStats$Uid;->getAudioTurnedOnTimer()Landroid/os/BatteryStats$Timer; Landroid/os/BatteryStats$Uid;->getFullWifiLockTime(JI)J +Landroid/os/BatteryStats$Uid;->getPackageStats()Landroid/util/ArrayMap; Landroid/os/BatteryStats$Uid;->getProcessStats()Landroid/util/ArrayMap; Landroid/os/BatteryStats$Uid;->getSensorStats()Landroid/util/SparseArray; Landroid/os/BatteryStats$Uid;->getUid()I +Landroid/os/BatteryStats$Uid;->getVideoTurnedOnTimer()Landroid/os/BatteryStats$Timer; Landroid/os/BatteryStats$Uid;->getWifiMulticastTime(JI)J Landroid/os/BatteryStats$Uid;->getWifiScanTime(JI)J +Landroid/os/BatteryStats$Uid$Pkg;->getServiceStats()Landroid/util/ArrayMap; +Landroid/os/BatteryStats$Uid$Pkg;->getWakeupAlarmStats()Landroid/util/ArrayMap; +Landroid/os/BatteryStats$Uid$Pkg$Serv;->getLaunches(I)I +Landroid/os/BatteryStats$Uid$Pkg$Serv;->getStartTime(JI)J +Landroid/os/BatteryStats$Uid$Proc;->countExcessivePowers()I +Landroid/os/BatteryStats$Uid$Proc$ExcessivePower;->overTime:J +Landroid/os/BatteryStats$Uid$Proc$ExcessivePower;->type:I +Landroid/os/BatteryStats$Uid$Proc$ExcessivePower;->usedTime:J +Landroid/os/BatteryStats$Uid$Proc;->getExcessivePower(I)Landroid/os/BatteryStats$Uid$Proc$ExcessivePower; Landroid/os/BatteryStats$Uid$Proc;->getForegroundTime(I)J Landroid/os/BatteryStats$Uid$Proc;->getSystemTime(I)J Landroid/os/BatteryStats$Uid$Proc;->getUserTime(I)J @@ -1222,6 +1347,7 @@ Landroid/os/Debug$MemoryInfo;->otherSwappedOut:I Landroid/os/Debug$MemoryInfo;->otherSwappedOutPss:I Landroid/os/Environment;->buildExternalStorageAppDataDirs(Ljava/lang/String;)[Ljava/io/File; Landroid/os/Environment;->getVendorDirectory()Ljava/io/File; +Landroid/os/Environment;->maybeTranslateEmulatedPathToInternal(Ljava/io/File;)Ljava/io/File; Landroid/os/FileObserver$ObserverThread;->onEvent(IILjava/lang/String;)V Landroid/os/FileUtils;->checksumCrc32(Ljava/io/File;)J Landroid/os/FileUtils;->copyFile(Ljava/io/File;Ljava/io/File;)Z @@ -1233,6 +1359,7 @@ Landroid/os/FileUtils;->setPermissions(Ljava/io/File;III)I Landroid/os/FileUtils;->setPermissions(Ljava/lang/String;III)I Landroid/os/FileUtils;->stringToFile(Ljava/io/File;Ljava/lang/String;)V Landroid/os/FileUtils;->stringToFile(Ljava/lang/String;Ljava/lang/String;)V +Landroid/os/FileUtils;->sync(Ljava/io/FileOutputStream;)Z Landroid/os/Handler;->getIMessenger()Landroid/os/IMessenger; Landroid/os/Handler;->hasCallbacks(Ljava/lang/Runnable;)Z Landroid/os/Handler;-><init>(Z)V @@ -1247,6 +1374,7 @@ Landroid/os/IPowerManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/IP Landroid/os/IPowerManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/os/IPowerManager$Stub$Proxy;->isLightDeviceIdleMode()Z Landroid/os/IPowerManager;->userActivity(JII)V +Landroid/os/IServiceManager;->checkService(Ljava/lang/String;)Landroid/os/IBinder; Landroid/os/IServiceManager;->getService(Ljava/lang/String;)Landroid/os/IBinder; Landroid/os/IUserManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/os/Looper;->mQueue:Landroid/os/MessageQueue; @@ -1290,6 +1418,7 @@ Landroid/os/Process;->getUidForPid(I)I Landroid/os/Process;->isIsolated(I)Z Landroid/os/Process;->readProcFile(Ljava/lang/String;[I[Ljava/lang/String;[J[F)Z Landroid/os/Process;->readProcLines(Ljava/lang/String;[Ljava/lang/String;[J)V +Landroid/os/Process;->setArgV0(Ljava/lang/String;)V Landroid/os/SELinux;->isSELinuxEnabled()Z Landroid/os/SELinux;->isSELinuxEnforced()Z Landroid/os/ServiceManager;->addService(Ljava/lang/String;Landroid/os/IBinder;)V @@ -1451,13 +1580,16 @@ Landroid/preference/PreferenceScreen;->mRootAdapter:Landroid/widget/ListAdapter; Landroid/print/PrinterId;->getServiceName()Landroid/content/ComponentName; Landroid/print/PrintJobInfo;->getAdvancedOptions()Landroid/os/Bundle; Landroid/print/PrintJobInfo;->getDocumentInfo()Landroid/print/PrintDocumentInfo; +Landroid/provider/Browser$BookmarkColumns;->DATE:Ljava/lang/String; Landroid/provider/Browser;->BOOKMARKS_URI:Landroid/net/Uri; Landroid/provider/Browser;->canClearHistory(Landroid/content/ContentResolver;)Z Landroid/provider/Browser;->clearHistory(Landroid/content/ContentResolver;)V Landroid/provider/Browser;->clearSearches(Landroid/content/ContentResolver;)V Landroid/provider/Browser;->deleteFromHistory(Landroid/content/ContentResolver;Ljava/lang/String;)V Landroid/provider/Browser;->getVisitedHistory(Landroid/content/ContentResolver;)[Ljava/lang/String; +Landroid/provider/Browser;->SEARCHES_URI:Landroid/net/Uri; Landroid/provider/Browser;->sendString(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)V +Landroid/provider/Browser;->updateVisitedHistory(Landroid/content/ContentResolver;Ljava/lang/String;Z)V Landroid/provider/CalendarContract$CalendarAlerts;->findNextAlarmTime(Landroid/content/ContentResolver;J)J Landroid/provider/CalendarContract$CalendarAlerts;->rescheduleMissedAlarms(Landroid/content/ContentResolver;Landroid/content/Context;Landroid/app/AlarmManager;)V Landroid/provider/Settings$ContentProviderHolder;->mContentProvider:Landroid/content/IContentProvider; @@ -1685,6 +1817,7 @@ Landroid/R$styleable;->View_visibility:I Landroid/R$styleable;->Window:[I Landroid/R$styleable;->Window_windowBackground:I Landroid/R$styleable;->Window_windowFrame:I +Landroid/security/keystore/AndroidKeyStoreProvider;->getKeyStoreOperationHandle(Ljava/lang/Object;)J Landroid/security/KeyStore;->getInstance()Landroid/security/KeyStore; Landroid/security/keystore/KeychainProtectionParams;->clearSecret()V Landroid/security/keystore/KeychainProtectionParams;->getKeyDerivationParams()Landroid/security/keystore/KeyDerivationParams; @@ -1833,6 +1966,7 @@ Landroid/telephony/SmsMessage;->mWrappedSmsMessage:Lcom/android/internal/telepho Landroid/telephony/SubscriptionManager;->getActiveSubscriptionIdList()[I Landroid/telephony/SubscriptionManager;->getAllSubscriptionInfoCount()I Landroid/telephony/SubscriptionManager;->getAllSubscriptionInfoList()Ljava/util/List; +Landroid/telephony/SubscriptionManager;->getDefaultDataPhoneId()I Landroid/telephony/SubscriptionManager;->getDefaultDataSubscriptionInfo()Landroid/telephony/SubscriptionInfo; Landroid/telephony/SubscriptionManager;->getDefaultSmsPhoneId()I Landroid/telephony/SubscriptionManager;->getDefaultVoiceSubscriptionInfo()Landroid/telephony/SubscriptionInfo; @@ -1872,6 +2006,7 @@ Landroid/telephony/TelephonyManager;-><init>()V Landroid/telephony/TelephonyManager;->isMultiSimEnabled()Z Landroid/telephony/TelephonyManager;->isNetworkRoaming(I)Z Landroid/telephony/TelephonyManager;->isVolteAvailable()Z +Landroid/telephony/TelephonyManager;->mSubscriptionManager:Landroid/telephony/SubscriptionManager; Landroid/text/AndroidBidi;->bidi(I[C[B)I Landroid/text/DynamicLayout;-><init>(Ljava/lang/CharSequence;Ljava/lang/CharSequence;Landroid/text/TextPaint;ILandroid/text/Layout$Alignment;Landroid/text/TextDirectionHeuristic;FFZIIILandroid/text/TextUtils$TruncateAt;I)V Landroid/text/DynamicLayout;->sStaticLayout:Landroid/text/StaticLayout; @@ -1928,6 +2063,7 @@ Landroid/text/StaticLayout$LineBreaks;->widths:[F Landroid/text/StaticLayout;->mColumns:I Landroid/text/StaticLayout;->mLineCount:I Landroid/text/StaticLayout;->mLines:[I +Landroid/text/StaticLayout;->mMaximumVisibleLineCount:I Landroid/text/TextLine;->mCharacterStyleSpanSet:Landroid/text/SpanSet; Landroid/text/TextLine;->mMetricAffectingSpanSpanSet:Landroid/text/SpanSet; Landroid/text/TextLine;->mReplacementSpanSpanSet:Landroid/text/SpanSet; @@ -1959,13 +2095,17 @@ Landroid/util/Pools$SynchronizedPool;->acquire()Ljava/lang/Object; Landroid/util/Pools$SynchronizedPool;-><init>(I)V Landroid/util/Rational;->mDenominator:I Landroid/util/Rational;->mNumerator:I +Landroid/util/Singleton;->get()Ljava/lang/Object; +Landroid/util/Singleton;-><init>()V Landroid/util/Singleton;->mInstance:Ljava/lang/Object; Landroid/util/Slog;->d(Ljava/lang/String;Ljava/lang/String;)I +Landroid/util/Slog;->w(Ljava/lang/String;Ljava/lang/String;)I Landroid/util/SparseIntArray;->mKeys:[I Landroid/util/SparseIntArray;->mSize:I Landroid/util/SparseIntArray;->mValues:[I Landroid/view/accessibility/AccessibilityManager;->getInstance(Landroid/content/Context;)Landroid/view/accessibility/AccessibilityManager; Landroid/view/accessibility/AccessibilityManager;->isHighTextContrastEnabled()Z +Landroid/view/accessibility/AccessibilityManager;->mAccessibilityStateChangeListeners:Landroid/util/ArrayMap; Landroid/view/accessibility/AccessibilityManager;->mIsEnabled:Z Landroid/view/accessibility/AccessibilityManager;->mIsHighTextContrastEnabled:Z Landroid/view/accessibility/AccessibilityManager;->sInstance:Landroid/view/accessibility/AccessibilityManager; @@ -2015,6 +2155,7 @@ Landroid/view/InputEventSender;->dispatchInputEventFinished(IZ)V Landroid/view/inputmethod/InputMethodInfo;->mSubtypes:Landroid/view/inputmethod/InputMethodSubtypeArray; Landroid/view/inputmethod/InputMethodManager;->finishInputLocked()V Landroid/view/inputmethod/InputMethodManager;->focusIn(Landroid/view/View;)V +Landroid/view/inputmethod/InputMethodManager;->focusOut(Landroid/view/View;)V Landroid/view/inputmethod/InputMethodManager;->getInputMethodWindowVisibleHeight()I Landroid/view/inputmethod/InputMethodManager;->mCurId:Ljava/lang/String; Landroid/view/inputmethod/InputMethodManager;->mCurRootView:Landroid/view/View; @@ -2023,6 +2164,7 @@ Landroid/view/inputmethod/InputMethodManager;->mNextServedView:Landroid/view/Vie Landroid/view/inputmethod/InputMethodManager;->mServedView:Landroid/view/View; Landroid/view/inputmethod/InputMethodManager;->mService:Lcom/android/internal/view/IInputMethodManager; Landroid/view/inputmethod/InputMethodManager;->notifyUserAction()V +Landroid/view/inputmethod/InputMethodManager;->peekInstance()Landroid/view/inputmethod/InputMethodManager; Landroid/view/inputmethod/InputMethodManager;->showSoftInputUnchecked(ILandroid/os/ResultReceiver;)V Landroid/view/inputmethod/InputMethodManager;->sInstance:Landroid/view/inputmethod/InputMethodManager; Landroid/view/inputmethod/InputMethodManager;->windowDismissed(Landroid/os/IBinder;)V @@ -2036,6 +2178,7 @@ Landroid/view/IWindowManager;->setShelfHeight(ZI)V Landroid/view/IWindowManager;->setStrictModeVisualIndicatorPreference(Ljava/lang/String;)V Landroid/view/IWindowManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/view/IWindowManager; Landroid/view/IWindowManager$Stub$Proxy;->getBaseDisplayDensity(I)I +Landroid/view/IWindowManager$Stub$Proxy;->getDockedStackSide()I Landroid/view/IWindowManager$Stub$Proxy;->getInitialDisplayDensity(I)I Landroid/view/IWindowManager$Stub$Proxy;->hasNavigationBar()Z Landroid/view/IWindowManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V @@ -2066,6 +2209,7 @@ Landroid/view/LayoutInflater;->mFactorySet:Z Landroid/view/LayoutInflater;->mPrivateFactory:Landroid/view/LayoutInflater$Factory2; Landroid/view/LayoutInflater;->sConstructorMap:Ljava/util/HashMap; Landroid/view/LayoutInflater;->setPrivateFactory(Landroid/view/LayoutInflater$Factory2;)V +Landroid/view/MotionEvent;->getPointerIdBits()I Landroid/view/MotionEvent;->HISTORY_CURRENT:I Landroid/view/MotionEvent;->mNativePtr:J Landroid/view/MotionEvent;->nativeGetRawAxisValue(JIII)F @@ -2073,6 +2217,7 @@ Landroid/view/MotionEvent;->obtain()Landroid/view/MotionEvent; Landroid/view/MotionEvent$PointerCoords;->mPackedAxisBits:J Landroid/view/MotionEvent$PointerCoords;->mPackedAxisValues:[F Landroid/view/MotionEvent;->scale(F)V +Landroid/view/MotionEvent;->split(I)Landroid/view/MotionEvent; Landroid/view/PointerIcon;->load(Landroid/content/Context;)Landroid/view/PointerIcon; Landroid/view/PointerIcon;->mBitmapFrames:[Landroid/graphics/Bitmap; Landroid/view/PointerIcon;->mBitmap:Landroid/graphics/Bitmap; @@ -2093,6 +2238,8 @@ Landroid/view/RemoteAnimationTarget;->sourceContainerBounds:Landroid/graphics/Re Landroid/view/RemoteAnimationTarget;->taskId:I Landroid/view/RemoteAnimationTarget;->windowConfiguration:Landroid/app/WindowConfiguration; Landroid/view/RenderNodeAnimator;->callOnFinished(Landroid/view/RenderNodeAnimator;)V +Landroid/view/RenderNode;->discardDisplayList()V +Landroid/view/RenderNode;->output()V Landroid/view/ScaleGestureDetector;->mListener:Landroid/view/ScaleGestureDetector$OnScaleGestureListener; Landroid/view/ScaleGestureDetector;->mMinSpan:I Landroid/view/SurfaceControl$PhysicalDisplayInfo;->appVsyncOffsetNanos:J @@ -2127,6 +2274,7 @@ Landroid/view/textclassifier/logging/SmartSelectionEventTracker$SelectionEvent;- Landroid/view/textclassifier/TextClassificationManager;->getTextClassifier(I)Landroid/view/textclassifier/TextClassifier; Landroid/view/textservice/TextServicesManager;->isSpellCheckerEnabled()Z Landroid/view/TextureView;->destroyHardwareLayer()V +Landroid/view/TextureView;->destroyHardwareResources()V Landroid/view/TextureView;->mLayer:Landroid/view/TextureLayer; Landroid/view/TextureView;->mNativeWindow:J Landroid/view/TextureView;->mSurface:Landroid/graphics/SurfaceTexture; @@ -2138,6 +2286,7 @@ Landroid/view/VelocityTracker$Estimator;->xCoeff:[F Landroid/view/VelocityTracker$Estimator;->yCoeff:[F Landroid/view/VelocityTracker;->obtain(Ljava/lang/String;)Landroid/view/VelocityTracker; Landroid/view/View;->applyDrawableToTransparentRegion(Landroid/graphics/drawable/Drawable;Landroid/graphics/Region;)V +Landroid/view/View$AttachInfo;->mContentInsets:Landroid/graphics/Rect; Landroid/view/View$AttachInfo;->mDrawingTime:J Landroid/view/View$AttachInfo;->mStableInsets:Landroid/graphics/Rect; Landroid/view/View;->clearAccessibilityFocus()V @@ -2156,12 +2305,14 @@ Landroid/view/View;->dispatchDetachedFromWindow()V Landroid/view/View;->fitsSystemWindows()Z Landroid/view/View;->getAccessibilityDelegate()Landroid/view/View$AccessibilityDelegate; Landroid/view/View;->getBoundsOnScreen(Landroid/graphics/Rect;)V +Landroid/view/View;->getHorizontalScrollFactor()F Landroid/view/View;->getInverseMatrix()Landroid/graphics/Matrix; Landroid/view/View;->getListenerInfo()Landroid/view/View$ListenerInfo; Landroid/view/View;->getLocationOnScreen()[I Landroid/view/View;->getRawTextAlignment()I Landroid/view/View;->getRawTextDirection()I Landroid/view/View;->getTransitionAlpha()F +Landroid/view/View;->getVerticalScrollFactor()F Landroid/view/View;->getViewRootImpl()Landroid/view/ViewRootImpl; Landroid/view/View;->getWindowDisplayFrame(Landroid/graphics/Rect;)V Landroid/view/ViewGroup;->dispatchViewAdded(Landroid/view/View;)V @@ -2176,18 +2327,23 @@ Landroid/view/ViewGroup;->mChildren:[Landroid/view/View; Landroid/view/ViewGroup;->mFirstTouchTarget:Landroid/view/ViewGroup$TouchTarget; Landroid/view/ViewGroup;->mGroupFlags:I Landroid/view/ViewGroup;->mOnHierarchyChangeListener:Landroid/view/ViewGroup$OnHierarchyChangeListener; +Landroid/view/ViewGroup;->mPersistentDrawingCache:I +Landroid/view/ViewGroup;->offsetChildrenTopAndBottom(I)V Landroid/view/ViewGroup;->resetResolvedDrawables()V Landroid/view/ViewGroup;->resetResolvedLayoutDirection()V Landroid/view/ViewGroup;->resetResolvedPadding()V Landroid/view/ViewGroup;->resetResolvedTextAlignment()V Landroid/view/ViewGroup;->resetResolvedTextDirection()V Landroid/view/ViewGroup;->suppressLayout(Z)V +Landroid/view/View;->includeForAccessibility()Z Landroid/view/View;->initializeScrollbars(Landroid/content/res/TypedArray;)V Landroid/view/View;->internalSetPadding(IIII)V Landroid/view/View;->isPaddingResolved()Z Landroid/view/View;->isVisibleToUser(Landroid/graphics/Rect;)Z Landroid/view/View;->isVisibleToUser()Z +Landroid/view/View$ListenerInfo;-><init>()V Landroid/view/View$ListenerInfo;->mOnClickListener:Landroid/view/View$OnClickListener; +Landroid/view/View$ListenerInfo;->mOnFocusChangeListener:Landroid/view/View$OnFocusChangeListener; Landroid/view/View$ListenerInfo;->mOnLongClickListener:Landroid/view/View$OnLongClickListener; Landroid/view/View$ListenerInfo;->mOnTouchListener:Landroid/view/View$OnTouchListener; Landroid/view/View;->mAccessibilityDelegate:Landroid/view/View$AccessibilityDelegate; @@ -2200,10 +2356,13 @@ Landroid/view/View;->mLeft:I Landroid/view/View;->mListenerInfo:Landroid/view/View$ListenerInfo; Landroid/view/View;->mMinHeight:I Landroid/view/View;->mMinWidth:I +Landroid/view/View;->mPaddingBottom:I Landroid/view/View;->mPaddingLeft:I Landroid/view/View;->mPaddingRight:I +Landroid/view/View;->mPaddingTop:I Landroid/view/View;->mParent:Landroid/view/ViewParent; Landroid/view/View;->mPrivateFlags3:I +Landroid/view/View;->mPrivateFlags:I Landroid/view/View;->mRecreateDisplayList:Z Landroid/view/View;->mResources:Landroid/content/res/Resources; Landroid/view/View;->mRight:I @@ -2216,6 +2375,8 @@ Landroid/view/View;->mTop:I Landroid/view/View;->mUnscaledDrawingCache:Landroid/graphics/Bitmap; Landroid/view/View;->mViewFlags:I Landroid/view/View;->notifySubtreeAccessibilityStateChangedIfNeeded()V +Landroid/view/View;->onDrawVerticalScrollBar(Landroid/graphics/Canvas;Landroid/graphics/drawable/Drawable;IIII)V +Landroid/view/View;->performAccessibilityActionInternal(ILandroid/os/Bundle;)Z Landroid/view/View;->recomputePadding()V Landroid/view/View;->requestAccessibilityFocus()Z Landroid/view/View;->resetDisplayList()V @@ -2276,10 +2437,14 @@ Landroid/view/WindowManager$LayoutParams;->NEEDS_MENU_SET_TRUE:I Landroid/view/WindowManager$LayoutParams;->userActivityTimeout:J Landroid/view/Window;->mAppName:Ljava/lang/String; Landroid/view/Window;->mAppToken:Landroid/os/IBinder; +Landroid/view/Window;->mCallback:Landroid/view/Window$Callback; +Landroid/view/Window;->mContext:Landroid/content/Context; Landroid/view/Window;->mHardwareAccelerated:Z +Landroid/view/Window;->mWindowStyle:Landroid/content/res/TypedArray; Landroid/webkit/IWebViewUpdateService$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/webkit/WebResourceResponse;->mImmutable:Z Landroid/webkit/WebSyncManager;->mHandler:Landroid/os/Handler; +Landroid/webkit/WebViewClient;->onUnhandledInputEvent(Landroid/webkit/WebView;Landroid/view/InputEvent;)V Landroid/webkit/WebView;->debugDump()V Landroid/webkit/WebView;->disablePlatformNotifications()V Landroid/webkit/WebView;->emulateShiftHeld()V @@ -2288,6 +2453,7 @@ Landroid/webkit/WebViewFactory;->getProvider()Landroid/webkit/WebViewFactoryProv Landroid/webkit/WebViewFactory;->getWebViewContextAndSetProvider()Landroid/content/Context; Landroid/webkit/WebViewFactory;->sPackageInfo:Landroid/content/pm/PackageInfo; Landroid/webkit/WebViewFactory;->sProviderInstance:Landroid/webkit/WebViewFactoryProvider; +Landroid/webkit/WebView;->getTouchIconUrl()Ljava/lang/String; Landroid/webkit/WebView;->getVisibleTitleHeight()I Landroid/webkit/WebView;->isPaused()Z Landroid/webkit/WebView;->mProvider:Landroid/webkit/WebViewProvider; @@ -2309,6 +2475,8 @@ Landroid/widget/AbsListView;->mIsChildViewEnabled:Z Landroid/widget/AbsListView;->mMaximumVelocity:I Landroid/widget/AbsListView;->mMotionPosition:I Landroid/widget/AbsListView;->mOnScrollListener:Landroid/widget/AbsListView$OnScrollListener; +Landroid/widget/AbsListView;->mPendingCheckForLongPress:Landroid/widget/AbsListView$CheckForLongPress; +Landroid/widget/AbsListView;->mPendingCheckForTap:Landroid/widget/AbsListView$CheckForTap; Landroid/widget/AbsListView;->mRecycler:Landroid/widget/AbsListView$RecycleBin; Landroid/widget/AbsListView;->mSelectionTopPadding:I Landroid/widget/AbsListView;->mSelectorPosition:I @@ -2321,6 +2489,8 @@ Landroid/widget/AbsListView;->performLongPress(Landroid/view/View;IJ)Z Landroid/widget/AbsListView$RecycleBin;->clear()V Landroid/widget/AbsListView$RecycleBin;->mRecyclerListener:Landroid/widget/AbsListView$RecyclerListener; Landroid/widget/AbsListView;->reportScrollStateChange(I)V +Landroid/widget/AbsListView$SavedState;->firstId:J +Landroid/widget/AbsListView$SavedState;->viewTop:I Landroid/widget/AbsListView;->smoothScrollBy(IIZZ)V Landroid/widget/AbsListView;->trackMotionScroll(II)Z Landroid/widget/AbsSeekBar;->mIsDragging:Z @@ -2339,7 +2509,9 @@ Landroid/widget/AutoCompleteTextView;->doBeforeTextChanged()V Landroid/widget/AutoCompleteTextView;->ensureImeVisible(Z)V Landroid/widget/AutoCompleteTextView;->mPopup:Landroid/widget/ListPopupWindow; Landroid/widget/AutoCompleteTextView;->setDropDownAlwaysVisible(Z)V +Landroid/widget/AutoCompleteTextView;->setForceIgnoreOutsideTouch(Z)V Landroid/widget/CompoundButton;->mButtonDrawable:Landroid/graphics/drawable/Drawable; +Landroid/widget/CompoundButton;->mOnCheckedChangeListener:Landroid/widget/CompoundButton$OnCheckedChangeListener; Landroid/widget/CursorAdapter;->mChangeObserver:Landroid/widget/CursorAdapter$ChangeObserver; Landroid/widget/CursorAdapter;->mDataSetObserver:Landroid/database/DataSetObserver; Landroid/widget/CursorAdapter;->mDataValid:Z @@ -2350,6 +2522,7 @@ Landroid/widget/Editor;->invalidateTextDisplayList()V Landroid/widget/Editor;->mShowCursor:J Landroid/widget/Editor;->mShowSoftInputOnFocus:Z Landroid/widget/ExpandableListView;->mChildDivider:Landroid/graphics/drawable/Drawable; +Landroid/widget/ExpandableListView;->mGroupIndicator:Landroid/graphics/drawable/Drawable; Landroid/widget/FastScroller;->mContainerRect:Landroid/graphics/Rect; Landroid/widget/FastScroller;->mHeaderCount:I Landroid/widget/FastScroller;->mLongList:Z @@ -2456,13 +2629,17 @@ Landroid/widget/RemoteViews;->mBitmapCache:Landroid/widget/RemoteViews$BitmapCac Landroid/widget/RemoteViews;->mergeRemoteViews(Landroid/widget/RemoteViews;)V Landroid/widget/RemoteViews;->mPortrait:Landroid/widget/RemoteViews; Landroid/widget/RemoteViews$ReflectionAction;->methodName:Ljava/lang/String; +Landroid/widget/RemoteViews$ReflectionAction;->value:Ljava/lang/Object; +Landroid/widget/RemoteViews$SetOnClickPendingIntent;->pendingIntent:Landroid/app/PendingIntent; Landroid/widget/ScrollBarDrawable;->mVerticalThumb:Landroid/graphics/drawable/Drawable; Landroid/widget/ScrollBarDrawable;->setHorizontalThumbDrawable(Landroid/graphics/drawable/Drawable;)V Landroid/widget/ScrollBarDrawable;->setVerticalThumbDrawable(Landroid/graphics/drawable/Drawable;)V +Landroid/widget/Scroller;->mInterpolator:Landroid/view/animation/Interpolator; Landroid/widget/ScrollView;->mChildToScrollTo:Landroid/view/View; Landroid/widget/ScrollView;->mEdgeGlowBottom:Landroid/widget/EdgeEffect; Landroid/widget/ScrollView;->mEdgeGlowTop:Landroid/widget/EdgeEffect; Landroid/widget/ScrollView;->mIsBeingDragged:Z +Landroid/widget/ScrollView;->mMinimumVelocity:I Landroid/widget/ScrollView;->mOverflingDistance:I Landroid/widget/ScrollView;->mOverscrollDistance:I Landroid/widget/ScrollView;->mScroller:Landroid/widget/OverScroller; @@ -2476,7 +2653,11 @@ Landroid/widget/SlidingDrawer;->mTopOffset:I Landroid/widget/Spinner;->mPopup:Landroid/widget/Spinner$SpinnerPopup; Landroid/widget/Switch;->mThumbDrawable:Landroid/graphics/drawable/Drawable; Landroid/widget/Switch;->mTrackDrawable:Landroid/graphics/drawable/Drawable; +Landroid/widget/TabHost$IntentContentStrategy;->getContentView()Landroid/view/View; +Landroid/widget/TabHost$IntentContentStrategy;->tabClosed()V +Landroid/widget/TabHost;->mTabSpecs:Ljava/util/List; Landroid/widget/TabHost$TabSpec;->mContentStrategy:Landroid/widget/TabHost$ContentStrategy; +Landroid/widget/TabWidget;->mSelectedTab:I Landroid/widget/TabWidget;->setTabSelectionListener(Landroid/widget/TabWidget$OnTabSelectionChanged;)V Landroid/widget/TextView;->assumeLayout()V Landroid/widget/TextView;->createEditorIfNeeded()V @@ -2494,7 +2675,11 @@ Landroid/widget/TextView;->mSingleLine:Z Landroid/widget/TextView;->mTextPaint:Landroid/text/TextPaint; Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;Landroid/widget/TextView$BufferType;ZI)V Landroid/widget/Toast;->getService()Landroid/app/INotificationManager; +Landroid/widget/Toast;->getWindowParams()Landroid/view/WindowManager$LayoutParams; +Landroid/widget/Toast;->mTN:Landroid/widget/Toast$TN; Landroid/widget/Toast;->sService:Landroid/app/INotificationManager; +Landroid/widget/Toast$TN;->mNextView:Landroid/view/View; +Landroid/widget/Toast$TN;->mParams:Landroid/view/WindowManager$LayoutParams; Landroid/widget/VideoView2;->getMediaController()Landroid/media/session/MediaController; Landroid/widget/VideoView2$OnViewTypeChangedListener;->onViewTypeChanged(Landroid/view/View;I)V Landroid/widget/VideoView2;->setOnViewTypeChangedListener(Landroid/widget/VideoView2$OnViewTypeChangedListener;)V @@ -2577,6 +2762,7 @@ Lcom/android/internal/app/AlertController$RecycleListView;-><init>(Landroid/cont Lcom/android/internal/app/IAppOpsService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/app/IAppOpsService; Lcom/android/internal/app/IAppOpsService$Stub$Proxy;->checkOperation(IILjava/lang/String;)I Lcom/android/internal/app/IAppOpsService$Stub$Proxy;-><init>(Landroid/os/IBinder;)V +Lcom/android/internal/app/IAppOpsService$Stub$Proxy;->setMode(IILjava/lang/String;I)V Lcom/android/internal/app/IBatteryStats;->getStatistics()[B Lcom/android/internal/app/IBatteryStats$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/app/IBatteryStats; Lcom/android/internal/app/IBatteryStats$Stub$Proxy;-><init>(Landroid/os/IBinder;)V @@ -2604,10 +2790,12 @@ Lcom/android/internal/os/BatteryStatsHelper;->refreshStats(II)V Lcom/android/internal/os/BatteryStatsImpl;->computeBatteryRealtime(JI)J Lcom/android/internal/os/BatteryStatsImpl;->computeBatteryUptime(JI)J Lcom/android/internal/os/BatteryStatsImpl;->CREATOR:Landroid/os/Parcelable$Creator; +Lcom/android/internal/os/BatteryStatsImpl;->getBatteryRealtime(J)J Lcom/android/internal/os/BatteryStatsImpl;->getDischargeAmount(I)I Lcom/android/internal/os/BatteryStatsImpl;->getGlobalWifiRunningTime(JI)J Lcom/android/internal/os/BatteryStatsImpl;->getScreenOnTime(JI)J Lcom/android/internal/os/BatteryStatsImpl;->getUidStats()Landroid/util/SparseArray; +Lcom/android/internal/os/BatteryStatsImpl;->getUidStatsLocked(I)Lcom/android/internal/os/BatteryStatsImpl$Uid; Lcom/android/internal/os/BatteryStatsImpl$Timer;->getCountLocked(I)I Lcom/android/internal/os/BatteryStatsImpl$Timer;->getTotalTimeLocked(JI)J Lcom/android/internal/os/BatteryStatsImpl$Uid;->getProcessStats()Landroid/util/ArrayMap; @@ -2675,6 +2863,18 @@ Lcom/android/internal/R$string;->kilobyteShort:I Lcom/android/internal/R$string;->megabyteShort:I Lcom/android/internal/R$string;->petabyteShort:I Lcom/android/internal/R$string;->terabyteShort:I +Lcom/android/internal/R$styleable;->AbsListView_cacheColorHint:I +Lcom/android/internal/R$styleable;->AbsListView_choiceMode:I +Lcom/android/internal/R$styleable;->AbsListView_drawSelectorOnTop:I +Lcom/android/internal/R$styleable;->AbsListView_fastScrollAlwaysVisible:I +Lcom/android/internal/R$styleable;->AbsListView_fastScrollEnabled:I +Lcom/android/internal/R$styleable;->AbsListView:[I +Lcom/android/internal/R$styleable;->AbsListView_listSelector:I +Lcom/android/internal/R$styleable;->AbsListView_scrollingCache:I +Lcom/android/internal/R$styleable;->AbsListView_smoothScrollbar:I +Lcom/android/internal/R$styleable;->AbsListView_stackFromBottom:I +Lcom/android/internal/R$styleable;->AbsListView_textFilterEnabled:I +Lcom/android/internal/R$styleable;->AbsListView_transcriptMode:I Lcom/android/internal/R$styleable;->AccountAuthenticator_accountPreferences:I Lcom/android/internal/R$styleable;->AccountAuthenticator_accountType:I Lcom/android/internal/R$styleable;->AccountAuthenticator_customTokens:I @@ -2682,6 +2882,28 @@ Lcom/android/internal/R$styleable;->AccountAuthenticator:[I Lcom/android/internal/R$styleable;->AccountAuthenticator_icon:I Lcom/android/internal/R$styleable;->AccountAuthenticator_label:I Lcom/android/internal/R$styleable;->AccountAuthenticator_smallIcon:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_allowTaskReparenting:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_configChanges:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_description:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_enabled:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_excludeFromRecents:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_exported:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_hardwareAccelerated:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity:[I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_icon:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_immersive:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_label:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_launchMode:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_logo:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_name:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_noHistory:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_permission:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_process:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_screenOrientation:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_taskAffinity:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_theme:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_uiOptions:I +Lcom/android/internal/R$styleable;->AndroidManifestActivity_windowSoftInputMode:I Lcom/android/internal/R$styleable;->AndroidManifestApplication_enabled:I Lcom/android/internal/R$styleable;->AndroidManifestApplication_hardwareAccelerated:I Lcom/android/internal/R$styleable;->AndroidManifestApplication:[I @@ -2693,8 +2915,21 @@ Lcom/android/internal/R$styleable;->AndroidManifestApplication_process:I Lcom/android/internal/R$styleable;->AndroidManifestApplication_supportsRtl:I Lcom/android/internal/R$styleable;->AndroidManifestApplication_theme:I Lcom/android/internal/R$styleable;->AndroidManifestApplication_uiOptions:I +Lcom/android/internal/R$styleable;->AndroidManifestData:[I Lcom/android/internal/R$styleable;->AndroidManifest:[I Lcom/android/internal/R$styleable;->AndroidManifest_installLocation:I +Lcom/android/internal/R$styleable;->AndroidManifestIntentFilter:[I +Lcom/android/internal/R$styleable;->AndroidManifestIntentFilter_priority:I +Lcom/android/internal/R$styleable;->AndroidManifestMetaData:[I +Lcom/android/internal/R$styleable;->AndroidManifestMetaData_name:I +Lcom/android/internal/R$styleable;->AndroidManifestMetaData_resource:I +Lcom/android/internal/R$styleable;->AndroidManifestMetaData_value:I +Lcom/android/internal/R$styleable;->AndroidManifestService_enabled:I +Lcom/android/internal/R$styleable;->AndroidManifestService_exported:I +Lcom/android/internal/R$styleable;->AndroidManifestService:[I +Lcom/android/internal/R$styleable;->AndroidManifestService_name:I +Lcom/android/internal/R$styleable;->AndroidManifestService_permission:I +Lcom/android/internal/R$styleable;->AndroidManifestService_process:I Lcom/android/internal/R$styleable;->AndroidManifest_sharedUserId:I Lcom/android/internal/R$styleable;->AndroidManifestUsesPermission:[I Lcom/android/internal/R$styleable;->AndroidManifestUsesPermission_name:I @@ -2708,12 +2943,26 @@ Lcom/android/internal/R$styleable;->CheckBoxPreference:[I Lcom/android/internal/R$styleable;->CheckBoxPreference_summaryOff:I Lcom/android/internal/R$styleable;->CheckBoxPreference_summaryOn:I Lcom/android/internal/R$styleable;->CompoundButton_button:I +Lcom/android/internal/R$styleable;->CompoundButton_checked:I Lcom/android/internal/R$styleable;->CompoundButton:[I +Lcom/android/internal/R$styleable;->DialogPreference_dialogTitle:I +Lcom/android/internal/R$styleable;->DialogPreference:[I Lcom/android/internal/R$styleable;->EdgeEffect_colorEdgeEffect:I Lcom/android/internal/R$styleable;->EdgeEffect:[I Lcom/android/internal/R$styleable;->IconMenuView:[I Lcom/android/internal/R$styleable;->ImageView:[I Lcom/android/internal/R$styleable;->ImageView_src:I +Lcom/android/internal/R$styleable;->ListPreference_entries:I +Lcom/android/internal/R$styleable;->ListPreference:[I +Lcom/android/internal/R$styleable;->ListView_dividerHeight:I +Lcom/android/internal/R$styleable;->ListView_divider:I +Lcom/android/internal/R$styleable;->ListView_entries:I +Lcom/android/internal/R$styleable;->ListView_footerDividersEnabled:I +Lcom/android/internal/R$styleable;->ListView_headerDividersEnabled:I +Lcom/android/internal/R$styleable;->ListView:[I +Lcom/android/internal/R$styleable;->ListView_overScrollFooter:I +Lcom/android/internal/R$styleable;->ListView_overScrollHeader:I +Lcom/android/internal/R$styleable;->PopupWindow:[I Lcom/android/internal/R$styleable;->Preference_defaultValue:I Lcom/android/internal/R$styleable;->Preference_dependency:I Lcom/android/internal/R$styleable;->Preference_enabled:I @@ -2742,12 +2991,15 @@ Lcom/android/internal/R$styleable;->SyncAdapter_settingsActivity:I Lcom/android/internal/R$styleable;->SyncAdapter_supportsUploading:I Lcom/android/internal/R$styleable;->SyncAdapter_userVisible:I Lcom/android/internal/R$styleable;->TabWidget:[I +Lcom/android/internal/R$styleable;->TextAppearance:[I Lcom/android/internal/R$styleable;->TextView_drawableBottom:I Lcom/android/internal/R$styleable;->TextView_drawableLeft:I Lcom/android/internal/R$styleable;->TextView_drawableRight:I Lcom/android/internal/R$styleable;->TextView_drawableTop:I Lcom/android/internal/R$styleable;->TextView:[I Lcom/android/internal/R$styleable;->TextView_maxLines:I +Lcom/android/internal/R$styleable;->TextView_textColorHint:I +Lcom/android/internal/R$styleable;->TextView_textColor:I Lcom/android/internal/R$styleable;->View_background:I Lcom/android/internal/R$styleable;->ViewGroup_Layout:[I Lcom/android/internal/R$styleable;->ViewGroup_Layout_layout_height:I @@ -2768,23 +3020,35 @@ Lcom/android/internal/telephony/IPhoneSubInfo$Stub;->asInterface(Landroid/os/IBi Lcom/android/internal/telephony/IPhoneSubInfo$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Lcom/android/internal/telephony/ISms$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/telephony/ISms; Lcom/android/internal/telephony/ISub$Stub$Proxy;-><init>(Landroid/os/IBinder;)V +Lcom/android/internal/telephony/ITelephony;->answerRingingCall()V Lcom/android/internal/telephony/ITelephony;->call(Ljava/lang/String;Ljava/lang/String;)V +Lcom/android/internal/telephony/ITelephony;->dial(Ljava/lang/String;)V Lcom/android/internal/telephony/ITelephony;->disableDataConnectivity()Z Lcom/android/internal/telephony/ITelephony;->enableDataConnectivity()Z Lcom/android/internal/telephony/ITelephony;->endCall()Z +Lcom/android/internal/telephony/ITelephony;->getCallState()I +Lcom/android/internal/telephony/ITelephony;->getDataState()I Lcom/android/internal/telephony/ITelephony;->isIdle(Ljava/lang/String;)Z Lcom/android/internal/telephony/ITelephony;->silenceRinger()V Lcom/android/internal/telephony/ITelephony$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/telephony/ITelephony; Lcom/android/internal/telephony/ITelephony$Stub$Proxy;->endCall()Z Lcom/android/internal/telephony/ITelephony$Stub$Proxy;-><init>(Landroid/os/IBinder;)V +Lcom/android/internal/telephony/ITelephony$Stub;->TRANSACTION_call:I +Lcom/android/internal/telephony/ITelephony$Stub;->TRANSACTION_endCall:I Lcom/android/internal/telephony/ITelephony$Stub;->TRANSACTION_getDeviceId:I +Lcom/android/internal/telephony/SmsRawData;->CREATOR:Landroid/os/Parcelable$Creator; +Lcom/android/internal/telephony/SmsRawData;-><init>([B)V Lcom/android/internal/textservice/ITextServicesManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V +Lcom/android/internal/util/AsyncChannel;->sendMessage(III)V Lcom/android/internal/util/FastPrintWriter;-><init>(Ljava/io/OutputStream;)V Lcom/android/internal/util/XmlUtils;->readMapXml(Ljava/io/InputStream;)Ljava/util/HashMap; +Lcom/android/internal/util/XmlUtils;->skipCurrentTag(Lorg/xmlpull/v1/XmlPullParser;)V +Lcom/android/internal/util/XmlUtils;->writeMapXml(Ljava/util/Map;Ljava/io/OutputStream;)V Lcom/android/internal/view/IInputMethodManager$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/view/IInputMethodManager; Lcom/android/internal/view/IInputMethodManager$Stub$Proxy;->getEnabledInputMethodList()Ljava/util/List; Lcom/android/internal/view/IInputMethodManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Lcom/android/internal/view/InputBindResult;->CREATOR:Landroid/os/Parcelable$Creator; +Lcom/android/internal/view/menu/MenuBuilder;-><init>(Landroid/content/Context;)V Lcom/android/internal/view/menu/MenuBuilder;->mContext:Landroid/content/Context; Lcom/android/internal/view/menu/MenuBuilder;->setCurrentMenuInfo(Landroid/view/ContextMenu$ContextMenuInfo;)V Lcom/android/internal/view/menu/MenuBuilder;->setOptionalIconsVisible(Z)V @@ -2840,6 +3104,7 @@ Lcom/android/org/conscrypt/ConscryptSocketBase;->getSoWriteTimeout()I Lcom/android/org/conscrypt/ConscryptSocketBase;->setHandshakeTimeout(I)V Lcom/android/org/conscrypt/ConscryptSocketBase;->setHostname(Ljava/lang/String;)V Lcom/android/org/conscrypt/ConscryptSocketBase;->setSoWriteTimeout(I)V +Lcom/android/org/conscrypt/ConscryptSocketBase;->socket:Ljava/net/Socket; Lcom/android/org/conscrypt/OpenSSLSocketImpl;->getAlpnSelectedProtocol()[B Lcom/android/org/conscrypt/OpenSSLSocketImpl;->getChannelId()[B Lcom/android/org/conscrypt/OpenSSLSocketImpl;->getHostname()Ljava/lang/String; @@ -2862,10 +3127,12 @@ Ldalvik/system/BaseDexClassLoader;->getLdLibraryPath()Ljava/lang/String; Ldalvik/system/BaseDexClassLoader;->pathList:Ldalvik/system/DexPathList; Ldalvik/system/BlockGuard;->getThreadPolicy()Ldalvik/system/BlockGuard$Policy; Ldalvik/system/BlockGuard$Policy;->onNetwork()V +Ldalvik/system/BlockGuard$Policy;->onReadFromDisk()V Ldalvik/system/CloseGuard;->close()V Ldalvik/system/CloseGuard;->get()Ldalvik/system/CloseGuard; Ldalvik/system/CloseGuard;->open(Ljava/lang/String;)V Ldalvik/system/CloseGuard;->warnIfOpen()V +Ldalvik/system/DexFile$DFEnum;->mNameList:[Ljava/lang/String; Ldalvik/system/DexFile;->getClassNameList(Ljava/lang/Object;)[Ljava/lang/String; Ldalvik/system/DexFile;->isBackedByOatFile()Z Ldalvik/system/DexFile;->loadClassBinaryName(Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/util/List;)Ljava/lang/Class; @@ -2874,6 +3141,7 @@ Ldalvik/system/DexFile;->mFileName:Ljava/lang/String; Ldalvik/system/DexFile;->mInternalCookie:Ljava/lang/Object; Ldalvik/system/DexFile;->openDexFile(Ljava/lang/String;Ljava/lang/String;ILjava/lang/ClassLoader;[Ldalvik/system/DexPathList$Element;)Ljava/lang/Object; Ldalvik/system/DexPathList;->addDexPath(Ljava/lang/String;Ljava/io/File;)V +Ldalvik/system/DexPathList;->definingContext:Ljava/lang/ClassLoader; Ldalvik/system/DexPathList;->dexElements:[Ldalvik/system/DexPathList$Element; Ldalvik/system/DexPathList$Element;->dexFile:Ldalvik/system/DexFile; Ldalvik/system/DexPathList$Element;-><init>(Ldalvik/system/DexFile;Ljava/io/File;)V @@ -2894,6 +3162,7 @@ Ldalvik/system/VMDebug;->dumpReferenceTables()V Ldalvik/system/VMRuntime;->addressOf(Ljava/lang/Object;)J Ldalvik/system/VMRuntime;->clearGrowthLimit()V Ldalvik/system/VMRuntime;->getCurrentInstructionSet()Ljava/lang/String; +Ldalvik/system/VMRuntime;->getInstructionSet(Ljava/lang/String;)Ljava/lang/String; Ldalvik/system/VMRuntime;->getRuntime()Ldalvik/system/VMRuntime; Ldalvik/system/VMRuntime;->is64Bit()Z Ldalvik/system/VMRuntime;->newNonMovableArray(Ljava/lang/Class;I)Ljava/lang/Object; @@ -2911,7 +3180,9 @@ Ldalvik/system/VMStack;->getCallingClassLoader()Ljava/lang/ClassLoader; Ldalvik/system/VMStack;->getStackClass2()Ljava/lang/Class; Ljava/io/FileDescriptor;->descriptor:I Ljava/io/FileDescriptor;->getInt$()I +Ljava/io/FileDescriptor;->isSocket$()Z Ljava/io/FileDescriptor;->setInt$(I)V +Ljava/io/File;->fs:Ljava/io/FileSystem; Ljava/io/FileInputStream;->fd:Ljava/io/FileDescriptor; Ljava/io/FileOutputStream;->fd:Ljava/io/FileDescriptor; Ljava/io/ObjectStreamClass;->getConstructorId(Ljava/lang/Class;)J @@ -2921,8 +3192,10 @@ Ljava/lang/AbstractStringBuilder;->value:[C Ljava/lang/Boolean;->value:Z Ljava/lang/Byte;->value:B Ljava/lang/Character;->value:C +Ljava/lang/Class;->accessFlags:I Ljava/lang/Class;->dexCache:Ljava/lang/Object; Ljava/lang/Class;->dexClassDefIndex:I +Ljava/lang/Class;->ifTable:[Ljava/lang/Object; Ljava/lang/ClassLoader;->parent:Ljava/lang/ClassLoader; Ljava/lang/Daemons$Daemon;->isRunning()Z Ljava/lang/Daemons$Daemon;->start()V @@ -2955,6 +3228,7 @@ Ljava/lang/Runtime;->load(Ljava/lang/String;Ljava/lang/ClassLoader;)V Ljava/lang/Runtime;->nativeLoad(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/String; Ljava/lang/Short;->value:S Ljava/lang/String;-><init>(II[C)V +Ljava/lang/System;->arraycopy([II[III)V Ljava/lang/System;-><init>()V Ljava/lang/Thread;->daemon:Z Ljava/lang/Thread;->dispatchUncaughtException(Ljava/lang/Throwable;)V @@ -2993,6 +3267,7 @@ Ljava/net/Inet6Address$Inet6AddressHolder;->scope_ifname:Ljava/net/NetworkInterf Ljava/net/Inet6Address;-><init>()V Ljava/net/InetAddress;->clearDnsCache()V Ljava/net/InetAddress;->holder:Ljava/net/InetAddress$InetAddressHolder; +Ljava/net/InetAddress;->holder()Ljava/net/InetAddress$InetAddressHolder; Ljava/net/InetAddress$InetAddressHolder;->address:I Ljava/net/InetAddress$InetAddressHolder;->family:I Ljava/net/InetAddress$InetAddressHolder;->hostName:Ljava/lang/String; @@ -3001,6 +3276,8 @@ Ljava/net/InetAddress;->isNumeric(Ljava/lang/String;)Z Ljava/net/InetAddress;->parseNumericAddress(Ljava/lang/String;)Ljava/net/InetAddress; Ljava/net/Socket;->getFileDescriptor$()Ljava/io/FileDescriptor; Ljava/net/Socket;->impl:Ljava/net/SocketImpl; +Ljava/net/SocketImpl;->serverSocket:Ljava/net/ServerSocket; +Ljava/net/SocketImpl;->socket:Ljava/net/Socket; Ljava/net/URI;->host:Ljava/lang/String; Ljava/net/URL;->handler:Ljava/net/URLStreamHandler; Ljava/net/URL;->handlers:Ljava/util/Hashtable; @@ -3035,11 +3312,16 @@ Ljava/util/Collections$SynchronizedMap;->m:Ljava/util/Map; Ljava/util/Collections$UnmodifiableCollection;->c:Ljava/util/Collection; Ljava/util/Collections$UnmodifiableMap;->m:Ljava/util/Map; Ljava/util/concurrent/ConcurrentHashMap$BaseIterator;->hasMoreElements()Z +Ljava/util/concurrent/Executors$RunnableAdapter;->task:Ljava/lang/Runnable; Ljava/util/concurrent/FutureTask;->callable:Ljava/util/concurrent/Callable; +Ljava/util/concurrent/FutureTask;->EXCEPTIONAL:I +Ljava/util/concurrent/FutureTask;->outcome:Ljava/lang/Object; +Ljava/util/concurrent/FutureTask;->state:I Ljava/util/concurrent/LinkedBlockingQueue;->capacity:I Ljava/util/EnumMap;->keyType:Ljava/lang/Class; Ljava/util/EnumSet;->elementType:Ljava/lang/Class; Ljava/util/HashMap$HashIterator;->hasNext()Z +Ljava/util/jar/JarFile;->manifest:Ljava/util/jar/Manifest; Ljava/util/LinkedHashMap;->eldest()Ljava/util/Map$Entry; Ljava/util/LinkedHashMap$LinkedHashIterator;->hasNext()Z Ljava/util/Locale;->createConstant(Ljava/lang/String;Ljava/lang/String;)Ljava/util/Locale; @@ -3062,9 +3344,15 @@ Ljava/util/zip/ZipEntry;-><init>(Ljava/lang/String;Ljava/lang/String;JJJII[BJ)V Ljava/util/zip/ZipFile;->jzfile:J Ljavax/net/ssl/SSLServerSocketFactory;->defaultServerSocketFactory:Ljavax/net/ssl/SSLServerSocketFactory; Ljavax/net/ssl/SSLSocketFactory;->defaultSocketFactory:Ljavax/net/ssl/SSLSocketFactory; +Llibcore/util/BasicLruCache;->map:Ljava/util/LinkedHashMap; Llibcore/util/ZoneInfo;->mTransitions:[J Lorg/apache/http/conn/ssl/SSLSocketFactory;-><init>(Ljavax/net/ssl/SSLSocketFactory;)V Lorg/apache/http/conn/ssl/SSLSocketFactory;-><init>()V +Lorg/ccil/cowan/tagsoup/AttributesImpl;->data:[Ljava/lang/String; +Lorg/ccil/cowan/tagsoup/AttributesImpl;->length:I Lorg/json/JSONArray;->values:Ljava/util/List; +Lorg/json/JSONObject;->append(Ljava/lang/String;Ljava/lang/Object;)Lorg/json/JSONObject; +Lorg/json/JSONObject;->keySet()Ljava/util/Set; Lorg/json/JSONObject;->writeTo(Lorg/json/JSONStringer;)V +Lorg/w3c/dom/traversal/NodeIterator;->nextNode()Lorg/w3c/dom/Node; Lsun/misc/Unsafe;->theUnsafe:Lsun/misc/Unsafe; diff --git a/config/hiddenapi-vendor-list.txt b/config/hiddenapi-vendor-list.txt index 77bb0bb3d7ab..61ae6e77cbfd 100644 --- a/config/hiddenapi-vendor-list.txt +++ b/config/hiddenapi-vendor-list.txt @@ -32,7 +32,6 @@ Landroid/app/IActivityManager;->getRunningAppProcesses()Ljava/util/List; Landroid/app/IActivityManager;->getTaskSnapshot(IZ)Landroid/app/ActivityManager$TaskSnapshot; Landroid/app/IActivityManager;->registerTaskStackListener(Landroid/app/ITaskStackListener;)V Landroid/app/IActivityManager;->removeTask(I)Z -Landroid/app/IActivityManager;->requestBugReport(I)V Landroid/app/IActivityManager;->startActivityAsUser(Landroid/app/IApplicationThread;Ljava/lang/String;Landroid/content/Intent;Ljava/lang/String;Landroid/os/IBinder;Ljava/lang/String;IILandroid/app/ProfilerInfo;Landroid/os/Bundle;I)I Landroid/app/IActivityManager;->startActivityFromRecents(ILandroid/os/Bundle;)I Landroid/app/IActivityManager;->startActivity(Landroid/app/IApplicationThread;Ljava/lang/String;Landroid/content/Intent;Ljava/lang/String;Landroid/os/IBinder;Ljava/lang/String;IILandroid/app/ProfilerInfo;Landroid/os/Bundle;)I @@ -108,11 +107,6 @@ Landroid/graphics/Bitmap;->createGraphicBufferHandle()Landroid/graphics/GraphicB Landroid/graphics/Bitmap;->createHardwareBitmap(Landroid/graphics/GraphicBuffer;)Landroid/graphics/Bitmap; Landroid/graphics/Canvas;->clipRegion(Landroid/graphics/Region;Landroid/graphics/Region$Op;)Z Landroid/graphics/Canvas;->clipRegion(Landroid/graphics/Region;)Z -Landroid/graphics/Canvas;->save(I)I -Landroid/graphics/Canvas;->saveLayerAlpha(FFFFII)I -Landroid/graphics/Canvas;->saveLayerAlpha(Landroid/graphics/RectF;II)I -Landroid/graphics/Canvas;->saveLayer(FFFFLandroid/graphics/Paint;I)I -Landroid/graphics/Canvas;->saveLayer(Landroid/graphics/RectF;Landroid/graphics/Paint;I)I Landroid/graphics/drawable/Drawable;->isProjected()Z Landroid/graphics/drawable/Drawable;->updateTintFilter(Landroid/graphics/PorterDuffColorFilter;Landroid/content/res/ColorStateList;Landroid/graphics/PorterDuff$Mode;)Landroid/graphics/PorterDuffColorFilter; Landroid/hardware/camera2/CaptureRequest$Key;-><init>(Ljava/lang/String;Ljava/lang/Class;)V diff --git a/core/java/android/app/AppComponentFactory.java b/core/java/android/app/AppComponentFactory.java index 4df737999434..cfaeec9096ed 100644 --- a/core/java/android/app/AppComponentFactory.java +++ b/core/java/android/app/AppComponentFactory.java @@ -36,6 +36,10 @@ public class AppComponentFactory { * Allows application to override the creation of the application object. This can be used to * perform things such as dependency injection or class loader changes to these * classes. + * <p> + * This method is only intended to provide a hook for instantiation. It does not provide + * earlier access to the Application object. The returned object will not be initialized + * as a Context yet and should not be used to interact with other android APIs. * * @param cl The default classloader to use for instantiation. * @param className The class to be instantiated. @@ -50,6 +54,10 @@ public class AppComponentFactory { * Allows application to override the creation of activities. This can be used to * perform things such as dependency injection or class loader changes to these * classes. + * <p> + * This method is only intended to provide a hook for instantiation. It does not provide + * earlier access to the Activity object. The returned object will not be initialized + * as a Context yet and should not be used to interact with other android APIs. * * @param cl The default classloader to use for instantiation. * @param className The class to be instantiated. @@ -80,6 +88,10 @@ public class AppComponentFactory { * Allows application to override the creation of services. This can be used to * perform things such as dependency injection or class loader changes to these * classes. + * <p> + * This method is only intended to provide a hook for instantiation. It does not provide + * earlier access to the Service object. The returned object will not be initialized + * as a Context yet and should not be used to interact with other android APIs. * * @param cl The default classloader to use for instantiation. * @param className The class to be instantiated. @@ -95,6 +107,10 @@ public class AppComponentFactory { * Allows application to override the creation of providers. This can be used to * perform things such as dependency injection or class loader changes to these * classes. + * <p> + * This method is only intended to provide a hook for instantiation. It does not provide + * earlier access to the ContentProvider object. The returned object will not be initialized + * with a Context yet and should not be used to interact with other android APIs. * * @param cl The default classloader to use for instantiation. * @param className The class to be instantiated. @@ -108,5 +124,5 @@ public class AppComponentFactory { /** * @hide */ - public static AppComponentFactory DEFAULT = new AppComponentFactory(); + public static final AppComponentFactory DEFAULT = new AppComponentFactory(); } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index f5e138c164b6..71b88fa4ad91 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1713,6 +1713,9 @@ class ContextImpl extends Context { Slog.w(TAG, "Missing ActivityManager; assuming " + uid + " holds " + permission); return PackageManager.PERMISSION_GRANTED; } + Slog.w(TAG, "Missing ActivityManager; assuming " + uid + " does not hold " + + permission); + return PackageManager.PERMISSION_DENIED; } try { diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index d3c1e99f1523..4326ee3e75e3 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1219,11 +1219,11 @@ public class Notification implements Parcelable public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName"; /** - * This is set on the notification shown by the activity manager about all apps - * running in the background. It indicates that the notification should be shown - * only if any of the given apps do not already have a {@link #FLAG_FOREGROUND_SERVICE} - * notification currently visible to the user. This is a string array of all - * package names of the apps. + * This is set on the notifications shown by system_server about apps running foreground + * services. It indicates that the notification should be shown + * only if any of the given apps do not already have a properly tagged + * {@link #FLAG_FOREGROUND_SERVICE} notification currently visible to the user. + * This is a string array of all package names of the apps. * @hide */ public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps"; diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 30f2697c1b1f..4a7cf62356e3 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -463,7 +463,11 @@ public final class NotificationChannel implements Parcelable { /** * Returns the user specified importance e.g. {@link NotificationManager#IMPORTANCE_LOW} for - * notifications posted to this channel. + * notifications posted to this channel. Note: This value might be > + * {@link NotificationManager#IMPORTANCE_NONE}, but notifications posted to this channel will + * not be shown to the user if the parent {@link NotificationChannelGroup} or app is blocked. + * See {@link NotificationChannelGroup#isBlocked()} and + * {@link NotificationManager#areNotificationsEnabled()}. */ public int getImportance() { return mImportance; diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java index 16166f7cf1cf..0fa3c7fa6492 100644 --- a/core/java/android/app/NotificationChannelGroup.java +++ b/core/java/android/app/NotificationChannelGroup.java @@ -145,7 +145,9 @@ public final class NotificationChannelGroup implements Parcelable { /** * Returns whether or not notifications posted to {@link NotificationChannel channels} belonging - * to this group are blocked. + * to this group are blocked. This value is independent of + * {@link NotificationManager#areNotificationsEnabled()} and + * {@link NotificationChannel#getImportance()}. */ public boolean isBlocked() { return mBlocked; diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 8f0168530273..bd4933a2081c 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -24,7 +24,6 @@ import android.accessibilityservice.IAccessibilityServiceConnection; import android.annotation.NonNull; import android.annotation.TestApi; import android.graphics.Bitmap; -import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; @@ -47,6 +46,7 @@ import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; + import libcore.io.IoUtils; import java.io.IOException; @@ -876,16 +876,36 @@ public final class UiAutomation { } /** - * Grants a runtime permission to a package for a user. + * Grants a runtime permission to a package. * @param packageName The package to which to grant. * @param permission The permission to grant. - * @return Whether granting succeeded. - * + * @throws SecurityException if unable to grant the permission. + */ + public void grantRuntimePermission(String packageName, String permission) { + grantRuntimePermissionAsUser(packageName, permission, android.os.Process.myUserHandle()); + } + + /** + * @deprecated replaced by + * {@link #grantRuntimePermissionAsUser(String, String, UserHandle)}. * @hide */ + @Deprecated @TestApi public boolean grantRuntimePermission(String packageName, String permission, UserHandle userHandle) { + grantRuntimePermissionAsUser(packageName, permission, userHandle); + return true; + } + + /** + * Grants a runtime permission to a package for a user. + * @param packageName The package to which to grant. + * @param permission The permission to grant. + * @throws SecurityException if unable to grant the permission. + */ + public void grantRuntimePermissionAsUser(String packageName, String permission, + UserHandle userHandle) { synchronized (mLock) { throwIfNotConnectedLocked(); } @@ -896,25 +916,42 @@ public final class UiAutomation { // Calling out without a lock held. mUiAutomationConnection.grantRuntimePermission(packageName, permission, userHandle.getIdentifier()); - // TODO: The package manager API should return boolean. - return true; - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error granting runtime permission", re); + } catch (Exception e) { + throw new SecurityException("Error granting runtime permission", e); } - return false; } /** - * Revokes a runtime permission from a package for a user. - * @param packageName The package from which to revoke. - * @param permission The permission to revoke. - * @return Whether revoking succeeded. - * + * Revokes a runtime permission from a package. + * @param packageName The package to which to grant. + * @param permission The permission to grant. + * @throws SecurityException if unable to revoke the permission. + */ + public void revokeRuntimePermission(String packageName, String permission) { + revokeRuntimePermissionAsUser(packageName, permission, android.os.Process.myUserHandle()); + } + + /** + * @deprecated replaced by + * {@link #revokeRuntimePermissionAsUser(String, String, UserHandle)}. * @hide */ + @Deprecated @TestApi public boolean revokeRuntimePermission(String packageName, String permission, UserHandle userHandle) { + revokeRuntimePermissionAsUser(packageName, permission, userHandle); + return true; + } + + /** + * Revokes a runtime permission from a package. + * @param packageName The package to which to grant. + * @param permission The permission to grant. + * @throws SecurityException if unable to revoke the permission. + */ + public void revokeRuntimePermissionAsUser(String packageName, String permission, + UserHandle userHandle) { synchronized (mLock) { throwIfNotConnectedLocked(); } @@ -925,12 +962,9 @@ public final class UiAutomation { // Calling out without a lock held. mUiAutomationConnection.revokeRuntimePermission(packageName, permission, userHandle.getIdentifier()); - // TODO: The package manager API should return boolean. - return true; - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error revoking runtime permission", re); + } catch (Exception e) { + throw new SecurityException("Error granting runtime permission", e); } - return false; } /** @@ -949,6 +983,7 @@ public final class UiAutomation { synchronized (mLock) { throwIfNotConnectedLocked(); } + warnIfBetterCommand(command); ParcelFileDescriptor source = null; ParcelFileDescriptor sink = null; @@ -991,6 +1026,7 @@ public final class UiAutomation { synchronized (mLock) { throwIfNotConnectedLocked(); } + warnIfBetterCommand(command); ParcelFileDescriptor source_read = null; ParcelFileDescriptor sink_read = null; @@ -1056,6 +1092,16 @@ public final class UiAutomation { } } + private void warnIfBetterCommand(String cmd) { + if (cmd.startsWith("pm grant ")) { + Log.w(LOG_TAG, "UiAutomation.grantRuntimePermission() " + + "is more robust and should be used instead of 'pm grant'"); + } else if (cmd.startsWith("pm revoke ")) { + Log.w(LOG_TAG, "UiAutomation.revokeRuntimePermission() " + + "is more robust and should be used instead of 'pm revoke'"); + } + } + private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper { public IAccessibilityServiceClientImpl(Looper looper) { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index aec33b35849a..b64aae5240b2 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -124,6 +124,7 @@ import java.util.concurrent.Executor; @SystemService(Context.DEVICE_POLICY_SERVICE) @RequiresFeature(PackageManager.FEATURE_DEVICE_ADMIN) public class DevicePolicyManager { + private static String TAG = "DevicePolicyManager"; private final Context mContext; @@ -1612,8 +1613,6 @@ public class DevicePolicyManager { * <li>keyguard * </ul> * - * This is the default configuration for LockTask. - * * @see #setLockTaskFeatures(ComponentName, int) */ public static final int LOCK_TASK_FEATURE_NONE = 0; @@ -1631,7 +1630,10 @@ public class DevicePolicyManager { /** * Enable notifications during LockTask mode. This includes notification icons on the status * bar, heads-up notifications, and the expandable notification shade. Note that the Quick - * Settings panel will still be disabled. + * Settings panel remains disabled. This feature flag can only be used in combination with + * {@link #LOCK_TASK_FEATURE_HOME}. {@link #setLockTaskFeatures(ComponentName, int)} + * throws an {@link IllegalArgumentException} if this feature flag is defined without + * {@link #LOCK_TASK_FEATURE_HOME}. * * @see #setLockTaskFeatures(ComponentName, int) */ @@ -1664,6 +1666,9 @@ public class DevicePolicyManager { * the user long-presses the power button, for example. Note that the user may not be able to * power off the device if this flag is not set. * + * <p>This flag is enabled by default until {@link #setLockTaskFeatures(ComponentName, int)} is + * called for the first time. + * * @see #setLockTaskFeatures(ComponentName, int) */ public static final int LOCK_TASK_FEATURE_GLOBAL_ACTIONS = 1 << 4; @@ -1747,6 +1752,25 @@ public class DevicePolicyManager { public static final int ID_TYPE_MEID = 8; /** + * Specifies that the calling app should be granted access to the installed credentials + * immediately. Otherwise, access to the credentials will be gated by user approval. + * For use with {@link #installKeyPair(ComponentName, PrivateKey, Certificate[], String, int)} + * + * @see #installKeyPair(ComponentName, PrivateKey, Certificate[], String, int) + */ + public static final int INSTALLKEY_REQUEST_CREDENTIALS_ACCESS = 1; + + /** + * Specifies that a user can select the key via the Certificate Selection prompt. + * If this flag is not set when calling {@link #installKeyPair}, the key can only be granted + * access by implementing {@link android.app.admin.DeviceAdminReceiver#onChoosePrivateKeyAlias}. + * For use with {@link #installKeyPair(ComponentName, PrivateKey, Certificate[], String, int)} + * + * @see #installKeyPair(ComponentName, PrivateKey, Certificate[], String, int) + */ + public static final int INSTALLKEY_SET_USER_SELECTABLE = 2; + + /** * Broadcast action: sent when the profile owner is set, changed or cleared. * * This broadcast is sent only to the user managed by the new profile owner. @@ -2723,110 +2747,6 @@ public class DevicePolicyManager { } /** - * The maximum number of characters allowed in the password blacklist. - */ - private static final int PASSWORD_BLACKLIST_CHARACTER_LIMIT = 128 * 1000; - - /** - * Throws an exception if the password blacklist is too large. - * - * @hide - */ - public static void enforcePasswordBlacklistSize(List<String> blacklist) { - if (blacklist == null) { - return; - } - long characterCount = 0; - for (final String item : blacklist) { - characterCount += item.length(); - } - if (characterCount > PASSWORD_BLACKLIST_CHARACTER_LIMIT) { - throw new IllegalArgumentException("128 thousand blacklist character limit exceeded by " - + (characterCount - PASSWORD_BLACKLIST_CHARACTER_LIMIT) + " characters"); - } - } - - /** - * Called by an application that is administering the device to blacklist passwords. - * <p> - * Any blacklisted password or PIN is prevented from being enrolled by the user or the admin. - * Note that the match against the blacklist is case insensitive. The blacklist applies for all - * password qualities requested by {@link #setPasswordQuality} however it is not taken into - * consideration by {@link #isActivePasswordSufficient}. - * <p> - * The blacklist can be cleared by passing {@code null} or an empty list. The blacklist is - * given a name that is used to track which blacklist is currently set by calling {@link - * #getPasswordBlacklistName}. If the blacklist is being cleared, the name is ignored and {@link - * #getPasswordBlacklistName} will return {@code null}. The name can only be {@code null} when - * the blacklist is being cleared. - * <p> - * The blacklist is limited to a total of 128 thousand characters rather than limiting to a - * number of entries. - * <p> - * This method can be called on the {@link DevicePolicyManager} instance returned by - * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent - * profile. - * - * @param admin the {@link DeviceAdminReceiver} this request is associated with - * @param name name to associate with the blacklist - * @param blacklist list of passwords to blacklist or {@code null} to clear the blacklist - * @return whether the new blacklist was successfully installed - * @throws SecurityException if {@code admin} is not a device or profile owner - * @throws IllegalArgumentException if the blacklist surpasses the character limit - * @throws NullPointerException if {@code name} is {@code null} when setting a non-empty list - * - * @see #getPasswordBlacklistName - * @see #isActivePasswordSufficient - * @see #resetPasswordWithToken - */ - public boolean setPasswordBlacklist(@NonNull ComponentName admin, @Nullable String name, - @Nullable List<String> blacklist) { - enforcePasswordBlacklistSize(blacklist); - - try { - return mService.setPasswordBlacklist(admin, name, blacklist, mParentInstance); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - } - - /** - * Get the name of the password blacklist set by the given admin. - * - * @param admin the {@link DeviceAdminReceiver} this request is associated with - * @return the name of the blacklist or {@code null} if no blacklist is set - * - * @see #setPasswordBlacklist - */ - public @Nullable String getPasswordBlacklistName(@NonNull ComponentName admin) { - try { - return mService.getPasswordBlacklistName(admin, myUserId(), mParentInstance); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - } - - /** - * Test if a given password is blacklisted. - * - * @param userId the user to valiate for - * @param password the password to check against the blacklist - * @return whether the password is blacklisted - * - * @see #setPasswordBlacklist - * - * @hide - */ - @RequiresPermission(android.Manifest.permission.TEST_BLACKLISTED_PASSWORD) - public boolean isPasswordBlacklisted(@UserIdInt int userId, @NonNull String password) { - try { - return mService.isPasswordBlacklisted(userId, password); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - } - - /** * Determine whether the current password the user has set is sufficient to meet the policy * requirements (e.g. quality, minimum length) that have been requested by the admins of this * user and its participating profiles. Restrictions on profiles that have a separate challenge @@ -4122,7 +4042,11 @@ public class DevicePolicyManager { */ public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey, @NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess) { - return installKeyPair(admin, privKey, certs, alias, requestAccess, true); + int flags = INSTALLKEY_SET_USER_SELECTABLE; + if (requestAccess) { + flags |= INSTALLKEY_REQUEST_CREDENTIALS_ACCESS; + } + return installKeyPair(admin, privKey, certs, alias, flags); } /** @@ -4146,13 +4070,9 @@ public class DevicePolicyManager { * {@link android.security.KeyChain#getCertificateChain}. * @param alias The private key alias under which to install the certificate. If a certificate * with that alias already exists, it will be overwritten. - * @param requestAccess {@code true} to request that the calling app be granted access to the - * credentials immediately. Otherwise, access to the credentials will be gated by user - * approval. - * @param isUserSelectable {@code true} to indicate that a user can select this key via the - * Certificate Selection prompt, false to indicate that this key can only be granted - * access by implementing - * {@link android.app.admin.DeviceAdminReceiver#onChoosePrivateKeyAlias}. + * @param flags Flags to request that the calling app be granted access to the credentials + * and set the key to be user-selectable. See {@link #INSTALLKEY_SET_USER_SELECTABLE} and + * {@link #INSTALLKEY_REQUEST_CREDENTIALS_ACCESS}. * @return {@code true} if the keys were installed, {@code false} otherwise. * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile * owner. @@ -4161,9 +4081,12 @@ public class DevicePolicyManager { * @see #DELEGATION_CERT_INSTALL */ public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey, - @NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess, - boolean isUserSelectable) { + @NonNull Certificate[] certs, @NonNull String alias, int flags) { throwIfParentInstance("installKeyPair"); + boolean requestAccess = (flags & INSTALLKEY_REQUEST_CREDENTIALS_ACCESS) + == INSTALLKEY_REQUEST_CREDENTIALS_ACCESS; + boolean isUserSelectable = (flags & INSTALLKEY_SET_USER_SELECTABLE) + == INSTALLKEY_SET_USER_SELECTABLE; try { final byte[] pemCert = Credentials.convertToPem(certs[0]); byte[] pemChain = null; @@ -4242,6 +4165,8 @@ public class DevicePolicyManager { * algorithm specification in {@code keySpec} is not {@code RSAKeyGenParameterSpec} * or {@code ECGenParameterSpec}, or if Device ID attestation was requested but the * {@code keySpec} does not contain an attestation challenge. + * @throws UnsupportedOperationException if Device ID attestation was requested but the + * underlying hardware does not support it. * @see KeyGenParameterSpec.Builder#setAttestationChallenge(byte[]) */ public AttestedKeyPair generateKeyPair(@Nullable ComponentName admin, @@ -6359,6 +6284,7 @@ public class DevicePolicyManager { * @hide */ @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public @Nullable List<String> getPermittedInputMethodsForCurrentUser() { throwIfParentInstance("getPermittedInputMethodsForCurrentUser"); if (mService != null) { @@ -7186,30 +7112,24 @@ public class DevicePolicyManager { } /** - * Sets which system features to enable for LockTask mode. - * <p> - * Feature flags set through this method will only take effect for the duration when the device - * is in LockTask mode. If this method is not called, none of the features listed here will be - * enabled. - * <p> - * This function can only be called by the device owner, a profile owner of an affiliated user - * or profile, or the profile owner when no device owner is set. See {@link #isAffiliatedUser}. - * Any features set via this method will be cleared if the user becomes unaffiliated. + * Sets which system features are enabled when the device runs in lock task mode. This method + * doesn't affect the features when lock task mode is inactive. Any system features not included + * in {@code flags} are implicitly disabled when calling this method. By default, only + * {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS} is enabled—all the other features are disabled. To + * disable the global actions dialog, call this method omitting + * {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS}. + * + * <p>This method can only be called by the device owner, a profile owner of an affiliated + * user or profile, or the profile owner when no device owner is set. See + * {@link #isAffiliatedUser}. + * Any features set using this method are cleared if the user becomes unaffiliated. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @param flags Bitfield of feature flags: - * {@link #LOCK_TASK_FEATURE_NONE} (default), - * {@link #LOCK_TASK_FEATURE_SYSTEM_INFO}, - * {@link #LOCK_TASK_FEATURE_NOTIFICATIONS}, - * {@link #LOCK_TASK_FEATURE_HOME}, - * {@link #LOCK_TASK_FEATURE_OVERVIEW}, - * {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS}, - * {@link #LOCK_TASK_FEATURE_KEYGUARD} + * @param flags The system features enabled during lock task mode. * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an * affiliated user or profile, or the profile owner when no device owner is set. * @see #isAffiliatedUser - * @throws SecurityException if {@code admin} is not the device owner or the profile owner. - */ + **/ public void setLockTaskFeatures(@NonNull ComponentName admin, @LockTaskFeature int flags) { throwIfParentInstance("setLockTaskFeatures"); if (mService != null) { @@ -7302,11 +7222,12 @@ public class DevicePolicyManager { public @interface SystemSettingsWhitelist {} /** - * Called by device owner to update {@link android.provider.Settings.System} settings. - * Validation that the value of the setting is in the correct form for the setting type should - * be performed by the caller. + * Called by a device or profile owner to update {@link android.provider.Settings.System} + * settings. Validation that the value of the setting is in the correct form for the setting + * type should be performed by the caller. * <p> - * The settings that can be updated with this method are: + * The settings that can be updated by a device owner or profile owner of secondary user with + * this method are: * <ul> * <li>{@link android.provider.Settings.System#SCREEN_BRIGHTNESS}</li> * <li>{@link android.provider.Settings.System#SCREEN_BRIGHTNESS_MODE}</li> @@ -7318,7 +7239,7 @@ public class DevicePolicyManager { * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param setting The name of the setting to update. * @param value The value to update the setting to. - * @throws SecurityException if {@code admin} is not a device owner. + * @throws SecurityException if {@code admin} is not a device or profile owner. */ public void setSystemSetting(@NonNull ComponentName admin, @NonNull @SystemSettingsWhitelist String setting, String value) { diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 4b39a9a53252..37508cdc1119 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -79,10 +79,6 @@ interface IDevicePolicyManager { long getPasswordExpiration(in ComponentName who, int userHandle, boolean parent); - boolean setPasswordBlacklist(in ComponentName who, String name, in List<String> blacklist, boolean parent); - String getPasswordBlacklistName(in ComponentName who, int userId, boolean parent); - boolean isPasswordBlacklisted(int userId, String password); - boolean isActivePasswordSufficient(int userHandle, boolean parent); boolean isProfileActivePasswordSufficientForParent(int userHandle); boolean isUsingUnifiedPassword(in ComponentName admin); diff --git a/core/java/android/app/slice/ISliceManager.aidl b/core/java/android/app/slice/ISliceManager.aidl index 74e3c3ee4c81..a2aaf12432f8 100644 --- a/core/java/android/app/slice/ISliceManager.aidl +++ b/core/java/android/app/slice/ISliceManager.aidl @@ -25,7 +25,8 @@ interface ISliceManager { void unpinSlice(String pkg, in Uri uri, in IBinder token); boolean hasSliceAccess(String pkg); SliceSpec[] getPinnedSpecs(in Uri uri, String pkg); - int checkSlicePermission(in Uri uri, String pkg, int pid, int uid); + int checkSlicePermission(in Uri uri, String pkg, int pid, int uid, + in String[] autoGrantPermissions); void grantPermissionFromUser(in Uri uri, String pkg, String callingPkg, boolean allSlices); Uri[] getPinnedSlices(String pkg); diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java index 95bb1f682701..fc3b38d2bcfb 100644 --- a/core/java/android/app/slice/Slice.java +++ b/core/java/android/app/slice/Slice.java @@ -70,6 +70,7 @@ public final class Slice implements Parcelable { HINT_ERROR, HINT_TTL, HINT_LAST_UPDATED, + HINT_PERMISSION_REQUEST, }) @Retention(RetentionPolicy.SOURCE) public @interface SliceHint {} @@ -184,6 +185,11 @@ public final class Slice implements Parcelable { */ public static final String HINT_LAST_UPDATED = "last_updated"; /** + * A hint to indicate that this slice represents a permission request for showing + * slices. + */ + public static final String HINT_PERMISSION_REQUEST = "permission_request"; + /** * Key to retrieve an extra added to an intent when a control is changed. */ public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE"; diff --git a/core/java/android/app/slice/SliceManager.java b/core/java/android/app/slice/SliceManager.java index 1afe53d8a7b5..0285e9f9cc59 100644 --- a/core/java/android/app/slice/SliceManager.java +++ b/core/java/android/app/slice/SliceManager.java @@ -26,6 +26,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.PermissionResult; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Binder; @@ -81,6 +82,17 @@ public class SliceManager { * An activity can be statically linked to a slice uri by including a meta-data item * for this key that contains a valid slice uri for the same application declaring * the activity. + * + * <pre class="prettyprint"> + * {@literal + * <activity android:name="com.example.mypkg.MyActivity"> + * <meta-data android:name="android.metadata.SLICE_URI" + * android:value="content://com.example.mypkg/main_slice" /> + * </activity>} + * </pre> + * + * @see #mapIntentToUri(Intent) + * @see SliceProvider#onMapIntentToUri(Intent) */ public static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI"; @@ -257,16 +269,17 @@ public class SliceManager { * <p> * This goes through a several stage resolution process to determine if any slice * can represent this intent. - * - If the intent contains data that {@link ContentResolver#getType} is - * {@link SliceProvider#SLICE_TYPE} then the data will be returned. - * - If the intent with {@link #CATEGORY_SLICE} added resolves to a provider, then + * <ol> + * <li> If the intent contains data that {@link ContentResolver#getType} is + * {@link SliceProvider#SLICE_TYPE} then the data will be returned.</li> + * <li>If the intent with {@link #CATEGORY_SLICE} added resolves to a provider, then * the provider will be asked to {@link SliceProvider#onMapIntentToUri} and that result - * will be returned. - * - Lastly, if the intent explicitly points at an activity, and that activity has + * will be returned.</li> + * <li>Lastly, if the intent explicitly points at an activity, and that activity has * meta-data for key {@link #SLICE_METADATA_KEY}, then the Uri specified there will be - * returned. - * - If no slice is found, then {@code null} is returned. - * + * returned.</li> + * <li>If no slice is found, then {@code null} is returned.</li> + * </ol> * @param intent The intent associated with a slice. * @return The Slice Uri provided by the app or null if none exists. * @see Slice @@ -389,10 +402,70 @@ public class SliceManager { } /** + * Determine whether a particular process and user ID has been granted + * permission to access a specific slice URI. + * + * @param uri The uri that is being checked. + * @param pid The process ID being checked against. Must be > 0. + * @param uid The user ID being checked against. A uid of 0 is the root + * user, which will pass every permission check. + * + * @return {@link PackageManager#PERMISSION_GRANTED} if the given + * pid/uid is allowed to access that uri, or + * {@link PackageManager#PERMISSION_DENIED} if it is not. + * + * @see #grantSlicePermission(String, Uri) + */ + public @PermissionResult int checkSlicePermission(@NonNull Uri uri, int pid, int uid) { + // TODO: Switch off Uri permissions. + return mContext.checkUriPermission(uri, pid, uid, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + } + + /** + * Grant permission to access a specific slice Uri to another package. + * + * @param toPackage The package you would like to allow to access the Uri. + * @param uri The Uri you would like to grant access to. + * + * @see #revokeSlicePermission + */ + public void grantSlicePermission(@NonNull String toPackage, @NonNull Uri uri) { + // TODO: Switch off Uri permissions. + mContext.grantUriPermission(toPackage, uri, + Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + } + + /** + * Remove permissions to access a particular content provider Uri + * that were previously added with {@link #grantSlicePermission} for a specific target + * package. The given Uri will match all previously granted Uris that are the same or a + * sub-path of the given Uri. That is, revoking "content://foo/target" will + * revoke both "content://foo/target" and "content://foo/target/sub", but not + * "content://foo". It will not remove any prefix grants that exist at a + * higher level. + * + * @param toPackage The package you would like to allow to access the Uri. + * @param uri The Uri you would like to revoke access to. + * + * @see #grantSlicePermission + */ + public void revokeSlicePermission(@NonNull String toPackage, @NonNull Uri uri) { + // TODO: Switch off Uri permissions. + mContext.revokeUriPermission(toPackage, uri, + Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + } + + /** * Does the permission check to see if a caller has access to a specific slice. * @hide */ - public void enforceSlicePermission(Uri uri, String pkg, int pid, int uid) { + public void enforceSlicePermission(Uri uri, String pkg, int pid, int uid, + String[] autoGrantPermissions) { try { if (UserHandle.isSameApp(uid, Process.myUid())) { return; @@ -400,7 +473,7 @@ public class SliceManager { if (pkg == null) { throw new SecurityException("No pkg specified"); } - int result = mService.checkSlicePermission(uri, pkg, pid, uid); + int result = mService.checkSlicePermission(uri, pkg, pid, uid, autoGrantPermissions); if (result == PERMISSION_DENIED) { throw new SecurityException("User " + uid + " does not have slice permission for " + uri + "."); @@ -412,6 +485,8 @@ public class SliceManager { Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + // Notify a change has happened because we just granted a permission. + mContext.getContentResolver().notifyChange(uri, null); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java index bf856b74f067..fe5742d6da38 100644 --- a/core/java/android/app/slice/SliceProvider.java +++ b/core/java/android/app/slice/SliceProvider.java @@ -149,10 +149,31 @@ public abstract class SliceProvider extends ContentProvider { private static final boolean DEBUG = false; private static final long SLICE_BIND_ANR = 2000; + private final String[] mAutoGrantPermissions; private String mCallback; private SliceManager mSliceManager; + /** + * A version of constructing a SliceProvider that allows autogranting slice permissions + * to apps that hold specific platform permissions. + * <p> + * When an app tries to bind a slice from this provider that it does not have access to, + * This provider will check if the caller holds permissions to any of the autoGrantPermissions + * specified, if they do they will be granted persisted uri access to all slices of this + * provider. + * + * @param autoGrantPermissions List of permissions that holders are auto-granted access + * to slices. + */ + public SliceProvider(@NonNull String... autoGrantPermissions) { + mAutoGrantPermissions = autoGrantPermissions; + } + + public SliceProvider() { + mAutoGrantPermissions = new String[0]; + } + @Override public void attachInfo(Context context, ProviderInfo info) { super.attachInfo(context, info); @@ -402,7 +423,7 @@ public abstract class SliceProvider extends ContentProvider { : getContext().getPackageManager().getNameForUid(callingUid); try { mSliceManager.enforceSlicePermission(sliceUri, pkg, - callingPid, callingUid); + callingPid, callingUid, mAutoGrantPermissions); } catch (SecurityException e) { return createPermissionSlice(getContext(), sliceUri, pkg); } @@ -428,15 +449,17 @@ public abstract class SliceProvider extends ContentProvider { } finally { Handler.getMain().removeCallbacks(mAnr); } - return new Slice.Builder(sliceUri) - .addAction(action, - new Slice.Builder(sliceUri.buildUpon().appendPath("permission").build()) - .addText(getPermissionString(context, callingPackage), null, - Collections.emptyList()) - .build(), - null) - .addHints(Arrays.asList(Slice.HINT_LIST_ITEM)) - .build(); + Slice.Builder parent = new Slice.Builder(sliceUri); + Slice.Builder childAction = new Slice.Builder(parent) + .addHints(Arrays.asList(Slice.HINT_TITLE, Slice.HINT_SHORTCUT)) + .addAction(action, new Slice.Builder(parent).build(), null); + + parent.addSubSlice(new Slice.Builder(sliceUri.buildUpon().appendPath("permission").build()) + .addText(getPermissionString(context, callingPackage), null, + Collections.emptyList()) + .addSubSlice(childAction.build(), null) + .build(), null); + return parent.addHints(Arrays.asList(Slice.HINT_PERMISSION_REQUEST)).build(); } /** diff --git a/core/java/android/app/usage/NetworkStats.java b/core/java/android/app/usage/NetworkStats.java index da36157d85f8..e315e9115f41 100644 --- a/core/java/android/app/usage/NetworkStats.java +++ b/core/java/android/app/usage/NetworkStats.java @@ -24,7 +24,6 @@ import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.net.TrafficStats; import android.os.RemoteException; -import android.os.ServiceManager; import android.util.IntArray; import android.util.Log; @@ -98,9 +97,8 @@ public final class NetworkStats implements AutoCloseable { /** @hide */ NetworkStats(Context context, NetworkTemplate template, int flags, long startTimestamp, - long endTimestamp) throws RemoteException, SecurityException { - final INetworkStatsService statsService = INetworkStatsService.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); + long endTimestamp, INetworkStatsService statsService) + throws RemoteException, SecurityException { // Open network stats session mSession = statsService.openSessionForUsageStats(flags, context.getOpPackageName()); mCloseGuard.open("close"); diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java index 5576e86edd8a..2357637b7270 100644 --- a/core/java/android/app/usage/NetworkStatsManager.java +++ b/core/java/android/app/usage/NetworkStatsManager.java @@ -37,6 +37,8 @@ import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; + /** * Provides access to network usage history and statistics. Usage data is collected in * discrete bins of time called 'Buckets'. See {@link NetworkStats.Bucket} for details. @@ -107,9 +109,15 @@ public class NetworkStatsManager { * {@hide} */ public NetworkStatsManager(Context context) throws ServiceNotFoundException { + this(context, INetworkStatsService.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.NETWORK_STATS_SERVICE))); + } + + /** @hide */ + @VisibleForTesting + public NetworkStatsManager(Context context, INetworkStatsService service) { mContext = context; - mService = INetworkStatsService.Stub.asInterface( - ServiceManager.getServiceOrThrow(Context.NETWORK_STATS_SERVICE)); + mService = service; setPollOnOpen(true); } @@ -135,7 +143,8 @@ public class NetworkStatsManager { public Bucket querySummaryForDevice(NetworkTemplate template, long startTime, long endTime) throws SecurityException, RemoteException { Bucket bucket = null; - NetworkStats stats = new NetworkStats(mContext, template, mFlags, startTime, endTime); + NetworkStats stats = new NetworkStats(mContext, template, mFlags, startTime, endTime, + mService); bucket = stats.getDeviceSummaryForNetwork(); stats.close(); @@ -208,7 +217,7 @@ public class NetworkStatsManager { } NetworkStats stats; - stats = new NetworkStats(mContext, template, mFlags, startTime, endTime); + stats = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); stats.startSummaryEnumeration(); stats.close(); @@ -245,7 +254,7 @@ public class NetworkStatsManager { } NetworkStats result; - result = new NetworkStats(mContext, template, mFlags, startTime, endTime); + result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); result.startSummaryEnumeration(); return result; @@ -295,7 +304,7 @@ public class NetworkStatsManager { NetworkStats result; try { - result = new NetworkStats(mContext, template, mFlags, startTime, endTime); + result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); result.startHistoryEnumeration(uid, tag); } catch (RemoteException e) { Log.e(TAG, "Error while querying stats for uid=" + uid + " tag=" + tag, e); @@ -341,7 +350,7 @@ public class NetworkStatsManager { } NetworkStats result; - result = new NetworkStats(mContext, template, mFlags, startTime, endTime); + result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); result.startUserUidEnumeration(); return result; } @@ -451,19 +460,20 @@ public class NetworkStatsManager { } private static NetworkTemplate createTemplate(int networkType, String subscriberId) { - NetworkTemplate template = null; + final NetworkTemplate template; switch (networkType) { - case ConnectivityManager.TYPE_MOBILE: { - template = NetworkTemplate.buildTemplateMobileAll(subscriberId); - } break; - case ConnectivityManager.TYPE_WIFI: { + case ConnectivityManager.TYPE_MOBILE: + template = subscriberId == null + ? NetworkTemplate.buildTemplateMobileWildcard() + : NetworkTemplate.buildTemplateMobileAll(subscriberId); + break; + case ConnectivityManager.TYPE_WIFI: template = NetworkTemplate.buildTemplateWifiWildcard(); - } break; - default: { + break; + default: throw new IllegalArgumentException("Cannot create template for network type " + networkType + ", subscriberId '" + NetworkIdentity.scrubSubscriberId(subscriberId) + "'."); - } } return template; } diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index 6feb5278a48c..eafe91a3450a 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -179,6 +179,16 @@ public final class UsageStatsManager { public static final int REASON_SUB_USAGE_ACTIVE_TIMEOUT = 0x0007; /** @hide */ public static final int REASON_SUB_USAGE_SYNC_ADAPTER = 0x0008; + /** @hide */ + public static final int REASON_SUB_USAGE_SLICE_PINNED = 0x0009; + /** @hide */ + public static final int REASON_SUB_USAGE_SLICE_PINNED_PRIV = 0x000A; + /** @hide */ + public static final int REASON_SUB_USAGE_EXEMPTED_SYNC_START = 0x000B; + + /** @hide */ + public static final int REASON_SUB_PREDICTED_RESTORED = 0x0001; + /** @hide */ @IntDef(flag = false, prefix = { "STANDBY_BUCKET_" }, value = { @@ -616,36 +626,50 @@ public final class UsageStatsManager { break; case REASON_MAIN_PREDICTED: sb.append("p"); + switch (standbyReason & REASON_SUB_MASK) { + case REASON_SUB_PREDICTED_RESTORED: + sb.append("-r"); + break; + } break; case REASON_MAIN_TIMEOUT: sb.append("t"); break; case REASON_MAIN_USAGE: - sb.append("u-"); + sb.append("u"); switch (standbyReason & REASON_SUB_MASK) { case REASON_SUB_USAGE_SYSTEM_INTERACTION: - sb.append("si"); + sb.append("-si"); break; case REASON_SUB_USAGE_NOTIFICATION_SEEN: - sb.append("ns"); + sb.append("-ns"); break; case REASON_SUB_USAGE_USER_INTERACTION: - sb.append("ui"); + sb.append("-ui"); break; case REASON_SUB_USAGE_MOVE_TO_FOREGROUND: - sb.append("mf"); + sb.append("-mf"); break; case REASON_SUB_USAGE_MOVE_TO_BACKGROUND: - sb.append("mb"); + sb.append("-mb"); break; case REASON_SUB_USAGE_SYSTEM_UPDATE: - sb.append("su"); + sb.append("-su"); break; case REASON_SUB_USAGE_ACTIVE_TIMEOUT: - sb.append("at"); + sb.append("-at"); break; case REASON_SUB_USAGE_SYNC_ADAPTER: - sb.append("sa"); + sb.append("-sa"); + break; + case REASON_SUB_USAGE_SLICE_PINNED: + sb.append("slp"); + break; + case REASON_SUB_USAGE_SLICE_PINNED_PRIV: + sb.append("slpp"); + break; + case REASON_SUB_USAGE_EXEMPTED_SYNC_START: + sb.append("es"); break; } break; diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java index 09ced2648de1..b8628a4d446b 100644 --- a/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -243,4 +243,12 @@ public abstract class UsageStatsManagerInternal { */ public abstract void reportAppJobState(String packageName, @UserIdInt int userId, int numDeferredJobs, long timeSinceLastJobRun); + + /** + * Report a sync that was scheduled by an active app is about to be executed. + * + * @param packageName name of the package that owns the sync adapter. + * @param userId which user the app is associated with + */ + public abstract void reportExemptedSyncStart(String packageName, @UserIdInt int userId); } diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java index cb1d1062681f..af99bf7dfd8e 100644 --- a/core/java/android/bluetooth/BluetoothHidDevice.java +++ b/core/java/android/bluetooth/BluetoothHidDevice.java @@ -701,29 +701,6 @@ public final class BluetoothHidDevice implements BluetoothProfile { } /** - * Sends Virtual Cable Unplug to currently connected host. - * - * @return - * {@hide} - */ - public boolean unplug(BluetoothDevice device) { - boolean result = false; - - final IBluetoothHidDevice service = mService; - if (service != null) { - try { - result = service.unplug(device); - } catch (RemoteException e) { - Log.e(TAG, e.toString()); - } - } else { - Log.w(TAG, "Proxy not attached to service"); - } - - return result; - } - - /** * Initiates connection to host which is currently paired with this device. If the application * is not registered, #connect(BluetoothDevice) will fail. The connection state should be * tracked by the application by handling callback from Callback#onConnectionStateChanged. The diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 440103a6d8a4..9f3df377c4e2 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -166,24 +166,13 @@ public abstract class ContentResolver { public static final String SYNC_EXTRAS_DISALLOW_METERED = "allow_metered"; /** - * {@hide} Flag only used by the requestsync command to treat a request as if it was made by - * a foreground app. + * {@hide} Integer extra containing a SyncExemption flag. * * Only the system and the shell user can set it. * * This extra is "virtual". Once passed to the system server, it'll be removed from the bundle. */ - public static final String SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC = "force_fg_sync"; - - /** - * {@hide} Flag only used by the requestsync command to treat a request as if it was made by - * a background app. - * - * Only the system and the shell user can set it. - * - * This extra is "virtual". Once passed to the system server, it'll be removed from the bundle. - */ - public static final String SYNC_VIRTUAL_EXTRAS_FORCE_BG_SYNC = "force_bg_sync"; + public static final String SYNC_VIRTUAL_EXTRAS_EXEMPTION_FLAG = "v_exemption"; /** * Set by the SyncManager to request that the SyncAdapter initialize itself for @@ -525,6 +514,38 @@ public abstract class ContentResolver { */ public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 1<<1; + /** + * No exception, throttled by app standby normally. + * @hide + */ + public static final int SYNC_EXEMPTION_NONE = 0; + + /** + * When executing a sync with this exemption, we'll put the target app in the ACTIVE bucket + * for 10 minutes. This will allow the sync adapter to schedule/run further syncs and jobs. + * + * Note this will still *not* let RARE apps to run syncs, because they still won't get network + * connection. + * @hide + */ + public static final int SYNC_EXEMPTION_ACTIVE = 1; + + /** + * In addition to {@link #SYNC_EXEMPTION_ACTIVE}, we put the sync adapter app in the + * temp whitelist for 10 minutes, so that even RARE apps can run syncs right away. + * @hide + */ + public static final int SYNC_EXEMPTION_ACTIVE_WITH_TEMP = 2; + + /** @hide */ + @IntDef(flag = false, prefix = { "SYNC_EXEMPTION_" }, value = { + SYNC_EXEMPTION_NONE, + SYNC_EXEMPTION_ACTIVE, + SYNC_EXEMPTION_ACTIVE_WITH_TEMP, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SyncExemption {} + // Always log queries which take 500ms+; shorter queries are // sampled accordingly. private static final boolean ENABLE_CONTENT_SAMPLE = false; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index d4ec5d509fcd..000912cd1e47 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1852,6 +1852,17 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED"; /** + * Intent extra: A {@link Bundle} of extras supplied for the launcher when any packages on + * device are suspended. Will be sent with {@link #ACTION_PACKAGES_SUSPENDED}. + * + * @see PackageManager#isPackageSuspended() + * @see #ACTION_PACKAGES_SUSPENDED + * + * @hide + */ + public static final String EXTRA_LAUNCHER_EXTRAS = "android.intent.extra.LAUNCHER_EXTRAS"; + + /** * Activity action: Launch UI to manage which apps have a given permission. * <p> * Input: {@link #EXTRA_PERMISSION_NAME} specifies the permission access @@ -2251,8 +2262,8 @@ public class Intent implements Parcelable, Cloneable { /** * Broadcast Action: Sent to a package that has been suspended by the system. This is sent - * whenever a package is put into a suspended state or any of it's app extras change while - * in the suspended state. + * whenever a package is put into a suspended state or any of its app extras change while in the + * suspended state. * <p> Optionally includes the following extras: * <ul> * <li> {@link #EXTRA_SUSPENDED_PACKAGE_EXTRAS} which is a {@link Bundle} which will contain diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index c08bd1db8302..ae1c2071eeca 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -49,6 +49,7 @@ interface ILauncherApps { String callingPackage, in ComponentName component, in Rect sourceBounds, in Bundle opts, in UserHandle user); boolean isPackageEnabled(String callingPackage, String packageName, in UserHandle user); + Bundle getSuspendedPackageLauncherExtras(String packageName, in UserHandle user); boolean isActivityEnabled( String callingPackage, in ComponentName component, in UserHandle user); ApplicationInfo getApplicationInfo( diff --git a/core/java/android/content/pm/IOnAppsChangedListener.aidl b/core/java/android/content/pm/IOnAppsChangedListener.aidl index e6525af311ed..fcb1de016078 100644 --- a/core/java/android/content/pm/IOnAppsChangedListener.aidl +++ b/core/java/android/content/pm/IOnAppsChangedListener.aidl @@ -17,6 +17,7 @@ package android.content.pm; import android.content.pm.ParceledListSlice; +import android.os.Bundle; import android.os.UserHandle; /** @@ -28,7 +29,8 @@ oneway interface IOnAppsChangedListener { void onPackageChanged(in UserHandle user, String packageName); void onPackagesAvailable(in UserHandle user, in String[] packageNames, boolean replacing); void onPackagesUnavailable(in UserHandle user, in String[] packageNames, boolean replacing); - void onPackagesSuspended(in UserHandle user, in String[] packageNames); + void onPackagesSuspended(in UserHandle user, in String[] packageNames, + in Bundle launcherExtras); void onPackagesUnsuspended(in UserHandle user, in String[] packageNames); void onShortcutChanged(in UserHandle user, String packageName, in ParceledListSlice shortcuts); } diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 21ede165efaf..9aace2e7ba8d 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -211,6 +211,10 @@ public class LauncherApps { * example, this can happen when a Device Administrator suspends * an applicaton. * + * <p>Note: On devices running {@link android.os.Build.VERSION_CODES#P Android P} or higher, + * any apps that override {@link #onPackagesSuspended(String[], Bundle, UserHandle)} will + * not receive this callback. + * * @param packageNames The names of the packages that have just been * suspended. * @param user The UserHandle of the profile that generated the change. @@ -219,6 +223,22 @@ public class LauncherApps { } /** + * Indicates that one or more packages have been suspended. A device administrator or an app + * with {@code android.permission.SUSPEND_APPS} can do this. + * + * @param packageNames The names of the packages that have just been suspended. + * @param launcherExtras A {@link Bundle} of extras for the launcher. + * @param user the user for which the given packages were suspended. + * + * @see PackageManager#isPackageSuspended() + * @see #getSuspendedPackageLauncherExtras(String, UserHandle) + */ + public void onPackagesSuspended(String[] packageNames, @Nullable Bundle launcherExtras, + UserHandle user) { + onPackagesSuspended(packageNames, user); + } + + /** * Indicates that one or more packages have been unsuspended. For * example, this can happen when a Device Administrator unsuspends * an applicaton. @@ -638,6 +658,31 @@ public class LauncherApps { } /** + * Gets the launcher extras supplied to the system when the given package was suspended via + * {@code PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle, + * PersistableBundle, String)}. + * + * <p>Note: This just returns whatever extras were provided to the system, <em>which might + * even be {@code null}.</em> + * + * @param packageName The package for which to fetch the launcher extras. + * @param user The {@link UserHandle} of the profile. + * @return A {@link Bundle} of launcher extras. Or {@code null} if the package is not currently + * suspended. + * + * @see Callback#onPackagesSuspended(String[], Bundle, UserHandle) + * @see PackageManager#isPackageSuspended() + */ + public @Nullable Bundle getSuspendedPackageLauncherExtras(String packageName, UserHandle user) { + logErrorForInvalidProfileAccess(user); + try { + return mService.getSuspendedPackageLauncherExtras(packageName, user); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Returns {@link ApplicationInfo} about an application installed for a specific user profile. * * @param packageName The package name of the application @@ -652,7 +697,7 @@ public class LauncherApps { @ApplicationInfoFlags int flags, @NonNull UserHandle user) throws PackageManager.NameNotFoundException { Preconditions.checkNotNull(packageName, "packageName"); - Preconditions.checkNotNull(packageName, "user"); + Preconditions.checkNotNull(user, "user"); logErrorForInvalidProfileAccess(user); try { final ApplicationInfo ai = mService @@ -1163,14 +1208,15 @@ public class LauncherApps { } @Override - public void onPackagesSuspended(UserHandle user, String[] packageNames) + public void onPackagesSuspended(UserHandle user, String[] packageNames, + Bundle launcherExtras) throws RemoteException { if (DEBUG) { Log.d(TAG, "onPackagesSuspended " + user.getIdentifier() + "," + packageNames); } synchronized (LauncherApps.this) { for (CallbackMessageHandler callback : mCallbacks) { - callback.postOnPackagesSuspended(packageNames, user); + callback.postOnPackagesSuspended(packageNames, launcherExtras, user); } } } @@ -1218,6 +1264,7 @@ public class LauncherApps { private static class CallbackInfo { String[] packageNames; String packageName; + Bundle launcherExtras; boolean replacing; UserHandle user; List<ShortcutInfo> shortcuts; @@ -1251,7 +1298,8 @@ public class LauncherApps { mCallback.onPackagesUnavailable(info.packageNames, info.user, info.replacing); break; case MSG_SUSPENDED: - mCallback.onPackagesSuspended(info.packageNames, info.user); + mCallback.onPackagesSuspended(info.packageNames, info.launcherExtras, + info.user); break; case MSG_UNSUSPENDED: mCallback.onPackagesUnsuspended(info.packageNames, info.user); @@ -1301,10 +1349,12 @@ public class LauncherApps { obtainMessage(MSG_UNAVAILABLE, info).sendToTarget(); } - public void postOnPackagesSuspended(String[] packageNames, UserHandle user) { + public void postOnPackagesSuspended(String[] packageNames, Bundle launcherExtras, + UserHandle user) { CallbackInfo info = new CallbackInfo(); info.packageNames = packageNames; info.user = user; + info.launcherExtras = launcherExtras; obtainMessage(MSG_SUSPENDED, info).sendToTarget(); } diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index dff51f77788e..c9b78c08202e 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -26,6 +26,7 @@ import android.content.pm.PackageManager.ComponentInfoFlags; import android.content.pm.PackageManager.PackageInfoFlags; import android.content.pm.PackageManager.ResolveInfoFlags; import android.os.Bundle; +import android.os.PersistableBundle; import android.util.SparseArray; import java.lang.annotation.Retention; @@ -188,6 +189,22 @@ public abstract class PackageManagerInternal { @PackageInfoFlags int flags, int filterCallingUid, int userId); /** + * Retrieve launcher extras for a suspended package provided to the system in + * {@link PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle, + * PersistableBundle, String)} + * + * @param packageName The package for which to return launcher extras. + * @param userId The user for which to check, + * @return The launcher extras. + * + * @see PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle, + * PersistableBundle, String) + * @see PackageManager#isPackageSuspended() + */ + public abstract Bundle getSuspendedPackageLauncherExtras(String packageName, + int userId); + + /** * Do a straight uid lookup for the given package/application in the given user. * @see PackageManager#getPackageUidAsUser(String, int, int) * @return The app's uid, or < 0 if the package was not found in that user diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index a3b2d22deccb..efb9517adc00 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -535,6 +535,19 @@ public final class DisplayManager { } /** + * Set the level of color saturation to apply to the display. + * @param level The amount of saturation to apply, between 0 and 1 inclusive. + * 0 produces a grayscale image, 1 is normal. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_SATURATION) + public void setSaturationLevel(float level) { + mGlobal.setSaturationLevel(level); + } + + /** * Creates a virtual display. * * @see #createVirtualDisplay(String, int, int, int, Surface, int, diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 1f67a6bf71a7..2d0ef2f23432 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -384,6 +384,17 @@ public final class DisplayManagerGlobal { } } + /** + * Set the level of color saturation to apply to the display. + */ + public void setSaturationLevel(float level) { + try { + mDm.setSaturationLevel(level); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + public VirtualDisplay createVirtualDisplay(Context context, MediaProjection projection, String name, int width, int height, int densityDpi, Surface surface, int flags, VirtualDisplay.Callback callback, Handler handler, String uniqueId) { diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index 9fcb9d3fc265..b77de748a987 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -65,6 +65,9 @@ interface IDisplayManager { // Requires CONFIGURE_DISPLAY_COLOR_MODE void requestColorMode(int displayId, int colorMode); + // Requires CONTROL_DISPLAY_SATURATION + void setSaturationLevel(float level); + // Requires CAPTURE_VIDEO_OUTPUT, CAPTURE_SECURE_VIDEO_OUTPUT, or an appropriate // MediaProjection token for certain combinations of flags. int createVirtualDisplay(in IVirtualDisplayCallback callback, diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java index b2aa9ba532a9..e6f523c07e70 100644 --- a/core/java/android/hardware/radio/ProgramList.java +++ b/core/java/android/hardware/radio/ProgramList.java @@ -263,6 +263,17 @@ public final class ProgramList implements AutoCloseable { /** * @hide for framework use only */ + public Filter() { + mIdentifierTypes = Collections.emptySet(); + mIdentifiers = Collections.emptySet(); + mIncludeCategories = false; + mExcludeModifications = false; + mVendorFilter = null; + } + + /** + * @hide for framework use only + */ public Filter(@Nullable Map<String, String> vendorFilter) { mIdentifierTypes = Collections.emptySet(); mIdentifiers = Collections.emptySet(); diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java index 8fde82ef2012..8263bb8dfd2d 100644 --- a/core/java/android/hardware/radio/RadioManager.java +++ b/core/java/android/hardware/radio/RadioManager.java @@ -211,6 +211,7 @@ public class RadioManager { private final String mSerial; private final int mNumTuners; private final int mNumAudioSources; + private final boolean mIsInitializationRequired; private final boolean mIsCaptureSupported; private final BandDescriptor[] mBands; private final boolean mIsBgScanSupported; @@ -222,7 +223,8 @@ public class RadioManager { /** @hide */ public ModuleProperties(int id, String serviceName, int classId, String implementor, String product, String version, String serial, int numTuners, int numAudioSources, - boolean isCaptureSupported, BandDescriptor[] bands, boolean isBgScanSupported, + boolean isInitializationRequired, boolean isCaptureSupported, + BandDescriptor[] bands, boolean isBgScanSupported, @ProgramSelector.ProgramType int[] supportedProgramTypes, @ProgramSelector.IdentifierType int[] supportedIdentifierTypes, @Nullable Map<String, Integer> dabFrequencyTable, @@ -236,6 +238,7 @@ public class RadioManager { mSerial = serial; mNumTuners = numTuners; mNumAudioSources = numAudioSources; + mIsInitializationRequired = isInitializationRequired; mIsCaptureSupported = isCaptureSupported; mBands = bands; mIsBgScanSupported = isBgScanSupported; @@ -329,6 +332,18 @@ public class RadioManager { return mNumAudioSources; } + /** + * Checks, if BandConfig initialization (after {@link RadioManager#openTuner}) + * is required to be done before other operations or not. + * + * If it is, the client has to wait for {@link RadioTuner.Callback#onConfigurationChanged} + * callback before executing any other operations. Otherwise, such operation will fail + * returning {@link RadioManager#STATUS_INVALID_OPERATION} error code. + */ + public boolean isInitializationRequired() { + return mIsInitializationRequired; + } + /** {@code true} if audio capture is possible from radio tuner output. * This indicates if routing to audio devices not connected to the same HAL as the FM radio * is possible (e.g. to USB) or DAR (Digital Audio Recorder) feature can be implemented. @@ -419,6 +434,7 @@ public class RadioManager { mSerial = in.readString(); mNumTuners = in.readInt(); mNumAudioSources = in.readInt(); + mIsInitializationRequired = in.readInt() == 1; mIsCaptureSupported = in.readInt() == 1; Parcelable[] tmp = in.readParcelableArray(BandDescriptor.class.getClassLoader()); mBands = new BandDescriptor[tmp.length]; @@ -454,6 +470,7 @@ public class RadioManager { dest.writeString(mSerial); dest.writeInt(mNumTuners); dest.writeInt(mNumAudioSources); + dest.writeInt(mIsInitializationRequired ? 1 : 0); dest.writeInt(mIsCaptureSupported ? 1 : 0); dest.writeParcelableArray(mBands, flags); dest.writeInt(mIsBgScanSupported ? 1 : 0); @@ -476,6 +493,7 @@ public class RadioManager { + ", mVersion=" + mVersion + ", mSerial=" + mSerial + ", mNumTuners=" + mNumTuners + ", mNumAudioSources=" + mNumAudioSources + + ", mIsInitializationRequired=" + mIsInitializationRequired + ", mIsCaptureSupported=" + mIsCaptureSupported + ", mIsBgScanSupported=" + mIsBgScanSupported + ", mBands=" + Arrays.toString(mBands) + "]"; @@ -484,8 +502,8 @@ public class RadioManager { @Override public int hashCode() { return Objects.hash(mId, mServiceName, mClassId, mImplementor, mProduct, mVersion, - mSerial, mNumTuners, mNumAudioSources, mIsCaptureSupported, mBands, - mIsBgScanSupported, mDabFrequencyTable, mVendorInfo); + mSerial, mNumTuners, mNumAudioSources, mIsInitializationRequired, + mIsCaptureSupported, mBands, mIsBgScanSupported, mDabFrequencyTable, mVendorInfo); } @Override @@ -503,6 +521,7 @@ public class RadioManager { if (!Objects.equals(mSerial, other.mSerial)) return false; if (mNumTuners != other.mNumTuners) return false; if (mNumAudioSources != other.mNumAudioSources) return false; + if (mIsInitializationRequired != other.mIsInitializationRequired) return false; if (mIsCaptureSupported != other.mIsCaptureSupported) return false; if (!Objects.equals(mBands, other.mBands)) return false; if (mIsBgScanSupported != other.mIsBgScanSupported) return false; diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java index 85f3115689cd..be2846f87079 100644 --- a/core/java/android/hardware/radio/TunerAdapter.java +++ b/core/java/android/hardware/radio/TunerAdapter.java @@ -60,6 +60,7 @@ class TunerAdapter extends RadioTuner { mLegacyListProxy.close(); mLegacyListProxy = null; } + mCallback.close(); } try { mTuner.close(); @@ -278,6 +279,7 @@ class TunerAdapter extends RadioTuner { try { mTuner.startProgramListUpdates(filter); } catch (UnsupportedOperationException ex) { + Log.i(TAG, "Program list is not supported with this hardware"); return null; } catch (RemoteException ex) { mCallback.setProgramListObserver(null, () -> { }); diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java index 7437c40351db..0fb93e532cd3 100644 --- a/core/java/android/hardware/radio/TunerCallbackAdapter.java +++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java @@ -53,6 +53,12 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { } } + void close() { + synchronized (mLock) { + if (mProgramList != null) mProgramList.close(); + } + } + void setProgramListObserver(@Nullable ProgramList programList, @NonNull ProgramList.OnCloseListener closeListener) { Objects.requireNonNull(closeListener); diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index e07f586a5701..80b1c3dfa06d 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -861,6 +861,10 @@ public class ConnectivityManager { * You should always check {@link NetworkInfo#isConnected()} before initiating * network traffic. This may return {@code null} when there is no default * network. + * Note that if the default network is a VPN, this method will return the + * NetworkInfo for one of its underlying networks instead, or null if the + * VPN agent did not specify any. Apps interested in learning about VPNs + * should use {@link #getNetworkInfo(android.net.Network)} instead. * * @return a {@link NetworkInfo} object for the current default network * or {@code null} if no default network is currently active @@ -1018,7 +1022,11 @@ public class ConnectivityManager { * which you're interested. * @return a {@link NetworkInfo} object for the requested * network type or {@code null} if the type is not - * supported by the device. + * supported by the device. If {@code networkType} is + * TYPE_VPN and a VPN is active for the calling app, + * then this method will try to return one of the + * underlying networks for the VPN or null if the + * VPN agent didn't specify any. * * @deprecated This method does not support multiple connected networks * of the same type. Use {@link #getAllNetworks} and diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java index 57f05884ce6d..8034bb62c94c 100644 --- a/core/java/android/net/IpSecAlgorithm.java +++ b/core/java/android/net/IpSecAlgorithm.java @@ -56,7 +56,8 @@ public final class IpSecAlgorithm implements Parcelable { * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b> * * <p>Keys for this algorithm must be 128 bits in length. - * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 128. + * + * <p>Valid truncation lengths are multiples of 8 bits from 96 to 128. */ public static final String AUTH_HMAC_MD5 = "hmac(md5)"; @@ -65,7 +66,8 @@ public final class IpSecAlgorithm implements Parcelable { * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b> * * <p>Keys for this algorithm must be 160 bits in length. - * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 160. + * + * <p>Valid truncation lengths are multiples of 8 bits from 96 to 160. */ public static final String AUTH_HMAC_SHA1 = "hmac(sha1)"; @@ -73,7 +75,8 @@ public final class IpSecAlgorithm implements Parcelable { * SHA256 HMAC Authentication/Integrity Algorithm. * * <p>Keys for this algorithm must be 256 bits in length. - * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 256. + * + * <p>Valid truncation lengths are multiples of 8 bits from 96 to 256. */ public static final String AUTH_HMAC_SHA256 = "hmac(sha256)"; @@ -81,7 +84,8 @@ public final class IpSecAlgorithm implements Parcelable { * SHA384 HMAC Authentication/Integrity Algorithm. * * <p>Keys for this algorithm must be 384 bits in length. - * <p>Valid truncation lengths are multiples of 8 bits from 192 to (default) 384. + * + * <p>Valid truncation lengths are multiples of 8 bits from 192 to 384. */ public static final String AUTH_HMAC_SHA384 = "hmac(sha384)"; @@ -89,7 +93,8 @@ public final class IpSecAlgorithm implements Parcelable { * SHA512 HMAC Authentication/Integrity Algorithm. * * <p>Keys for this algorithm must be 512 bits in length. - * <p>Valid truncation lengths are multiples of 8 bits from 256 to (default) 512. + * + * <p>Valid truncation lengths are multiples of 8 bits from 256 to 512. */ public static final String AUTH_HMAC_SHA512 = "hmac(sha512)"; @@ -112,6 +117,7 @@ public final class IpSecAlgorithm implements Parcelable { AUTH_HMAC_MD5, AUTH_HMAC_SHA1, AUTH_HMAC_SHA256, + AUTH_HMAC_SHA384, AUTH_HMAC_SHA512, AUTH_CRYPT_AES_GCM }) @@ -126,11 +132,14 @@ public final class IpSecAlgorithm implements Parcelable { * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are * defined as constants in this class. * + * <p>For algorithms that produce an integrity check value, the truncation length is a required + * parameter. See {@link #IpSecAlgorithm(String algorithm, byte[] key, int truncLenBits)} + * * @param algorithm name of the algorithm. * @param key key padded to a multiple of 8 bits. */ public IpSecAlgorithm(@NonNull @AlgorithmName String algorithm, @NonNull byte[] key) { - this(algorithm, key, key.length * 8); + this(algorithm, key, 0); } /** @@ -228,6 +237,7 @@ public final class IpSecAlgorithm implements Parcelable { case AUTH_CRYPT_AES_GCM: // The keying material for GCM is a key plus a 32-bit salt isValidLen = keyLen == 128 + 32 || keyLen == 192 + 32 || keyLen == 256 + 32; + isValidTruncLen = truncLen == 64 || truncLen == 96 || truncLen == 128; break; default: throw new IllegalArgumentException("Couldn't find an algorithm: " + name); diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index 972b9c074690..15255083260b 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -274,7 +274,8 @@ public final class IpSecManager { * * @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 + * @param requestedSpi the requested SPI, or '0' to allocate a random SPI. The range 1-255 is + * reserved and may not be used. See RFC 4303 Section 2.1. * @return the reserved SecurityParameterIndex * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are * currently allocated for this user @@ -305,6 +306,19 @@ public final class IpSecManager { * will throw IOException if the user deactivates the transform (by calling {@link * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}. * + * <p>Note that when applied to TCP sockets, calling {@link IpSecTransform#close()} on an + * applied transform before completion of graceful shutdown may result in the shutdown sequence + * failing to complete. As such, applications requiring graceful shutdown MUST close the socket + * prior to deactivating the applied transform. Socket closure may be performed asynchronously + * (in batches), so the returning of a close function does not guarantee shutdown of a socket. + * Setting an SO_LINGER timeout results in socket closure being performed synchronously, and is + * sufficient to ensure shutdown. + * + * Specifically, if the transform is deactivated (by calling {@link IpSecTransform#close()}), + * prior to the socket being closed, the standard [FIN - FIN/ACK - ACK], or the reset [RST] + * packets are dropped due to the lack of a valid Transform. Similarly, if a socket without the + * SO_LINGER option set is closed, the delayed/batched FIN packets may be dropped. + * * <h4>Rekey Procedure</h4> * * <p>When applying a new tranform to a socket in the outbound direction, the previous transform @@ -373,6 +387,19 @@ public final class IpSecManager { * will throw IOException if the user deactivates the transform (by calling {@link * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}. * + * <p>Note that when applied to TCP sockets, calling {@link IpSecTransform#close()} on an + * applied transform before completion of graceful shutdown may result in the shutdown sequence + * failing to complete. As such, applications requiring graceful shutdown MUST close the socket + * prior to deactivating the applied transform. Socket closure may be performed asynchronously + * (in batches), so the returning of a close function does not guarantee shutdown of a socket. + * Setting an SO_LINGER timeout results in socket closure being performed synchronously, and is + * sufficient to ensure shutdown. + * + * Specifically, if the transform is deactivated (by calling {@link IpSecTransform#close()}), + * prior to the socket being closed, the standard [FIN - FIN/ACK - ACK], or the reset [RST] + * packets are dropped due to the lack of a valid Transform. Similarly, if a socket without the + * SO_LINGER option set is closed, the delayed/batched FIN packets may be dropped. + * * <h4>Rekey Procedure</h4> * * <p>When applying a new tranform to a socket in the outbound direction, the previous transform @@ -476,7 +503,7 @@ public final class IpSecManager { * signalling and UDP encapsulated IPsec traffic. Instances can be obtained by calling {@link * IpSecManager#openUdpEncapsulationSocket}. The provided socket cannot be re-bound by the * caller. The caller should not close the {@code FileDescriptor} returned by {@link - * #getSocket}, but should use {@link #close} instead. + * #getFileDescriptor}, but should use {@link #close} instead. * * <p>Allowing the user to close or unbind a UDP encapsulation socket could impact the traffic * of the next user who binds to that port. To prevent this scenario, these sockets are held @@ -515,8 +542,8 @@ public final class IpSecManager { mCloseGuard.open("constructor"); } - /** Get the wrapped socket. */ - public FileDescriptor getSocket() { + /** Get the encapsulation socket's file descriptor. */ + public FileDescriptor getFileDescriptor() { if (mPfd == null) { return null; } diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java index 5df168d20586..15a0ee5eb2a6 100644 --- a/core/java/android/net/Network.java +++ b/core/java/android/net/Network.java @@ -26,6 +26,8 @@ import android.util.proto.ProtoOutputStream; import com.android.okhttp.internalandroidapi.Dns; import com.android.okhttp.internalandroidapi.HttpURLConnectionFactory; +import libcore.io.IoUtils; + import java.io.FileDescriptor; import java.io.IOException; import java.net.DatagramSocket; @@ -77,6 +79,11 @@ public class Network implements Parcelable { httpKeepAlive ? Integer.parseInt(System.getProperty("http.maxConnections", "5")) : 0; private static final long httpKeepAliveDurationMs = Long.parseLong(System.getProperty("http.keepAliveDuration", "300000")); // 5 minutes. + // Value used to obfuscate network handle longs. + // The HANDLE_MAGIC value MUST be kept in sync with the corresponding + // value in the native/android/net.c NDK implementation. + private static final long HANDLE_MAGIC = 0xcafed00dL; + private static final int HANDLE_MAGIC_SIZE = 32; /** * @hide @@ -137,9 +144,15 @@ public class Network implements Parcelable { for (int i = 0; i < hostAddresses.length; i++) { try { Socket socket = createSocket(); - if (localAddress != null) socket.bind(localAddress); - socket.connect(new InetSocketAddress(hostAddresses[i], port)); - return socket; + boolean failed = true; + try { + if (localAddress != null) socket.bind(localAddress); + socket.connect(new InetSocketAddress(hostAddresses[i], port)); + failed = false; + return socket; + } finally { + if (failed) IoUtils.closeQuietly(socket); + } } catch (IOException e) { if (i == (hostAddresses.length - 1)) throw e; } @@ -156,15 +169,27 @@ public class Network implements Parcelable { public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { Socket socket = createSocket(); - socket.bind(new InetSocketAddress(localAddress, localPort)); - socket.connect(new InetSocketAddress(address, port)); + boolean failed = true; + try { + socket.bind(new InetSocketAddress(localAddress, localPort)); + socket.connect(new InetSocketAddress(address, port)); + failed = false; + } finally { + if (failed) IoUtils.closeQuietly(socket); + } return socket; } @Override public Socket createSocket(InetAddress host, int port) throws IOException { Socket socket = createSocket(); - socket.connect(new InetSocketAddress(host, port)); + boolean failed = true; + try { + socket.connect(new InetSocketAddress(host, port)); + failed = false; + } finally { + if (failed) IoUtils.closeQuietly(socket); + } return socket; } @@ -176,7 +201,13 @@ public class Network implements Parcelable { @Override public Socket createSocket() throws IOException { Socket socket = new Socket(); - bindSocket(socket); + boolean failed = true; + try { + bindSocket(socket); + failed = false; + } finally { + if (failed) IoUtils.closeQuietly(socket); + } return socket; } } @@ -335,6 +366,25 @@ public class Network implements Parcelable { } /** + * Returns a {@link Network} object given a handle returned from {@link #getNetworkHandle}. + * + * @param networkHandle a handle returned from {@link #getNetworkHandle}. + * @return A {@link Network} object derived from {@code networkHandle}. + */ + public static Network fromNetworkHandle(long networkHandle) { + if (networkHandle == 0) { + throw new IllegalArgumentException( + "Network.fromNetworkHandle refusing to instantiate NETID_UNSET Network."); + } + if ((networkHandle & ((1L << HANDLE_MAGIC_SIZE) - 1)) != HANDLE_MAGIC + || networkHandle < 0) { + throw new IllegalArgumentException( + "Value passed to fromNetworkHandle() is not a network handle."); + } + return new Network((int) (networkHandle >> HANDLE_MAGIC_SIZE)); + } + + /** * Returns a handle representing this {@code Network}, for use with the NDK API. */ public long getNetworkHandle() { @@ -356,14 +406,10 @@ public class Network implements Parcelable { // At some future date it may be desirable to realign the handle with // Multiple Provisioning Domains API recommendations, as made by the // IETF mif working group. - // - // 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 handleMagic = 0xcafed00dL; - return (((long) netId) << 32) | handleMagic; + return (((long) netId) << HANDLE_MAGIC_SIZE) | HANDLE_MAGIC; } // implement the Parcelable interface diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index ff5714b38212..374b3abcb641 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -17,6 +17,7 @@ package android.net; import android.annotation.IntDef; +import android.annotation.SystemApi; import android.net.ConnectivityManager.NetworkCallback; import android.os.Parcel; import android.os.Parcelable; @@ -276,6 +277,7 @@ public final class NetworkCapabilities implements Parcelable { * this network can be used by system apps to upload telemetry data. * @hide */ + @SystemApi public static final int NET_CAPABILITY_OEM_PAID = 22; private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS; diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java index 4f92fa6a7340..caefd896ef16 100644 --- a/core/java/android/net/NetworkRequest.java +++ b/core/java/android/net/NetworkRequest.java @@ -17,6 +17,8 @@ package android.net; import android.annotation.NonNull; +import android.net.NetworkCapabilities.NetCapability; +import android.net.NetworkCapabilities.Transport; import android.os.Parcel; import android.os.Parcelable; import android.os.Process; @@ -427,6 +429,20 @@ public class NetworkRequest implements Parcelable { return type == Type.BACKGROUND_REQUEST; } + /** + * @see Builder#addCapability(int) + */ + public boolean hasCapability(@NetCapability int capability) { + return networkCapabilities.hasCapability(capability); + } + + /** + * @see Builder#addTransportType(int) + */ + public boolean hasTransport(@Transport int transportType) { + return networkCapabilities.hasTransport(transportType); + } + public String toString() { return "NetworkRequest [ " + type + " id=" + requestId + (legacyType != ConnectivityManager.TYPE_NONE ? ", legacyType=" + legacyType : "") + diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl index 402c995452e8..116262e347de 100644 --- a/core/java/android/os/IStatsCompanionService.aidl +++ b/core/java/android/os/IStatsCompanionService.aidl @@ -47,10 +47,10 @@ interface IStatsCompanionService { * Uses AlarmManager.setRepeating API, so if the timestamp is in past, alarm fires immediately, * and alarm is inexact. */ - oneway void setPullingAlarms(long timestampMs, long intervalMs); + oneway void setPullingAlarm(long nextPullTimeMs); /** Cancel any repeating pulling alarm. */ - oneway void cancelPullingAlarms(); + oneway void cancelPullingAlarm(); /** * Register an alarm when we want to trigger subscribers at the given diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java index 5be72bc5c54b..094f0046649b 100644 --- a/core/java/android/os/UserHandle.java +++ b/core/java/android/os/UserHandle.java @@ -158,7 +158,7 @@ public final class UserHandle implements Parcelable { * @hide */ public static boolean isCore(int uid) { - if (uid > 0) { + if (uid >= 0) { final int appId = getAppId(uid); return appId < Process.FIRST_APPLICATION_UID; } else { diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index ca4c796ab62d..b9dd376f464b 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -32,6 +32,7 @@ import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.UUID; @@ -158,6 +159,13 @@ public class ZygoteProcess { private final Object mLock = new Object(); /** + * List of exemptions to the API blacklist. These are prefix matches on the runtime format + * symbol signature. Any matching symbol is treated by the runtime as being on the light grey + * list. + */ + private List<String> mApiBlacklistExemptions = Collections.emptyList(); + + /** * The state of the connection to the primary zygote. */ private ZygoteState primaryZygoteState; @@ -175,7 +183,7 @@ public class ZygoteProcess { * The process will continue running after this function returns. * * <p>If processes are not enabled, a new thread in the caller's - * process is created and main() of <var>processClass</var> called there. + * process is created and main() of <var>processclass</var> called there. * * <p>The niceName parameter, if not an empty string, is a custom name to * give to the process instead of using processClass. This allows you to @@ -454,6 +462,49 @@ public class ZygoteProcess { } /** + * Push hidden API blacklisting exemptions into the zygote process(es). + * + * <p>The list of exemptions will take affect for all new processes forked from the zygote after + * this call. + * + * @param exemptions List of hidden API exemption prefixes. + */ + public void setApiBlacklistExemptions(List<String> exemptions) { + synchronized (mLock) { + mApiBlacklistExemptions = exemptions; + maybeSetApiBlacklistExemptions(primaryZygoteState, true); + maybeSetApiBlacklistExemptions(secondaryZygoteState, true); + } + } + + @GuardedBy("mLock") + private void maybeSetApiBlacklistExemptions(ZygoteState state, boolean sendIfEmpty) { + if (state == null || state.isClosed()) { + return; + } + if (!sendIfEmpty && mApiBlacklistExemptions.isEmpty()) { + return; + } + try { + state.writer.write(Integer.toString(mApiBlacklistExemptions.size() + 1)); + state.writer.newLine(); + state.writer.write("--set-api-blacklist-exemptions"); + state.writer.newLine(); + for (int i = 0; i < mApiBlacklistExemptions.size(); ++i) { + state.writer.write(mApiBlacklistExemptions.get(i)); + state.writer.newLine(); + } + state.writer.flush(); + int status = state.inputStream.readInt(); + if (status != 0) { + Slog.e(LOG_TAG, "Failed to set API blacklist exemptions; status " + status); + } + } catch (IOException ioe) { + Slog.e(LOG_TAG, "Failed to set API blacklist exemptions", ioe); + } + } + + /** * Tries to open socket to Zygote process if not already open. If * already open, does nothing. May block and retry. Requires that mLock be held. */ @@ -467,8 +518,8 @@ public class ZygoteProcess { } catch (IOException ioe) { throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe); } + maybeSetApiBlacklistExemptions(primaryZygoteState, false); } - if (primaryZygoteState.matches(abi)) { return primaryZygoteState; } @@ -480,6 +531,7 @@ public class ZygoteProcess { } catch (IOException ioe) { throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe); } + maybeSetApiBlacklistExemptions(secondaryZygoteState, false); } if (secondaryZygoteState.matches(abi)) { diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index bf20e6a82962..8905ad1e6abc 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -756,10 +756,15 @@ public class StorageManager { } try { for (VolumeInfo vol : mStorageManager.getVolumes(0)) { - if (vol.path != null && FileUtils.contains(vol.path, pathString)) { + if (vol.path != null && FileUtils.contains(vol.path, pathString) + && vol.type != VolumeInfo.TYPE_PUBLIC) { // TODO: verify that emulated adopted devices have UUID of // underlying volume - return convert(vol.fsUuid); + try { + return convert(vol.fsUuid); + } catch (IllegalArgumentException e) { + continue; + } } } } catch (RemoteException e) { diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java index 5c99f6c87d7c..9e3e386eedb2 100644 --- a/core/java/android/os/storage/VolumeInfo.java +++ b/core/java/android/os/storage/VolumeInfo.java @@ -312,7 +312,7 @@ public class VolumeInfo implements Parcelable { * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}. */ public File getInternalPathForUser(int userId) { - if (type == TYPE_PUBLIC) { + if (type == TYPE_PUBLIC && !isVisible()) { // TODO: plumb through cleaner path from vold return new File(path.replace("/storage/", "/mnt/media_rw/")); } else { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 70f343ee5f57..b0367dc3f238 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7809,6 +7809,14 @@ public final class Settings { "suppress_auto_battery_saver_suggestion"; /** + * List of packages, which data need to be unconditionally cleared before full restore. + * Type: string + * @hide + */ + public static final String PACKAGES_TO_CLEAR_DATA_BEFORE_FULL_RESTORE = + "packages_to_clear_data_before_full_restore"; + + /** * This are the settings to be backed up. * * NOTE: Settings are backed up and restored in the order they appear @@ -11053,7 +11061,14 @@ public final class Settings { */ public static final String LOW_POWER_MODE_TRIGGER_LEVEL_MAX = "low_power_trigger_level_max"; - /** + /** + * See com.android.settingslib.fuelgauge.BatterySaverUtils. + * @hide + */ + public static final String LOW_POWER_MODE_SUGGESTION_PARAMS = + "low_power_mode_suggestion_params"; + + /** * If not 0, the activity manager will aggressively finish activities and * processes as soon as they are no longer needed. If 0, the normal * extended lifetime is used. diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java index 1d1333504350..f4dcce1e7e58 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -75,6 +75,7 @@ public final class KeymasterDefs { public static final int KM_TAG_ALLOW_WHILE_ON_BODY = KM_BOOL | 506; public static final int KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED = KM_BOOL | 507; public static final int KM_TAG_TRUSTED_CONFIRMATION_REQUIRED = KM_BOOL | 508; + public static final int KM_TAG_UNLOCKED_DEVICE_REQUIRED = KM_BOOL | 509; public static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600; public static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601; @@ -216,6 +217,7 @@ public final class KeymasterDefs { public static final int KM_ERROR_MISSING_MIN_MAC_LENGTH = -58; public static final int KM_ERROR_UNSUPPORTED_MIN_MAC_LENGTH = -59; public static final int KM_ERROR_CANNOT_ATTEST_IDS = -66; + public static final int KM_ERROR_DEVICE_LOCKED = -72; public static final int KM_ERROR_UNIMPLEMENTED = -100; public static final int KM_ERROR_VERSION_MISMATCH = -101; public static final int KM_ERROR_UNKNOWN_ERROR = -1000; @@ -262,6 +264,7 @@ public final class KeymasterDefs { sErrorCodeToString.put(KM_ERROR_INVALID_MAC_LENGTH, "Invalid MAC or authentication tag length"); sErrorCodeToString.put(KM_ERROR_CANNOT_ATTEST_IDS, "Unable to attest device ids"); + sErrorCodeToString.put(KM_ERROR_DEVICE_LOCKED, "Device locked"); sErrorCodeToString.put(KM_ERROR_UNIMPLEMENTED, "Not implemented"); sErrorCodeToString.put(KM_ERROR_UNKNOWN_ERROR, "Unknown error"); } diff --git a/core/java/android/security/keystore/RecoveryController.java b/core/java/android/security/keystore/RecoveryController.java index d50424db4e56..741af1297763 100644 --- a/core/java/android/security/keystore/RecoveryController.java +++ b/core/java/android/security/keystore/RecoveryController.java @@ -443,16 +443,7 @@ public class RecoveryController { */ public byte[] generateAndStoreKey(@NonNull String alias) throws InternalRecoveryServiceException, LockScreenRequiredException { - try { - return mBinder.generateAndStoreKey(alias); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } catch (ServiceSpecificException e) { - if (e.errorCode == ERROR_INSECURE_USER) { - throw new LockScreenRequiredException(e.getMessage()); - } - throw wrapUnexpectedServiceSpecificException(e); - } + throw new UnsupportedOperationException(); } /** diff --git a/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java b/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java index 2a66206e805e..4af1af5f90cf 100644 --- a/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java +++ b/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java @@ -50,6 +50,22 @@ import java.util.Arrays; */ @SystemApi public final class KeyChainProtectionParams implements Parcelable { + + // IMPORTANT! PLEASE READ! + // ----------------------- + // If you edit this file (e.g., to add new fields), please MAKE SURE to also do the following: + // - Update the #writeToParcel(Parcel) method below + // - Update the #(Parcel) constructor below + // - Update android.security.keystore.recovery.KeyChainSnapshotTest to make sure nobody + // accidentally breaks your fields in the Parcel in the future. + // - Update com.android.server.locksettings.recoverablekeystore.serialization + // .KeyChainSnapshotSerializer to correctly serialize your new field + // - Update com.android.server.locksettings.recoverablekeystore.serialization + // .KeyChainSnapshotSerializer to correctly deserialize your new field + // - Update com.android.server.locksettings.recoverablekeystore.serialization + // .KeychainSnapshotSerializerTest to make sure nobody breaks serialization of your field + // in the future. + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"TYPE_"}, value = {TYPE_LOCKSCREEN}) diff --git a/core/java/android/security/keystore/recovery/KeyChainSnapshot.java b/core/java/android/security/keystore/recovery/KeyChainSnapshot.java index 24ff182ab4bd..9334aa99c86c 100644 --- a/core/java/android/security/keystore/recovery/KeyChainSnapshot.java +++ b/core/java/android/security/keystore/recovery/KeyChainSnapshot.java @@ -48,6 +48,22 @@ import java.util.List; */ @SystemApi public final class KeyChainSnapshot implements Parcelable { + + // IMPORTANT! PLEASE READ! + // ----------------------- + // If you edit this file (e.g., to add new fields), please MAKE SURE to also do the following: + // - Update the #writeToParcel(Parcel) method below + // - Update the #(Parcel) constructor below + // - Update android.security.keystore.recovery.KeyChainSnapshotTest to make sure nobody + // accidentally breaks your fields in the Parcel in the future. + // - Update com.android.server.locksettings.recoverablekeystore.serialization + // .KeyChainSnapshotSerializer to correctly serialize your new field + // - Update com.android.server.locksettings.recoverablekeystore.serialization + // .KeyChainSnapshotSerializer to correctly deserialize your new field + // - Update com.android.server.locksettings.recoverablekeystore.serialization + // .KeychainSnapshotSerializerTest to make sure nobody breaks serialization of your field + // in the future. + private static final int DEFAULT_MAX_ATTEMPTS = 10; private static final long DEFAULT_COUNTER_ID = 1L; @@ -62,23 +78,8 @@ public final class KeyChainSnapshot implements Parcelable { private byte[] mEncryptedRecoveryKeyBlob; /** - * @hide - * Deprecated, consider using builder. + * Use builder to create an instance of the class. */ - public KeyChainSnapshot( - int snapshotVersion, - @NonNull List<KeyChainProtectionParams> keyChainProtectionParams, - @NonNull List<WrappedApplicationKey> wrappedApplicationKeys, - @NonNull byte[] encryptedRecoveryKeyBlob) { - mSnapshotVersion = snapshotVersion; - mKeyChainProtectionParams = - Preconditions.checkCollectionElementsNotNull(keyChainProtectionParams, - "KeyChainProtectionParams"); - mEntryRecoveryData = Preconditions.checkCollectionElementsNotNull(wrappedApplicationKeys, - "wrappedApplicationKeys"); - mEncryptedRecoveryKeyBlob = Preconditions.checkNotNull(encryptedRecoveryKeyBlob); - } - private KeyChainSnapshot() { } @@ -92,7 +93,7 @@ public final class KeyChainSnapshot implements Parcelable { } /** - * Number of user secret guesses allowed during Keychain recovery. + * Number of user secret guesses allowed during KeyChain recovery. */ public int getMaxAttempts() { return mMaxAttempts; diff --git a/core/java/android/security/keystore/recovery/KeyDerivationParams.java b/core/java/android/security/keystore/recovery/KeyDerivationParams.java index 225b592d7595..5165f0c30a36 100644 --- a/core/java/android/security/keystore/recovery/KeyDerivationParams.java +++ b/core/java/android/security/keystore/recovery/KeyDerivationParams.java @@ -35,6 +35,22 @@ import java.lang.annotation.RetentionPolicy; */ @SystemApi public final class KeyDerivationParams implements Parcelable { + + // IMPORTANT! PLEASE READ! + // ----------------------- + // If you edit this file (e.g., to add new fields), please MAKE SURE to also do the following: + // - Update the #writeToParcel(Parcel) method below + // - Update the #(Parcel) constructor below + // - Update android.security.keystore.recovery.KeyChainSnapshotTest to make sure nobody + // accidentally breaks your fields in the Parcel in the future. + // - Update com.android.server.locksettings.recoverablekeystore.serialization + // .KeyChainSnapshotSerializer to correctly serialize your new field + // - Update com.android.server.locksettings.recoverablekeystore.serialization + // .KeyChainSnapshotSerializer to correctly deserialize your new field + // - Update com.android.server.locksettings.recoverablekeystore.serialization + // .KeychainSnapshotSerializerTest to make sure nobody breaks serialization of your field + // in the future. + private final int mAlgorithm; private final byte[] mSalt; private final int mMemoryDifficulty; @@ -59,7 +75,7 @@ public final class KeyDerivationParams implements Parcelable { * Creates instance of the class to to derive keys using salted SHA256 hash. * * <p>The salted SHA256 hash is computed over the concatenation of four byte strings, salt_len + - * salt + key_material_len + key_material, where salt_len and key_material_len are one-byte, and + * salt + key_material_len + key_material, where salt_len and key_material_len are 4-byte, and * denote the number of bytes for salt and key_material, respectively. */ public static @NonNull KeyDerivationParams createSha256Params(@NonNull byte[] salt) { @@ -90,7 +106,7 @@ public final class KeyDerivationParams implements Parcelable { /** * @hide */ - KeyDerivationParams(@KeyDerivationAlgorithm int algorithm, @NonNull byte[] salt, + private KeyDerivationParams(@KeyDerivationAlgorithm int algorithm, @NonNull byte[] salt, int memoryDifficulty) { mAlgorithm = algorithm; mSalt = Preconditions.checkNotNull(salt); diff --git a/core/java/android/security/keystore/recovery/RecoveryController.java b/core/java/android/security/keystore/recovery/RecoveryController.java index ab52d32ce963..281822a342f9 100644 --- a/core/java/android/security/keystore/recovery/RecoveryController.java +++ b/core/java/android/security/keystore/recovery/RecoveryController.java @@ -41,21 +41,133 @@ import java.util.List; import java.util.Map; /** - * An assistant for generating {@link javax.crypto.SecretKey} instances that can be recovered by - * other Android devices belonging to the user. The exported keychain is protected by the user's - * lock screen. + * Backs up cryptographic keys to remote secure hardware, encrypted with the user's lock screen. * - * <p>The RecoveryController must be paired with a recovery agent. The recovery agent is responsible - * for transporting the keychain to remote trusted hardware. This hardware must prevent brute force - * attempts against the user's lock screen by limiting the number of allowed guesses (to, e.g., 10). - * After that number of incorrect guesses, the trusted hardware no longer allows access to the - * key chain. + * <p>A system app with the {@code android.permission.RECOVER_KEYSTORE} permission may generate or + * import recoverable keys using this class. To generate a key, the app must call + * {@link #generateKey(String)} with the desired alias for the key. This returns an AndroidKeyStore + * reference to a 256-bit {@link javax.crypto.SecretKey}, which can be used for AES/GCM/NoPadding. + * In order to get the same key again at a later time, the app can call {@link #getKey(String)} with + * the same alias. If a key is generated in this way the key's raw material is never directly + * exposed to the calling app. The system app may also import key material using + * {@link #importKey(String, byte[])}. The app may only generate and import keys for its own + * {@code uid}. * - * <p>Only the recovery agent itself is able to create keys, so it is expected that the recovery - * agent is itself the system app. + * <p>The same system app must also register a Recovery Agent to manage syncing recoverable keys to + * remote secure hardware. The Recovery Agent is a service that registers itself with the controller + * as follows: * - * <p>A recovery agent requires the privileged permission - * {@code android.Manifest.permission#RECOVER_KEYSTORE}. + * <ul> + * <li>Invokes {@link #initRecoveryService(String, byte[], byte[])} + * <ul> + * <li>The first argument is the alias of the root certificate used to verify trusted + * hardware modules. Each trusted hardware module must have a public key signed with this + * root of trust. Roots of trust must be shipped with the framework. The app can list all + * valid roots of trust by calling {@link #getRootCertificates()}. + * <li>The second argument is the UTF-8 bytes of the XML listing file. It lists the X509 + * certificates containing the public keys of all available remote trusted hardware modules. + * Each of the X509 certificates can be validated against the chosen root of trust. + * <li>The third argument is the UTF-8 bytes of the XML signing file. The file contains a + * signature of the XML listing file. The signature can be validated against the chosen root + * of trust. + * </ul> + * <p>This will cause the controller to choose a random public key from the list. From then + * on the controller will attempt to sync the key chain with the trusted hardware module to whom + * that key belongs. + * <li>Invokes {@link #setServerParams(byte[])} with a byte string that identifies the device + * to a remote server. This server may act as the front-end to the trusted hardware modules. It + * is up to the Recovery Agent to decide how best to identify devices, but this could be, e.g., + * based on the <a href="https://developers.google.com/instance-id/">Instance ID</a> of the + * system app. + * <li>Invokes {@link #setRecoverySecretTypes(int[])} with a list of types of secret used to + * secure the recoverable key chain. For now only + * {@link KeyChainProtectionParams#TYPE_LOCKSCREEN} is supported. + * <li>Invokes {@link #setSnapshotCreatedPendingIntent(PendingIntent)} with a + * {@link PendingIntent} that is to be invoked whenever a new snapshot is created. Although the + * controller can create snapshots without the Recovery Agent registering this intent, it is a + * good idea to register the intent so that the Recovery Agent is able to sync this snapshot to + * the trusted hardware module as soon as it is available. + * </ul> + * + * <p>The trusted hardware module's public key MUST be generated on secure hardware with protections + * equivalent to those described in the + * <a href="https://developer.android.com/preview/features/security/ckv-whitepaper.html">Google + * Cloud Key Vault Service whitepaper</a>. The trusted hardware module itself must protect the key + * chain from brute-forcing using the methods also described in the whitepaper: i.e., it should + * limit the number of allowed attempts to enter the lock screen. If the number of attempts is + * exceeded the key material must no longer be recoverable. + * + * <p>A recoverable key chain snapshot is considered pending if any of the following conditions + * are met: + * + * <ul> + * <li>The system app mutates the key chain. i.e., generates, imports, or removes a key. + * <li>The user changes their lock screen. + * </ul> + * + * <p>Whenever the user unlocks their device, if a snapshot is pending, the Recovery Controller + * generates a new snapshot. It follows these steps to do so: + * + * <ul> + * <li>Generates a 256-bit AES key using {@link java.security.SecureRandom}. This is the + * Recovery Key. + * <li>Wraps the key material of all keys in the recoverable key chain with the Recovery Key. + * <li>Encrypts the Recovery Key with both the public key of the trusted hardware module and a + * symmetric key derived from the user's lock screen. + * </ul> + * + * <p>The controller then writes this snapshot to disk, and uses the {@link PendingIntent} that was + * set by the Recovery Agent during initialization to inform it that a new snapshot is available. + * The snapshot only contains keys for that Recovery Agent's {@code uid} - i.e., keys the agent's + * app itself generated. If multiple Recovery Agents exist on the device, each will be notified of + * their new snapshots, and each snapshots' keys will be only those belonging to the same + * {@code uid}. + * + * <p>The Recovery Agent retrieves its most recent snapshot by calling + * {@link #getKeyChainSnapshot()}. It syncs the snapshot to the remote server. The snapshot contains + * the public key used for encryption, which the server uses to forward the encrypted recovery key + * to the correct trusted hardware module. The snapshot also contains the server params, which are + * used to identify this device to the server. + * + * <p>The client uses the server params to identify a device whose key chain it wishes to restore. + * This may be on a different device to the device that originally synced the key chain. The client + * sends the server params identifying the previous device to the server. The server returns the + * X509 certificate identifying the trusted hardware module in which the encrypted Recovery Key is + * stored. It also returns some vault parameters identifying that particular Recovery Key to the + * trusted hardware module. And it also returns a vault challenge, which is used as part of the + * vault opening protocol to ensure the recovery claim is fresh. See the whitepaper for more + * details. + * + * <p>The key chain is recovered via a {@link RecoverySession}. A Recovery Agent creates one by + * invoking {@link #createRecoverySession()}. It then invokes + * {@link RecoverySession#start(String, CertPath, byte[], byte[], List)} with these arguments: + * + * <ul> + * <li>The alias of the root of trust used to verify the trusted hardware module. + * <li>The X509 certificate of the trusted hardware module. + * <li>The vault parameters used to identify the Recovery Key to the trusted hardware module. + * <li>The vault challenge, as issued by the trusted hardware module. + * <li>A list of secrets, corresponding to the secrets used to protect the key chain. At the + * moment this is a single {@link KeyChainProtectionParams} containing the lock screen of the + * device whose key chain is to be recovered. + * </ul> + * + * <p>This method returns a byte array containing the Recovery Claim, which can be issued to the + * remote trusted hardware module. It is encrypted with the trusted hardware module's public key + * (which has itself been certified with the root of trust). It also contains an ephemeral symmetric + * key generated for this recovery session, which the remote trusted hardware module uses to encrypt + * its responses. This is the Session Key. + * + * <p>If the lock screen provided is correct, the remote trusted hardware module decrypts one of the + * layers of lock-screen encryption from the Recovery Key. It then returns this key, encrypted with + * the Session Key to the Recovery Agent. As the Recovery Agent does not know the Session Key, it + * must then invoke {@link RecoverySession#recoverKeyChainSnapshot(byte[], List)} with the encrypted + * Recovery Key and the list of wrapped application keys. The controller then decrypts the layer of + * encryption provided by the Session Key, and uses the lock screen to decrypt the final layer of + * encryption. It then uses the Recovery Key to decrypt all of the wrapped application keys, and + * imports them into its own KeyStore. The Recovery Agent's app may then access these keys by + * calling {@link #getKey(String)}. Only this app's {@code uid} may access the keys that have been + * recovered. * * @hide */ @@ -180,7 +292,7 @@ public class RecoveryController { } catch (ServiceSpecificException e) { if (e.errorCode == ERROR_BAD_CERTIFICATE_FORMAT || e.errorCode == ERROR_INVALID_CERTIFICATE) { - throw new CertificateException(e.getMessage()); + throw new CertificateException("Invalid certificate for recovery service", e); } throw wrapUnexpectedServiceSpecificException(e); } @@ -226,7 +338,7 @@ public class RecoveryController { } catch (ServiceSpecificException e) { if (e.errorCode == ERROR_BAD_CERTIFICATE_FORMAT || e.errorCode == ERROR_INVALID_CERTIFICATE) { - throw new CertificateException(e.getMessage()); + throw new CertificateException("Invalid certificate for recovery service", e); } throw wrapUnexpectedServiceSpecificException(e); } @@ -465,16 +577,7 @@ public class RecoveryController { @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) public byte[] generateAndStoreKey(@NonNull String alias, byte[] account) throws InternalRecoveryServiceException, LockScreenRequiredException { - try { - return mBinder.generateAndStoreKey(alias); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } catch (ServiceSpecificException e) { - if (e.errorCode == ERROR_INSECURE_USER) { - throw new LockScreenRequiredException(e.getMessage()); - } - throw wrapUnexpectedServiceSpecificException(e); - } + throw new UnsupportedOperationException("Operation is not supported, use generateKey"); } /** diff --git a/core/java/android/security/keystore/recovery/RecoverySession.java b/core/java/android/security/keystore/recovery/RecoverySession.java index 0690bd5fb612..835338940905 100644 --- a/core/java/android/security/keystore/recovery/RecoverySession.java +++ b/core/java/android/security/keystore/recovery/RecoverySession.java @@ -102,7 +102,7 @@ public class RecoverySession implements AutoCloseable { } catch (ServiceSpecificException e) { if (e.errorCode == RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT || e.errorCode == RecoveryController.ERROR_INVALID_CERTIFICATE) { - throw new CertificateException(e.getMessage()); + throw new CertificateException("Invalid certificate for recovery session", e); } throw mRecoveryController.wrapUnexpectedServiceSpecificException(e); } @@ -137,7 +137,7 @@ public class RecoverySession implements AutoCloseable { } catch (ServiceSpecificException e) { if (e.errorCode == RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT || e.errorCode == RecoveryController.ERROR_INVALID_CERTIFICATE) { - throw new CertificateException(e.getMessage()); + throw new CertificateException("Invalid certificate for recovery session", e); } throw mRecoveryController.wrapUnexpectedServiceSpecificException(e); } @@ -157,8 +157,8 @@ public class RecoverySession implements AutoCloseable { * @param vaultChallenge Data passed from server for this recovery session and used to prevent * replay attacks. * @param secrets Secrets provided by user, the method only uses type and secret fields. - * @return The recovery claim. Claim provides a b binary blob with recovery claim. It is - * encrypted with verifierPublicKey and contains a proof of user secrets, session symmetric + * @return The binary blob with recovery claim. It is encrypted with verifierPublicKey + * and contains a proof of user secrets possession, session symmetric * key and parameters necessary to identify the counter with the number of failed recovery * attempts. * @throws CertificateException if the {@code verifierCertPath} is invalid. @@ -191,7 +191,7 @@ public class RecoverySession implements AutoCloseable { } catch (ServiceSpecificException e) { if (e.errorCode == RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT || e.errorCode == RecoveryController.ERROR_INVALID_CERTIFICATE) { - throw new CertificateException(e.getMessage()); + throw new CertificateException("Invalid certificate for recovery session", e); } throw mRecoveryController.wrapUnexpectedServiceSpecificException(e); } @@ -228,7 +228,8 @@ public class RecoverySession implements AutoCloseable { * * @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. + * and session key generated by {@link #start}. + * @return {@code Map} from recovered keys aliases to their references. * @throws SessionExpiredException if {@code session} has since been closed. * @throws DecryptionFailedException if unable to decrypt the snapshot. * @throws InternalRecoveryServiceException if an error occurs internal to the recovery service. @@ -288,8 +289,7 @@ public class RecoverySession implements AutoCloseable { } /** - * Deletes all data associated with {@code session}. Should not be invoked directly but via - * {@link RecoverySession#close()}. + * Deletes all data associated with {@code session}. */ @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) @Override diff --git a/core/java/android/security/keystore/recovery/TrustedRootCertificates.java b/core/java/android/security/keystore/recovery/TrustedRootCertificates.java index 383af424989c..63faac3d525d 100644 --- a/core/java/android/security/keystore/recovery/TrustedRootCertificates.java +++ b/core/java/android/security/keystore/recovery/TrustedRootCertificates.java @@ -37,6 +37,40 @@ public final class TrustedRootCertificates { public static final String GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS = "GoogleCloudKeyVaultServiceV1"; + /** + * Certificate used for client-side end-to-end encryption tests. + * When recovery controller is initialized with the certificate, recovery snapshots will only + * contain application keys started with {@link INSECURE_KEY_ALIAS}. + * Recovery snapshot will only be created if device is unlocked with password started with + * {@link #INSECURE_PASSWORD_PREFIX}. + * + * @hide + */ + public static final String TEST_ONLY_INSECURE_CERTIFICATE_ALIAS = + "TEST_ONLY_INSECURE_CERTIFICATE_ALIAS"; + + /** + * TODO: Add insecure certificate to TestApi. + * @hide + */ + public static @NonNull X509Certificate getTestOnlyInsecureCertificate() { + return parseBase64Certificate(TEST_ONLY_INSECURE_CERTIFICATE_BASE64); + } + /** + * Keys, which alias starts with the prefix are not protected if + * recovery agent uses {@link #TEST_ONLY_INSECURE_CERTIFICATE_ALIAS} root certificate. + * @hide + */ + public static final String INSECURE_KEY_ALIAS_PREFIX = + "INSECURE_KEY_ALIAS_KEY_MATERIAL_IS_NOT_PROTECTED_"; + /** + * Prefix for insecure passwords with length 14. + * Passwords started with the prefix are not protected if recovery agent uses + * {@link #TEST_ONLY_INSECURE_CERTIFICATE_ALIAS} root certificate. + * @hide + */ + public static final String INSECURE_PASSWORD_PREFIX = + "INSECURE_PSWD_"; private static final String GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_BASE64 = "" + "MIIFJjCCAw6gAwIBAgIJAIobXsJlzhNdMA0GCSqGSIb3DQEBDQUAMCAxHjAcBgNV" @@ -68,13 +102,43 @@ public final class TrustedRootCertificates { + "/oM58v0orUWINtIc2hBlka36PhATYQiLf+AiWKnwhCaaHExoYKfQlMtXBodNvOK8" + "xqx69x05q/qbHKEcTHrsss630vxrp1niXvA="; + private static final String TEST_ONLY_INSECURE_CERTIFICATE_BASE64 = "" + + "MIIFMDCCAxigAwIBAgIJAIZ9/G8KQie9MA0GCSqGSIb3DQEBDQUAMCUxIzAhBgNV" + + "BAMMGlRlc3QgT25seSBVbnNlY3VyZSBSb290IENBMB4XDTE4MDMyODAwMzIyM1oX" + + "DTM4MDMyMzAwMzIyM1owJTEjMCEGA1UEAwwaVGVzdCBPbmx5IFVuc2VjdXJlIFJv" + + "b3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDGxFNzAEyzSPmw" + + "E5gfuBXdXq++bl9Ep62V7Xn1UiejvmS+pRHT39pf/M7sl4Zr9ezanJTrFvf9+B85" + + "VGehdsD32TgfEjThcqaoQCI6pKkHYsUo7FZ5n+G3eE8oabWRZJMVo3QDjnnFYp7z" + + "20vnpjDofI2oQyxHcb/1yep+ca1+4lIvbUp/ybhNFqhRXAMcDXo7pyH38eUQ1JdK" + + "Q/QlBbShpFEqx1Y6KilKfTDf7Wenqr67LkaEim//yLZjlHzn/BpuRTrpo+XmJZx1" + + "P9CX9LGOXTtmsaCcYgD4yijOvV8aEsIJaf1kCIO558oH0oQc+0JG5aXeLN7BDlyZ" + + "vH0RdSx5nQLS9kj2I6nthOw/q00/L+S6A0m5jyNZOAl1SY78p+wO0d9eHbqQzJwf" + + "EsSq3qGAqlgQyyjp6oxHBqT9hZtN4rxw+iq0K1S4kmTLNF1FvmIB1BE+lNvvoGdY" + + "5G0b6Pe4R5JFn9LV3C3PEmSYnae7iG0IQlKmRADIuvfJ7apWAVanJPJAAWh2Akfp" + + "8Uxr02cHoY6o7vsEhJJOeMkipaBHThESm/XeFVubQzNfZ9gjQnB9ZX2v+lyj+WYZ" + + "SAz3RuXx6TlLrmWccMpQDR1ibcgyyjLUtX3kwZl2OxmJXitjuD7xlxvAXYob15N+" + + "K4xKHgxUDrbt2zU/tY0vgepAUg/xbwIDAQABo2MwYTAdBgNVHQ4EFgQUwyeNpYgs" + + "XXYvh9z0/lFrja7sV+swHwYDVR0jBBgwFoAUwyeNpYgsXXYvh9z0/lFrja7sV+sw" + + "DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQENBQAD" + + "ggIBAGuOsvMN5SD3RIQnMJtBpcHNrxun+QFjPZFlYCLfIPrUkHpn5O1iIIq8tVLd" + + "2V+12VKnToUEANsYBD3MP8XjP+6GZ7ZQ2rwLGvUABKSX4YXvmjEEXZUZp0y3tIV4" + + "kUDlbACzguPneZDp5Qo7YWH4orgqzHkn0sD/ikO5XrAqmzc245ewJlrf+V11mjcu" + + "ELfDrEejpPhi7Hk/ZNR0ftP737Hs/dNoCLCIaVNgYzBZhgo4kd220TeJu2ttW0XZ" + + "ldyShtpcOmyWKBgVseixR6L/3sspPHyAPXkSuRo0Eh1xvzDKCg9ttb0qoacTlXMF" + + "GkBpNzmVq67NWFGGa9UElift1mv6RfktPCAGZ+Ai8xUiKAUB0Eookpt/8gX9Senq" + + "yP/jMxkxXmHWxUu8+KnLvj6WLrfftuuD7u3cfc7j5kkrheDz3O4h4477GnqL5wdo" + + "9DuEsNc4FxJVz8Iy8RS6cJuW4pihYpM1Tyn7uopLnImpYzEY+R5aQqqr+q/A1diq" + + "ogbEKPH6oUiqJUwq3nD70gPBUKJmIzS4vLwLouqUHEm1k/MgHV/BkEU0uVHszPFa" + + "XUMMCHb0iT9P8LuZ7Ajer3SR/0TRVApCrk/6OV68e+6k/OFpM5kcZnNMD5ANyBri" + + "Tsz3NrDwSw4i4+Dsfh6A9dB/cEghw4skLaBxnQLQIgVeqCzK"; + /** * The X509 certificate of the trusted root CA cert for the recoverable key store service. * * TODO: Change it to the production certificate root CA before the final launch. */ private static final X509Certificate GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_CERTIFICATE = - parseGoogleCloudKeyVaultServiceV1Certificate(); + parseBase64Certificate(GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_BASE64); private static final int NUMBER_OF_ROOT_CERTIFICATES = 1; @@ -107,9 +171,9 @@ public final class TrustedRootCertificates { return certificates; } - private static X509Certificate parseGoogleCloudKeyVaultServiceV1Certificate() { + private static X509Certificate parseBase64Certificate(String base64Certificate) { try { - return decodeBase64Cert(GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_BASE64); + return decodeBase64Cert(base64Certificate); } catch (CertificateException e) { // Should not happen throw new RuntimeException(e); diff --git a/core/java/android/security/keystore/recovery/WrappedApplicationKey.java b/core/java/android/security/keystore/recovery/WrappedApplicationKey.java index 714e35a2e6fc..32952db7037d 100644 --- a/core/java/android/security/keystore/recovery/WrappedApplicationKey.java +++ b/core/java/android/security/keystore/recovery/WrappedApplicationKey.java @@ -42,6 +42,21 @@ public final class WrappedApplicationKey implements Parcelable { // The only supported format is AES-256 symmetric key. private byte[] mEncryptedKeyMaterial; + // IMPORTANT! PLEASE READ! + // ----------------------- + // If you edit this file (e.g., to add new fields), please MAKE SURE to also do the following: + // - Update the #writeToParcel(Parcel) method below + // - Update the #(Parcel) constructor below + // - Update android.security.keystore.recovery.KeyChainSnapshotTest to make sure nobody + // accidentally breaks your fields in the Parcel in the future. + // - Update com.android.server.locksettings.recoverablekeystore.serialization + // .KeyChainSnapshotSerializer to correctly serialize your new field + // - Update com.android.server.locksettings.recoverablekeystore.serialization + // .KeyChainSnapshotSerializer to correctly deserialize your new field + // - Update com.android.server.locksettings.recoverablekeystore.serialization + // .KeychainSnapshotSerializerTest to make sure nobody breaks serialization of your field + // in the future. + /** * Builder for creating {@link WrappedApplicationKey}. */ diff --git a/core/java/android/service/autofill/AutofillFieldClassificationService.java b/core/java/android/service/autofill/AutofillFieldClassificationService.java index cf1674989fe5..1cd76d2e9ec9 100644 --- a/core/java/android/service/autofill/AutofillFieldClassificationService.java +++ b/core/java/android/service/autofill/AutofillFieldClassificationService.java @@ -41,11 +41,11 @@ import java.util.List; * * <p>A field classification score is a {@code float} representing how well an * {@link AutofillValue} filled matches a expected value predicted by an autofill service - * —a full-match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}. + * —a full match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}. * - * <p>The exact score depends on the algorithm used to calculate it— the service must provide + * <p>The exact score depends on the algorithm used to calculate it—the service must provide * at least one default algorithm (which is used when the algorithm is not specified or is invalid), - * but it could provide more (in which case the algorithm name should be specifiied by the caller + * but it could provide more (in which case the algorithm name should be specified by the caller * when calculating the scores). * * {@hide} @@ -113,23 +113,67 @@ public abstract class AutofillFieldClassificationService extends Service { /** * Calculates field classification scores in a batch. * - * <p>See {@link AutofillFieldClassificationService} for more info about field classification - * scores. + * <p>A field classification score is a {@code float} representing how well an + * {@link AutofillValue} filled matches a expected value predicted by an autofill service + * —a full match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}. * - * @param algorithm name of the algorithm to be used to calculate the scores. If invalid, the - * default algorithm will be used instead. - * @param args optional arguments to be passed to the algorithm. + * <p>The exact score depends on the algorithm used to calculate it—the service must + * provide at least one default algorithm (which is used when the algorithm is not specified + * or is invalid), but it could provide more (in which case the algorithm name should be + * specified by the caller when calculating the scores). + * + * <p>For example, if the service provides an algorithm named {@code EXACT_MATCH} that + * returns {@code 1.0} if all characters match or {@code 0.0} otherwise, a call to: + * + * <pre> + * service.onGetScores("EXACT_MATCH", null, + * Arrays.asList(AutofillValue.forText("email1"), AutofillValue.forText("PHONE1")), + * Arrays.asList("email1", "phone1")); + * </pre> + * + * <p>Returns: + * + * <pre> + * [ + * [1.0, 0.0], // "email1" compared against ["email1", "phone1"] + * [0.0, 0.0] // "PHONE1" compared against ["email1", "phone1"] + * ]; + * </pre> + * + * <p>If the same algorithm allows the caller to specify whether the comparisons should be + * case sensitive by passing a boolean option named {@code "case_sensitive"}, then a call to: + * + * <pre> + * Bundle algorithmOptions = new Bundle(); + * algorithmOptions.putBoolean("case_sensitive", false); + * + * service.onGetScores("EXACT_MATCH", algorithmOptions, + * Arrays.asList(AutofillValue.forText("email1"), AutofillValue.forText("PHONE1")), + * Arrays.asList("email1", "phone1")); + * </pre> + * + * <p>Returns: + * + * <pre> + * [ + * [1.0, 0.0], // "email1" compared against ["email1", "phone1"] + * [0.0, 1.0] // "PHONE1" compared against ["email1", "phone1"] + * ]; + * </pre> + * + * @param algorithm name of the algorithm to be used to calculate the scores. If invalid or + * {@code null}, the default algorithm is used instead. + * @param algorithmOptions optional arguments to be passed to the algorithm. * @param actualValues values entered by the user. * @param userDataValues values predicted from the user data. - * @return the calculated scores, with the first dimension representing actual values and the - * second dimension values from {@link UserData}. + * @return the calculated scores of {@code actualValues} x {@code userDataValues}. * * {@hide} */ @Nullable @SystemApi public float[][] onGetScores(@Nullable String algorithm, - @Nullable Bundle args, @NonNull List<AutofillValue> actualValues, + @Nullable Bundle algorithmOptions, @NonNull List<AutofillValue> actualValues, @NonNull List<String> userDataValues) { Log.e(TAG, "service implementation (" + getClass() + " does not implement onGetScore()"); return null; diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java index df62446427d3..6e5bacf1aad1 100644 --- a/core/java/android/service/autofill/FillEventHistory.java +++ b/core/java/android/service/autofill/FillEventHistory.java @@ -424,7 +424,7 @@ public final class FillEventHistory implements Parcelable { * @return map map whose key is the id of the manually-entered field, and value is the * ids of the datasets that have that value but were not selected by the user. */ - @Nullable public Map<AutofillId, Set<String>> getManuallyEnteredField() { + @NonNull public Map<AutofillId, Set<String>> getManuallyEnteredField() { if (mManuallyFilledFieldIds == null || mManuallyFilledDatasetIds == null) { return Collections.emptyMap(); } diff --git a/core/java/android/service/notification/ScheduleCalendar.java b/core/java/android/service/notification/ScheduleCalendar.java index 8a7ff4da26e3..01287104f9b2 100644 --- a/core/java/android/service/notification/ScheduleCalendar.java +++ b/core/java/android/service/notification/ScheduleCalendar.java @@ -144,7 +144,8 @@ public class ScheduleCalendar { } return mSchedule.exitAtAlarm && mSchedule.nextAlarm != 0 - && time >= mSchedule.nextAlarm; + && time >= mSchedule.nextAlarm + && isInSchedule(mSchedule.nextAlarm); } private boolean isInSchedule(int daysOffset, long time, long start, long end) { diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java index 4431bcef1ff4..758cd2b877f2 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java @@ -62,6 +62,7 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Map; @@ -457,6 +458,7 @@ public class ApkSignatureSchemeV3Verifier { // get the version code, but don't do anything with it: creator knew about all our flags porBuf.getInt(); + HashSet<X509Certificate> certHistorySet = new HashSet<>(); while (porBuf.hasRemaining()) { levelCount++; ByteBuffer level = getLengthPrefixedSlice(porBuf); @@ -495,6 +497,12 @@ public class ApkSignatureSchemeV3Verifier { lastCert = new VerbatimX509Certificate(lastCert, encodedCert); lastSigAlgorithm = sigAlgorithm; + if (certHistorySet.contains(lastCert)) { + throw new SecurityException("Encountered duplicate entries in " + + "Proof-of-rotation record at certificate #" + levelCount + ". All " + + "signing certificates should be unique"); + } + certHistorySet.add(lastCert); certs.add(lastCert); flagsList.add(flags); } diff --git a/core/java/android/util/apk/ApkVerityBuilder.java b/core/java/android/util/apk/ApkVerityBuilder.java index 3b8fc5c53d2a..f15e1a1a7c52 100644 --- a/core/java/android/util/apk/ApkVerityBuilder.java +++ b/core/java/android/util/apk/ApkVerityBuilder.java @@ -72,22 +72,31 @@ abstract class ApkVerityBuilder { signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset; long dataSize = apk.length() - signingBlockSize; int[] levelOffset = calculateVerityLevelOffset(dataSize); + int merkleTreeSize = levelOffset[levelOffset.length - 1]; ByteBuffer output = bufferFactory.create( - CHUNK_SIZE_BYTES + // fsverity header + extensions + padding - levelOffset[levelOffset.length - 1]); // Merkle tree size + merkleTreeSize + + CHUNK_SIZE_BYTES); // maximum size of fsverity metadata output.order(ByteOrder.LITTLE_ENDIAN); - ByteBuffer header = slice(output, 0, FSVERITY_HEADER_SIZE_BYTES); - ByteBuffer extensions = slice(output, FSVERITY_HEADER_SIZE_BYTES, CHUNK_SIZE_BYTES); - ByteBuffer tree = slice(output, CHUNK_SIZE_BYTES, output.limit()); + ByteBuffer tree = slice(output, 0, merkleTreeSize); + ByteBuffer header = slice(output, merkleTreeSize, + merkleTreeSize + FSVERITY_HEADER_SIZE_BYTES); + ByteBuffer extensions = slice(output, merkleTreeSize + FSVERITY_HEADER_SIZE_BYTES, + merkleTreeSize + CHUNK_SIZE_BYTES); byte[] apkDigestBytes = new byte[DIGEST_SIZE_BYTES]; ByteBuffer apkDigest = ByteBuffer.wrap(apkDigestBytes); apkDigest.order(ByteOrder.LITTLE_ENDIAN); + // NB: Buffer limit is set inside once finished. calculateFsveritySignatureInternal(apk, signatureInfo, tree, apkDigest, header, extensions); - output.rewind(); + // Put the reverse offset to fs-verity header at the end. + output.position(merkleTreeSize + FSVERITY_HEADER_SIZE_BYTES + extensions.limit()); + output.putInt(FSVERITY_HEADER_SIZE_BYTES + extensions.limit() + + 4); // size of this integer right before EOF + output.flip(); + return new ApkVerityResult(output, apkDigestBytes); } @@ -101,7 +110,8 @@ abstract class ApkVerityBuilder { ByteBuffer verityBlock = ByteBuffer.allocate(CHUNK_SIZE_BYTES) .order(ByteOrder.LITTLE_ENDIAN); ByteBuffer header = slice(verityBlock, 0, FSVERITY_HEADER_SIZE_BYTES); - ByteBuffer extensions = slice(verityBlock, FSVERITY_HEADER_SIZE_BYTES, CHUNK_SIZE_BYTES); + ByteBuffer extensions = slice(verityBlock, FSVERITY_HEADER_SIZE_BYTES, + CHUNK_SIZE_BYTES - FSVERITY_HEADER_SIZE_BYTES); calculateFsveritySignatureInternal(apk, signatureInfo, null, null, header, extensions); @@ -328,10 +338,10 @@ abstract class ApkVerityBuilder { buffer.put((byte) 12); // log2(block-size): log2(4096) buffer.put((byte) 7); // log2(leaves-per-node): log2(4096 / 32) - buffer.putShort((short) 1); // meta algorithm, SHA256_MODE == 1 - buffer.putShort((short) 1); // data algorithm, SHA256_MODE == 1 + buffer.putShort((short) 1); // meta algorithm, SHA256 == 1 + buffer.putShort((short) 1); // data algorithm, SHA256 == 1 - buffer.putInt(0x0); // flags + buffer.putInt(0); // flags buffer.putInt(0); // reserved buffer.putLong(fileSize); // original file size @@ -362,12 +372,11 @@ abstract class ApkVerityBuilder { // // struct fsverity_extension_patch { // __le64 offset; - // u8 length; - // u8 reserved[7]; // u8 databytes[]; // }; final int kSizeOfFsverityExtensionHeader = 8; + final int kExtensionSizeAlignment = 8; { // struct fsverity_extension #1 @@ -385,24 +394,25 @@ abstract class ApkVerityBuilder { { // struct fsverity_extension #2 - final int kSizeOfFsverityPatchExtension = - 8 + // offset size - 1 + // size of length from offset (up to 255) - 7 + // reserved - ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE; - final int kPadding = (int) divideRoundup(kSizeOfFsverityPatchExtension % 8, 8); + final int kTotalSize = kSizeOfFsverityExtensionHeader + + 8 // offset size + + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE; - buffer.putShort((short) // total size of extension, padded to 64-bit alignment - (kSizeOfFsverityExtensionHeader + kSizeOfFsverityPatchExtension + kPadding)); + buffer.putShort((short) kTotalSize); buffer.put((byte) 1); // ID of patch extension skip(buffer, 5); // reserved // struct fsverity_extension_patch - buffer.putLong(eocdOffset); // offset - buffer.put((byte) ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE); // length - skip(buffer, 7); // reserved - buffer.putInt(Math.toIntExact(signingBlockOffset)); // databytes - skip(buffer, kPadding); // padding + buffer.putLong(eocdOffset + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET); // offset + buffer.putInt(Math.toIntExact(signingBlockOffset)); // databytes + + // The extension needs to be 0-padded at the end, since the length may not be multiple + // of 8. + int kPadding = kExtensionSizeAlignment - kTotalSize % kExtensionSizeAlignment; + if (kPadding == kExtensionSizeAlignment) { + kPadding = 0; + } + skip(buffer, kPadding); // padding } buffer.flip(); diff --git a/core/java/android/util/apk/VerbatimX509Certificate.java b/core/java/android/util/apk/VerbatimX509Certificate.java index 9984c6d26c64..391c5fc39416 100644 --- a/core/java/android/util/apk/VerbatimX509Certificate.java +++ b/core/java/android/util/apk/VerbatimX509Certificate.java @@ -18,6 +18,7 @@ package android.util.apk; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; +import java.util.Arrays; /** * For legacy reasons we need to return exactly the original encoded certificate bytes, instead @@ -25,6 +26,7 @@ import java.security.cert.X509Certificate; */ class VerbatimX509Certificate extends WrappedX509Certificate { private final byte[] mEncodedVerbatim; + private int mHash = -1; VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) { super(wrapped); @@ -35,4 +37,30 @@ class VerbatimX509Certificate extends WrappedX509Certificate { public byte[] getEncoded() throws CertificateEncodingException { return mEncodedVerbatim; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof VerbatimX509Certificate)) return false; + + try { + byte[] a = this.getEncoded(); + byte[] b = ((VerbatimX509Certificate) o).getEncoded(); + return Arrays.equals(a, b); + } catch (CertificateEncodingException e) { + return false; + } + } + + @Override + public int hashCode() { + if (mHash == -1) { + try { + mHash = Arrays.hashCode(this.getEncoded()); + } catch (CertificateEncodingException e) { + mHash = 0; + } + } + return mHash; + } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index f6c669b52370..ea3710c799bf 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -13917,11 +13917,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mAttachInfo.mUnbufferedDispatchRequested = true; } + private boolean hasSize() { + return (mBottom > mTop) && (mRight > mLeft); + } + private boolean canTakeFocus() { return ((mViewFlags & VISIBILITY_MASK) == VISIBLE) && ((mViewFlags & FOCUSABLE) == FOCUSABLE) && ((mViewFlags & ENABLED_MASK) == ENABLED) - && (sCanFocusZeroSized || !isLayoutValid() || (mBottom > mTop) && (mRight > mLeft)); + && (sCanFocusZeroSized || !isLayoutValid() || hasSize()); } /** @@ -13982,7 +13986,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, || focusableChangedByAuto == 0 || viewRootImpl == null || viewRootImpl.mThread == Thread.currentThread()) { - shouldNotifyFocusableAvailable = true; + shouldNotifyFocusableAvailable = canTakeFocus(); } } } @@ -14001,11 +14005,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, needGlobalAttributesUpdate(true); - // a view becoming visible is worth notifying the parent - // about in case nothing has focus. even if this specific view - // isn't focusable, it may contain something that is, so let - // the root view try to give this focus if nothing else does. - shouldNotifyFocusableAvailable = true; + // a view becoming visible is worth notifying the parent about in case nothing has + // focus. Even if this specific view isn't focusable, it may contain something that + // is, so let the root view try to give this focus if nothing else does. + shouldNotifyFocusableAvailable = hasSize(); } } @@ -14014,16 +14017,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // a view becoming enabled should notify the parent as long as the view is also // visible and the parent wasn't already notified by becoming visible during this // setFlags invocation. - shouldNotifyFocusableAvailable = true; + shouldNotifyFocusableAvailable = canTakeFocus(); } else { if (isFocused()) clearFocus(); } } - if (shouldNotifyFocusableAvailable) { - if (mParent != null && canTakeFocus()) { - mParent.focusableViewAvailable(this); - } + if (shouldNotifyFocusableAvailable && mParent != null) { + mParent.focusableViewAvailable(this); } /* Check if the GONE bit has changed */ diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java index 630007bad9b1..b413d48c6c78 100644 --- a/core/java/android/view/textclassifier/TextClassification.java +++ b/core/java/android/view/textclassifier/TextClassification.java @@ -43,8 +43,8 @@ import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.time.ZonedDateTime; import java.util.ArrayList; -import java.util.Calendar; import java.util.Collections; import java.util.List; import java.util.Locale; @@ -512,7 +512,7 @@ public final class TextClassification implements Parcelable { public static final class Options implements Parcelable { private @Nullable LocaleList mDefaultLocales; - private @Nullable Calendar mReferenceTime; + private @Nullable ZonedDateTime mReferenceTime; public Options() {} @@ -531,7 +531,7 @@ public final class TextClassification implements Parcelable { * be interpreted. This should usually be the time when the text was originally * composed. If no reference time is set, now is used. */ - public Options setReferenceTime(Calendar referenceTime) { + public Options setReferenceTime(ZonedDateTime referenceTime) { mReferenceTime = referenceTime; return this; } @@ -550,7 +550,7 @@ public final class TextClassification implements Parcelable { * interpreted. */ @Nullable - public Calendar getReferenceTime() { + public ZonedDateTime getReferenceTime() { return mReferenceTime; } @@ -567,7 +567,7 @@ public final class TextClassification implements Parcelable { } dest.writeInt(mReferenceTime != null ? 1 : 0); if (mReferenceTime != null) { - dest.writeSerializable(mReferenceTime); + dest.writeString(mReferenceTime.toString()); } } @@ -589,7 +589,7 @@ public final class TextClassification implements Parcelable { mDefaultLocales = LocaleList.CREATOR.createFromParcel(in); } if (in.readInt() > 0) { - mReferenceTime = (Calendar) in.readSerializable(); + mReferenceTime = ZonedDateTime.parse(in.readString()); } } } diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 5ba470afd961..8d1ed0eb68cb 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -16,6 +16,8 @@ package android.view.textclassifier; +import static java.time.temporal.ChronoUnit.MILLIS; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.WorkerThread; @@ -45,9 +47,10 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; +import java.time.Instant; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; -import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -119,7 +122,7 @@ public final class TextClassifierImpl implements TextClassifier { && rangeLength <= mSettings.getSuggestSelectionMaxRangeLength()) { final LocaleList locales = (options == null) ? null : options.getDefaultLocales(); final String localesString = concatenateLocales(locales); - final Calendar refTime = Calendar.getInstance(); + final ZonedDateTime refTime = ZonedDateTime.now(); final boolean darkLaunchAllowed = options != null && options.isDarkLaunchAllowed(); final TextClassifierImplNative nativeImpl = getNative(locales); final String string = text.toString(); @@ -143,8 +146,8 @@ public final class TextClassifierImpl implements TextClassifier { nativeImpl.classifyText( string, start, end, new TextClassifierImplNative.ClassificationOptions( - refTime.getTimeInMillis(), - refTime.getTimeZone().getID(), + refTime.toInstant().toEpochMilli(), + refTime.getZone().getId(), localesString)); final int size = results.length; for (int i = 0; i < size; i++) { @@ -183,19 +186,20 @@ public final class TextClassifierImpl implements TextClassifier { final String string = text.toString(); final LocaleList locales = (options == null) ? null : options.getDefaultLocales(); final String localesString = concatenateLocales(locales); - final Calendar refTime = (options != null && options.getReferenceTime() != null) - ? options.getReferenceTime() : Calendar.getInstance(); + final ZonedDateTime refTime = + (options != null && options.getReferenceTime() != null) + ? options.getReferenceTime() : ZonedDateTime.now(); final TextClassifierImplNative.ClassificationResult[] results = getNative(locales) .classifyText(string, startIndex, endIndex, new TextClassifierImplNative.ClassificationOptions( - refTime.getTimeInMillis(), - refTime.getTimeZone().getID(), + refTime.toInstant().toEpochMilli(), + refTime.getZone().getId(), localesString)); if (results.length > 0) { return createClassificationResult( - results, string, startIndex, endIndex, refTime); + results, string, startIndex, endIndex, refTime.toInstant()); } } } catch (Throwable t) { @@ -224,7 +228,7 @@ public final class TextClassifierImpl implements TextClassifier { try { final long startTimeMs = System.currentTimeMillis(); final LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null; - final Calendar refTime = Calendar.getInstance(); + final ZonedDateTime refTime = ZonedDateTime.now(); final Collection<String> entitiesToIdentify = options != null && options.getEntityConfig() != null ? options.getEntityConfig().resolveEntityListModifications( @@ -236,8 +240,8 @@ public final class TextClassifierImpl implements TextClassifier { nativeImpl.annotate( textString, new TextClassifierImplNative.AnnotationOptions( - refTime.getTimeInMillis(), - refTime.getTimeZone().getID(), + refTime.toInstant().toEpochMilli(), + refTime.getZone().getId(), concatenateLocales(defaultLocales))); for (TextClassifierImplNative.AnnotatedSpan span : annotations) { final TextClassifierImplNative.ClassificationResult[] results = @@ -416,7 +420,7 @@ public final class TextClassifierImpl implements TextClassifier { private TextClassification createClassificationResult( TextClassifierImplNative.ClassificationResult[] classifications, - String text, int start, int end, @Nullable Calendar referenceTime) { + String text, int start, int end, @Nullable Instant referenceTime) { final String classifiedText = text.substring(start, end); final TextClassification.Builder builder = new TextClassification.Builder() .setText(classifiedText); @@ -646,7 +650,7 @@ public final class TextClassifierImpl implements TextClassifier { @NonNull public static List<LabeledIntent> create( Context context, - @Nullable Calendar referenceTime, + @Nullable Instant referenceTime, TextClassifierImplNative.ClassificationResult classification, String text) { final String type = classification.getCollection().trim().toLowerCase(Locale.ENGLISH); @@ -663,10 +667,9 @@ public final class TextClassifierImpl implements TextClassifier { case TextClassifier.TYPE_DATE: case TextClassifier.TYPE_DATE_TIME: if (classification.getDatetimeResult() != null) { - Calendar eventTime = Calendar.getInstance(); - eventTime.setTimeInMillis( + final Instant parsedTime = Instant.ofEpochMilli( classification.getDatetimeResult().getTimeMsUtc()); - return createForDatetime(context, type, referenceTime, eventTime); + return createForDatetime(context, type, referenceTime, parsedTime); } else { return new ArrayList<>(); } @@ -758,18 +761,17 @@ public final class TextClassifierImpl implements TextClassifier { @NonNull private static List<LabeledIntent> createForDatetime( - Context context, String type, @Nullable Calendar referenceTime, - Calendar eventTime) { + Context context, String type, @Nullable Instant referenceTime, + Instant parsedTime) { if (referenceTime == null) { // If no reference time was given, use now. - referenceTime = Calendar.getInstance(); + referenceTime = Instant.now(); } List<LabeledIntent> actions = new ArrayList<>(); - actions.add(createCalendarViewIntent(context, eventTime)); - final long millisSinceReference = - eventTime.getTimeInMillis() - referenceTime.getTimeInMillis(); - if (millisSinceReference > MIN_EVENT_FUTURE_MILLIS) { - actions.add(createCalendarCreateEventIntent(context, eventTime, type)); + actions.add(createCalendarViewIntent(context, parsedTime)); + final long millisUntilEvent = referenceTime.until(parsedTime, MILLIS); + if (millisUntilEvent > MIN_EVENT_FUTURE_MILLIS) { + actions.add(createCalendarCreateEventIntent(context, parsedTime, type)); } return actions; } @@ -784,10 +786,10 @@ public final class TextClassifierImpl implements TextClassifier { } @NonNull - private static LabeledIntent createCalendarViewIntent(Context context, Calendar eventTime) { + private static LabeledIntent createCalendarViewIntent(Context context, Instant parsedTime) { Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon(); builder.appendPath("time"); - ContentUris.appendId(builder, eventTime.getTimeInMillis()); + ContentUris.appendId(builder, parsedTime.toEpochMilli()); return new LabeledIntent( context.getString(com.android.internal.R.string.view_calendar), context.getString(com.android.internal.R.string.view_calendar_desc), @@ -796,7 +798,7 @@ public final class TextClassifierImpl implements TextClassifier { @NonNull private static LabeledIntent createCalendarCreateEventIntent( - Context context, Calendar eventTime, @EntityType String type) { + Context context, Instant parsedTime, @EntityType String type) { final boolean isAllDay = TextClassifier.TYPE_DATE.equals(type); return new LabeledIntent( context.getString(com.android.internal.R.string.add_calendar_event), @@ -805,9 +807,9 @@ public final class TextClassifierImpl implements TextClassifier { .setData(CalendarContract.Events.CONTENT_URI) .putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, isAllDay) .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, - eventTime.getTimeInMillis()) + parsedTime.toEpochMilli()) .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, - eventTime.getTimeInMillis() + DEFAULT_EVENT_DURATION)); + parsedTime.toEpochMilli() + DEFAULT_EVENT_DURATION)); } } } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 92f496a87c3f..99467265b5c5 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -4585,8 +4585,8 @@ public class Editor { return mContainer.isShowing(); } - private boolean isVisible() { - // Always show a dragging handle. + private boolean shouldShow() { + // A dragging handle should always be shown. if (mIsDragging) { return true; } @@ -4599,6 +4599,10 @@ public class Editor { mPositionX + mHotspotX + getHorizontalOffset(), mPositionY); } + private void setVisible(final boolean visible) { + mContainer.getContentView().setVisibility(visible ? VISIBLE : INVISIBLE); + } + public abstract int getCurrentCursorOffset(); protected abstract void updateSelection(int offset); @@ -4692,7 +4696,7 @@ public class Editor { onHandleMoved(); } - if (isVisible()) { + if (shouldShow()) { // Transform to the window coordinates to follow the view tranformation. final int[] pts = { mPositionX + mHotspotX + getHorizontalOffset(), mPositionY}; mTextView.transformFromViewToWindowSpace(pts); @@ -4745,6 +4749,15 @@ public class Editor { return 0; } + private boolean tooLargeTextForMagnifier() { + final float magnifierContentHeight = Math.round( + mMagnifierAnimator.mMagnifier.getHeight() + / mMagnifierAnimator.mMagnifier.getZoom()); + final Paint.FontMetrics fontMetrics = mTextView.getPaint().getFontMetrics(); + final float glyphHeight = fontMetrics.descent - fontMetrics.ascent; + return glyphHeight > magnifierContentHeight; + } + /** * Computes the position where the magnifier should be shown, relative to * {@code mTextView}, and writes them to {@code showPosInView}. Also decides @@ -4824,13 +4837,12 @@ public class Editor { return true; } - private boolean tooLargeTextForMagnifier() { - final float magnifierContentHeight = Math.round( - mMagnifierAnimator.mMagnifier.getHeight() - / mMagnifierAnimator.mMagnifier.getZoom()); - final Paint.FontMetrics fontMetrics = mTextView.getPaint().getFontMetrics(); - final float glyphHeight = fontMetrics.descent - fontMetrics.ascent; - return glyphHeight > magnifierContentHeight; + private boolean handleOverlapsMagnifier() { + final int handleY = mContainer.getDecorViewLayoutParams().y; + final int magnifierBottomWhenAtWindowTop = + mTextView.getRootWindowInsets().getSystemWindowInsetTop() + + mMagnifierAnimator.mMagnifier.getHeight(); + return handleY <= magnifierBottomWhenAtWindowTop; } protected final void updateMagnifier(@NonNull final MotionEvent event) { @@ -4846,6 +4858,13 @@ public class Editor { mRenderCursorRegardlessTiming = true; mTextView.invalidateCursorPath(); suspendBlink(); + // Hide handle if it overlaps the magnifier. + if (handleOverlapsMagnifier()) { + setVisible(false); + } else { + setVisible(true); + } + mMagnifierAnimator.show(showPosInView.x, showPosInView.y); } else { dismissMagnifier(); @@ -4857,6 +4876,7 @@ public class Editor { mMagnifierAnimator.dismiss(); mRenderCursorRegardlessTiming = false; resumeBlink(); + setVisible(true); } } diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java index b049db379341..f4b7032a774c 100644 --- a/core/java/com/android/internal/backup/LocalTransport.java +++ b/core/java/com/android/internal/backup/LocalTransport.java @@ -187,11 +187,27 @@ public class LocalTransport extends BackupTransport { @Override public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) { + return performBackup(packageInfo, data, /*flags=*/ 0); + } + + @Override + public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data, int flags) { + boolean isIncremental = (flags & FLAG_INCREMENTAL) != 0; + boolean isNonIncremental = (flags & FLAG_NON_INCREMENTAL) != 0; + + if (isIncremental) { + Log.i(TAG, "Performing incremental backup for " + packageInfo.packageName); + } else if (isNonIncremental) { + Log.i(TAG, "Performing non-incremental backup for " + packageInfo.packageName); + } else { + Log.i(TAG, "Performing backup for " + packageInfo.packageName); + } + if (DEBUG) { try { - StructStat ss = Os.fstat(data.getFileDescriptor()); - Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName - + " size=" + ss.st_size); + StructStat ss = Os.fstat(data.getFileDescriptor()); + Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName + + " size=" + ss.st_size + " flags=" + flags); } catch (ErrnoException e) { Log.w(TAG, "Unable to stat input file in performBackup() on " + packageInfo.packageName); @@ -199,7 +215,26 @@ public class LocalTransport extends BackupTransport { } File packageDir = new File(mCurrentSetIncrementalDir, packageInfo.packageName); - packageDir.mkdirs(); + boolean hasDataForPackage = !packageDir.mkdirs(); + + if (isIncremental) { + if (mParameters.isNonIncrementalOnly() || !hasDataForPackage) { + if (mParameters.isNonIncrementalOnly()) { + Log.w(TAG, "Transport is in non-incremental only mode."); + + } else { + Log.w(TAG, + "Requested incremental, but transport currently stores no data for the " + + "package, requesting non-incremental retry."); + } + return TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED; + } + } + if (isNonIncremental && hasDataForPackage) { + Log.w(TAG, "Requested non-incremental, deleting existing data."); + clearBackupData(packageInfo); + packageDir.mkdirs(); + } // Each 'record' in the restore set is kept in its own file, named by // the record key. Wind through the data file, extracting individual diff --git a/core/java/com/android/internal/backup/LocalTransportParameters.java b/core/java/com/android/internal/backup/LocalTransportParameters.java index 154e79d4f7ef..2427d39fd65e 100644 --- a/core/java/com/android/internal/backup/LocalTransportParameters.java +++ b/core/java/com/android/internal/backup/LocalTransportParameters.java @@ -26,8 +26,10 @@ class LocalTransportParameters extends KeyValueSettingObserver { private static final String TAG = "LocalTransportParams"; private static final String SETTING = Settings.Secure.BACKUP_LOCAL_TRANSPORT_PARAMETERS; private static final String KEY_FAKE_ENCRYPTION_FLAG = "fake_encryption_flag"; + private static final String KEY_NON_INCREMENTAL_ONLY = "non_incremental_only"; private boolean mFakeEncryptionFlag; + private boolean mIsNonIncrementalOnly; LocalTransportParameters(Handler handler, ContentResolver resolver) { super(handler, resolver, Settings.Secure.getUriFor(SETTING)); @@ -37,11 +39,16 @@ class LocalTransportParameters extends KeyValueSettingObserver { return mFakeEncryptionFlag; } + boolean isNonIncrementalOnly() { + return mIsNonIncrementalOnly; + } + public String getSettingValue(ContentResolver resolver) { return Settings.Secure.getString(resolver, SETTING); } public void update(KeyValueListParser parser) { mFakeEncryptionFlag = parser.getBoolean(KEY_FAKE_ENCRYPTION_FLAG, false); + mIsNonIncrementalOnly = parser.getBoolean(KEY_NON_INCREMENTAL_ONLY, false); } } diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java index d2e97897c042..3f8fd9bbf0c0 100644 --- a/core/java/com/android/internal/content/PackageMonitor.java +++ b/core/java/com/android/internal/content/PackageMonitor.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; @@ -196,6 +197,10 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { public void onPackagesSuspended(String[] packages) { } + public void onPackagesSuspended(String[] packages, Bundle launcherExtras) { + onPackagesSuspended(packages); + } + public void onPackagesUnsuspended(String[] packages) { } @@ -433,8 +438,9 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { } } else if (Intent.ACTION_PACKAGES_SUSPENDED.equals(action)) { String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + final Bundle launcherExtras = intent.getBundleExtra(Intent.EXTRA_LAUNCHER_EXTRAS); mSomePackagesChanged = true; - onPackagesSuspended(pkgList); + onPackagesSuspended(pkgList, launcherExtras); } else if (Intent.ACTION_PACKAGES_UNSUSPENDED.equals(action)) { String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); mSomePackagesChanged = true; diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index cd83c57f60f9..5d40a7300919 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -47,6 +47,8 @@ import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; + import libcore.io.IoUtils; /** @@ -159,6 +161,11 @@ class ZygoteConnection { return null; } + if (parsedArgs.apiBlacklistExemptions != null) { + handleApiBlacklistExemptions(parsedArgs.apiBlacklistExemptions); + return null; + } + if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) { throw new ZygoteSecurityException("Client may not specify capabilities: " + "permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) + @@ -278,6 +285,15 @@ class ZygoteConnection { } } + private void handleApiBlacklistExemptions(String[] exemptions) { + try { + ZygoteInit.setApiBlacklistExemptions(exemptions); + mSocketOutStream.writeInt(0); + } catch (IOException ioe) { + throw new IllegalStateException("Error writing to command socket", ioe); + } + } + protected void preload() { ZygoteInit.lazyPreload(); } @@ -439,6 +455,12 @@ class ZygoteConnection { boolean startChildZygote; /** + * Exemptions from API blacklisting. These are sent to the pre-forked zygote at boot time, + * or when they change, via --set-api-blacklist-exemptions. + */ + String[] apiBlacklistExemptions; + + /** * Constructs instance and parses args * @param args zygote command-line args * @throws IllegalArgumentException @@ -592,6 +614,11 @@ class ZygoteConnection { preloadDefault = true; } else if (arg.equals("--start-child-zygote")) { startChildZygote = true; + } else if (arg.equals("--set-api-blacklist-exemptions")) { + // consume all remaining args; this is a stand-alone command, never included + // with the regular fork command. + apiBlacklistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length); + curArg = args.length; } else { break; } @@ -606,7 +633,7 @@ class ZygoteConnection { throw new IllegalArgumentException( "Unexpected arguments after --preload-package."); } - } else if (!preloadDefault) { + } else if (!preloadDefault && apiBlacklistExemptions == null) { if (!seenRuntimeArgs) { throw new IllegalArgumentException("Unexpected argument : " + args[curArg]); } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 9467eccd3ff7..c5d41db934da 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -514,6 +514,10 @@ public class ZygoteInit { /* should never reach here */ } + public static void setApiBlacklistExemptions(String[] exemptions) { + VMRuntime.getRuntime().setHiddenApiExemptions(exemptions); + } + /** * Creates a PathClassLoader for the given class path that is associated with a shared * namespace, i.e., this classloader can access platform-private native libraries. The diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index 59b14f18eb07..ae7ba1948c00 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -61,7 +61,6 @@ interface ILockSettings { void initRecoveryServiceWithSigFile(in String rootCertificateAlias, in byte[] recoveryServiceCertFile, in byte[] recoveryServiceSigFile); KeyChainSnapshot getKeyChainSnapshot(); - byte[] generateAndStoreKey(String alias); String generateKey(String alias); String importKey(String alias, in byte[] keyBytes); String getKey(String alias); diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index 957c784087af..e8fc5989354a 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -1000,6 +1000,11 @@ public class LockPatternView extends View { setPatternInProgress(false); cancelLineAnimations(); notifyPatternDetected(); + // Also clear pattern if fading is enabled + if (mFadePattern) { + clearPatternDrawLookup(); + mPatternDisplayMode = DisplayMode.Correct; + } invalidate(); } if (PROFILE_DRAWING) { @@ -1008,9 +1013,6 @@ public class LockPatternView extends View { mDrawingProfilingStarted = false; } } - if (mFadePattern) { - clearPattern(); - } } private void cancelLineAnimations() { diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp index 726c450a4af2..825b7a0a9884 100644 --- a/core/jni/android/graphics/ImageDecoder.cpp +++ b/core/jni/android/graphics/ImageDecoder.cpp @@ -210,7 +210,7 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong jint desiredWidth, jint desiredHeight, jobject jsubset, jboolean requireMutable, jint allocator, jboolean requireUnpremul, jboolean preferRamOverQuality, - jboolean asAlphaMask) { + jboolean asAlphaMask, jobject jcolorSpace) { auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr); SkAndroidCodec* codec = decoder->mCodec.get(); const SkISize desiredSize = SkISize::Make(desiredWidth, desiredHeight); @@ -264,7 +264,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong // This is currently the only way to know that we should decode to F16. colorType = codec->computeOutputColorType(colorType); } - sk_sp<SkColorSpace> colorSpace = codec->computeOutputColorSpace(colorType); + sk_sp<SkColorSpace> colorSpace = GraphicsJNI::getNativeColorSpace(env, jcolorSpace); + colorSpace = codec->computeOutputColorSpace(colorType, colorSpace); decodeInfo = decodeInfo.makeColorType(colorType).makeColorSpace(colorSpace); SkBitmap bm; @@ -507,18 +508,26 @@ static jstring ImageDecoder_nGetMimeType(JNIEnv* env, jobject /*clazz*/, jlong n return encodedFormatToString(env, decoder->mCodec->getEncodedFormat()); } +static jobject ImageDecoder_nGetColorSpace(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* codec = reinterpret_cast<ImageDecoder*>(nativePtr)->mCodec.get(); + auto colorType = codec->computeOutputColorType(codec->getInfo().colorType()); + sk_sp<SkColorSpace> colorSpace = codec->computeOutputColorSpace(colorType); + return GraphicsJNI::getColorSpace(env, colorSpace, colorType); +} + static const JNINativeMethod gImageDecoderMethods[] = { { "nCreate", "(JLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateAsset }, { "nCreate", "(Ljava/nio/ByteBuffer;IILandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteBuffer }, { "nCreate", "([BIILandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteArray }, { "nCreate", "(Ljava/io/InputStream;[BLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateInputStream }, { "nCreate", "(Ljava/io/FileDescriptor;Landroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateFd }, - { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder;ZIILandroid/graphics/Rect;ZIZZZ)Landroid/graphics/Bitmap;", + { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder;ZIILandroid/graphics/Rect;ZIZZZLandroid/graphics/ColorSpace;)Landroid/graphics/Bitmap;", (void*) ImageDecoder_nDecodeBitmap }, { "nGetSampledSize","(JI)Landroid/util/Size;", (void*) ImageDecoder_nGetSampledSize }, { "nGetPadding", "(JLandroid/graphics/Rect;)V", (void*) ImageDecoder_nGetPadding }, { "nClose", "(J)V", (void*) ImageDecoder_nClose}, { "nGetMimeType", "(J)Ljava/lang/String;", (void*) ImageDecoder_nGetMimeType }, + { "nGetColorSpace", "(J)Landroid/graphics/ColorSpace;", (void*) ImageDecoder_nGetColorSpace }, }; int register_android_graphics_ImageDecoder(JNIEnv* env) { diff --git a/core/jni/android/graphics/Path.cpp b/core/jni/android/graphics/Path.cpp index d3d68826affe..97abd82eaac5 100644 --- a/core/jni/android/graphics/Path.cpp +++ b/core/jni/android/graphics/Path.cpp @@ -37,6 +37,14 @@ namespace android { class SkPathGlue { public: + static void finalizer(SkPath* obj) { + // Purge entries from the HWUI path cache if this path's data is unique + if (obj->unique() && android::uirenderer::Caches::hasInstance()) { + android::uirenderer::Caches::getInstance().pathCache.removeDeferred(obj); + } + delete obj; + } + // ---------------- Regular JNI ----------------------------- static jlong init(JNIEnv* env, jclass clazz) { @@ -48,13 +56,8 @@ public: return reinterpret_cast<jlong>(new SkPath(*val)); } - static void finalize(JNIEnv* env, jclass clazz, jlong objHandle) { - SkPath* obj = reinterpret_cast<SkPath*>(objHandle); - // Purge entries from the HWUI path cache if this path's data is unique - if (obj->unique() && android::uirenderer::Caches::hasInstance()) { - android::uirenderer::Caches::getInstance().pathCache.removeDeferred(obj); - } - delete obj; + static jlong getFinalizer(JNIEnv* env, jclass clazz) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&finalizer)); } static void set(JNIEnv* env, jclass clazz, jlong dstHandle, jlong srcHandle) { @@ -469,7 +472,9 @@ public: SkRect rect; SkPath* obj = reinterpret_cast<SkPath*>(objHandle); jboolean result = obj->isRect(&rect); - GraphicsJNI::rect_to_jrectf(rect, env, jrect); + if (jrect) { + GraphicsJNI::rect_to_jrectf(rect, env, jrect); + } return result; } @@ -510,7 +515,7 @@ public: static const JNINativeMethod methods[] = { {"nInit","()J", (void*) SkPathGlue::init}, {"nInit","(J)J", (void*) SkPathGlue::init_Path}, - {"nFinalize", "(J)V", (void*) SkPathGlue::finalize}, + {"nGetFinalizer", "()J", (void*) SkPathGlue::getFinalizer}, {"nSet","(JJ)V", (void*) SkPathGlue::set}, {"nComputeBounds","(JLandroid/graphics/RectF;)V", (void*) SkPathGlue::computeBounds}, {"nIncReserve","(JI)V", (void*) SkPathGlue::incReserve}, diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp index c9bfa137c7ac..7f90d8ee3e4e 100644 --- a/core/jni/android_graphics_Canvas.cpp +++ b/core/jni/android_graphics_Canvas.cpp @@ -448,8 +448,9 @@ static void drawBitmapArray(JNIEnv* env, jobject, jlong canvasHandle, jboolean hasAlpha, jlong paintHandle) { // Note: If hasAlpha is false, kRGB_565_SkColorType will be used, which will // correct the alphaType to kOpaque_SkAlphaType. - SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType, - GraphicsJNI::defaultColorSpace()); + SkImageInfo info = SkImageInfo::Make(width, height, + hasAlpha ? kN32_SkColorType : kRGB_565_SkColorType, + kPremul_SkAlphaType); SkBitmap bitmap; bitmap.setInfo(info); sk_sp<Bitmap> androidBitmap = Bitmap::allocateHeapBitmap(&bitmap); diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 04cb08f51efa..b0f68cde1486 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -37,7 +37,7 @@ #include <system/graphics.h> #include <ui/DisplayInfo.h> #include <ui/FrameStats.h> -#include <ui/GraphicsTypes.h> +#include <ui/GraphicTypes.h> #include <ui/HdrCapabilities.h> #include <ui/Rect.h> #include <ui/Region.h> @@ -598,7 +598,7 @@ static jboolean nativeSetActiveConfig(JNIEnv* env, jclass clazz, jobject tokenOb static jintArray nativeGetDisplayColorModes(JNIEnv* env, jclass, jobject tokenObj) { sp<IBinder> token(ibinderForJavaObject(env, tokenObj)); if (token == NULL) return NULL; - Vector<ColorMode> colorModes; + Vector<ui::ColorMode> colorModes; if (SurfaceComposerClient::getDisplayColorModes(token, &colorModes) != NO_ERROR || colorModes.isEmpty()) { return NULL; @@ -628,7 +628,7 @@ static jboolean nativeSetActiveColorMode(JNIEnv* env, jclass, sp<IBinder> token(ibinderForJavaObject(env, tokenObj)); if (token == NULL) return JNI_FALSE; status_t err = SurfaceComposerClient::setActiveColorMode(token, - static_cast<ColorMode>(colorMode)); + static_cast<ui::ColorMode>(colorMode)); return err == NO_ERROR ? JNI_TRUE : JNI_FALSE; } diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index b5fd7929eeae..b2853c9f1c61 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -260,7 +260,7 @@ static void SetUpSeccompFilter(uid_t uid) { } // Apply system or app filter based on uid. - if (getuid() >= AID_APP_START) { + if (uid >= AID_APP_START) { set_app_seccomp_filter(); } else { set_system_seccomp_filter(); @@ -619,11 +619,6 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra fail_fn(CREATE_ERROR("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno))); } - // Must be called when the new process still has CAP_SYS_ADMIN. The other alternative is to - // call prctl(PR_SET_NO_NEW_PRIVS, 1) afterward, but that breaks SELinux domain transition (see - // b/71859146). - SetUpSeccompFilter(uid); - // Keep capabilities across UID change, unless we're staying root. if (uid != 0) { if (!EnableKeepCapabilities(&error_msg)) { @@ -699,6 +694,13 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra fail_fn(CREATE_ERROR("setresgid(%d) failed: %s", gid, strerror(errno))); } + // Must be called when the new process still has CAP_SYS_ADMIN, in this case, before changing + // uid from 0, which clears capabilities. The other alternative is to call + // prctl(PR_SET_NO_NEW_PRIVS, 1) afterward, but that breaks SELinux domain transition (see + // b/71859146). As the result, privileged syscalls used below still need to be accessible in + // app process. + SetUpSeccompFilter(uid); + rc = setresuid(uid, uid, uid); if (rc == -1) { fail_fn(CREATE_ERROR("setresuid(%d) failed: %s", uid, strerror(errno))); diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 0fea0dc1c428..ea0b825b01a8 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -175,9 +175,9 @@ message IncidentProto { ]; optional GZippedFileProto last_kmsg = 2007 [ - (section).type = SECTION_NONE, // disable until selinux permission is gained + (section).type = SECTION_GZIP, (section).args = "/sys/fs/pstore/console-ramoops /sys/fs/pstore/console-ramoops-0 /proc/last_kmsg", - (privacy).dest = DEST_AUTOMATIC + (privacy).dest = DEST_EXPLICIT ]; // System Services diff --git a/core/proto/android/os/system_properties.proto b/core/proto/android/os/system_properties.proto index 694b94b6fd2e..8bf3772a6a5d 100644 --- a/core/proto/android/os/system_properties.proto +++ b/core/proto/android/os/system_properties.proto @@ -192,6 +192,8 @@ message SystemPropertiesProto { optional string libc_debug_malloc_program = 15; message Log { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional string tag_WifiHAL = 1; optional string tag_stats_log = 2; diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index c7de947cdca9..b5303c8a4eec 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -672,8 +672,10 @@ message GlobalSettingsProto { option (android.msg_privacy).dest = DEST_LOCAL; // The requested Private DNS mode and an accompanying specifier. - optional SettingProto dns_mode = 1; - optional SettingProto dns_specifier = 2; + // msg_privacy settings don't apply to sub messages, only to primitive + // fields, so these must also be explicitly set to LOCAL. + optional SettingProto dns_mode = 1 [ (android.privacy).dest = DEST_LOCAL ]; + optional SettingProto dns_specifier = 2 [ (android.privacy).dest = DEST_LOCAL ]; } optional Private private = 96; diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index cfb89808b1b2..593747df4ee3 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -145,6 +145,7 @@ message SecureSettingsProto { optional SettingProto transport = 4 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto manager_constants = 5; optional SettingProto local_transport_parameters = 6; + optional SettingProto packages_to_clear_data_before_full_restore = 7; } optional Backup backup = 10; diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index bb8ce813b2f9..4df3b63a3eab 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -527,9 +527,7 @@ message JobStatusShortInfoProto { optional int32 calling_uid = 1; // Job IDs can technically be negative. optional int32 job_id = 2; - optional string battery_name = 3 [ - (.android.privacy).dest = DEST_EXPLICIT - ]; + optional string battery_name = 3; } // Dump from a com.android.server.job.controllers.JobStatus object. diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto index b60c56995223..8240d8aff153 100644 --- a/core/proto/android/service/usb.proto +++ b/core/proto/android/service/usb.proto @@ -80,7 +80,7 @@ message UsbAccessoryProto { optional string model = 2; optional string description = 3; optional string version = 4; - optional string uri = 5; + optional string uri = 5 [ (android.privacy).dest = DEST_EXPLICIT ]; optional string serial = 6 [ (android.privacy).dest = DEST_EXPLICIT ]; } @@ -155,6 +155,7 @@ message UsbEndPointProto { message UsbConnectionRecordProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; + // usb device's address, e.g. 001/002, nothing about the phone optional string device_address = 1; optional android.service.UsbConnectionRecordMode mode = 2; optional int64 timestamp = 3; @@ -251,6 +252,7 @@ message UsbAlsaDeviceProto { optional string name = 3; optional bool has_playback = 4; optional bool has_capture = 5; + // usb device's address, e.g. 001/002, nothing about the phone optional string address = 6; } @@ -259,6 +261,7 @@ message UsbMidiDeviceProto { optional int32 card = 1; optional int32 device = 2; + // usb device's address, e.g. 001/002, nothing about the phone optional string device_address = 3; } diff --git a/core/proto/android/view/displayinfo.proto b/core/proto/android/view/displayinfo.proto index cbd06fdb8c04..2a030509e617 100644 --- a/core/proto/android/view/displayinfo.proto +++ b/core/proto/android/view/displayinfo.proto @@ -29,5 +29,5 @@ message DisplayInfoProto { optional int32 logical_height = 2; optional int32 app_width = 3; optional int32 app_height = 4; - optional string name = 5; + optional string name = 5 [ (.android.privacy).dest = DEST_EXPLICIT ]; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 48d394a299c3..f4715fc7645d 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2016,11 +2016,11 @@ <permission android:name="android.permission.REMOVE_TASKS" android:protectionLevel="signature" /> - <!-- @SystemApi @hide Allows an application to create/manage/remove stacks --> + <!-- @SystemApi @TestApi @hide Allows an application to create/manage/remove stacks --> <permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" android:protectionLevel="signature|privileged|development" /> - <!-- @SystemApi @hide Allows an application to embed other activities --> + <!-- @SystemApi @TestApi @hide Allows an application to embed other activities --> <permission android:name="android.permission.ACTIVITY_EMBEDDING" android:protectionLevel="signature|privileged|development" /> @@ -3121,6 +3121,12 @@ <permission android:name="android.permission.CONFIGURE_DISPLAY_COLOR_MODE" android:protectionLevel="signature" /> + <!-- Allows an application to control the color saturation of the display. + @hide + @SystemApi --> + <permission android:name="android.permission.CONTROL_DISPLAY_SATURATION" + android:protectionLevel="signature|privileged" /> + <!-- Allows an application to collect usage infomation about brightness slider changes. <p>Not for use by third-party applications.</p> @hide diff --git a/core/res/res/values-land/dimens.xml b/core/res/res/values-land/dimens.xml index 351bd818b09a..265eaaf39713 100644 --- a/core/res/res/values-land/dimens.xml +++ b/core/res/res/values-land/dimens.xml @@ -29,10 +29,7 @@ <!-- Height of the status bar --> <dimen name="status_bar_height">@dimen/status_bar_height_landscape</dimen> - <!-- Height of area above QQS where battery/time go --> - <dimen name="quick_qs_offset_height">@dimen/status_bar_height_landscape</dimen> - <!-- Total height of QQS in landscape, this is effectively status_bar_height_landscape + 128 --> - <dimen name="quick_qs_total_height">152dp</dimen> + <!-- Default height of an action bar. --> <dimen name="action_bar_default_height">40dip</dimen> <!-- Vertical padding around action bar icons. --> diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 449d3e7beca1..c63f31997bcb 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -33,11 +33,16 @@ a light UI more visible. --> <drawable name="screen_background_light_transparent">#80ffffff</drawable> <color name="safe_mode_text">#80ffffff</color> + <!-- The color white, equivalent to 0xffffffff --> <color name="white">#ffffffff</color> + <!-- The color black, equivalent to 0xff000000 --> <color name="black">#ff000000</color> <color name="red">#ffff0000</color> + <!-- Fully transparent, equivalent to 0x00000000 --> <color name="transparent">#00000000</color> + <!-- Equivalent to 0xff000000 --> <color name="background_dark">#ff000000</color> + <!-- Equivalent to 0xffffffff --> <color name="background_light">#ffffffff</color> <color name="bright_foreground_dark">@android:color/background_light</color> <color name="bright_foreground_light">@android:color/background_dark</color> @@ -80,6 +85,7 @@ <!-- For settings framework --> <color name="lighter_gray">#ddd</color> + <!-- A dark gray, equivalent to 0xffaaaaaa --> <color name="darker_gray">#aaa</color> <!-- For security permissions --> diff --git a/core/res/res/values/colors_holo.xml b/core/res/res/values/colors_holo.xml index 917c781812ca..4297eeaafdc4 100644 --- a/core/res/res/values/colors_holo.xml +++ b/core/res/res/values/colors_holo.xml @@ -50,29 +50,29 @@ <!-- General purpose colors for Holo-themed elements --> <eat-comment /> - <!-- A light Holo shade of blue --> + <!-- A light Holo shade of blue. Equivalent to #ff33b5e5. --> <color name="holo_blue_light">#ff33b5e5</color> - <!-- A light Holo shade of gray --> + <!-- A light Holo shade of gray. Equivalent to #33999999. --> <color name="holo_gray_light">#33999999</color> - <!-- A light Holo shade of green --> + <!-- A light Holo shade of green. Equivalent to #ff99cc00. --> <color name="holo_green_light">#ff99cc00</color> - <!-- A light Holo shade of red --> + <!-- A light Holo shade of red. Equivalent to #ffff4444. <--> <color name="holo_red_light">#ffff4444</color> - <!-- A dark Holo shade of blue --> + <!-- A dark Holo shade of blue. Equivalent to #ff0099cc --> <color name="holo_blue_dark">#ff0099cc</color> - <!-- A dark Holo shade of green --> + <!-- A dark Holo shade of green. Equivalent to #ff669900 --> <color name="holo_green_dark">#ff669900</color> - <!-- A dark Holo shade of red --> + <!-- A dark Holo shade of red. Equivalent to #ffcc0000 --> <color name="holo_red_dark">#ffcc0000</color> - <!-- A Holo shade of purple --> + <!-- A Holo shade of purple. Equivalent to #ffaa66cc --> <color name="holo_purple">#ffaa66cc</color> - <!-- A light Holo shade of orange --> + <!-- A light Holo shade of orange. Equivalent to #ffffbb33. --> <color name="holo_orange_light">#ffffbb33</color> - <!-- A dark Holo shade of orange --> + <!-- A dark Holo shade of orange. Equivalent to ffff8800. --> <color name="holo_orange_dark">#ffff8800</color> - <!-- A really bright Holo shade of blue --> + <!-- A really bright Holo shade of blue. Equivalent to #ff00ddff. --> <color name="holo_blue_bright">#ff00ddff</color> - <!-- A really bright Holo shade of gray --> + <!-- A really bright Holo shade of gray. Equivalent to #33cccccc. --> <color name="holo_gray_bright">#33CCCCCC</color> <!-- Forward compatibility for Material-style theme colors --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 66d25df41e63..2a6b33108881 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -30,33 +30,32 @@ <item><xliff:g id="id">@string/status_bar_rotate</xliff:g></item> <item><xliff:g id="id">@string/status_bar_headset</xliff:g></item> <item><xliff:g id="id">@string/status_bar_data_saver</xliff:g></item> - <item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item> <item><xliff:g id="id">@string/status_bar_ime</xliff:g></item> <item><xliff:g id="id">@string/status_bar_sync_failing</xliff:g></item> <item><xliff:g id="id">@string/status_bar_sync_active</xliff:g></item> - <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item> - <item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item> - <item><xliff:g id="id">@string/status_bar_location</xliff:g></item> - <item><xliff:g id="id">@string/status_bar_bluetooth</xliff:g></item> <item><xliff:g id="id">@string/status_bar_nfc</xliff:g></item> <item><xliff:g id="id">@string/status_bar_tty</xliff:g></item> <item><xliff:g id="id">@string/status_bar_speakerphone</xliff:g></item> <item><xliff:g id="id">@string/status_bar_zen</xliff:g></item> <item><xliff:g id="id">@string/status_bar_mute</xliff:g></item> <item><xliff:g id="id">@string/status_bar_volume</xliff:g></item> + <item><xliff:g id="id">@string/status_bar_cdma_eri</xliff:g></item> + <item><xliff:g id="id">@string/status_bar_data_connection</xliff:g></item> + <item><xliff:g id="id">@string/status_bar_phone_evdo_signal</xliff:g></item> + <item><xliff:g id="id">@string/status_bar_phone_signal</xliff:g></item> + <item><xliff:g id="id">@string/status_bar_secure</xliff:g></item> + <item><xliff:g id="id">@string/status_bar_alarm_clock</xliff:g></item> + <item><xliff:g id="id">@string/status_bar_bluetooth</xliff:g></item> + <item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item> + <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item> <item><xliff:g id="id">@string/status_bar_vpn</xliff:g></item> + <item><xliff:g id="id">@string/status_bar_location</xliff:g></item> + <item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item> <item><xliff:g id="id">@string/status_bar_ethernet</xliff:g></item> <item><xliff:g id="id">@string/status_bar_wifi</xliff:g></item> <item><xliff:g id="id">@string/status_bar_mobile</xliff:g></item> <item><xliff:g id="id">@string/status_bar_airplane</xliff:g></item> - <item><xliff:g id="id">@string/status_bar_cdma_eri</xliff:g></item> - <item><xliff:g id="id">@string/status_bar_data_connection</xliff:g></item> - <item><xliff:g id="id">@string/status_bar_phone_evdo_signal</xliff:g></item> - <item><xliff:g id="id">@string/status_bar_phone_signal</xliff:g></item> <item><xliff:g id="id">@string/status_bar_battery</xliff:g></item> - <item><xliff:g id="id">@string/status_bar_alarm_clock</xliff:g></item> - <item><xliff:g id="id">@string/status_bar_secure</xliff:g></item> - <item><xliff:g id="id">@string/status_bar_clock</xliff:g></item> </string-array> <string translatable="false" name="status_bar_rotate">rotate</string> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 0246c804952c..42cc54f83f46 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -708,23 +708,38 @@ <public type="dimen" name="thumbnail_height" id="0x01050001" /> <public type="dimen" name="thumbnail_width" id="0x01050002" /> + <!-- Equivalent to 0xffaaaaaa --> <public type="color" name="darker_gray" id="0x01060000" /> + <!-- {@deprecated Use a text color from your theme instead.} --> <public type="color" name="primary_text_dark" id="0x01060001" /> + <!-- {@deprecated Use a text color from your theme instead.} --> <public type="color" name="primary_text_dark_nodisable" id="0x01060002" /> + <!-- {@deprecated Use a text color from your theme instead.} --> <public type="color" name="primary_text_light" id="0x01060003" /> + <!-- {@deprecated Use a text color from your theme instead.} --> <public type="color" name="primary_text_light_nodisable" id="0x01060004" /> + <!-- {@deprecated Use a text color from your theme instead.} --> <public type="color" name="secondary_text_dark" id="0x01060005" /> + <!-- {@deprecated Use a text color from your theme instead.} --> <public type="color" name="secondary_text_dark_nodisable" id="0x01060006" /> + <!-- {@deprecated Use a text color from your theme instead.} --> <public type="color" name="secondary_text_light" id="0x01060007" /> + <!-- {@deprecated Use a text color from your theme instead.} --> <public type="color" name="secondary_text_light_nodisable" id="0x01060008" /> + <!-- Equivalent to 0xff808080 --> <public type="color" name="tab_indicator_text" id="0x01060009" /> + <!-- Equivalent to 0xff000000 --> <public type="color" name="widget_edittext_dark" id="0x0106000a" /> <public type="color" name="white" id="0x0106000b" /> <public type="color" name="black" id="0x0106000c" /> <public type="color" name="transparent" id="0x0106000d" /> + <!-- Equivalent to 0xff000000 --> <public type="color" name="background_dark" id="0x0106000e" /> + <!-- Equivalent to 0xffffffff --> <public type="color" name="background_light" id="0x0106000f" /> + <!-- {@deprecated Use a text color from your theme instead.} --> <public type="color" name="tertiary_text_dark" id="0x01060010" /> + <!-- {@deprecated Use a text color from your theme instead.} --> <public type="color" name="tertiary_text_light" id="0x01060011" /> <public type="array" name="emailAddressTypes" id="0x01070000" /> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 354880cad6d2..cbd7b4ddb2bc 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1670,7 +1670,6 @@ <java-symbol type="dimen" name="navigation_bar_height_landscape_car_mode" /> <java-symbol type="dimen" name="navigation_bar_width_car_mode" /> <java-symbol type="dimen" name="status_bar_height" /> - <java-symbol type="dimen" name="quick_qs_offset_height" /> <java-symbol type="dimen" name="quick_qs_total_height" /> <java-symbol type="drawable" name="ic_jog_dial_sound_off" /> <java-symbol type="drawable" name="ic_jog_dial_sound_on" /> @@ -2733,6 +2732,10 @@ <java-symbol type="string" name="status_bar_alarm_clock" /> <java-symbol type="string" name="status_bar_secure" /> <java-symbol type="string" name="status_bar_clock" /> + <java-symbol type="string" name="status_bar_airplane" /> + <java-symbol type="string" name="status_bar_mobile" /> + <java-symbol type="string" name="status_bar_ethernet" /> + <java-symbol type="string" name="status_bar_vpn" /> <!-- Locale picker --> <java-symbol type="id" name="locale_search_menu" /> diff --git a/core/tests/coretests/src/android/os/MemoryFileTest.java b/core/tests/coretests/src/android/os/MemoryFileTest.java index 82af662e207e..20b298d639d2 100644 --- a/core/tests/coretests/src/android/os/MemoryFileTest.java +++ b/core/tests/coretests/src/android/os/MemoryFileTest.java @@ -23,6 +23,7 @@ import android.test.suitebuilder.annotation.SmallTest; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.BufferOverflowException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -110,7 +111,7 @@ public class MemoryFileTest extends AndroidTestCase { try { os.write(new byte[] { -1, -1 }); fail(); - } catch (IndexOutOfBoundsException expected) { + } catch (IndexOutOfBoundsException | BufferOverflowException expected) { } byte[] copy = new byte[file.length()]; diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 2a3fcadb5144..dfc99f6fcb3a 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -267,6 +267,7 @@ public class SettingsBackupTest { Settings.Global.LOW_POWER_MODE, Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL_MAX, Settings.Global.LOW_POWER_MODE_STICKY, + Settings.Global.LOW_POWER_MODE_SUGGESTION_PARAMS, Settings.Global.LTE_SERVICE_FORCED, Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE, Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY, @@ -587,7 +588,8 @@ public class SettingsBackupTest { Settings.Secure.BLUETOOTH_ON_WHILE_DRIVING, Settings.Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED, - Settings.Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION); + Settings.Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, + Settings.Secure.PACKAGES_TO_CLEAR_DATA_BEFORE_FULL_RESTORE); @Test public void systemSettingsBackedUpOrBlacklisted() { diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java index afc4bd5aa783..5d58f550b3f5 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java @@ -37,9 +37,10 @@ import android.view.View; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.Calendar; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Locale; -import java.util.TimeZone; @SmallTest @RunWith(AndroidJUnit4.class) @@ -163,8 +164,9 @@ public class TextClassificationTest { @Test public void testParcelOptions() { - Calendar referenceTime = Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.US); - referenceTime.setTimeInMillis(946771200000L); // 2000-01-02 + ZonedDateTime referenceTime = ZonedDateTime.ofInstant( + Instant.ofEpochMilli(946771200000L), // 2000-01-02 + ZoneId.of("UTC")); TextClassification.Options reference = new TextClassification.Options(); reference.setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY)); diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index 5ca0ad63159f..00dc22ea162b 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -21,10 +21,12 @@ import static android.system.OsConstants.SEEK_SET; import static java.lang.annotation.RetentionPolicy.SOURCE; +import android.annotation.AnyThread; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; +import android.annotation.WorkerThread; import android.content.ContentResolver; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; @@ -65,6 +67,16 @@ public final class ImageDecoder implements AutoCloseable { /** * Source of the encoded image data. + * + * <p>This object references the data that will be used to decode a + * Drawable or Bitmap in {@link #decodeDrawable} or {@link #decodeBitmap}. + * Constructing a {@code Source} (with one of the overloads of + * {@code createSource}) can be done on any thread because the construction + * simply captures values. The real work is done in decodeDrawable or + * decodeBitmap.</p> + * + * <p>Further, a Source object can be reused with different settings, or + * even used simultaneously in multiple threads.</p> */ public static abstract class Source { private Source() {} @@ -120,7 +132,8 @@ public final class ImageDecoder implements AutoCloseable { int length = mBuffer.limit() - mBuffer.position(); return nCreate(mBuffer.array(), offset, length, this); } - return nCreate(mBuffer, mBuffer.position(), mBuffer.limit(), this); + ByteBuffer buffer = mBuffer.slice(); + return nCreate(buffer, buffer.position(), buffer.limit(), this); } } @@ -232,6 +245,8 @@ public final class ImageDecoder implements AutoCloseable { /** * For backwards compatibility, this does *not* close the InputStream. + * + * Further, unlike other Sources, this one is not reusable. */ private static class InputStreamSource extends Source { InputStreamSource(Resources res, InputStream is, int inputDensity) { @@ -322,12 +337,17 @@ public final class ImageDecoder implements AutoCloseable { final Resources mResources; final int mResId; int mResDensity; + private Object mLock = new Object(); @Override public Resources getResources() { return mResources; } @Override - public int getDensity() { return mResDensity; } + public int getDensity() { + synchronized (mLock) { + return mResDensity; + } + } @Override public ImageDecoder createImageDecoder() throws IOException { @@ -336,10 +356,12 @@ public final class ImageDecoder implements AutoCloseable { // keep it alive. InputStream is = mResources.openRawResource(mResId, value); - if (value.density == TypedValue.DENSITY_DEFAULT) { - mResDensity = DisplayMetrics.DENSITY_DEFAULT; - } else if (value.density != TypedValue.DENSITY_NONE) { - mResDensity = value.density; + synchronized (mLock) { + if (value.density == TypedValue.DENSITY_DEFAULT) { + mResDensity = DisplayMetrics.DENSITY_DEFAULT; + } else if (value.density != TypedValue.DENSITY_NONE) { + mResDensity = value.density; + } } return createFromAsset((AssetInputStream) is, this); @@ -432,6 +454,18 @@ public final class ImageDecoder implements AutoCloseable { public boolean isAnimated() { return mDecoder.mAnimated; } + + /** + * If known, the color space the decoded bitmap will have. Note that the + * output color space is not guaranteed to be the color space the bitmap + * is encoded with. If not known (when the config is + * {@link Bitmap.Config#ALPHA_8} for instance), or there is an error, + * it is set to null. + */ + @Nullable + public ColorSpace getColorSpace() { + return mDecoder.getColorSpace(); + } }; /** @removed @@ -443,6 +477,9 @@ public final class ImageDecoder implements AutoCloseable { /** * Optional listener supplied to {@link #decodeDrawable} or * {@link #decodeBitmap}. + * + * <p>This is necessary in order to change the default settings of the + * decode.</p> */ public static interface OnHeaderDecodedListener { /** @@ -534,6 +571,9 @@ public final class ImageDecoder implements AutoCloseable { /** * Retrieve the {@link Source} that was interrupted. + * + * <p>This can be used for equality checking to find the Source which + * failed to completely decode.</p> */ @NonNull public Source getSource() { @@ -582,16 +622,17 @@ public final class ImageDecoder implements AutoCloseable { private final int mHeight; private final boolean mAnimated; - private int mDesiredWidth; - private int mDesiredHeight; - private int mAllocator = ALLOCATOR_DEFAULT; - private boolean mRequireUnpremultiplied = false; - private boolean mMutable = false; - private boolean mConserveMemory = false; - private boolean mDecodeAsAlphaMask = false; - private Rect mCropRect; - private Rect mOutPaddingRect; - private Source mSource; + private int mDesiredWidth; + private int mDesiredHeight; + private int mAllocator = ALLOCATOR_DEFAULT; + private boolean mUnpremultipliedRequired = false; + private boolean mMutable = false; + private boolean mConserveMemory = false; + private boolean mDecodeAsAlphaMask = false; + private ColorSpace mDesiredColorSpace = null; + private Rect mCropRect; + private Rect mOutPaddingRect; + private Source mSource; private PostProcessor mPostProcessor; private OnPartialImageListener mOnPartialImageListener; @@ -645,6 +686,7 @@ public final class ImageDecoder implements AutoCloseable { * @return a new Source object, which can be passed to * {@link #decodeDrawable} or {@link #decodeBitmap}. */ + @AnyThread @NonNull public static Source createSource(@NonNull Resources res, int resId) { @@ -659,6 +701,7 @@ public final class ImageDecoder implements AutoCloseable { * @return a new Source object, which can be passed to * {@link #decodeDrawable} or {@link #decodeBitmap}. */ + @AnyThread @NonNull public static Source createSource(@NonNull ContentResolver cr, @NonNull Uri uri) { @@ -670,6 +713,7 @@ public final class ImageDecoder implements AutoCloseable { * * @hide */ + @AnyThread @NonNull public static Source createSource(@NonNull ContentResolver cr, @NonNull Uri uri, @Nullable Resources res) { @@ -679,6 +723,7 @@ public final class ImageDecoder implements AutoCloseable { /** * Create a new {@link Source} from a file in the "assets" directory. */ + @AnyThread @NonNull public static Source createSource(@NonNull AssetManager assets, @NonNull String fileName) { return new AssetSource(assets, fileName); @@ -696,6 +741,7 @@ public final class ImageDecoder implements AutoCloseable { * not within data. * @hide */ + @AnyThread @NonNull public static Source createSource(@NonNull byte[] data, int offset, int length) throws ArrayIndexOutOfBoundsException { @@ -714,6 +760,7 @@ public final class ImageDecoder implements AutoCloseable { * See {@link #createSource(byte[], int, int). * @hide */ + @AnyThread @NonNull public static Source createSource(@NonNull byte[] data) { return createSource(data, 0, data.length); @@ -731,24 +778,35 @@ public final class ImageDecoder implements AutoCloseable { * be modified, even after the {@code AnimatedImageDrawable} is returned. * {@code buffer}'s contents should never be modified during decode.</p> */ + @AnyThread @NonNull public static Source createSource(@NonNull ByteBuffer buffer) { - return new ByteBufferSource(buffer.slice()); + return new ByteBufferSource(buffer); } /** * Internal API used to generate bitmaps for use by Drawables (i.e. BitmapDrawable) + * + * <p>Unlike other Sources, this one cannot be reused.</p> + * * @hide */ + @AnyThread + @NonNull public static Source createSource(Resources res, InputStream is) { return new InputStreamSource(res, is, Bitmap.getDefaultDensity()); } /** * Internal API used to generate bitmaps for use by Drawables (i.e. BitmapDrawable) + * + * <p>Unlike other Sources, this one cannot be reused.</p> + * * @hide */ + @AnyThread @TestApi + @NonNull public static Source createSource(Resources res, InputStream is, int density) { return new InputStreamSource(res, is, density); } @@ -756,6 +814,7 @@ public final class ImageDecoder implements AutoCloseable { /** * Create a new {@link Source} from a {@link java.io.File}. */ + @AnyThread @NonNull public static Source createSource(@NonNull File file) { return new FileSource(file); @@ -766,14 +825,16 @@ public final class ImageDecoder implements AutoCloseable { * * <p>This takes an input that functions like * {@link BitmapFactory.Options#inSampleSize}. It returns a width and - * height that can be acheived by sampling the encoded image. Other widths + * height that can be achieved by sampling the encoded image. Other widths * and heights may be supported, but will require an additional (internal) * scaling step. Such internal scaling is *not* supported with - * {@link #setRequireUnpremultiplied} set to {@code true}.</p> + * {@link #setUnpremultipliedRequired} set to {@code true}.</p> * * @param sampleSize Sampling rate of the encoded image. * @return {@link android.util.Size} of the width and height after * sampling. + * + * @hide */ @NonNull public Size getSampledSize(int sampleSize) { @@ -789,14 +850,32 @@ public final class ImageDecoder implements AutoCloseable { } // Modifiers + /** @removed + * @deprecated Renamed to {@link #setTargetSize}. + */ + @java.lang.Deprecated + public ImageDecoder setResize(int width, int height) { + this.setTargetSize(width, height); + return this; + } + /** - * Resize the output to have the following size. + * Specify the size of the output {@link Drawable} or {@link Bitmap}. + * + * <p>By default, the output size will match the size of the encoded + * image, which can be retrieved from the {@link ImageInfo} in + * {@link OnHeaderDecodedListener#onHeaderDecoded}.</p> + * + * <p>Only the last call to this or {@link #setTargetSampleSize} is + * respected.</p> + * + * <p>Like all setters on ImageDecoder, this must be called inside + * {@link OnHeaderDecodedListener#onHeaderDecoded}.</p> * * @param width must be greater than 0. * @param height must be greater than 0. - * @return this object for chaining. */ - public ImageDecoder setResize(int width, int height) { + public void setTargetSize(int width, int height) { if (width <= 0 || height <= 0) { throw new IllegalArgumentException("Dimensions must be positive! " + "provided (" + width + ", " + height + ")"); @@ -804,21 +883,70 @@ public final class ImageDecoder implements AutoCloseable { mDesiredWidth = width; mDesiredHeight = height; + } + + /** @removed + * @deprecated Renamed to {@link #setTargetSampleSize}. + */ + @java.lang.Deprecated + public ImageDecoder setResize(int sampleSize) { + this.setTargetSampleSize(sampleSize); return this; } + private int getTargetDimension(int original, int sampleSize, int computed) { + // Sampling will never result in a smaller size than 1. + if (sampleSize >= original) { + return 1; + } + + // Use integer divide to find the desired size. If that is what + // getSampledSize computed, that is the size to use. + int target = original / sampleSize; + if (computed == target) { + return computed; + } + + // If sampleSize does not divide evenly into original, the decoder + // may round in either direction. It just needs to get a result that + // is close. + int reverse = computed * sampleSize; + if (Math.abs(reverse - original) < sampleSize) { + // This is the size that can be decoded most efficiently. + return computed; + } + + // The decoder could not get close (e.g. it is a DNG image). + return target; + } + /** - * Resize based on a sample size. + * Set the target size with a sampleSize. * - * <p>This has the same effect as passing the result of - * {@link #getSampledSize} to {@link #setResize(int, int)}.</p> + * <p>By default, the output size will match the size of the encoded + * image, which can be retrieved from the {@link ImageInfo} in + * {@link OnHeaderDecodedListener#onHeaderDecoded}.</p> + * + * <p>Requests the decoder to subsample the original image, returning a + * smaller image to save memory. The sample size is the number of pixels + * in either dimension that correspond to a single pixel in the output. + * For example, sampleSize == 4 returns an image that is 1/4 the + * width/height of the original, and 1/16 the number of pixels.</p> + * + * <p>Must be greater than or equal to 1.</p> + * + * <p>Only the last call to this or {@link #setTargetSize} is respected.</p> + * + * <p>Like all setters on ImageDecoder, this must be called inside + * {@link OnHeaderDecodedListener#onHeaderDecoded}.</p> * * @param sampleSize Sampling rate of the encoded image. - * @return this object for chaining. */ - public ImageDecoder setResize(int sampleSize) { + public void setTargetSampleSize(int sampleSize) { Size size = this.getSampledSize(sampleSize); - return this.setResize(size.getWidth(), size.getHeight()); + int targetWidth = getTargetDimension(mWidth, sampleSize, size.getWidth()); + int targetHeight = getTargetDimension(mHeight, sampleSize, size.getHeight()); + this.setTargetSize(targetWidth, targetHeight); } private boolean requestedResize() { @@ -832,7 +960,7 @@ public final class ImageDecoder implements AutoCloseable { * Will typically result in a {@link Bitmap.Config#HARDWARE} * allocation, but may be software for small images. In addition, this will * switch to software when HARDWARE is incompatible, e.g. - * {@link #setMutable}, {@link #setDecodeAsAlphaMask}. + * {@link #setMutableRequired}, {@link #setDecodeAsAlphaMaskEnabled}. */ public static final int ALLOCATOR_DEFAULT = 0; @@ -855,8 +983,8 @@ public final class ImageDecoder implements AutoCloseable { * Require a {@link Bitmap.Config#HARDWARE} {@link Bitmap}. * * When this is combined with incompatible options, like - * {@link #setMutable} or {@link #setDecodeAsAlphaMask}, {@link #decodeDrawable} - * / {@link #decodeBitmap} will throw an + * {@link #setMutableRequired} or {@link #setDecodeAsAlphaMaskEnabled}, + * {@link #decodeDrawable} / {@link #decodeBitmap} will throw an * {@link java.lang.IllegalStateException}. */ public static final int ALLOCATOR_HARDWARE = 3; @@ -871,17 +999,18 @@ public final class ImageDecoder implements AutoCloseable { /** * Choose the backing for the pixel memory. * - * This is ignored for animated drawables. + * <p>This is ignored for animated drawables.</p> + * + * <p>Like all setters on ImageDecoder, this must be called inside + * {@link OnHeaderDecodedListener#onHeaderDecoded}.</p> * * @param allocator Type of allocator to use. - * @return this object for chaining. */ - public ImageDecoder setAllocator(@Allocator int allocator) { + public void setAllocator(@Allocator int allocator) { if (allocator < ALLOCATOR_DEFAULT || allocator > ALLOCATOR_HARDWARE) { throw new IllegalArgumentException("invalid allocator " + allocator); } mAllocator = allocator; - return this; } /** @@ -905,18 +1034,35 @@ public final class ImageDecoder implements AutoCloseable { * {@link Drawable} will throw an {@link java.lang.IllegalStateException}. * </p> * - * @return this object for chaining. + * <p>Like all setters on ImageDecoder, this must be called inside + * {@link OnHeaderDecodedListener#onHeaderDecoded}.</p> + */ + public void setUnpremultipliedRequired(boolean unpremultipliedRequired) { + mUnpremultipliedRequired = unpremultipliedRequired; + } + + /** @removed + * @deprecated Renamed to {@link #setUnpremultipliedRequired}. */ - public ImageDecoder setRequireUnpremultiplied(boolean requireUnpremultiplied) { - mRequireUnpremultiplied = requireUnpremultiplied; + @java.lang.Deprecated + public ImageDecoder setRequireUnpremultiplied(boolean unpremultipliedRequired) { + this.setUnpremultipliedRequired(unpremultipliedRequired); return this; } /** * Return whether the {@link Bitmap} will have unpremultiplied pixels. */ + public boolean isUnpremultipliedRequired() { + return mUnpremultipliedRequired; + } + + /** @removed + * @deprecated Renamed to {@link #isUnpremultipliedRequired}. + */ + @java.lang.Deprecated public boolean getRequireUnpremultiplied() { - return mRequireUnpremultiplied; + return this.isUnpremultipliedRequired(); } /** @@ -932,11 +1078,12 @@ public final class ImageDecoder implements AutoCloseable { * {@link Canvas} will be recorded immediately and then applied to each * frame.</p> * - * @return this object for chaining. + * <p>Like all setters on ImageDecoder, this must be called inside + * {@link OnHeaderDecodedListener#onHeaderDecoded}.</p> + * */ - public ImageDecoder setPostProcessor(@Nullable PostProcessor p) { + public void setPostProcessor(@Nullable PostProcessor p) { mPostProcessor = p; - return this; } /** @@ -953,11 +1100,12 @@ public final class ImageDecoder implements AutoCloseable { * <p>Will be called if there is an error in the input. Without one, an * error will result in an Exception being thrown.</p> * - * @return this object for chaining. + * <p>Like all setters on ImageDecoder, this must be called inside + * {@link OnHeaderDecodedListener#onHeaderDecoded}.</p> + * */ - public ImageDecoder setOnPartialImageListener(@Nullable OnPartialImageListener l) { + public void setOnPartialImageListener(@Nullable OnPartialImageListener l) { mOnPartialImageListener = l; - return this; } /** @@ -972,19 +1120,20 @@ public final class ImageDecoder implements AutoCloseable { * Crop the output to {@code subset} of the (possibly) scaled image. * * <p>{@code subset} must be contained within the size set by - * {@link #setResize} or the bounds of the image if setResize was not - * called. Otherwise an {@link IllegalStateException} will be thrown by + * {@link #setTargetSize} or the bounds of the image if setTargetSize was + * not called. Otherwise an {@link IllegalStateException} will be thrown by * {@link #decodeDrawable}/{@link #decodeBitmap}.</p> * * <p>NOT intended as a replacement for * {@link BitmapRegionDecoder#decodeRegion}. This supports all formats, * but merely crops the output.</p> * - * @return this object for chaining. + * <p>Like all setters on ImageDecoder, this must be called inside + * {@link OnHeaderDecodedListener#onHeaderDecoded}.</p> + * */ - public ImageDecoder setCrop(@Nullable Rect subset) { + public void setCrop(@Nullable Rect subset) { mCropRect = subset; - return this; } /** @@ -1001,13 +1150,13 @@ public final class ImageDecoder implements AutoCloseable { * If the image is a nine patch, this Rect will be set to the padding * rectangle during decode. Otherwise it will not be modified. * - * @return this object for chaining. + * <p>Like all setters on ImageDecoder, this must be called inside + * {@link OnHeaderDecodedListener#onHeaderDecoded}.</p> * * @hide */ - public ImageDecoder setOutPaddingRect(@NonNull Rect outPadding) { + public void setOutPaddingRect(@NonNull Rect outPadding) { mOutPaddingRect = outPadding; - return this; } /** @@ -1026,20 +1175,37 @@ public final class ImageDecoder implements AutoCloseable { * order to modify. Attempting to decode a mutable {@link Drawable} will * throw an {@link java.lang.IllegalStateException}.</p> * - * @return this object for chaining. + * <p>Like all setters on ImageDecoder, this must be called inside + * {@link OnHeaderDecodedListener#onHeaderDecoded}.</p> */ - public ImageDecoder setMutable(boolean mutable) { + public void setMutableRequired(boolean mutable) { mMutable = mutable; + } + + /** @removed + * @deprecated Renamed to {@link #setMutableRequired}. + */ + @java.lang.Deprecated + public ImageDecoder setMutable(boolean mutable) { + this.setMutableRequired(mutable); return this; } /** * Return whether the {@link Bitmap} will be mutable. */ - public boolean getMutable() { + public boolean isMutableRequired() { return mMutable; } + /** @removed + * @deprecated Renamed to {@link #isMutableRequired}. + */ + @java.lang.Deprecated + public boolean getMutable() { + return this.isMutableRequired(); + } + /** * Specify whether to potentially save RAM at the expense of quality. * @@ -1052,11 +1218,11 @@ public final class ImageDecoder implements AutoCloseable { * This necessarily lowers the quality of the output, but saves half * the memory used.</p> * - * @return this object for chaining. + * <p>Like all setters on ImageDecoder, this must be called inside + * {@link OnHeaderDecodedListener#onHeaderDecoded}.</p> */ - public ImageDecoder setConserveMemory(boolean conserveMemory) { + public void setConserveMemory(boolean conserveMemory) { mConserveMemory = conserveMemory; - return this; } /** @@ -1077,45 +1243,97 @@ public final class ImageDecoder implements AutoCloseable { * with only one channel, treat that channel as alpha. Otherwise this call has * no effect.</p> * - * <p>setDecodeAsAlphaMask is incompatible with {@link #ALLOCATOR_HARDWARE}. Trying to + * <p>This is incompatible with {@link #ALLOCATOR_HARDWARE}. Trying to * combine them will result in {@link #decodeDrawable}/ * {@link #decodeBitmap} throwing an * {@link java.lang.IllegalStateException}.</p> * - * @return this object for chaining. + * <p>Like all setters on ImageDecoder, this must be called inside + * {@link OnHeaderDecodedListener#onHeaderDecoded}.</p> + */ + public void setDecodeAsAlphaMaskEnabled(boolean enabled) { + mDecodeAsAlphaMask = enabled; + } + + /** @removed + * @deprecated Renamed to {@link #setDecodeAsAlphaMaskEnabled}. */ - public ImageDecoder setDecodeAsAlphaMask(boolean decodeAsAlphaMask) { - mDecodeAsAlphaMask = decodeAsAlphaMask; + @java.lang.Deprecated + public ImageDecoder setDecodeAsAlphaMask(boolean enabled) { + this.setDecodeAsAlphaMaskEnabled(enabled); return this; } /** @removed - * @deprecated Call {@link #setDecodeAsAlphaMask} instead. + * @deprecated Renamed to {@link #setDecodeAsAlphaMaskEnabled}. */ @java.lang.Deprecated public ImageDecoder setAsAlphaMask(boolean asAlphaMask) { - return this.setDecodeAsAlphaMask(asAlphaMask); + this.setDecodeAsAlphaMask(asAlphaMask); + return this; } /** * Return whether to treat single channel input as alpha. * - * <p>This returns whether {@link #setDecodeAsAlphaMask} was set to {@code true}. - * It may still return {@code true} even if the image has more than one - * channel and therefore will not be treated as an alpha mask.</p> + * <p>This returns whether {@link #setDecodeAsAlphaMaskEnabled} was set to + * {@code true}. It may still return {@code true} even if the image has + * more than one channel and therefore will not be treated as an alpha + * mask.</p> */ + public boolean isDecodeAsAlphaMaskEnabled() { + return mDecodeAsAlphaMask; + } + + /** @removed + * @deprecated Renamed to {@link #isDecodeAsAlphaMaskEnabled}. + */ + @java.lang.Deprecated public boolean getDecodeAsAlphaMask() { return mDecodeAsAlphaMask; } /** @removed - * @deprecated Call {@link #getDecodeAsAlphaMask} instead. + * @deprecated Renamed to {@link #isDecodeAsAlphaMaskEnabled}. */ @java.lang.Deprecated public boolean getAsAlphaMask() { return this.getDecodeAsAlphaMask(); } + /** + * Specify the desired {@link ColorSpace} for the output. + * + * <p>If non-null, the decoder will try to decode into this + * color space. If it is null, which is the default, or the request cannot + * be met, the decoder will pick either the color space embedded in the + * image or the color space best suited for the requested image + * configuration (for instance {@link ColorSpace.Named#SRGB sRGB} for + * the {@link Bitmap.Config#ARGB_8888} configuration).</p> + * + * <p>{@link Bitmap.Config#RGBA_F16} always uses the + * {@link ColorSpace.Named#LINEAR_EXTENDED_SRGB scRGB} color space). + * Bitmaps in other configurations without an embedded color space are + * assumed to be in the {@link ColorSpace.Named#SRGB sRGB} color space.</p> + * + * <p class="note">Only {@link ColorSpace.Model#RGB} color spaces are + * currently supported. An <code>IllegalArgumentException</code> will + * be thrown by the decode methods when setting a non-RGB color space + * such as {@link ColorSpace.Named#CIE_LAB Lab}.</p> + * + * <p class="note">The specified color space's transfer function must be + * an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}. An + * <code>IllegalArgumentException</code> will be thrown by the decode methods + * if calling {@link ColorSpace.Rgb#getTransferParameters()} on the + * specified color space returns null.</p> + * + * <p>Like all setters on ImageDecoder, this must be called inside + * {@link OnHeaderDecodedListener#onHeaderDecoded}.</p> + */ + public void setTargetColorSpace(ColorSpace colorSpace) { + mDesiredColorSpace = colorSpace; + } + @Override public void close() { mCloseGuard.close(); @@ -1151,9 +1369,20 @@ public final class ImageDecoder implements AutoCloseable { } } - if (mPostProcessor != null && mRequireUnpremultiplied) { + if (mPostProcessor != null && mUnpremultipliedRequired) { throw new IllegalStateException("Cannot draw to unpremultiplied pixels!"); } + + if (mDesiredColorSpace != null) { + if (!(mDesiredColorSpace instanceof ColorSpace.Rgb)) { + throw new IllegalArgumentException("The target color space must use the " + + "RGB color model - provided: " + mDesiredColorSpace); + } + if (((ColorSpace.Rgb) mDesiredColorSpace).getTransferParameters() == null) { + throw new IllegalArgumentException("The target color space must use an " + + "ICC parametric transfer function - provided: " + mDesiredColorSpace); + } + } } private static void checkSubset(int width, int height, Rect r) { @@ -1166,13 +1395,14 @@ public final class ImageDecoder implements AutoCloseable { } } + @WorkerThread @NonNull private Bitmap decodeBitmapInternal() throws IOException { checkState(); return nDecodeBitmap(mNativePtr, this, mPostProcessor != null, mDesiredWidth, mDesiredHeight, mCropRect, - mMutable, mAllocator, mRequireUnpremultiplied, - mConserveMemory, mDecodeAsAlphaMask); + mMutable, mAllocator, mUnpremultipliedRequired, + mConserveMemory, mDecodeAsAlphaMask, mDesiredColorSpace); } private void callHeaderDecoded(@Nullable OnHeaderDecodedListener listener, @@ -1194,10 +1424,12 @@ public final class ImageDecoder implements AutoCloseable { * @param listener for learning the {@link ImageInfo} and changing any * default settings on the {@code ImageDecoder}. This will be called on * the same thread as {@code decodeDrawable} before that method returns. + * This is required in order to change any of the default settings. * @return Drawable for displaying the image. * @throws IOException if {@code src} is not found, is an unsupported * format, or cannot be decoded for any reason. */ + @WorkerThread @NonNull public static Drawable decodeDrawable(@NonNull Source src, @NonNull OnHeaderDecodedListener listener) throws IOException { @@ -1208,6 +1440,7 @@ public final class ImageDecoder implements AutoCloseable { return decodeDrawableImpl(src, listener); } + @WorkerThread @NonNull private static Drawable decodeDrawableImpl(@NonNull Source src, @Nullable OnHeaderDecodedListener listener) throws IOException { @@ -1215,7 +1448,7 @@ public final class ImageDecoder implements AutoCloseable { decoder.mSource = src; decoder.callHeaderDecoded(listener, src); - if (decoder.mRequireUnpremultiplied) { + if (decoder.mUnpremultipliedRequired) { // Though this could be supported (ignored) for opaque images, // it seems better to always report this error. throw new IllegalStateException("Cannot decode a Drawable " + @@ -1268,8 +1501,18 @@ public final class ImageDecoder implements AutoCloseable { } /** - * See {@link #decodeDrawable(Source, OnHeaderDecodedListener)}. + * Create a {@link Drawable} from a {@code Source}. + * + * <p>Since there is no {@link OnHeaderDecodedListener}, the default + * settings will be used. In order to change any settings, call + * {@link #decodeDrawable(Source, OnHeaderDecodedListener)} instead.</p> + * + * @param src representing the encoded image. + * @return Drawable for displaying the image. + * @throws IOException if {@code src} is not found, is an unsupported + * format, or cannot be decoded for any reason. */ + @WorkerThread @NonNull public static Drawable decodeDrawable(@NonNull Source src) throws IOException { @@ -1283,10 +1526,12 @@ public final class ImageDecoder implements AutoCloseable { * @param listener for learning the {@link ImageInfo} and changing any * default settings on the {@code ImageDecoder}. This will be called on * the same thread as {@code decodeBitmap} before that method returns. + * This is required in order to change any of the default settings. * @return Bitmap containing the image. * @throws IOException if {@code src} is not found, is an unsupported * format, or cannot be decoded for any reason. */ + @WorkerThread @NonNull public static Bitmap decodeBitmap(@NonNull Source src, @NonNull OnHeaderDecodedListener listener) throws IOException { @@ -1297,6 +1542,7 @@ public final class ImageDecoder implements AutoCloseable { return decodeBitmapImpl(src, listener); } + @WorkerThread @NonNull private static Bitmap decodeBitmapImpl(@NonNull Source src, @Nullable OnHeaderDecodedListener listener) throws IOException { @@ -1353,7 +1599,7 @@ public final class ImageDecoder implements AutoCloseable { float scale = (float) dstDensity / srcDensity; int scaledWidth = (int) (decoder.mWidth * scale + 0.5f); int scaledHeight = (int) (decoder.mHeight * scale + 0.5f); - decoder.setResize(scaledWidth, scaledHeight); + decoder.setTargetSize(scaledWidth, scaledHeight); return dstDensity; } @@ -1362,9 +1608,24 @@ public final class ImageDecoder implements AutoCloseable { return nGetMimeType(mNativePtr); } + @Nullable + private ColorSpace getColorSpace() { + return nGetColorSpace(mNativePtr); + } + /** - * See {@link #decodeBitmap(Source, OnHeaderDecodedListener)}. + * Create a {@link Bitmap} from a {@code Source}. + * + * <p>Since there is no {@link OnHeaderDecodedListener}, the default + * settings will be used. In order to change any settings, call + * {@link #decodeBitmap(Source, OnHeaderDecodedListener)} instead.</p> + * + * @param src representing the encoded image. + * @return Bitmap containing the image. + * @throws IOException if {@code src} is not found, is an unsupported + * format, or cannot be decoded for any reason. */ + @WorkerThread @NonNull public static Bitmap decodeBitmap(@NonNull Source src) throws IOException { return decodeBitmapImpl(src, null); @@ -1410,12 +1671,14 @@ public final class ImageDecoder implements AutoCloseable { boolean doPostProcess, int width, int height, @Nullable Rect cropRect, boolean mutable, - int allocator, boolean requireUnpremul, - boolean conserveMemory, boolean decodeAsAlphaMask) + int allocator, boolean unpremulRequired, + boolean conserveMemory, boolean decodeAsAlphaMask, + @Nullable ColorSpace desiredColorSpace) throws IOException; private static native Size nGetSampledSize(long nativePtr, int sampleSize); private static native void nGetPadding(long nativePtr, @NonNull Rect outRect); private static native void nClose(long nativePtr); private static native String nGetMimeType(long nativePtr); + private static native ColorSpace nGetColorSpace(long nativePtr); } diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java index 098cdc67555d..cd0862cd13fe 100644 --- a/graphics/java/android/graphics/Path.java +++ b/graphics/java/android/graphics/Path.java @@ -24,6 +24,8 @@ import android.annotation.Size; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; +import libcore.util.NativeAllocationRegistry; + /** * The Path class encapsulates compound (multiple contour) geometric paths * consisting of straight line segments, quadratic curves, and cubic curves. @@ -32,10 +34,14 @@ import dalvik.annotation.optimization.FastNative; * text on a path. */ public class Path { + + private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( + Path.class.getClassLoader(), nGetFinalizer(), 48 /* dummy size */); + /** * @hide */ - public long mNativePath; + public final long mNativePath; /** * @hide @@ -52,6 +58,7 @@ public class Path { */ public Path() { mNativePath = nInit(); + sRegistry.registerNativeAllocation(this, mNativePath); } /** @@ -69,6 +76,7 @@ public class Path { } } mNativePath = nInit(valNative); + sRegistry.registerNativeAllocation(this, mNativePath); } /** @@ -297,7 +305,7 @@ public class Path { * a rectangle * @return true if the path specifies a rectangle */ - public boolean isRect(RectF rect) { + public boolean isRect(@Nullable RectF rect) { return nIsRect(mNativePath, rect); } @@ -771,15 +779,6 @@ public class Path { nTransform(mNativePath, matrix.native_instance); } - protected void finalize() throws Throwable { - try { - nFinalize(mNativePath); - mNativePath = 0; // Other finalizers can still call us. - } finally { - super.finalize(); - } - } - /** @hide */ public final long readOnlyNI() { return mNativePath; @@ -820,7 +819,7 @@ public class Path { private static native long nInit(); private static native long nInit(long nPath); - private static native void nFinalize(long nPath); + private static native long nGetFinalizer(); private static native void nSet(long native_dst, long nSrc); private static native void nComputeBounds(long nPath, RectF bounds); private static native void nIncReserve(long nPath, int extraPtCount); diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl index 5a8fa0700328..0d32075d20d2 100644 --- a/keystore/java/android/security/IKeyChainService.aidl +++ b/keystore/java/android/security/IKeyChainService.aidl @@ -33,8 +33,8 @@ interface IKeyChainService { boolean isUserSelectable(String alias); void setUserSelectable(String alias, boolean isUserSelectable); - boolean generateKeyPair(in String algorithm, in ParcelableKeyGenParameterSpec spec); - boolean attestKey(in String alias, in byte[] challenge, in int[] idAttestationFlags, + int generateKeyPair(in String algorithm, in ParcelableKeyGenParameterSpec spec); + int attestKey(in String alias, in byte[] challenge, in int[] idAttestationFlags, out KeymasterCertificateChain chain); boolean setKeyPairCertificate(String alias, in byte[] userCert, in byte[] certChain); diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index 2daf733d057f..46a7fa8d5e28 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -246,6 +246,82 @@ public final class KeyChain { public static final String EXTRA_KEY_ACCESSIBLE = "android.security.extra.KEY_ACCESSIBLE"; /** + * Indicates that a call to {@link #generateKeyPair} was successful. + * @hide + */ + public static final int KEY_GEN_SUCCESS = 0; + + /** + * An alias was missing from the key specifications when calling {@link #generateKeyPair}. + * @hide + */ + public static final int KEY_GEN_MISSING_ALIAS = 1; + + /** + * A key attestation challenge was provided to {@link #generateKeyPair}, but it shouldn't + * have been provided. + * @hide + */ + public static final int KEY_GEN_SUPERFLUOUS_ATTESTATION_CHALLENGE = 2; + + /** + * Algorithm not supported by {@link #generateKeyPair} + * @hide + */ + public static final int KEY_GEN_NO_SUCH_ALGORITHM = 3; + + /** + * Invalid algorithm parameters when calling {@link #generateKeyPair} + * @hide + */ + public static final int KEY_GEN_INVALID_ALGORITHM_PARAMETERS = 4; + + /** + * Keystore is not available when calling {@link #generateKeyPair} + * @hide + */ + public static final int KEY_GEN_NO_KEYSTORE_PROVIDER = 5; + + /** + * General failure while calling {@link #generateKeyPair} + * @hide + */ + public static final int KEY_GEN_FAILURE = 6; + + /** + * Successful call to {@link #attestKey} + * @hide + */ + public static final int KEY_ATTESTATION_SUCCESS = 0; + + /** + * Attestation challenge missing when calling {@link #attestKey} + * @hide + */ + public static final int KEY_ATTESTATION_MISSING_CHALLENGE = 1; + + /** + * The caller requested Device ID attestation when calling {@link #attestKey}, but has no + * permissions to get device identifiers. + * @hide + */ + public static final int KEY_ATTESTATION_CANNOT_COLLECT_DATA = 2; + + /** + * The underlying hardware does not support Device ID attestation or cannot attest to the + * identifiers that are stored on the device. This indicates permanent inability + * to get attestation records on the device. + * @hide + */ + public static final int KEY_ATTESTATION_CANNOT_ATTEST_IDS = 3; + + /** + * General failure when calling {@link #attestKey} + * @hide + */ + public static final int KEY_ATTESTATION_FAILURE = 4; + + /** * Returns an {@code Intent} that can be used for credential * installation. The intent may be used without any extras, in * which case the user will be able to install credentials from diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 1924bbe764c7..fe05c13c999b 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -16,6 +16,7 @@ package android.security; +import android.app.ActivityManager; import android.app.ActivityThread; import android.app.Application; import android.app.KeyguardManager; @@ -66,6 +67,7 @@ public class KeyStore { public static final int VALUE_CORRUPTED = 8; public static final int UNDEFINED_ACTION = 9; public static final int WRONG_PASSWORD = 10; + public static final int CANNOT_ATTEST_IDS = -66; public static final int HARDWARE_TYPE_UNAVAILABLE = -68; /** @@ -278,7 +280,7 @@ public class KeyStore { /** * Attempt to lock the keystore for {@code user}. * - * @param user Android user to lock. + * @param userId Android user to lock. * @return whether {@code user}'s keystore was locked. */ public boolean lock(int userId) { @@ -299,7 +301,7 @@ public class KeyStore { * This is required before keystore entries created with FLAG_ENCRYPTED can be accessed or * created. * - * @param user Android user ID to operate on + * @param userId Android user ID to operate on * @param password user's keystore password. Should be the most recent value passed to * {@link #onUserPasswordChanged} for the user. * diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 4b9f3c803481..c342acdf101e 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -266,6 +266,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private final boolean mInvalidatedByBiometricEnrollment; private final boolean mIsStrongBoxBacked; private final boolean mUserConfirmationRequired; + private final boolean mUnlockedDeviceRequired; /** * @hide should be built with Builder @@ -296,7 +297,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu boolean userAuthenticationValidWhileOnBody, boolean invalidatedByBiometricEnrollment, boolean isStrongBoxBacked, - boolean userConfirmationRequired) { + boolean userConfirmationRequired, + boolean unlockedDeviceRequired) { if (TextUtils.isEmpty(keyStoreAlias)) { throw new IllegalArgumentException("keyStoreAlias must not be empty"); } @@ -345,6 +347,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment; mIsStrongBoxBacked = isStrongBoxBacked; mUserConfirmationRequired = userConfirmationRequired; + mUnlockedDeviceRequired = unlockedDeviceRequired; } /** @@ -670,6 +673,15 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * Returns {@code true} if the key cannot be used unless the device screen is unlocked. + * + * @see Builder#setUnlockedDeviceRequired(boolean) + */ + public boolean isUnlockedDeviceRequired() { + return mUnlockedDeviceRequired; + } + + /** * @hide */ public long getBoundToSpecificSecureUserId() { @@ -707,6 +719,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private boolean mInvalidatedByBiometricEnrollment = true; private boolean mIsStrongBoxBacked = false; private boolean mUserConfirmationRequired; + private boolean mUnlockedDeviceRequired = false; /** * Creates a new instance of the {@code Builder}. @@ -1275,6 +1288,18 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * Sets whether the keystore requires the screen to be unlocked before allowing decryption + * using this key. If this is set to {@code true}, any attempt to decrypt using this key + * while the screen is locked will fail. A locked device requires a PIN, password, + * fingerprint, or other trusted factor to access. + */ + @NonNull + public Builder setUnlockedDeviceRequired(boolean unlockedDeviceRequired) { + mUnlockedDeviceRequired = unlockedDeviceRequired; + return this; + } + + /** * Builds an instance of {@code KeyGenParameterSpec}. */ @NonNull @@ -1305,7 +1330,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUserAuthenticationValidWhileOnBody, mInvalidatedByBiometricEnrollment, mIsStrongBoxBacked, - mUserConfirmationRequired); + mUserConfirmationRequired, + mUnlockedDeviceRequired); } } } diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java index 95eeec704847..22568ce7a596 100644 --- a/keystore/java/android/security/keystore/KeyProtection.java +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -224,12 +224,13 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { private final boolean mRandomizedEncryptionRequired; private final boolean mUserAuthenticationRequired; private final int mUserAuthenticationValidityDurationSeconds; - private final boolean mTrustedUserPresenceRequred; + private final boolean mTrustedUserPresenceRequired; private final boolean mUserAuthenticationValidWhileOnBody; private final boolean mInvalidatedByBiometricEnrollment; private final long mBoundToSecureUserId; private final boolean mCriticalToDeviceEncryption; private final boolean mUserConfirmationRequired; + private final boolean mUnlockedDeviceRequired; private KeyProtection( Date keyValidityStart, @@ -243,12 +244,13 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { boolean randomizedEncryptionRequired, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds, - boolean trustedUserPresenceRequred, + boolean trustedUserPresenceRequired, boolean userAuthenticationValidWhileOnBody, boolean invalidatedByBiometricEnrollment, long boundToSecureUserId, boolean criticalToDeviceEncryption, - boolean userConfirmationRequired) { + boolean userConfirmationRequired, + boolean unlockedDeviceRequired) { mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart); mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd); mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd); @@ -262,12 +264,13 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { mRandomizedEncryptionRequired = randomizedEncryptionRequired; mUserAuthenticationRequired = userAuthenticationRequired; mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; - mTrustedUserPresenceRequred = trustedUserPresenceRequred; + mTrustedUserPresenceRequired = trustedUserPresenceRequired; mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody; mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment; mBoundToSecureUserId = boundToSecureUserId; mCriticalToDeviceEncryption = criticalToDeviceEncryption; mUserConfirmationRequired = userConfirmationRequired; + mUnlockedDeviceRequired = unlockedDeviceRequired; } /** @@ -444,7 +447,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { * been performed between the {@code Signature.initSign()} and {@code Signature.sign()} calls. */ public boolean isTrustedUserPresenceRequired() { - return mTrustedUserPresenceRequred; + return mTrustedUserPresenceRequired; } /** @@ -505,6 +508,15 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { } /** + * Returns {@code true} if the key cannot be used unless the device screen is unlocked. + * + * @see Builder#setUnlockedDeviceRequired(boolean) + */ + public boolean isUnlockedDeviceRequired() { + return mUnlockedDeviceRequired; + } + + /** * Builder of {@link KeyProtection} instances. */ public final static class Builder { @@ -524,6 +536,8 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { private boolean mUserAuthenticationValidWhileOnBody; private boolean mInvalidatedByBiometricEnrollment = true; private boolean mUserConfirmationRequired; + private boolean mUnlockedDeviceRequired = false; + private long mBoundToSecureUserId = GateKeeper.INVALID_SECURE_USER_ID; private boolean mCriticalToDeviceEncryption = false; @@ -914,6 +928,18 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { } /** + * Sets whether the keystore requires the screen to be unlocked before allowing decryption + * using this key. If this is set to {@code true}, any attempt to decrypt using this key + * while the screen is locked will fail. A locked device requires a PIN, password, + * fingerprint, or other trusted factor to access. + */ + @NonNull + public Builder setUnlockedDeviceRequired(boolean unlockedDeviceRequired) { + mUnlockedDeviceRequired = unlockedDeviceRequired; + return this; + } + + /** * Builds an instance of {@link KeyProtection}. * * @throws IllegalArgumentException if a required field is missing @@ -937,7 +963,8 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { mInvalidatedByBiometricEnrollment, mBoundToSecureUserId, mCriticalToDeviceEncryption, - mUserConfirmationRequired); + mUserConfirmationRequired, + mUnlockedDeviceRequired); } } } diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java index 0ef08f237b4e..14a9970c66d4 100644 --- a/keystore/java/android/security/keystore/KeymasterUtils.java +++ b/keystore/java/android/security/keystore/KeymasterUtils.java @@ -16,9 +16,8 @@ package android.security.keystore; -import android.util.Log; +import android.app.ActivityManager; import android.hardware.fingerprint.FingerprintManager; -import android.os.UserHandle; import android.security.GateKeeper; import android.security.KeyStore; import android.security.keymaster.KeymasterArguments; @@ -101,8 +100,8 @@ public abstract class KeymasterUtils { * state (e.g., secure lock screen not set up) for generating or importing keys that * require user authentication. */ - public static void addUserAuthArgs(KeymasterArguments args, - UserAuthArgs spec) { + public static void addUserAuthArgs(KeymasterArguments args, UserAuthArgs spec) { + if (spec.isUserConfirmationRequired()) { args.addBoolean(KeymasterDefs.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED); } @@ -111,6 +110,10 @@ public abstract class KeymasterUtils { args.addBoolean(KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED); } + if (spec.isUnlockedDeviceRequired()) { + args.addBoolean(KeymasterDefs.KM_TAG_UNLOCKED_DEVICE_REQUIRED); + } + if (!spec.isUserAuthenticationRequired()) { args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); return; diff --git a/keystore/java/android/security/keystore/UserAuthArgs.java b/keystore/java/android/security/keystore/UserAuthArgs.java index 1949592e7240..ad18ff8aef76 100644 --- a/keystore/java/android/security/keystore/UserAuthArgs.java +++ b/keystore/java/android/security/keystore/UserAuthArgs.java @@ -33,5 +33,6 @@ public interface UserAuthArgs { boolean isUserConfirmationRequired(); long getBoundToSpecificSecureUserId(); boolean isTrustedUserPresenceRequired(); + boolean isUnlockedDeviceRequired(); } diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp index d9584db2df9d..293e45f2273f 100644 --- a/libs/hwui/pipeline/skia/LayerDrawable.cpp +++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp @@ -34,7 +34,8 @@ void LayerDrawable::onDraw(SkCanvas* canvas) { } } -bool LayerDrawable::DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer) { +bool LayerDrawable::DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer, + const SkRect* dstRect) { if (context == nullptr) { SkDEBUGF(("Attempting to draw LayerDrawable into an unsupported surface")); return false; @@ -43,8 +44,8 @@ bool LayerDrawable::DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer SkMatrix layerTransform; layer->getTransform().copyTo(layerTransform); sk_sp<SkImage> layerImage; - int layerWidth = layer->getWidth(); - int layerHeight = layer->getHeight(); + const int layerWidth = layer->getWidth(); + const int layerHeight = layer->getHeight(); if (layer->getApi() == Layer::Api::OpenGL) { GlLayer* glLayer = static_cast<GlLayer*>(layer); GrGLTextureInfo externalTexture; @@ -62,21 +63,21 @@ bool LayerDrawable::DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer } if (layerImage) { - SkMatrix textureMatrix; - layer->getTexTransform().copyTo(textureMatrix); + SkMatrix textureMatrixInv; + layer->getTexTransform().copyTo(textureMatrixInv); // TODO: after skia bug https://bugs.chromium.org/p/skia/issues/detail?id=7075 is fixed // use bottom left origin and remove flipV and invert transformations. SkMatrix flipV; flipV.setAll(1, 0, 0, 0, -1, 1, 0, 0, 1); - textureMatrix.preConcat(flipV); - textureMatrix.preScale(1.0f / layerWidth, 1.0f / layerHeight); - textureMatrix.postScale(layerWidth, layerHeight); - SkMatrix textureMatrixInv; - if (!textureMatrix.invert(&textureMatrixInv)) { - textureMatrixInv = textureMatrix; + textureMatrixInv.preConcat(flipV); + textureMatrixInv.preScale(1.0f / layerWidth, 1.0f / layerHeight); + textureMatrixInv.postScale(layerWidth, layerHeight); + SkMatrix textureMatrix; + if (!textureMatrixInv.invert(&textureMatrix)) { + textureMatrix = textureMatrixInv; } - SkMatrix matrix = SkMatrix::Concat(layerTransform, textureMatrixInv); + SkMatrix matrix = SkMatrix::Concat(layerTransform, textureMatrix); SkPaint paint; paint.setAlpha(layer->getAlpha()); @@ -88,7 +89,20 @@ bool LayerDrawable::DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer canvas->save(); canvas->concat(matrix); } - canvas->drawImage(layerImage.get(), 0, 0, &paint); + if (dstRect) { + SkMatrix matrixInv; + if (!matrix.invert(&matrixInv)) { + matrixInv = matrix; + } + SkRect srcRect = SkRect::MakeIWH(layerWidth, layerHeight); + matrixInv.mapRect(&srcRect); + SkRect skiaDestRect = *dstRect; + matrixInv.mapRect(&skiaDestRect); + canvas->drawImageRect(layerImage.get(), srcRect, skiaDestRect, &paint, + SkCanvas::kFast_SrcRectConstraint); + } else { + canvas->drawImage(layerImage.get(), 0, 0, &paint); + } // restore the original matrix if (nonIdentityMatrix) { canvas->restore(); diff --git a/libs/hwui/pipeline/skia/LayerDrawable.h b/libs/hwui/pipeline/skia/LayerDrawable.h index 345038769306..18d118405a39 100644 --- a/libs/hwui/pipeline/skia/LayerDrawable.h +++ b/libs/hwui/pipeline/skia/LayerDrawable.h @@ -32,7 +32,8 @@ class LayerDrawable : public SkDrawable { public: explicit LayerDrawable(DeferredLayerUpdater* layerUpdater) : mLayerUpdater(layerUpdater) {} - static bool DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer); + static bool DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer, + const SkRect* dstRect = nullptr); protected: virtual SkRect onGetBounds() override { diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp index 2fa56f613144..670074871c71 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.cpp +++ b/libs/hwui/pipeline/skia/ShaderCache.cpp @@ -83,10 +83,12 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) { int maxTries = 3; while (valueSize > mObservedBlobValueSize && maxTries > 0) { mObservedBlobValueSize = std::min(valueSize, maxValueSize); - valueBuffer = realloc(valueBuffer, mObservedBlobValueSize); - if (!valueBuffer) { + void *newValueBuffer = realloc(valueBuffer, mObservedBlobValueSize); + if (!newValueBuffer) { + free(valueBuffer); return nullptr; } + valueBuffer = newValueBuffer; valueSize = bc->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize); maxTries--; } diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index 365d7403e046..74cfb2854e62 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -138,7 +138,9 @@ bool SkiaOpenGLPipeline::copyLayerInto(DeferredLayerUpdater* deferredLayer, SkBi SkBudgeted::kYes, bitmap->info()); Layer* layer = deferredLayer->backingLayer(); - if (LayerDrawable::DrawLayer(mRenderThread.getGrContext(), tmpSurface->getCanvas(), layer)) { + const SkRect dstRect = SkRect::MakeIWH(bitmap->width(), bitmap->height()); + if (LayerDrawable::DrawLayer(mRenderThread.getGrContext(), tmpSurface->getCanvas(), layer, + &dstRect)) { sk_sp<SkImage> tmpImage = tmpSurface->makeImageSnapshot(); if (tmpImage->readPixels(bitmap->info(), bitmap->getPixels(), bitmap->rowBytes(), 0, 0)) { bitmap->notifyPixelsChanged(); diff --git a/libs/incident/proto/android/privacy.proto b/libs/incident/proto/android/privacy.proto index f29f57f1c197..1ef36df8121f 100644 --- a/libs/incident/proto/android/privacy.proto +++ b/libs/incident/proto/android/privacy.proto @@ -16,13 +16,13 @@ syntax = "proto2"; -option java_package = "android"; +package android; + +option java_package = "com.android.incident"; option java_multiple_files = true; import "google/protobuf/descriptor.proto"; -package android; - enum Destination { // Fields or messages annotated with DEST_LOCAL must never be // extracted from the device automatically. They can be accessed diff --git a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java index 98e67c21c1a1..3643ca4a02f7 100644 --- a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java +++ b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java @@ -224,7 +224,9 @@ public class GnssMetrics { s.append("GNSS_KPI_END").append("\n"); GpsBatteryStats stats = mGnssPowerMetrics.getGpsBatteryStats(); if (stats != null) { - s.append("Power Metrics").append('\n'); + s.append("Power Metrics").append("\n"); + s.append(" Time on battery (min): " + + stats.getLoggingDurationMs() / ((double) DateUtils.MINUTE_IN_MILLIS)).append("\n"); long[] t = stats.getTimeInGpsSignalQualityLevel(); if (t != null && t.length == NUM_GPS_SIGNAL_QUALITY_LEVELS) { s.append(" Amount of time (while on battery) Top 4 Avg CN0 > " + diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java index 86dfc9c88723..ca895fcdfc4a 100644 --- a/media/java/android/media/AudioDeviceInfo.java +++ b/media/java/android/media/AudioDeviceInfo.java @@ -226,11 +226,10 @@ public final class AudioDeviceInfo { } /** - * @hide * @return The "address" string of the device. This generally contains device-specific * parameters. */ - public String getAddress() { + public @NonNull String getAddress() { return mPort.address(); } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index e408a115787c..aeef2158b20f 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -4596,8 +4596,7 @@ public class AudioManager { private static boolean checkTypes(AudioDevicePort port) { return AudioDeviceInfo.convertInternalDeviceToDeviceType(port.type()) != - AudioDeviceInfo.TYPE_UNKNOWN && - port.type() != AudioSystem.DEVICE_IN_BACK_MIC; + AudioDeviceInfo.TYPE_UNKNOWN; } /** diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java index e0c567b6212a..51737437d692 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -654,9 +654,11 @@ public final class MediaDrm implements AutoCloseable { * ID is returned. * * @param level the new security level, one of - * {@link #SW_SECURE_CRYPTO}, {@link #SW_SECURE_DECODE}, - * {@link #HW_SECURE_CRYPTO}, {@link #HW_SECURE_DECODE} or - * {@link #HW_SECURE_ALL}. + * {@link #SECURITY_LEVEL_SW_SECURE_CRYPTO}, + * {@link #SECURITY_LEVEL_SW_SECURE_DECODE}, + * {@link #SECURITY_LEVEL_HW_SECURE_CRYPTO}, + * {@link #SECURITY_LEVEL_HW_SECURE_DECODE} or + * {@link #SECURITY_LEVEL_HW_SECURE_ALL}. * * @throws NotProvisionedException if provisioning is needed * @throws ResourceBusyException if required resources are in use @@ -703,7 +705,9 @@ public final class MediaDrm implements AutoCloseable { public @interface KeyType {} /** - * Contains the opaque data an app uses to request keys from a license server + * Contains the opaque data an app uses to request keys from a license server. + * These request types may or may not be generated by a given plugin. Refer + * to plugin vendor documentation for more information. */ public static final class KeyRequest { private byte[] mData; @@ -728,8 +732,8 @@ public final class MediaDrm implements AutoCloseable { public static final int REQUEST_TYPE_RELEASE = 2; /** - * Keys are already loaded. No license request is necessary, and no - * key request data is returned. + * Keys are already loaded and are available for use. No license request is necessary, and + * no key request data is returned. */ public static final int REQUEST_TYPE_NONE = 3; @@ -1140,8 +1144,9 @@ public final class MediaDrm implements AutoCloseable { * implementation. */ @Retention(RetentionPolicy.SOURCE) - @IntDef({SECURITY_LEVEL_UNKNOWN, SW_SECURE_CRYPTO, SW_SECURE_DECODE, - HW_SECURE_CRYPTO, HW_SECURE_DECODE, HW_SECURE_ALL}) + @IntDef({SECURITY_LEVEL_UNKNOWN, SECURITY_LEVEL_SW_SECURE_CRYPTO, + SECURITY_LEVEL_SW_SECURE_DECODE, SECURITY_LEVEL_HW_SECURE_CRYPTO, + SECURITY_LEVEL_HW_SECURE_DECODE, SECURITY_LEVEL_HW_SECURE_ALL}) public @interface SecurityLevel {} /** @@ -1153,31 +1158,31 @@ public final class MediaDrm implements AutoCloseable { /** * DRM key management uses software-based whitebox crypto. */ - public static final int SW_SECURE_CRYPTO = 1; + public static final int SECURITY_LEVEL_SW_SECURE_CRYPTO = 1; /** * DRM key management and decoding use software-based whitebox crypto. */ - public static final int SW_SECURE_DECODE = 2; + public static final int SECURITY_LEVEL_SW_SECURE_DECODE = 2; /** * DRM key management and crypto operations are performed within a hardware * backed trusted execution environment. */ - public static final int HW_SECURE_CRYPTO = 3; + public static final int SECURITY_LEVEL_HW_SECURE_CRYPTO = 3; /** * DRM key management, crypto operations and decoding of content are * performed within a hardware backed trusted execution environment. */ - public static final int HW_SECURE_DECODE = 4; + public static final int SECURITY_LEVEL_HW_SECURE_DECODE = 4; /** * DRM key management, crypto operations, decoding of content and all * handling of the media (compressed and uncompressed) is handled within a * hardware backed trusted execution environment. */ - public static final int HW_SECURE_ALL = 5; + public static final int SECURITY_LEVEL_HW_SECURE_ALL = 5; /** * The maximum security level supported by the device. This is the default @@ -1203,9 +1208,9 @@ public final class MediaDrm implements AutoCloseable { * @param sessionId the session to query. * <p> * @return one of {@link #SECURITY_LEVEL_UNKNOWN}, - * {@link #SW_SECURE_CRYPTO}, {@link #SW_SECURE_DECODE}, - * {@link #HW_SECURE_CRYPTO}, {@link #HW_SECURE_DECODE} or - * {@link #HW_SECURE_ALL}. + * {@link #SECURITY_LEVEL_SW_SECURE_CRYPTO}, {@link #SECURITY_LEVEL_SW_SECURE_DECODE}, + * {@link #SECURITY_LEVEL_HW_SECURE_CRYPTO}, {@link #SECURITY_LEVEL_HW_SECURE_DECODE} or + * {@link #SECURITY_LEVEL_HW_SECURE_ALL}. */ @SecurityLevel public native int getSecurityLevel(@NonNull byte[] sessionId); diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index befbade0c701..aef31b116dd6 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -4099,8 +4099,8 @@ public class MediaPlayer extends PlayerBase /** The player was started because it was used as the next player for another * player, which just completed playback. + * @see android.media.MediaPlayer#setNextMediaPlayer(MediaPlayer) * @see android.media.MediaPlayer.OnInfoListener - * @hide */ public static final int MEDIA_INFO_STARTED_AS_NEXT = 2; diff --git a/media/java/android/media/MicrophoneInfo.java b/media/java/android/media/MicrophoneInfo.java index 004efea64d2d..d6399a4163fe 100644 --- a/media/java/android/media/MicrophoneInfo.java +++ b/media/java/android/media/MicrophoneInfo.java @@ -17,6 +17,7 @@ package android.media; import android.annotation.IntDef; +import android.annotation.NonNull; import android.util.Pair; import java.lang.annotation.Retention; @@ -224,12 +225,11 @@ public final class MicrophoneInfo { } /** - * @hide * Returns The "address" string of the microphone that corresponds to the * address returned by {@link AudioDeviceInfo#getAddress()} * @return the address of the microphone */ - public String getAddress() { + public @NonNull String getAddress() { return mAddress; } diff --git a/media/java/android/media/audiofx/DynamicsProcessing.java b/media/java/android/media/audiofx/DynamicsProcessing.java index d09c9a895e0c..4c17ae1d93b3 100644 --- a/media/java/android/media/audiofx/DynamicsProcessing.java +++ b/media/java/android/media/audiofx/DynamicsProcessing.java @@ -16,10 +16,11 @@ package android.media.audiofx; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.media.AudioTrack; import android.media.MediaPlayer; import android.media.audiofx.AudioEffect; -import android.media.audiofx.DynamicsProcessing.Settings; import android.util.Log; import java.nio.ByteBuffer; @@ -90,66 +91,18 @@ public final class DynamicsProcessing extends AudioEffect { private final static String TAG = "DynamicsProcessing"; - /** - * Config object used to initialize and change effect parameters at runtime. - */ - private Config mConfig = null; - - // These parameter constants must be synchronized with those in // /system/media/audio_effects/include/audio_effects/effect_dynamicsprocessing.h - - private static final int PARAM_GET_CHANNEL_COUNT = 0x0; - private static final int PARAM_EQ_BAND_COUNT = 0x1; - private static final int PARAM_MBC_BAND_COUNT = 0x2; - private static final int PARAM_INPUT_GAIN = 0x3; - private static final int PARAM_PRE_EQ_ENABLED = 0x10; - private static final int PARAM_PRE_EQ_BAND_ENABLED = 0x11; - private static final int PARAM_PRE_EQ_BAND_FREQUENCY = 0x12; - private static final int PARAM_PRE_EQ_BAND_GAIN = 0x13; - private static final int PARAM_EQ_FREQUENCY_RANGE = 0x22; - private static final int PARAM_EQ_GAIN_RANGE = 0x23; - private static final int PARAM_MBC_ENABLED = 0x30; - private static final int PARAM_MBC_BAND_ENABLED = 0x31; - private static final int PARAM_MBC_BAND_FREQUENCY = 0x32; - private static final int PARAM_MBC_BAND_ATTACK_TIME = 0x33; - private static final int PARAM_MBC_BAND_RELEASE_TIME = 0x34; - private static final int PARAM_MBC_BAND_RATIO = 0x35; - private static final int PARAM_MBC_BAND_THRESHOLD = 0x36; - private static final int PARAM_MBC_BAND_KNEE_WIDTH = 0x37; - private static final int PARAM_MBC_BAND_NOISE_GATE_THRESHOLD = 0x38; - private static final int PARAM_MBC_BAND_EXPANDER_RATIO = 0x39; - private static final int PARAM_MBC_BAND_GAIN_PRE = 0x3A; - private static final int PARAM_MBC_BAND_GAIN_POST = 0x3B; - private static final int PARAM_MBC_FREQUENCY_RANGE = 0x42; - private static final int PARAM_MBC_ATTACK_TIME_RANGE = 0x43; - private static final int PARAM_MBC_RELEASE_TIME_RANGE = 0x44; - private static final int PARAM_MBC_RATIO_RANGE = 0x45; - private static final int PARAM_MBC_THRESHOLD_RANGE = 0x46; - private static final int PARAM_MBC_KNEE_WIDTH_RANGE = 0x47; - private static final int PARAM_MBC_NOISE_GATE_THRESHOLD_RANGE = 0x48; - private static final int PARAM_MBC_EXPANDER_RATIO_RANGE = 0x49; - private static final int PARAM_MBC_GAIN_RANGE = 0x4A; - private static final int PARAM_POST_EQ_ENABLED = 0x50; - private static final int PARAM_POST_EQ_BAND_ENABLED = 0x51; - private static final int PARAM_POST_EQ_BAND_FREQUENCY = 0x52; - private static final int PARAM_POST_EQ_BAND_GAIN = 0x53; - private static final int PARAM_LIMITER_ENABLED = 0x60; - private static final int PARAM_LIMITER_LINK_GROUP = 0x61; - private static final int PARAM_LIMITER_ATTACK_TIME = 0x62; - private static final int PARAM_LIMITER_RELEASE_TIME = 0x63; - private static final int PARAM_LIMITER_RATIO = 0x64; - private static final int PARAM_LIMITER_THRESHOLD = 0x65; - private static final int PARAM_LIMITER_GAIN_POST = 0x66; - private static final int PARAM_LIMITER_ATTACK_TIME_RANGE = 0x72; - private static final int PARAM_LIMITER_RELEASE_TIME_RANGE = 0x73; - private static final int PARAM_LIMITER_RATIO_RANGE = 0x74; - private static final int PARAM_LIMITER_THRESHOLD_RANGE = 0x75; - private static final int PARAM_LIMITER_GAIN_RANGE = 0x76; - private static final int PARAM_VARIANT = 0x100; - private static final int PARAM_VARIANT_DESCRIPTION = 0x101; - private static final int PARAM_VARIANT_COUNT = 0x102; - private static final int PARAM_SET_ENGINE_ARCHITECTURE = 0x200; + private static final int PARAM_GET_CHANNEL_COUNT = 0x10; + private static final int PARAM_INPUT_GAIN = 0x20; + private static final int PARAM_ENGINE_ARCHITECTURE = 0x30; + private static final int PARAM_PRE_EQ = 0x40; + private static final int PARAM_PRE_EQ_BAND = 0x45; + private static final int PARAM_MBC = 0x50; + private static final int PARAM_MBC_BAND = 0x55; + private static final int PARAM_POST_EQ = 0x60; + private static final int PARAM_POST_EQ_BAND = 0x65; + private static final int PARAM_LIMITER = 0x70; /** * Index of variant that favors frequency resolution. Frequency domain based implementation. @@ -226,12 +179,13 @@ public final class DynamicsProcessing extends AudioEffect { * Config object that suits your needs. A null cfg parameter will create and use a default * configuration for the effect */ - public DynamicsProcessing(int priority, int audioSession, Config cfg) { + public DynamicsProcessing(int priority, int audioSession, @Nullable Config cfg) { super(EFFECT_TYPE_DYNAMICS_PROCESSING, EFFECT_TYPE_NULL, priority, audioSession); if (audioSession == 0) { Log.w(TAG, "WARNING: attaching a DynamicsProcessing to global output mix is" + "deprecated!"); } + final Config config; mChannelCount = getChannelCount(); if (cfg == null) { //create a default configuration and effect, with the number of channels this effect has @@ -239,21 +193,33 @@ public final class DynamicsProcessing extends AudioEffect { new DynamicsProcessing.Config.Builder( CONFIG_DEFAULT_VARIANT, mChannelCount, - true /*use preEQ*/, 6 /*pre eq bands*/, - true /*use mbc*/, 6 /*mbc bands*/, - true /*use postEQ*/, 6 /*postEq bands*/, - true /*use Limiter*/); - mConfig = builder.build(); + CONFIG_DEFAULT_USE_PREEQ, + CONFIG_DEFAULT_PREEQ_BANDS, + CONFIG_DEFAULT_USE_MBC, + CONFIG_DEFAULT_MBC_BANDS, + CONFIG_DEFAULT_USE_POSTEQ, + CONFIG_DEFAULT_POSTEQ_BANDS, + CONFIG_DEFAULT_USE_LIMITER); + config = builder.build(); } else { - //validate channels are ok. decide what to do: replicate channels if more, or fail, or - mConfig = new DynamicsProcessing.Config(mChannelCount, cfg); + //validate channels are ok. decide what to do: replicate channels if more + config = new DynamicsProcessing.Config(mChannelCount, cfg); + } + + //configure engine + setEngineArchitecture(config.getVariant(), + config.getPreferredFrameDuration(), + config.isPreEqInUse(), + config.getPreEqBandCount(), + config.isMbcInUse(), + config.getMbcBandCount(), + config.isPostEqInUse(), + config.getPostEqBandCount(), + config.isLimiterInUse()); + //update all the parameters + for (int ch = 0; ch < mChannelCount; ch++) { + updateEngineChannelByChannelIndex(ch, config.getChannelByChannelIndex(ch)); } - - setEngineArchitecture(mConfig.getVariant(), - mConfig.isPreEqInUse(), mConfig.getPreEqBandCount(), - mConfig.isMbcInUse(), mConfig.getMbcBandCount(), - mConfig.isPostEqInUse(), mConfig.getPostEqBandCount(), - mConfig.isLimiterInUse()); } /** @@ -261,11 +227,51 @@ public final class DynamicsProcessing extends AudioEffect { * @return Config Current Config object used to setup this DynamicsProcessing effect. */ public Config getConfig() { - return mConfig; + //Query engine architecture to create config object + Number[] params = { PARAM_ENGINE_ARCHITECTURE }; + Number[] values = { 0 /*0 variant */, + 0.0f /* 1 preferredFrameDuration */, + 0 /*2 preEqInUse */, + 0 /*3 preEqBandCount */, + 0 /*4 mbcInUse */, + 0 /*5 mbcBandCount*/, + 0 /*6 postEqInUse */, + 0 /*7 postEqBandCount */, + 0 /*8 limiterInUse */}; + byte[] paramBytes = numberArrayToByteArray(params); + byte[] valueBytes = numberArrayToByteArray(values); //just interest in the byte size. + getParameter(paramBytes, valueBytes); + byteArrayToNumberArray(valueBytes, values); + DynamicsProcessing.Config.Builder builder = + new DynamicsProcessing.Config.Builder( + values[0].intValue(), + mChannelCount, + values[2].intValue() > 0 /*use preEQ*/, + values[3].intValue() /*pre eq bands*/, + values[4].intValue() > 0 /*use mbc*/, + values[5].intValue() /*mbc bands*/, + values[6].intValue() > 0 /*use postEQ*/, + values[7].intValue()/*postEq bands*/, + values[8].intValue() > 0 /*use Limiter*/). + setPreferredFrameDuration(values[1].floatValue()); + Config config = builder.build(); + for (int ch = 0; ch < mChannelCount; ch++) { + Channel channel = queryEngineByChannelIndex(ch); + config.setChannelTo(ch, channel); + } + return config; } - private static final int CONFIG_DEFAULT_VARIANT = 0; //favor frequency + private static final int CONFIG_DEFAULT_VARIANT = VARIANT_FAVOR_FREQUENCY_RESOLUTION; + private static final boolean CONFIG_DEFAULT_USE_PREEQ = true; + private static final int CONFIG_DEFAULT_PREEQ_BANDS = 6; + private static final boolean CONFIG_DEFAULT_USE_MBC = true; + private static final int CONFIG_DEFAULT_MBC_BANDS = 6; + private static final boolean CONFIG_DEFAULT_USE_POSTEQ = true; + private static final int CONFIG_DEFAULT_POSTEQ_BANDS = 6; + private static final boolean CONFIG_DEFAULT_USE_LIMITER = true; + private static final float CHANNEL_DEFAULT_INPUT_GAIN = 0; // dB private static final float CONFIG_PREFERRED_FRAME_DURATION_MS = 10.0f; //milliseconds @@ -1276,7 +1282,7 @@ public final class DynamicsProcessing extends AudioEffect { mChannel = new Channel[mChannelCount]; //check if channelconfig is null or has less channels than channel count. //options: fill the missing with default options. - // or fail? + // or fail? for (int ch = 0; ch < mChannelCount; ch++) { if (ch < channel.length) { mChannel[ch] = new Channel(channel[ch]); //copy create @@ -1331,7 +1337,7 @@ public final class DynamicsProcessing extends AudioEffect { * Class constructor for Config * @param cfg Configuration object copy constructor */ - public Config(Config cfg) { + public Config(@NonNull Config cfg) { this(cfg.mChannelCount, cfg); } @@ -1763,138 +1769,115 @@ public final class DynamicsProcessing extends AudioEffect { } //=== CHANNEL public Channel getChannelByChannelIndex(int channelIndex) { - return mConfig.getChannelByChannelIndex(channelIndex); + return queryEngineByChannelIndex(channelIndex); } public void setChannelTo(int channelIndex, Channel channel) { - mConfig.setChannelTo(channelIndex, channel); + updateEngineChannelByChannelIndex(channelIndex, channel); } public void setAllChannelsTo(Channel channel) { - mConfig.setAllChannelsTo(channel); + for (int ch = 0; ch < mChannelCount; ch++) { + setChannelTo(ch, channel); + } } //=== channel params public float getInputGainByChannelIndex(int channelIndex) { - //TODO: return info from engine instead of cached config - return mConfig.getInputGainByChannelIndex(channelIndex); + return getTwoFloat(PARAM_INPUT_GAIN, channelIndex); } public void setInputGainbyChannel(int channelIndex, float inputGain) { - mConfig.setInputGainByChannelIndex(channelIndex, inputGain); - //TODO: communicate change to engine + setTwoFloat(PARAM_INPUT_GAIN, channelIndex, inputGain); } public void setInputGainAllChannelsTo(float inputGain) { - mConfig.setInputGainAllChannelsTo(inputGain); - //TODO: communicate change to engine + for (int ch = 0; ch < mChannelCount; ch++) { + setInputGainbyChannel(ch, inputGain); + } } //=== PreEQ public Eq getPreEqByChannelIndex(int channelIndex) { - //TODO: return info from engine instead of cached config - return mConfig.getPreEqByChannelIndex(channelIndex); + return queryEngineEqByChannelIndex(PARAM_PRE_EQ, channelIndex); } - public void setPreEqByChannelIndex(int channelIndex, Eq preEq) { - mConfig.setPreEqByChannelIndex(channelIndex, preEq); - //TODO: communicate change to engine + updateEngineEqByChannelIndex(PARAM_PRE_EQ, channelIndex, preEq); } - public void setPreEqAllChannelsTo(Eq preEq) { - mConfig.setPreEqAllChannelsTo(preEq); - //TODO: communicate change to engine + for (int ch = 0; ch < mChannelCount; ch++) { + setPreEqByChannelIndex(ch, preEq); + } } - public EqBand getPreEqBandByChannelIndex(int channelIndex, int band) { - //TODO: return info from engine instead of cached config - return mConfig.getPreEqBandByChannelIndex(channelIndex, band); + return queryEngineEqBandByChannelIndex(PARAM_PRE_EQ_BAND, channelIndex, band); } - public void setPreEqBandByChannelIndex(int channelIndex, int band, EqBand preEqBand) { - mConfig.setPreEqBandByChannelIndex(channelIndex, band, preEqBand); - //TODO: communicate change to engine + updateEngineEqBandByChannelIndex(PARAM_PRE_EQ_BAND, channelIndex, band, preEqBand); } - public void setPreEqBandAllChannelsTo(int band, EqBand preEqBand) { - mConfig.setPreEqBandAllChannelsTo(band, preEqBand); - //TODO: communicate change to engine + for (int ch = 0; ch < mChannelCount; ch++) { + setPreEqBandByChannelIndex(ch, band, preEqBand); + } } //=== MBC public Mbc getMbcByChannelIndex(int channelIndex) { - //TODO: return info from engine instead of cached config - return mConfig.getMbcByChannelIndex(channelIndex); + return queryEngineMbcByChannelIndex(channelIndex); } - public void setMbcByChannelIndex(int channelIndex, Mbc mbc) { - mConfig.setMbcByChannelIndex(channelIndex, mbc); - //TODO: communicate change to engine + updateEngineMbcByChannelIndex(channelIndex, mbc); } - public void setMbcAllChannelsTo(Mbc mbc) { - mConfig.setMbcAllChannelsTo(mbc); - //TODO: communicate change to engine + for (int ch = 0; ch < mChannelCount; ch++) { + setMbcByChannelIndex(ch, mbc); + } } - public MbcBand getMbcBandByChannelIndex(int channelIndex, int band) { - //TODO: return info from engine instead of cached config - return mConfig.getMbcBandByChannelIndex(channelIndex, band); + return queryEngineMbcBandByChannelIndex(channelIndex, band); } - public void setMbcBandByChannelIndex(int channelIndex, int band, MbcBand mbcBand) { - mConfig.setMbcBandByChannelIndex(channelIndex, band, mbcBand); - //TODO: communicate change to engine + updateEngineMbcBandByChannelIndex(channelIndex, band, mbcBand); } - public void setMbcBandAllChannelsTo(int band, MbcBand mbcBand) { - mConfig.setMbcBandAllChannelsTo(band, mbcBand); - //TODO: communicate change to engine + for (int ch = 0; ch < mChannelCount; ch++) { + setMbcBandByChannelIndex(ch, band, mbcBand); + } } //== PostEq public Eq getPostEqByChannelIndex(int channelIndex) { - //TODO: return info from engine instead of cached config - return mConfig.getPostEqByChannelIndex(channelIndex); + return queryEngineEqByChannelIndex(PARAM_POST_EQ, channelIndex); } - public void setPostEqByChannelIndex(int channelIndex, Eq postEq) { - mConfig.setPostEqByChannelIndex(channelIndex, postEq); - //TODO: communicate change to engine + updateEngineEqByChannelIndex(PARAM_POST_EQ, channelIndex, postEq); } - public void setPostEqAllChannelsTo(Eq postEq) { - mConfig.setPostEqAllChannelsTo(postEq); - //TODO: communicate change to engine + for (int ch = 0; ch < mChannelCount; ch++) { + setPostEqByChannelIndex(ch, postEq); + } } - public EqBand getPostEqBandByChannelIndex(int channelIndex, int band) { - //TODO: return info from engine instead of cached config - return mConfig.getPostEqBandByChannelIndex(channelIndex, band); + return queryEngineEqBandByChannelIndex(PARAM_POST_EQ_BAND, channelIndex, band); } - public void setPostEqBandByChannelIndex(int channelIndex, int band, EqBand postEqBand) { - mConfig.setPostEqBandByChannelIndex(channelIndex, band, postEqBand); - //TODO: communicate change to engine + updateEngineEqBandByChannelIndex(PARAM_POST_EQ_BAND, channelIndex, band, postEqBand); } - public void setPostEqBandAllChannelsTo(int band, EqBand postEqBand) { - mConfig.setPostEqBandAllChannelsTo(band, postEqBand); - //TODO: communicate change to engine + for (int ch = 0; ch < mChannelCount; ch++) { + setPostEqBandByChannelIndex(ch, band, postEqBand); + } } //==== Limiter public Limiter getLimiterByChannelIndex(int channelIndex) { - //TODO: return info from engine instead of cached config - return mConfig.getLimiterByChannelIndex(channelIndex); + return queryEngineLimiterByChannelIndex(channelIndex); } - public void setLimiterByChannelIndex(int channelIndex, Limiter limiter) { - mConfig.setLimiterByChannelIndex(channelIndex, limiter); - //TODO: communicate change to engine + updateEngineLimiterByChannelIndex(channelIndex, limiter); } - public void setLimiterAllChannelsTo(Limiter limiter) { - mConfig.setLimiterAllChannelsTo(limiter); - //TODO: communicate change to engine + for (int ch = 0; ch < mChannelCount; ch++) { + setLimiterByChannelIndex(ch, limiter); + } } /** @@ -1905,165 +1888,327 @@ public final class DynamicsProcessing extends AudioEffect { return getOneInt(PARAM_GET_CHANNEL_COUNT); } - private void setEngineArchitecture(int variant, boolean preEqInUse, int preEqBandCount, - boolean mbcInUse, int mbcBandCount, boolean postEqInUse, int postEqBandCount, - boolean limiterInUse) { - int[] values = { variant, (preEqInUse ? 1 : 0), preEqBandCount, - (mbcInUse ? 1 : 0), mbcBandCount, (postEqInUse ? 1 : 0), postEqBandCount, + //=== Engine calls + private void setEngineArchitecture(int variant, float preferredFrameDuration, + boolean preEqInUse, int preEqBandCount, boolean mbcInUse, int mbcBandCount, + boolean postEqInUse, int postEqBandCount, boolean limiterInUse) { + + Number[] params = { PARAM_ENGINE_ARCHITECTURE }; + Number[] values = { variant /* variant */, + preferredFrameDuration, + (preEqInUse ? 1 : 0), + preEqBandCount, + (mbcInUse ? 1 : 0), + mbcBandCount, + (postEqInUse ? 1 : 0), + postEqBandCount, (limiterInUse ? 1 : 0)}; - //TODO: enable later setIntArray(PARAM_SET_ENGINE_ARCHITECTURE, values); + setNumberArray(params, values); + } + + private void updateEngineEqBandByChannelIndex(int param, int channelIndex, int bandIndex, + @NonNull EqBand eqBand) { + Number[] params = {param, + channelIndex, + bandIndex}; + Number[] values = {(eqBand.isEnabled() ? 1 : 0), + eqBand.getCutoffFrequency(), + eqBand.getGain()}; + setNumberArray(params, values); + } + private Eq queryEngineEqByChannelIndex(int param, int channelIndex) { + + Number[] params = {param == PARAM_PRE_EQ ? PARAM_PRE_EQ : PARAM_POST_EQ, + channelIndex}; + Number[] values = {0 /*0 in use */, + 0 /*1 enabled*/, + 0 /*2 band count */}; + byte[] paramBytes = numberArrayToByteArray(params); + byte[] valueBytes = numberArrayToByteArray(values); //just interest in the byte size. + getParameter(paramBytes, valueBytes); + byteArrayToNumberArray(valueBytes, values); + int bandCount = values[2].intValue(); + Eq eq = new Eq(values[0].intValue() > 0 /* in use */, + values[1].intValue() > 0 /* enabled */, + bandCount /*band count*/); + for (int b = 0; b < bandCount; b++) { + EqBand eqBand = queryEngineEqBandByChannelIndex(param == PARAM_PRE_EQ ? + PARAM_PRE_EQ_BAND : PARAM_POST_EQ_BAND, channelIndex, b); + eq.setBand(b, eqBand); + } + return eq; + } + private EqBand queryEngineEqBandByChannelIndex(int param, int channelIndex, int bandIndex) { + Number[] params = {param, + channelIndex, + bandIndex}; + Number[] values = {0 /*0 enabled*/, + 0.0f /*1 cutoffFrequency */, + 0.0f /*2 gain */}; + + byte[] paramBytes = numberArrayToByteArray(params); + byte[] valueBytes = numberArrayToByteArray(values); //just interest in the byte size. + getParameter(paramBytes, valueBytes); + + byteArrayToNumberArray(valueBytes, values); + + return new EqBand(values[0].intValue() > 0 /* enabled */, + values[1].floatValue() /* cutoffFrequency */, + values[2].floatValue() /* gain*/); + } + private void updateEngineEqByChannelIndex(int param, int channelIndex, @NonNull Eq eq) { + int bandCount = eq.getBandCount(); + Number[] params = {param, + channelIndex}; + Number[] values = { (eq.isInUse() ? 1 : 0), + (eq.isEnabled() ? 1 : 0), + bandCount}; + setNumberArray(params, values); + for (int b = 0; b < bandCount; b++) { + EqBand eqBand = eq.getBand(b); + updateEngineEqBandByChannelIndex(param == PARAM_PRE_EQ ? + PARAM_PRE_EQ_BAND : PARAM_POST_EQ_BAND, channelIndex, b, eqBand); + } + } + + private Mbc queryEngineMbcByChannelIndex(int channelIndex) { + Number[] params = {PARAM_MBC, + channelIndex}; + Number[] values = {0 /*0 in use */, + 0 /*1 enabled*/, + 0 /*2 band count */}; + byte[] paramBytes = numberArrayToByteArray(params); + byte[] valueBytes = numberArrayToByteArray(values); //just interest in the byte size. + getParameter(paramBytes, valueBytes); + byteArrayToNumberArray(valueBytes, values); + int bandCount = values[2].intValue(); + Mbc mbc = new Mbc(values[0].intValue() > 0 /* in use */, + values[1].intValue() > 0 /* enabled */, + bandCount /*band count*/); + for (int b = 0; b < bandCount; b++) { + MbcBand mbcBand = queryEngineMbcBandByChannelIndex(channelIndex, b); + mbc.setBand(b, mbcBand); + } + return mbc; + } + private MbcBand queryEngineMbcBandByChannelIndex(int channelIndex, int bandIndex) { + Number[] params = {PARAM_MBC_BAND, + channelIndex, + bandIndex}; + Number[] values = {0 /*0 enabled */, + 0.0f /*1 cutoffFrequency */, + 0.0f /*2 AttackTime */, + 0.0f /*3 ReleaseTime */, + 0.0f /*4 Ratio */, + 0.0f /*5 Threshold */, + 0.0f /*6 KneeWidth */, + 0.0f /*7 NoiseGateThreshold */, + 0.0f /*8 ExpanderRatio */, + 0.0f /*9 PreGain */, + 0.0f /*10 PostGain*/}; + + byte[] paramBytes = numberArrayToByteArray(params); + byte[] valueBytes = numberArrayToByteArray(values); //just interest in the byte size. + getParameter(paramBytes, valueBytes); + + byteArrayToNumberArray(valueBytes, values); + + return new MbcBand(values[0].intValue() > 0 /* enabled */, + values[1].floatValue() /* cutoffFrequency */, + values[2].floatValue()/*2 AttackTime */, + values[3].floatValue()/*3 ReleaseTime */, + values[4].floatValue()/*4 Ratio */, + values[5].floatValue()/*5 Threshold */, + values[6].floatValue()/*6 KneeWidth */, + values[7].floatValue()/*7 NoiseGateThreshold */, + values[8].floatValue()/*8 ExpanderRatio */, + values[9].floatValue()/*9 PreGain */, + values[10].floatValue()/*10 PostGain*/); + } + private void updateEngineMbcBandByChannelIndex(int channelIndex, int bandIndex, + @NonNull MbcBand mbcBand) { + Number[] params = { PARAM_MBC_BAND, + channelIndex, + bandIndex}; + Number[] values = {(mbcBand.isEnabled() ? 1 : 0), + mbcBand.getCutoffFrequency(), + mbcBand.getAttackTime(), + mbcBand.getReleaseTime(), + mbcBand.getRatio(), + mbcBand.getThreshold(), + mbcBand.getKneeWidth(), + mbcBand.getNoiseGateThreshold(), + mbcBand.getExpanderRatio(), + mbcBand.getPreGain(), + mbcBand.getPostGain()}; + setNumberArray(params, values); + } + + private void updateEngineMbcByChannelIndex(int channelIndex, @NonNull Mbc mbc) { + int bandCount = mbc.getBandCount(); + Number[] params = { PARAM_MBC, + channelIndex}; + Number[] values = {(mbc.isInUse() ? 1 : 0), + (mbc.isEnabled() ? 1 : 0), + bandCount}; + setNumberArray(params, values); + for (int b = 0; b < bandCount; b++) { + MbcBand mbcBand = mbc.getBand(b); + updateEngineMbcBandByChannelIndex(channelIndex, b, mbcBand); + } + } + + private void updateEngineLimiterByChannelIndex(int channelIndex, @NonNull Limiter limiter) { + Number[] params = { PARAM_LIMITER, + channelIndex}; + Number[] values = {(limiter.isInUse() ? 1 : 0), + (limiter.isEnabled() ? 1 : 0), + limiter.getLinkGroup(), + limiter.getAttackTime(), + limiter.getReleaseTime(), + limiter.getRatio(), + limiter.getThreshold(), + limiter.getPostGain()}; + setNumberArray(params, values); + } + + private Limiter queryEngineLimiterByChannelIndex(int channelIndex) { + Number[] params = {PARAM_LIMITER, + channelIndex}; + Number[] values = {0 /*0 in use (int)*/, + 0 /*1 enabled (int)*/, + 0 /*2 link group (int)*/, + 0.0f /*3 attack time (float)*/, + 0.0f /*4 release time (float)*/, + 0.0f /*5 ratio (float)*/, + 0.0f /*6 threshold (float)*/, + 0.0f /*7 post gain(float)*/}; + + byte[] paramBytes = numberArrayToByteArray(params); + byte[] valueBytes = numberArrayToByteArray(values); //just interest in the byte size. + getParameter(paramBytes, valueBytes); + byteArrayToNumberArray(valueBytes, values); + + return new Limiter(values[0].intValue() > 0 /*in use*/, + values[1].intValue() > 0 /*enabled*/, + values[2].intValue() /*linkGroup*/, + values[3].floatValue() /*attackTime*/, + values[4].floatValue() /*releaseTime*/, + values[5].floatValue() /*ratio*/, + values[6].floatValue() /*threshold*/, + values[7].floatValue() /*postGain*/); + } + + private Channel queryEngineByChannelIndex(int channelIndex) { + float inputGain = getTwoFloat(PARAM_INPUT_GAIN, channelIndex); + Eq preEq = queryEngineEqByChannelIndex(PARAM_PRE_EQ, channelIndex); + Mbc mbc = queryEngineMbcByChannelIndex(channelIndex); + Eq postEq = queryEngineEqByChannelIndex(PARAM_POST_EQ, channelIndex); + Limiter limiter = queryEngineLimiterByChannelIndex(channelIndex); + + Channel channel = new Channel(inputGain, + preEq.isInUse(), preEq.getBandCount(), + mbc.isInUse(), mbc.getBandCount(), + postEq.isInUse(), postEq.getBandCount(), + limiter.isInUse()); + channel.setInputGain(inputGain); + channel.setPreEq(preEq); + channel.setMbc(mbc); + channel.setPostEq(postEq); + channel.setLimiter(limiter); + return channel; + } + + private void updateEngineChannelByChannelIndex(int channelIndex, @NonNull Channel channel) { + //send things with as few calls as possible + setTwoFloat(PARAM_INPUT_GAIN, channelIndex, channel.getInputGain()); + Eq preEq = channel.getPreEq(); + updateEngineEqByChannelIndex(PARAM_PRE_EQ, channelIndex, preEq); + Mbc mbc = channel.getMbc(); + updateEngineMbcByChannelIndex(channelIndex, mbc); + Eq postEq = channel.getPostEq(); + updateEngineEqByChannelIndex(PARAM_POST_EQ, channelIndex, postEq); + Limiter limiter = channel.getLimiter(); + updateEngineLimiterByChannelIndex(channelIndex, limiter); } //****** convenience methods: // - private int getOneInt(int paramGet) { - int[] param = new int[1]; - int[] result = new int[1]; + private int getOneInt(int param) { + final int[] params = { param }; + final int[] result = new int[1]; - param[0] = paramGet; - checkStatus(getParameter(param, result)); + checkStatus(getParameter(params, result)); return result[0]; } - private int getTwoInt(int paramGet, int paramA) { - int[] param = new int[2]; - int[] result = new int[1]; + private void setTwoFloat(int param, int paramA, float valueSet) { + final int[] params = { param, paramA }; + final byte[] value; - param[0] = paramGet; - param[1] = paramA; - checkStatus(getParameter(param, result)); - return result[0]; - } - - private int getThreeInt(int paramGet, int paramA, int paramB) { - //have to use bytearrays, with more than 2 parameters. - byte[] paramBytes = concatArrays(intToByteArray(paramGet), - intToByteArray(paramA), - intToByteArray(paramB)); - byte[] resultBytes = new byte[4]; //single int - - checkStatus(getParameter(paramBytes, resultBytes)); - - return byteArrayToInt(resultBytes); - } - - private void setOneInt(int paramSet, int valueSet) { - int[] param = new int[1]; - int[] value = new int[1]; - - param[0] = paramSet; - value[0] = valueSet; - checkStatus(setParameter(param, value)); - } - - private void setTwoInt(int paramSet, int paramA, int valueSet) { - int[] param = new int[2]; - int[] value = new int[1]; - - param[0] = paramSet; - param[1] = paramA; - value[0] = valueSet; - checkStatus(setParameter(param, value)); - } - - private void setThreeInt(int paramSet, int paramA, int paramB, int valueSet) { - //have to use bytearrays, with more than 2 parameters. - byte[] paramBytes = concatArrays(intToByteArray(paramSet), - intToByteArray(paramA), - intToByteArray(paramB)); - byte[] valueBytes = intToByteArray(valueSet); - - checkStatus(setParameter(paramBytes, valueBytes)); - } - - private void setOneFloat(int paramSet, float valueSet) { - int[] param = new int[1]; - byte[] value; - - param[0] = paramSet; - value = floatToByteArray(valueSet); - checkStatus(setParameter(param, value)); - } - - private void setTwoFloat(int paramSet, int paramA, float valueSet) { - int[] param = new int[2]; - byte[] value; - - param[0] = paramSet; - param[1] = paramA; value = floatToByteArray(valueSet); - checkStatus(setParameter(param, value)); + checkStatus(setParameter(params, value)); } - private void setThreeFloat(int paramSet, int paramA, int paramB, float valueSet) { - //have to use bytearrays, with more than 2 parameters. - byte[] paramBytes = concatArrays(intToByteArray(paramSet), - intToByteArray(paramA), - intToByteArray(paramB)); - byte[] valueBytes = floatToByteArray(valueSet); - - checkStatus(setParameter(paramBytes, valueBytes)); - } - private byte[] intArrayToByteArray(int[] values) { - int expectedBytes = values.length * 4; + private byte[] numberArrayToByteArray(Number[] values) { + int expectedBytes = 0; + for (int i = 0; i < values.length; i++) { + if (values[i] instanceof Integer) { + expectedBytes += Integer.BYTES; + } else if (values[i] instanceof Float) { + expectedBytes += Float.BYTES; + } else { + throw new IllegalArgumentException("unknown value type " + + values[i].getClass()); + } + } ByteBuffer converter = ByteBuffer.allocate(expectedBytes); converter.order(ByteOrder.nativeOrder()); - for (int k = 0; k < values.length; k++) { - converter.putFloat(values[k]); + for (int i = 0; i < values.length; i++) { + if (values[i] instanceof Integer) { + converter.putInt(values[i].intValue()); + } else if (values[i] instanceof Float) { + converter.putFloat(values[i].floatValue()); + } } return converter.array(); } - private void setIntArray(int paramSet, int[] paramArray) { - //have to use bytearrays, with more than 2 parameters. - byte[] paramBytes = intToByteArray(paramSet); - byte[] valueBytes = intArrayToByteArray(paramArray); - checkStatus(setParameter(paramBytes, valueBytes)); + private void byteArrayToNumberArray(byte[] valuesIn, Number[] valuesOut) { + int inIndex = 0; + int outIndex = 0; + while (inIndex < valuesIn.length && outIndex < valuesOut.length) { + if (valuesOut[outIndex] instanceof Integer) { + valuesOut[outIndex++] = byteArrayToInt(valuesIn, inIndex); + inIndex += Integer.BYTES; + } else if (valuesOut[outIndex] instanceof Float) { + valuesOut[outIndex++] = byteArrayToFloat(valuesIn, inIndex); + inIndex += Float.BYTES; + } else { + throw new IllegalArgumentException("can't convert " + + valuesOut[outIndex].getClass()); + } + } + if (outIndex != valuesOut.length) { + throw new IllegalArgumentException("only converted " + outIndex + + " values out of "+ valuesOut.length + " expected"); + } } - private float getOneFloat(int paramGet) { - int[] param = new int[1]; - byte[] result = new byte[4]; - - param[0] = paramGet; - checkStatus(getParameter(param, result)); - return byteArrayToFloat(result); + private void setNumberArray(Number[] params, Number[] values) { + byte[] paramBytes = numberArrayToByteArray(params); + byte[] valueBytes = numberArrayToByteArray(values); + checkStatus(setParameter(paramBytes, valueBytes)); } - private float getTwoFloat(int paramGet, int paramA) { - int[] param = new int[2]; - byte[] result = new byte[4]; + private float getTwoFloat(int param, int paramA) { + final int[] params = { param, paramA }; + final byte[] result = new byte[4]; - param[0] = paramGet; - param[1] = paramA; - checkStatus(getParameter(param, result)); + checkStatus(getParameter(params, result)); return byteArrayToFloat(result); } - private float getThreeFloat(int paramGet, int paramA, int paramB) { - //have to use bytearrays, with more than 2 parameters. - byte[] paramBytes = concatArrays(intToByteArray(paramGet), - intToByteArray(paramA), - intToByteArray(paramB)); - byte[] resultBytes = new byte[4]; //single float - - checkStatus(getParameter(paramBytes, resultBytes)); - - return byteArrayToFloat(resultBytes); - } - - private float[] getOneFloatArray(int paramGet, int expectedSize) { - int[] param = new int[1]; - byte[] result = new byte[4 * expectedSize]; - - param[0] = paramGet; - checkStatus(getParameter(param, result)); - float[] returnArray = new float[expectedSize]; - for (int k = 0; k < expectedSize; k++) { - returnArray[k] = byteArrayToFloat(result, 4 * k); - } - return returnArray; - } /** * @hide * The OnParameterChangeListener interface defines a method called by the DynamicsProcessing diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl index 3affee5c0aa7..bd0019f0f1d8 100644 --- a/media/java/android/media/session/ISession.aidl +++ b/media/java/android/media/session/ISession.aidl @@ -50,6 +50,4 @@ interface ISession { void setPlaybackToLocal(in AudioAttributes attributes); void setPlaybackToRemote(int control, int max); void setCurrentVolume(int currentVolume); - - String getCallingPackage(); } diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl index 893bd3c8cbb1..9634c7fe257b 100644 --- a/media/java/android/media/session/ISessionCallback.aidl +++ b/media/java/android/media/session/ISessionCallback.aidl @@ -25,30 +25,33 @@ import android.os.ResultReceiver; * @hide */ oneway interface ISessionCallback { - void onCommand(String command, in Bundle args, in ResultReceiver cb); - void onMediaButton(in Intent mediaButtonIntent, int sequenceNumber, in ResultReceiver cb); + void onCommand(String packageName, int pid, int uid, String command, in Bundle args, + in ResultReceiver cb); + void onMediaButton(String packageName, int pid, int uid, in Intent mediaButtonIntent, + int sequenceNumber, in ResultReceiver cb); // These callbacks are for the TransportPerformer - void onPrepare(); - void onPrepareFromMediaId(String mediaId, in Bundle extras); - void onPrepareFromSearch(String query, in Bundle extras); - void onPrepareFromUri(in Uri uri, in Bundle extras); - void onPlay(); - void onPlayFromMediaId(String mediaId, in Bundle extras); - void onPlayFromSearch(String query, in Bundle extras); - void onPlayFromUri(in Uri uri, in Bundle extras); - void onSkipToTrack(long id); - void onPause(); - void onStop(); - void onNext(); - void onPrevious(); - void onFastForward(); - void onRewind(); - void onSeekTo(long pos); - void onRate(in Rating rating); - void onCustomAction(String action, in Bundle args); + void onPrepare(String packageName, int pid, int uid); + void onPrepareFromMediaId(String packageName, int pid, int uid, String mediaId, + in Bundle extras); + void onPrepareFromSearch(String packageName, int pid, int uid, String query, in Bundle extras); + void onPrepareFromUri(String packageName, int pid, int uid, in Uri uri, in Bundle extras); + void onPlay(String packageName, int pid, int uid); + void onPlayFromMediaId(String packageName, int pid, int uid, String mediaId, in Bundle extras); + void onPlayFromSearch(String packageName, int pid, int uid, String query, in Bundle extras); + void onPlayFromUri(String packageName, int pid, int uid, in Uri uri, in Bundle extras); + void onSkipToTrack(String packageName, int pid, int uid, long id); + void onPause(String packageName, int pid, int uid); + void onStop(String packageName, int pid, int uid); + void onNext(String packageName, int pid, int uid); + void onPrevious(String packageName, int pid, int uid); + void onFastForward(String packageName, int pid, int uid); + void onRewind(String packageName, int pid, int uid); + void onSeekTo(String packageName, int pid, int uid, long pos); + void onRate(String packageName, int pid, int uid, in Rating rating); + void onCustomAction(String packageName, int pid, int uid, String action, in Bundle args); // These callbacks are for volume handling - void onAdjustVolume(int direction); - void onSetVolumeTo(int value); + void onAdjustVolume(String packageName, int pid, int uid, int direction); + void onSetVolumeTo(String packageName, int pid, int uid, int value); } diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl index 249bcdc802ce..06f5863a6944 100644 --- a/media/java/android/media/session/ISessionController.aidl +++ b/media/java/android/media/session/ISessionController.aidl @@ -36,8 +36,8 @@ import java.util.List; * @hide */ interface ISessionController { - void sendCommand(String command, in Bundle args, in ResultReceiver cb); - boolean sendMediaButton(in KeyEvent mediaButton); + void sendCommand(String packageName, String command, in Bundle args, in ResultReceiver cb); + boolean sendMediaButton(String packageName, in KeyEvent mediaButton); void registerCallbackListener(in ISessionControllerCallback cb); void unregisterCallbackListener(in ISessionControllerCallback cb); boolean isTransportControlEnabled(); @@ -46,28 +46,28 @@ interface ISessionController { PendingIntent getLaunchPendingIntent(); long getFlags(); ParcelableVolumeInfo getVolumeAttributes(); - void adjustVolume(int direction, int flags, String packageName); - void setVolumeTo(int value, int flags, String packageName); + void adjustVolume(String packageName, int direction, int flags); + void setVolumeTo(String packageName, int value, int flags); // These commands are for the TransportControls - void prepare(); - void prepareFromMediaId(String mediaId, in Bundle extras); - void prepareFromSearch(String string, in Bundle extras); - void prepareFromUri(in Uri uri, in Bundle extras); - void play(); - void playFromMediaId(String mediaId, in Bundle extras); - void playFromSearch(String string, in Bundle extras); - void playFromUri(in Uri uri, in Bundle extras); - void skipToQueueItem(long id); - void pause(); - void stop(); - void next(); - void previous(); - void fastForward(); - void rewind(); - void seekTo(long pos); - void rate(in Rating rating); - void sendCustomAction(String action, in Bundle args); + void prepare(String packageName); + void prepareFromMediaId(String packageName, String mediaId, in Bundle extras); + void prepareFromSearch(String packageName, String string, in Bundle extras); + void prepareFromUri(String packageName, in Uri uri, in Bundle extras); + void play(String packageName); + void playFromMediaId(String packageName, String mediaId, in Bundle extras); + void playFromSearch(String packageName, String string, in Bundle extras); + void playFromUri(String packageName, in Uri uri, in Bundle extras); + void skipToQueueItem(String packageName, long id); + void pause(String packageName); + void stop(String packageName); + void next(String packageName); + void previous(String packageName); + void fastForward(String packageName); + void rewind(String packageName); + void seekTo(String packageName, long pos); + void rate(String packageName, in Rating rating); + void sendCustomAction(String packageName, String action, in Bundle args); MediaMetadata getMetadata(); PlaybackState getPlaybackState(); ParceledListSlice getQueue(); diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java index 622900f5c7f9..f16804c9286d 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/java/android/media/session/MediaController.java @@ -133,7 +133,7 @@ public final class MediaController { return false; } try { - return mSessionBinder.sendMediaButton(keyEvent); + return mSessionBinder.sendMediaButton(mContext.getPackageName(), keyEvent); } catch (RemoteException e) { // System is dead. =( } @@ -301,7 +301,7 @@ public final class MediaController { */ public void setVolumeTo(int value, int flags) { try { - mSessionBinder.setVolumeTo(value, flags, mContext.getPackageName()); + mSessionBinder.setVolumeTo(mContext.getPackageName(), value, flags); } catch (RemoteException e) { Log.wtf(TAG, "Error calling setVolumeTo.", e); } @@ -322,7 +322,7 @@ public final class MediaController { */ public void adjustVolume(int direction, int flags) { try { - mSessionBinder.adjustVolume(direction, flags, mContext.getPackageName()); + mSessionBinder.adjustVolume(mContext.getPackageName(), direction, flags); } catch (RemoteException e) { Log.wtf(TAG, "Error calling adjustVolumeBy.", e); } @@ -388,7 +388,7 @@ public final class MediaController { throw new IllegalArgumentException("command cannot be null or empty"); } try { - mSessionBinder.sendCommand(command, args, cb); + mSessionBinder.sendCommand(mContext.getPackageName(), command, args, cb); } catch (RemoteException e) { Log.d(TAG, "Dead object in sendCommand.", e); } @@ -600,7 +600,7 @@ public final class MediaController { */ public void prepare() { try { - mSessionBinder.prepare(); + mSessionBinder.prepare(mContext.getPackageName()); } catch (RemoteException e) { Log.wtf(TAG, "Error calling prepare.", e); } @@ -624,7 +624,7 @@ public final class MediaController { "You must specify a non-empty String for prepareFromMediaId."); } try { - mSessionBinder.prepareFromMediaId(mediaId, extras); + mSessionBinder.prepareFromMediaId(mContext.getPackageName(), mediaId, extras); } catch (RemoteException e) { Log.wtf(TAG, "Error calling prepare(" + mediaId + ").", e); } @@ -650,7 +650,7 @@ public final class MediaController { query = ""; } try { - mSessionBinder.prepareFromSearch(query, extras); + mSessionBinder.prepareFromSearch(mContext.getPackageName(), query, extras); } catch (RemoteException e) { Log.wtf(TAG, "Error calling prepare(" + query + ").", e); } @@ -674,7 +674,7 @@ public final class MediaController { "You must specify a non-empty Uri for prepareFromUri."); } try { - mSessionBinder.prepareFromUri(uri, extras); + mSessionBinder.prepareFromUri(mContext.getPackageName(), uri, extras); } catch (RemoteException e) { Log.wtf(TAG, "Error calling prepare(" + uri + ").", e); } @@ -685,7 +685,7 @@ public final class MediaController { */ public void play() { try { - mSessionBinder.play(); + mSessionBinder.play(mContext.getPackageName()); } catch (RemoteException e) { Log.wtf(TAG, "Error calling play.", e); } @@ -704,7 +704,7 @@ public final class MediaController { "You must specify a non-empty String for playFromMediaId."); } try { - mSessionBinder.playFromMediaId(mediaId, extras); + mSessionBinder.playFromMediaId(mContext.getPackageName(), mediaId, extras); } catch (RemoteException e) { Log.wtf(TAG, "Error calling play(" + mediaId + ").", e); } @@ -726,7 +726,7 @@ public final class MediaController { query = ""; } try { - mSessionBinder.playFromSearch(query, extras); + mSessionBinder.playFromSearch(mContext.getPackageName(), query, extras); } catch (RemoteException e) { Log.wtf(TAG, "Error calling play(" + query + ").", e); } @@ -745,7 +745,7 @@ public final class MediaController { "You must specify a non-empty Uri for playFromUri."); } try { - mSessionBinder.playFromUri(uri, extras); + mSessionBinder.playFromUri(mContext.getPackageName(), uri, extras); } catch (RemoteException e) { Log.wtf(TAG, "Error calling play(" + uri + ").", e); } @@ -757,7 +757,7 @@ public final class MediaController { */ public void skipToQueueItem(long id) { try { - mSessionBinder.skipToQueueItem(id); + mSessionBinder.skipToQueueItem(mContext.getPackageName(), id); } catch (RemoteException e) { Log.wtf(TAG, "Error calling skipToItem(" + id + ").", e); } @@ -769,7 +769,7 @@ public final class MediaController { */ public void pause() { try { - mSessionBinder.pause(); + mSessionBinder.pause(mContext.getPackageName()); } catch (RemoteException e) { Log.wtf(TAG, "Error calling pause.", e); } @@ -781,7 +781,7 @@ public final class MediaController { */ public void stop() { try { - mSessionBinder.stop(); + mSessionBinder.stop(mContext.getPackageName()); } catch (RemoteException e) { Log.wtf(TAG, "Error calling stop.", e); } @@ -794,7 +794,7 @@ public final class MediaController { */ public void seekTo(long pos) { try { - mSessionBinder.seekTo(pos); + mSessionBinder.seekTo(mContext.getPackageName(), pos); } catch (RemoteException e) { Log.wtf(TAG, "Error calling seekTo.", e); } @@ -806,7 +806,7 @@ public final class MediaController { */ public void fastForward() { try { - mSessionBinder.fastForward(); + mSessionBinder.fastForward(mContext.getPackageName()); } catch (RemoteException e) { Log.wtf(TAG, "Error calling fastForward.", e); } @@ -817,7 +817,7 @@ public final class MediaController { */ public void skipToNext() { try { - mSessionBinder.next(); + mSessionBinder.next(mContext.getPackageName()); } catch (RemoteException e) { Log.wtf(TAG, "Error calling next.", e); } @@ -829,7 +829,7 @@ public final class MediaController { */ public void rewind() { try { - mSessionBinder.rewind(); + mSessionBinder.rewind(mContext.getPackageName()); } catch (RemoteException e) { Log.wtf(TAG, "Error calling rewind.", e); } @@ -840,7 +840,7 @@ public final class MediaController { */ public void skipToPrevious() { try { - mSessionBinder.previous(); + mSessionBinder.previous(mContext.getPackageName()); } catch (RemoteException e) { Log.wtf(TAG, "Error calling previous.", e); } @@ -855,7 +855,7 @@ public final class MediaController { */ public void setRating(Rating rating) { try { - mSessionBinder.rate(rating); + mSessionBinder.rate(mContext.getPackageName(), rating); } catch (RemoteException e) { Log.wtf(TAG, "Error calling rate.", e); } @@ -890,7 +890,7 @@ public final class MediaController { throw new IllegalArgumentException("CustomAction cannot be null."); } try { - mSessionBinder.sendCustomAction(action, args); + mSessionBinder.sendCustomAction(mContext.getPackageName(), action, args); } catch (RemoteException e) { Log.d(TAG, "Dead object in sendCustomAction.", e); } diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index b8d01c44348b..5e8b8caf3ef6 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -39,6 +39,7 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.UserHandle; +import android.media.session.MediaSessionManager.RemoteUserInfo; import android.service.media.MediaBrowserService; import android.text.TextUtils; import android.util.Log; @@ -103,6 +104,16 @@ public final class MediaSession { */ public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 1 << 16; + /** + * @hide + */ + public static final int INVALID_UID = -1; + + /** + * @hide + */ + public static final int INVALID_PID = -1; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, value = { @@ -501,6 +512,22 @@ public final class MediaSession { } /** + * Gets the controller information who sent the current request. + * <p> + * Note: This is only valid while in a request callback, such as {@link Callback#onPlay}. + * + * @throws IllegalStateException If this method is called outside of {@link Callback} methods. + * @see MediaSessionManager#isTrustedForMediaControl(RemoteUserInfo) + */ + public final @NonNull RemoteUserInfo getCurrentControllerInfo() { + if (mCallback == null || mCallback.mCurrentControllerInfo == null) { + throw new IllegalStateException( + "This should be called inside of MediaSession.Callback methods"); + } + return mCallback.mCurrentControllerInfo; + } + + /** * Notify the system that the remote volume changed. * * @param provider The provider that is handling volume changes. @@ -528,16 +555,14 @@ public final class MediaSession { * @hide */ public String getCallingPackage() { - try { - return mBinder.getCallingPackage(); - } catch (RemoteException e) { - Log.wtf(TAG, "Dead object in getCallingPackage.", e); + if (mCallback != null) { + return mCallback.mCurrentControllerInfo.getPackageName(); } return null; } - private void dispatchPrepare() { - postToCallback(CallbackMessageHandler.MSG_PREPARE); + private void dispatchPrepare(Bundle extras) { + postToCallback(CallbackMessageHandler.MSG_PREPARE, null, extras); } private void dispatchPrepareFromMediaId(String mediaId, Bundle extras) { @@ -552,8 +577,8 @@ public final class MediaSession { postToCallback(CallbackMessageHandler.MSG_PREPARE_URI, uri, extras); } - private void dispatchPlay() { - postToCallback(CallbackMessageHandler.MSG_PLAY); + private void dispatchPlay(Bundle extras) { + postToCallback(CallbackMessageHandler.MSG_PLAY, null, extras); } private void dispatchPlayFromMediaId(String mediaId, Bundle extras) { @@ -568,69 +593,61 @@ public final class MediaSession { postToCallback(CallbackMessageHandler.MSG_PLAY_URI, uri, extras); } - private void dispatchSkipToItem(long id) { - postToCallback(CallbackMessageHandler.MSG_SKIP_TO_ITEM, id); + private void dispatchSkipToItem(long id, Bundle extras) { + postToCallback(CallbackMessageHandler.MSG_SKIP_TO_ITEM, id, extras); } - private void dispatchPause() { - postToCallback(CallbackMessageHandler.MSG_PAUSE); + private void dispatchPause(Bundle extras) { + postToCallback(CallbackMessageHandler.MSG_PAUSE, null, extras); } - private void dispatchStop() { - postToCallback(CallbackMessageHandler.MSG_STOP); + private void dispatchStop(Bundle extras) { + postToCallback(CallbackMessageHandler.MSG_STOP, null, extras); } - private void dispatchNext() { - postToCallback(CallbackMessageHandler.MSG_NEXT); + private void dispatchNext(Bundle extras) { + postToCallback(CallbackMessageHandler.MSG_NEXT, null, extras); } - private void dispatchPrevious() { - postToCallback(CallbackMessageHandler.MSG_PREVIOUS); + private void dispatchPrevious(Bundle extras) { + postToCallback(CallbackMessageHandler.MSG_PREVIOUS, null, extras); } - private void dispatchFastForward() { - postToCallback(CallbackMessageHandler.MSG_FAST_FORWARD); + private void dispatchFastForward(Bundle extras) { + postToCallback(CallbackMessageHandler.MSG_FAST_FORWARD, null, extras); } - private void dispatchRewind() { - postToCallback(CallbackMessageHandler.MSG_REWIND); + private void dispatchRewind(Bundle extras) { + postToCallback(CallbackMessageHandler.MSG_REWIND, null, extras); } - private void dispatchSeekTo(long pos) { - postToCallback(CallbackMessageHandler.MSG_SEEK_TO, pos); + private void dispatchSeekTo(long pos, Bundle extras) { + postToCallback(CallbackMessageHandler.MSG_SEEK_TO, pos, extras); } - private void dispatchRate(Rating rating) { - postToCallback(CallbackMessageHandler.MSG_RATE, rating); + private void dispatchRate(Rating rating, Bundle extras) { + postToCallback(CallbackMessageHandler.MSG_RATE, rating, extras); } - private void dispatchCustomAction(String action, Bundle args) { - postToCallback(CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args); + private void dispatchCustomAction(String action, Bundle extras) { + postToCallback(CallbackMessageHandler.MSG_CUSTOM_ACTION, action, extras); } - private void dispatchMediaButton(Intent mediaButtonIntent) { - postToCallback(CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent); + private void dispatchMediaButton(Intent mediaButtonIntent, Bundle extras) { + postToCallback(CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent, extras); } - private void dispatchAdjustVolume(int direction) { - postToCallback(CallbackMessageHandler.MSG_ADJUST_VOLUME, direction); + private void dispatchAdjustVolume(int direction, Bundle extras) { + postToCallback(CallbackMessageHandler.MSG_ADJUST_VOLUME, direction, extras); } - private void dispatchSetVolumeTo(int volume) { - postToCallback(CallbackMessageHandler.MSG_SET_VOLUME, volume); + private void dispatchSetVolumeTo(int volume, Bundle extras) { + postToCallback(CallbackMessageHandler.MSG_SET_VOLUME, volume, extras); } - private void postToCallback(int what) { - postToCallback(what, null); - } - - private void postCommand(String command, Bundle args, ResultReceiver resultCb) { + private void postCommand(String command, Bundle args, ResultReceiver resultCb, Bundle extras) { Command cmd = new Command(command, args, resultCb); - postToCallback(CallbackMessageHandler.MSG_COMMAND, cmd); - } - - private void postToCallback(int what, Object obj) { - postToCallback(what, obj, null); + postToCallback(CallbackMessageHandler.MSG_COMMAND, cmd, extras); } private void postToCallback(int what, Object obj, Bundle extras) { @@ -734,9 +751,13 @@ public final class MediaSession { * and the system. A callback may be set using {@link #setCallback}. */ public abstract static class Callback { + private MediaSession mSession; private CallbackMessageHandler mHandler; private boolean mMediaPlayPauseKeyPending; + private String mCallingPackage; + private int mCallingPid; + private int mCallingUid; public Callback() { } @@ -1023,24 +1044,26 @@ public final class MediaSession { private WeakReference<MediaSession> mMediaSession; public CallbackStub(MediaSession session) { - mMediaSession = new WeakReference<MediaSession>(session); + mMediaSession = new WeakReference<>(session); } @Override - public void onCommand(String command, Bundle args, ResultReceiver cb) { + public void onCommand(String packageName, int pid, int uid, String command, Bundle args, + ResultReceiver cb) { MediaSession session = mMediaSession.get(); if (session != null) { - session.postCommand(command, args, cb); + session.postCommand(command, args, cb, createExtraBundle(packageName, pid, uid)); } } @Override - public void onMediaButton(Intent mediaButtonIntent, int sequenceNumber, - ResultReceiver cb) { + public void onMediaButton(String packageName, int pid, int uid, Intent mediaButtonIntent, + int sequenceNumber, ResultReceiver cb) { MediaSession session = mMediaSession.get(); try { if (session != null) { - session.dispatchMediaButton(mediaButtonIntent); + session.dispatchMediaButton( + mediaButtonIntent, createExtraBundle(packageName, pid, uid)); } } finally { if (cb != null) { @@ -1050,165 +1073,191 @@ public final class MediaSession { } @Override - public void onPrepare() { + public void onPrepare(String packageName, int pid, int uid) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchPrepare(); + session.dispatchPrepare(createExtraBundle(packageName, pid, uid)); } } @Override - public void onPrepareFromMediaId(String mediaId, Bundle extras) { + public void onPrepareFromMediaId(String packageName, int pid, int uid, String mediaId, + Bundle extras) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchPrepareFromMediaId(mediaId, extras); + session.dispatchPrepareFromMediaId( + mediaId, createExtraBundle(packageName, pid, uid, extras)); } } @Override - public void onPrepareFromSearch(String query, Bundle extras) { + public void onPrepareFromSearch(String packageName, int pid, int uid, String query, + Bundle extras) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchPrepareFromSearch(query, extras); + session.dispatchPrepareFromSearch( + query, createExtraBundle(packageName, pid, uid, extras)); } } @Override - public void onPrepareFromUri(Uri uri, Bundle extras) { + public void onPrepareFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchPrepareFromUri(uri, extras); + session.dispatchPrepareFromUri(uri, + createExtraBundle(packageName, pid, uid, extras)); } } @Override - public void onPlay() { + public void onPlay(String packageName, int pid, int uid) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchPlay(); + session.dispatchPlay(createExtraBundle(packageName, pid, uid)); } } @Override - public void onPlayFromMediaId(String mediaId, Bundle extras) { + public void onPlayFromMediaId(String packageName, int pid, int uid, String mediaId, + Bundle extras) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchPlayFromMediaId(mediaId, extras); + session.dispatchPlayFromMediaId( + mediaId, createExtraBundle(packageName, pid, uid, extras)); } } @Override - public void onPlayFromSearch(String query, Bundle extras) { + public void onPlayFromSearch(String packageName, int pid, int uid, String query, + Bundle extras) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchPlayFromSearch(query, extras); + session.dispatchPlayFromSearch(query, createExtraBundle(packageName, pid, uid, + extras)); } } @Override - public void onPlayFromUri(Uri uri, Bundle extras) { + public void onPlayFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchPlayFromUri(uri, extras); + session.dispatchPlayFromUri(uri, createExtraBundle(packageName, pid, uid, extras)); } } @Override - public void onSkipToTrack(long id) { + public void onSkipToTrack(String packageName, int pid, int uid, long id) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchSkipToItem(id); + session.dispatchSkipToItem(id, createExtraBundle(packageName, pid, uid)); } } @Override - public void onPause() { + public void onPause(String packageName, int pid, int uid) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchPause(); + session.dispatchPause(createExtraBundle(packageName, pid, uid)); } } @Override - public void onStop() { + public void onStop(String packageName, int pid, int uid) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchStop(); + session.dispatchStop(createExtraBundle(packageName, pid, uid)); } } @Override - public void onNext() { + public void onNext(String packageName, int pid, int uid) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchNext(); + session.dispatchNext(createExtraBundle(packageName, pid, uid)); } } @Override - public void onPrevious() { + public void onPrevious(String packageName, int pid, int uid) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchPrevious(); + session.dispatchPrevious(createExtraBundle(packageName, pid, uid)); } } @Override - public void onFastForward() { + public void onFastForward(String packageName, int pid, int uid) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchFastForward(); + session.dispatchFastForward(createExtraBundle(packageName, pid, uid)); } } @Override - public void onRewind() { + public void onRewind(String packageName, int pid, int uid) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchRewind(); + session.dispatchRewind(createExtraBundle(packageName, pid, uid)); } } @Override - public void onSeekTo(long pos) { + public void onSeekTo(String packageName, int pid, int uid, long pos) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchSeekTo(pos); + session.dispatchSeekTo(pos, createExtraBundle(packageName, pid, uid)); } } @Override - public void onRate(Rating rating) { + public void onRate(String packageName, int pid, int uid, Rating rating) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchRate(rating); + session.dispatchRate(rating, createExtraBundle(packageName, pid, uid)); } } @Override - public void onCustomAction(String action, Bundle args) { + public void onCustomAction(String packageName, int pid, int uid, String action, + Bundle args) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchCustomAction(action, args); + session.dispatchCustomAction( + action, createExtraBundle(packageName, pid, uid, args)); } } @Override - public void onAdjustVolume(int direction) { + public void onAdjustVolume(String packageName, int pid, int uid, int direction) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchAdjustVolume(direction); + session.dispatchAdjustVolume(direction, createExtraBundle(packageName, pid, uid)); } } @Override - public void onSetVolumeTo(int value) { + public void onSetVolumeTo(String packageName, int pid, int uid, int value) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchSetVolumeTo(value); + session.dispatchSetVolumeTo(value, createExtraBundle(packageName, pid, uid)); } } + private Bundle createExtraBundle(String packageName, int pid, int uid) { + return createExtraBundle(packageName, pid, uid, null); + } + + private Bundle createExtraBundle(String packageName, int pid, int uid, + Bundle originalBundle) { + Bundle bundle = new Bundle(); + bundle.putString(CallbackMessageHandler.EXTRA_KEY_CALLING_PACKAGE, packageName); + bundle.putInt(CallbackMessageHandler.EXTRA_KEY_CALLING_PID, pid); + bundle.putInt(CallbackMessageHandler.EXTRA_KEY_CALLING_UID, uid); + if (originalBundle != null) { + bundle.putBundle(CallbackMessageHandler.EXTRA_KEY_ORIGINAL_BUNDLE, originalBundle); + } + return bundle; + } } /** @@ -1272,7 +1321,8 @@ public final class MediaSession { return 0; } - public static final Creator<MediaSession.QueueItem> CREATOR = new Creator<MediaSession.QueueItem>() { + public static final Creator<MediaSession.QueueItem> CREATOR = + new Creator<MediaSession.QueueItem>() { @Override public MediaSession.QueueItem createFromParcel(Parcel p) { @@ -1329,6 +1379,15 @@ public final class MediaSession { private class CallbackMessageHandler extends Handler { + private static final String EXTRA_KEY_CALLING_PACKAGE = + "android.media.session.extra.CALLING_PACKAGE"; + private static final String EXTRA_KEY_CALLING_PID = + "android.media.session.extra.CALLING_PID"; + private static final String EXTRA_KEY_CALLING_UID = + "android.media.session.extra.CALLING_UID"; + private static final String EXTRA_KEY_ORIGINAL_BUNDLE = + "android.media.session.extra.ORIGINAL_BUNDLE"; + private static final int MSG_COMMAND = 1; private static final int MSG_MEDIA_BUTTON = 2; private static final int MSG_PREPARE = 3; @@ -1355,6 +1414,8 @@ public final class MediaSession { private MediaSession.Callback mCallback; + private RemoteUserInfo mCurrentControllerInfo; + public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) { super(looper, null, true); mCallback = callback; @@ -1367,21 +1428,17 @@ public final class MediaSession { msg.sendToTarget(); } - public void post(int what, Object obj) { - obtainMessage(what, obj).sendToTarget(); - } - - public void post(int what) { - post(what, null); - } - - public void post(int what, Object obj, int arg1) { - obtainMessage(what, arg1, 0, obj).sendToTarget(); - } - @Override public void handleMessage(Message msg) { VolumeProvider vp; + Bundle bundle = msg.getData(); + Bundle originalBundle = bundle.getBundle(EXTRA_KEY_ORIGINAL_BUNDLE); + + mCurrentControllerInfo = new RemoteUserInfo( + bundle.getString(EXTRA_KEY_CALLING_PACKAGE), + bundle.getInt(EXTRA_KEY_CALLING_PID, INVALID_PID), + bundle.getInt(EXTRA_KEY_CALLING_UID, INVALID_UID)); + switch (msg.what) { case MSG_COMMAND: Command cmd = (Command) msg.obj; @@ -1394,25 +1451,25 @@ public final class MediaSession { mCallback.onPrepare(); break; case MSG_PREPARE_MEDIA_ID: - mCallback.onPrepareFromMediaId((String) msg.obj, msg.getData()); + mCallback.onPrepareFromMediaId((String) msg.obj, originalBundle); break; case MSG_PREPARE_SEARCH: - mCallback.onPrepareFromSearch((String) msg.obj, msg.getData()); + mCallback.onPrepareFromSearch((String) msg.obj, originalBundle); break; case MSG_PREPARE_URI: - mCallback.onPrepareFromUri((Uri) msg.obj, msg.getData()); + mCallback.onPrepareFromUri((Uri) msg.obj, originalBundle); break; case MSG_PLAY: mCallback.onPlay(); break; case MSG_PLAY_MEDIA_ID: - mCallback.onPlayFromMediaId((String) msg.obj, msg.getData()); + mCallback.onPlayFromMediaId((String) msg.obj, originalBundle); break; case MSG_PLAY_SEARCH: - mCallback.onPlayFromSearch((String) msg.obj, msg.getData()); + mCallback.onPlayFromSearch((String) msg.obj, originalBundle); break; case MSG_PLAY_URI: - mCallback.onPlayFromUri((Uri) msg.obj, msg.getData()); + mCallback.onPlayFromUri((Uri) msg.obj, originalBundle); break; case MSG_SKIP_TO_ITEM: mCallback.onSkipToQueueItem((Long) msg.obj); @@ -1442,7 +1499,7 @@ public final class MediaSession { mCallback.onSetRating((Rating) msg.obj); break; case MSG_CUSTOM_ACTION: - mCallback.onCustomAction((String) msg.obj, msg.getData()); + mCallback.onCustomAction((String) msg.obj, originalBundle); break; case MSG_ADJUST_VOLUME: synchronized (mLock) { @@ -1464,6 +1521,7 @@ public final class MediaSession { mCallback.handleMediaPlayPauseKeySingleTapIfPending(); break; } + mCurrentControllerInfo = null; } } } diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index 3b12fca32ecb..519af1ba14a5 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -37,7 +37,9 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.UserHandle; +import android.service.media.MediaBrowserService; import android.service.notification.NotificationListenerService; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.view.KeyEvent; @@ -340,19 +342,20 @@ public final class MediaSessionManager { } /** - * Returns whether the api + * Returns whether the app is trusted. + * <p> + * An app is trusted if the app holds the android.Manifest.permission.MEDIA_CONTENT_CONTROL + * permission or has an enabled notification listener. * - * @param packageName packageName - * @param pid pid of the app - * @param uid uid of the app - * @hide + * @param userInfo The remote user info */ - public boolean isTrusted(@NonNull String packageName, int pid, int uid) { - if (packageName == null) { + public boolean isTrustedForMediaControl(RemoteUserInfo userInfo) { + if (userInfo.getPackageName() == null) { return false; } try { - return mService.isTrusted(packageName, pid, uid); + return mService.isTrusted( + userInfo.getPackageName(), userInfo.getPid(), userInfo.getUid()); } catch (RemoteException e) { Log.wtf(TAG, "Cannot communicate with the service.", e); } @@ -763,6 +766,56 @@ public final class MediaSessionManager { public abstract void onAddressedPlayerChanged(ComponentName mediaButtonReceiver); } + /** + * Information of a remote user of {@link MediaSession} or {@link MediaBrowserService}. + * This can be used to decide whether the remote user is trusted app. + * + * @see #isTrustedForMediaControl(RemoteUserInfo) + */ + public static final class RemoteUserInfo { + private String mPackageName; + private int mPid; + private int mUid; + + public RemoteUserInfo(String packageName, int pid, int uid) { + mPackageName = packageName; + mPid = pid; + mUid = uid; + } + + /** + * @return package name of the controller + */ + public String getPackageName() { + return mPackageName; + } + + /** + * @return pid of the controller + */ + public int getPid() { + return mPid; + } + + /** + * @return uid of the controller + */ + public int getUid() { + return mUid; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof RemoteUserInfo)) { + return false; + } + RemoteUserInfo otherUserInfo = (RemoteUserInfo) obj; + return TextUtils.equals(mPackageName, otherUserInfo.mPackageName) + && mPid == otherUserInfo.mPid + && mUid == otherUserInfo.mUid; + } + } + private static final class SessionsChangedWrapper { private Context mContext; private OnActiveSessionsChangedListener mListener; diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java index 4fc43ea78537..6d1bd4522c37 100644 --- a/media/java/android/service/media/MediaBrowserService.java +++ b/media/java/android/service/media/MediaBrowserService.java @@ -31,6 +31,8 @@ import android.media.session.MediaSession; import android.os.Binder; import android.os.Bundle; import android.os.Handler; +import android.media.session.MediaSessionManager; +import android.media.session.MediaSessionManager.RemoteUserInfo; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; @@ -112,6 +114,8 @@ public abstract class MediaBrowserService extends Service { */ private class ConnectionRecord implements IBinder.DeathRecipient { String pkg; + int uid; + int pid; Bundle rootHints; IMediaBrowserServiceCallbacks callbacks; BrowserRoot root; @@ -199,6 +203,7 @@ public abstract class MediaBrowserService extends Service { public void connect(final String pkg, final Bundle rootHints, final IMediaBrowserServiceCallbacks callbacks) { + final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); if (!isValidPackage(pkg, uid)) { throw new IllegalArgumentException("Package/uid mismatch: uid=" + uid @@ -215,9 +220,14 @@ public abstract class MediaBrowserService extends Service { final ConnectionRecord connection = new ConnectionRecord(); connection.pkg = pkg; + connection.pid = pid; + connection.uid = uid; connection.rootHints = rootHints; connection.callbacks = callbacks; + + mCurConnection = connection; connection.root = MediaBrowserService.this.onGetRoot(pkg, uid, rootHints); + mCurConnection = null; // If they didn't return something, don't allow this client. if (connection.root == null) { @@ -505,21 +515,36 @@ public abstract class MediaBrowserService extends Service { * media browser service when connecting and retrieving the root id for browsing, or null if * none. The contents of this bundle may affect the information returned when browsing. * - * @throws IllegalStateException If this method is called outside of {@link #onLoadChildren} or - * {@link #onLoadItem}. + * @throws IllegalStateException If this method is called outside of {@link #onGetRoot} or + * {@link #onLoadChildren} or {@link #onLoadItem}. * @see MediaBrowserService.BrowserRoot#EXTRA_RECENT * @see MediaBrowserService.BrowserRoot#EXTRA_OFFLINE * @see MediaBrowserService.BrowserRoot#EXTRA_SUGGESTED */ public final Bundle getBrowserRootHints() { if (mCurConnection == null) { - throw new IllegalStateException("This should be called inside of onLoadChildren or" - + " onLoadItem methods"); + throw new IllegalStateException("This should be called inside of onGetRoot or" + + " onLoadChildren or onLoadItem methods"); } return mCurConnection.rootHints == null ? null : new Bundle(mCurConnection.rootHints); } /** + * Gets the browser information who sent the current request. + * + * @throws IllegalStateException If this method is called outside of {@link #onGetRoot} or + * {@link #onLoadChildren} or {@link #onLoadItem}. + * @see MediaSessionManager#isTrustedForMediaControl(RemoteUserInfo) + */ + public final RemoteUserInfo getCurrentBrowserInfo() { + if (mCurConnection == null) { + throw new IllegalStateException("This should be called inside of onGetRoot or" + + " onLoadChildren or onLoadItem methods"); + } + return new RemoteUserInfo(mCurConnection.pkg, mCurConnection.pid, mCurConnection.uid); + } + + /** * Notifies all connected media browsers that the children of * the specified parent id have changed in some way. * This will cause browsers to fetch subscribed content again. diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index 4c20f050087d..54541f02f575 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -729,15 +729,15 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) { GET_STATIC_FIELD_ID(field, clazz, "SECURITY_LEVEL_UNKNOWN", "I"); gSecurityLevels.kSecurityLevelUnknown = env->GetStaticIntField(clazz, field); - GET_STATIC_FIELD_ID(field, clazz, "SW_SECURE_CRYPTO", "I"); + GET_STATIC_FIELD_ID(field, clazz, "SECURITY_LEVEL_SW_SECURE_CRYPTO", "I"); gSecurityLevels.kSecurityLevelSwSecureCrypto = env->GetStaticIntField(clazz, field); - GET_STATIC_FIELD_ID(field, clazz, "SW_SECURE_DECODE", "I"); + GET_STATIC_FIELD_ID(field, clazz, "SECURITY_LEVEL_SW_SECURE_DECODE", "I"); gSecurityLevels.kSecurityLevelSwSecureDecode = env->GetStaticIntField(clazz, field); - GET_STATIC_FIELD_ID(field, clazz, "HW_SECURE_CRYPTO", "I"); + GET_STATIC_FIELD_ID(field, clazz, "SECURITY_LEVEL_HW_SECURE_CRYPTO", "I"); gSecurityLevels.kSecurityLevelHwSecureCrypto = env->GetStaticIntField(clazz, field); - GET_STATIC_FIELD_ID(field, clazz, "HW_SECURE_DECODE", "I"); + GET_STATIC_FIELD_ID(field, clazz, "SECURITY_LEVEL_HW_SECURE_DECODE", "I"); gSecurityLevels.kSecurityLevelHwSecureDecode = env->GetStaticIntField(clazz, field); - GET_STATIC_FIELD_ID(field, clazz, "HW_SECURE_ALL", "I"); + GET_STATIC_FIELD_ID(field, clazz, "SECURITY_LEVEL_HW_SECURE_ALL", "I"); gSecurityLevels.kSecurityLevelHwSecureAll = env->GetStaticIntField(clazz, field); jmethodID getMaxSecurityLevel; diff --git a/packages/MtpDocumentsProvider/perf_tests/Android.mk b/packages/MtpDocumentsProvider/perf_tests/Android.mk index 6504af12db23..e873157dcf34 100644 --- a/packages/MtpDocumentsProvider/perf_tests/Android.mk +++ b/packages/MtpDocumentsProvider/perf_tests/Android.mk @@ -8,5 +8,6 @@ LOCAL_PACKAGE_NAME := MtpDocumentsProviderPerfTests LOCAL_PRIVATE_PLATFORM_APIS := true LOCAL_INSTRUMENTATION_FOR := MtpDocumentsProvider LOCAL_CERTIFICATE := media +LOCAL_COMPATIBILITY_SUITE += device-tests include $(BUILD_PACKAGE) diff --git a/packages/MtpDocumentsProvider/perf_tests/AndroidTest.xml b/packages/MtpDocumentsProvider/perf_tests/AndroidTest.xml new file mode 100644 index 000000000000..8b7292b625e1 --- /dev/null +++ b/packages/MtpDocumentsProvider/perf_tests/AndroidTest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Runs MtpDocumentsProviderPerfTests metric instrumentation."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-metric-instrumentation" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="MtpDocumentsProviderPerfTests.apk" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.mtp.perftests" /> + </test> +</configuration> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 82bc6af870f0..6368607d7bdd 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -543,30 +543,30 @@ <!-- UI debug setting: Select Bluetooth AVRCP Version --> <string name="bluetooth_select_avrcp_version_dialog_title">Select Bluetooth AVRCP Version</string> - <!-- UI debug setting: Select Bluetooth Audio Codec --> + <!-- UI debug setting: Trigger Bluetooth Audio Codec Selection --> <string name="bluetooth_select_a2dp_codec_type">Bluetooth Audio Codec</string> - <!-- UI debug setting: Select Bluetooth Audio Codec --> - <string name="bluetooth_select_a2dp_codec_type_dialog_title">Select Bluetooth Audio Codec</string> + <!-- UI debug setting: Trigger Bluetooth Audio Codec Selection --> + <string name="bluetooth_select_a2dp_codec_type_dialog_title">Trigger Bluetooth Audio Codec\u000ASelection</string> - <!-- UI debug setting: Select Bluetooth Audio Sample Rate --> + <!-- UI debug setting: Trigger Bluetooth Audio Sample Rate Selection --> <string name="bluetooth_select_a2dp_codec_sample_rate">Bluetooth Audio Sample Rate</string> - <!-- UI debug setting: Select Bluetooth Audio Codec: Sample Rate --> - <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title">Select Bluetooth Audio Codec:\u000ASample Rate</string> + <!-- UI debug setting: Trigger Bluetooth Audio Codec Selection: Sample Rate --> + <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title">Trigger Bluetooth Audio Codec\u000ASelection: Sample Rate</string> - <!-- UI debug setting: Select Bluetooth Audio Bits Per Sample --> + <!-- UI debug setting: Trigger Bluetooth Audio Bits Per Sample Selection --> <string name="bluetooth_select_a2dp_codec_bits_per_sample">Bluetooth Audio Bits Per Sample</string> - <!-- UI debug setting: Select Bluetooth Audio Codec: Bits Per Sample --> - <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title">Select Bluetooth Audio Codec:\u000ABits Per Sample</string> + <!-- UI debug setting: Trigger Bluetooth Audio Codec Selection: Bits Per Sample --> + <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title">Trigger Bluetooth Audio Codec\u000ASelection: Bits Per Sample</string> - <!-- UI debug setting: Select Bluetooth Audio Channel Mode --> + <!-- UI debug setting: Trigger Bluetooth Audio Channel Mode Selection --> <string name="bluetooth_select_a2dp_codec_channel_mode">Bluetooth Audio Channel Mode</string> - <!-- UI debug setting: Select Bluetooth Audio Codec: Channel Mode --> - <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title">Select Bluetooth Audio Codec:\u000AChannel Mode</string> + <!-- UI debug setting: Trigger Bluetooth Audio Codec Selection: Channel Mode --> + <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title">Trigger Bluetooth Audio Codec\u000ASelection: Channel Mode</string> - <!-- UI debug setting: Select Bluetooth Audio LDAC Playback Quality --> + <!-- UI debug setting: Trigger Bluetooth Audio LDAC Playback Quality Selection --> <string name="bluetooth_select_a2dp_codec_ldac_playback_quality">Bluetooth Audio LDAC Codec: Playback Quality</string> <!-- UI debug setting: Select Bluetooth Audio LDAC Codec: LDAC Playback Quality --> - <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title">Select Bluetooth Audio LDAC Codec:\u000APlayback Quality</string> + <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title">Trigger Bluetooth Audio LDAC Codec\u000ASelection: Playback Quality</string> <!-- [CHAR LIMIT=NONE] Label for displaying Bluetooth Audio Codec Parameters while streaming --> <string name="bluetooth_select_a2dp_codec_streaming_label">Streaming: <xliff:g id="streaming_parameter">%1$s</xliff:g></string> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java index ee1219126fe3..f9f80eb0d3c4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java @@ -163,6 +163,11 @@ public class HeadsetProfile implements LocalBluetoothProfile { return mService.getActiveDevice(); } + public boolean isAudioOn() { + if (mService == null) return false; + return mService.isAudioOn(); + } + public boolean isPreferred(BluetoothDevice device) { if (mService == null) return false; return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java index 28833a349372..835ff07c4006 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java @@ -22,8 +22,9 @@ import android.content.Intent; import android.os.PowerManager; import android.provider.Settings.Global; import android.provider.Settings.Secure; -import android.support.annotation.VisibleForTesting; +import android.util.KeyValueListParser; import android.util.Log; +import android.util.Slog; /** * Utilities related to battery saver. @@ -48,13 +49,35 @@ public class BatterySaverUtils { public static final String ACTION_SHOW_AUTO_SAVER_SUGGESTION = "PNW.autoSaverSuggestion"; - /** - * We show the auto battery saver suggestion notification when the user manually enables - * battery saver for the START_NTH time through the END_NTH time. - * (We won't show it for END_NTH + 1 time and after.) - */ - private static final int AUTO_SAVER_SUGGESTION_START_NTH = 4; - private static final int AUTO_SAVER_SUGGESTION_END_NTH = 8; + private static class Parameters { + private final Context mContext; + + /** + * We show the auto battery saver suggestion notification when the user manually enables + * battery saver for the START_NTH time through the END_NTH time. + * (We won't show it for END_NTH + 1 time and after.) + */ + private static final int AUTO_SAVER_SUGGESTION_START_NTH = 4; + private static final int AUTO_SAVER_SUGGESTION_END_NTH = 8; + + public final int startNth; + public final int endNth; + + public Parameters(Context context) { + mContext = context; + + final String newValue = Global.getString(mContext.getContentResolver(), + Global.LOW_POWER_MODE_SUGGESTION_PARAMS); + final KeyValueListParser parser = new KeyValueListParser(','); + try { + parser.setString(newValue); + } catch (IllegalArgumentException e) { + Slog.wtf(TAG, "Bad constants: " + newValue); + } + startNth = parser.getInt("start_nth", AUTO_SAVER_SUGGESTION_START_NTH); + endNth = parser.getInt("end_nth", AUTO_SAVER_SUGGESTION_END_NTH); + } + } /** * Enable / disable battery saver by user request. @@ -85,8 +108,10 @@ public class BatterySaverUtils { Secure.getInt(cr, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 0) + 1; Secure.putInt(cr, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, count); - if ((count >= AUTO_SAVER_SUGGESTION_START_NTH) - && (count <= AUTO_SAVER_SUGGESTION_END_NTH) + final Parameters parameters = new Parameters(context); + + if ((count >= parameters.startNth) + && (count <= parameters.endNth) && Global.getInt(cr, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0) == 0 && Secure.getInt(cr, Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, 0) == 0) { diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java index 4cd23f9eac38..9347674ff0ee 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java @@ -50,7 +50,9 @@ public class WifiStatusTracker extends ConnectivityManager.NetworkCallback { } }; private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder() - .clearCapabilities().addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build(); + .clearCapabilities() + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build(); private final ConnectivityManager.NetworkCallback mNetworkCallback = new ConnectivityManager .NetworkCallback() { @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index 8f80527036ec..a128b542d84e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -213,6 +213,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro mNetworkRequest = new NetworkRequest.Builder() .clearCapabilities() + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) .build(); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HeadsetProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HeadsetProfileTest.java new file mode 100644 index 000000000000..117f4472825c --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HeadsetProfileTest.java @@ -0,0 +1,60 @@ +package com.android.settingslib.bluetooth; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; +import android.content.Context; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class HeadsetProfileTest { + + @Mock + private LocalBluetoothAdapter mAdapter; + @Mock + private CachedBluetoothDeviceManager mDeviceManager; + @Mock + private LocalBluetoothProfileManager mProfileManager; + @Mock + private BluetoothHeadset mService; + + private BluetoothProfile.ServiceListener mServiceListener; + private HeadsetProfile mProfile; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + Context context = spy(RuntimeEnvironment.application); + + doAnswer((invocation) -> { + mServiceListener = (BluetoothProfile.ServiceListener) invocation.getArguments()[1]; + return null; + }).when(mAdapter).getProfileProxy(any(Context.class), any(), eq(BluetoothProfile.HEADSET)); + + mProfile = new HeadsetProfile(context, mAdapter, mDeviceManager, mProfileManager); + mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, mService); + } + + @Test + public void bluetoothProfile_shouldReturnTheAudioStatusFromBlueToothHeadsetService() { + when(mService.isAudioOn()).thenReturn(true); + assertThat(mProfile.isAudioOn()).isTrue(); + + when(mService.isAudioOn()).thenReturn(false); + assertThat(mProfile.isAudioOn()).isFalse(); + } +} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index a444ac8090d6..f43e719d47a3 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1724,6 +1724,9 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Secure.BACKUP_LOCAL_TRANSPORT_PARAMETERS, SecureSettingsProto.Backup.LOCAL_TRANSPORT_PARAMETERS); + dumpSetting(s, p, + Settings.Secure.PACKAGES_TO_CLEAR_DATA_BEFORE_FULL_RESTORE, + SecureSettingsProto.Backup.PACKAGES_TO_CLEAR_DATA_BEFORE_FULL_RESTORE); p.end(backupToken); // Settings.Secure.BLUETOOTH_ON intentionally excluded since it's deprecated. diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java index c8bcdaac1d4a..5f4cf033a45a 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java @@ -19,6 +19,7 @@ import android.content.ComponentName; import android.media.AudioManager; import android.media.AudioSystem; import android.os.Handler; +import android.os.VibrationEffect; import android.util.SparseArray; import com.android.systemui.plugins.VolumeDialogController.Callbacks; @@ -44,7 +45,8 @@ public interface VolumeDialogController { void setRingerMode(int ringerModeNormal, boolean external); boolean hasVibrator(); - void vibrate(); + void vibrate(VibrationEffect effect); + void scheduleTouchFeedback(); AudioManager getAudioManager(); diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java index 53f7e44bc25a..ad300f43d88d 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java @@ -50,4 +50,6 @@ public abstract class QSTileView extends LinearLayout { public abstract void onStateChanged(State state); public abstract int getDetailY(); + + public void setExpansion(float expansion) {} } diff --git a/packages/SystemUI/res/drawable/ic_1x_mobiledata.xml b/packages/SystemUI/res/drawable/ic_1x_mobiledata.xml index 726d814f7eb2..c0e0e59a88e2 100644 --- a/packages/SystemUI/res/drawable/ic_1x_mobiledata.xml +++ b/packages/SystemUI/res/drawable/ic_1x_mobiledata.xml @@ -14,17 +14,17 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="14dp" - android:height="24dp" - android:viewportWidth="14" - android:viewportHeight="24"> + android:width="14dp" + android:height="17dp" + android:viewportWidth="14" + android:viewportHeight="17"> <path android:fillColor="#FFFFFFFF" - android:pathData="M5.62,16.29H4.58V9.06l-1.79,0.8V8.88l2.67-1.17h0.16V16.29z" /> + android:pathData="M3.77,13.48H2.55V5.05L0.46,5.98V4.84l3.12-1.36h0.19V13.48z" /> <path android:fillColor="#FFFFFFFF" - android:pathData="M11.08,11.02l1.61-3.27h1.26l-2.22,4.23l2.27,4.3h-1.27l-1.64-3.33l-1.65,3.33H8.16l2.28-4.3L8.21,7.75h1.25L11.08,11.02z" /> + android:pathData="M10.14,7.34l1.87-3.81h1.47L10.9,8.46l2.65,5.02h-1.48l-1.91-3.88l-1.92,3.88H6.74L9.4,8.46l-2.6-4.94h1.46L10.14,7.34z" /> <path - android:pathData="M 0 0 H 13.99 V 24 H 0 V 0 Z" /> -</vector>
\ No newline at end of file + android:pathData="M 0 0 H 14 V 17 H 0 V 0 Z" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_3g_mobiledata.xml b/packages/SystemUI/res/drawable/ic_3g_mobiledata.xml index 7a539ffe3471..e4a5bf8a9130 100644 --- a/packages/SystemUI/res/drawable/ic_3g_mobiledata.xml +++ b/packages/SystemUI/res/drawable/ic_3g_mobiledata.xml @@ -14,17 +14,17 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="14dp" - android:height="24dp" - android:viewportWidth="14" - android:viewportHeight="24"> + android:width="14dp" + android:height="17dp" + android:viewportWidth="14" + android:viewportHeight="17"> <path android:fillColor="#FFFFFFFF" - android:pathData="M3.83,11.38h0.66c0.43,0,0.75-0.13,0.98-0.39s0.35-0.62,0.35-1.07c0-1-0.39-1.5-1.16-1.5c-0.37,0-0.66,0.13-0.87,0.4 S3.47,9.44,3.47,9.88H2.44c0-0.68,0.21-1.25,0.62-1.69s0.95-0.67,1.6-0.67c0.67,0,1.21,0.21,1.6,0.63s0.59,1.01,0.59,1.78 c0,0.39-0.1,0.76-0.31,1.1s-0.47,0.59-0.8,0.75c0.8,0.3,1.21,0.96,1.21,2c0,0.76-0.21,1.37-0.64,1.82s-0.98,0.68-1.66,0.68 c-0.68,0-1.22-0.21-1.64-0.64s-0.62-1-0.62-1.73h1.04c0,0.45,0.11,0.81,0.33,1.08s0.52,0.4,0.9,0.4c0.39,0,0.69-0.13,0.92-0.39 s0.34-0.66,0.34-1.2c0-1.04-0.49-1.55-1.47-1.55H3.83V11.38z" /> + android:pathData="M1.9,7.88h0.77c0.5,0,0.88-0.15,1.15-0.46s0.4-0.72,0.4-1.25c0-1.17-0.45-1.75-1.35-1.75c-0.43,0-0.77,0.16-1.02,0.47 S1.49,5.62,1.49,6.13h-1.2c0-0.8,0.24-1.46,0.73-1.97s1.11-0.78,1.86-0.78c0.78,0,1.41,0.25,1.87,0.73S5.43,5.31,5.43,6.2 c0,0.46-0.12,0.89-0.36,1.29S4.52,8.18,4.14,8.37c0.94,0.35,1.41,1.12,1.41,2.33c0,0.89-0.25,1.6-0.74,2.12 c-0.49,0.53-1.14,0.79-1.94,0.79c-0.79,0-1.43-0.25-1.91-0.75c-0.49-0.5-0.73-1.17-0.73-2.01h1.21c0,0.53,0.13,0.95,0.38,1.26 c0.26,0.31,0.6,0.47,1.05,0.47c0.45,0,0.81-0.15,1.08-0.46s0.4-0.77,0.4-1.39c0-1.21-0.57-1.81-1.72-1.81H1.9V7.88z" /> <path android:fillColor="#FFFFFFFF" - android:pathData="M14,15.11l-0.19,0.23c-0.54,0.63-1.33,0.94-2.37,0.94c-0.92,0-1.65-0.31-2.17-0.92s-0.79-1.48-0.81-2.59V11.1 c0-1.2,0.24-2.09,0.72-2.69s1.19-0.89,2.15-0.89c0.81,0,1.45,0.23,1.91,0.68s0.71,1.1,0.76,1.94h-1.07 c-0.04-0.53-0.19-0.95-0.44-1.25s-0.63-0.45-1.15-0.45c-0.61,0-1.06,0.2-1.35,0.6s-0.43,1.04-0.45,1.92v1.74 c0,0.86,0.16,1.52,0.49,1.98s0.8,0.69,1.41,0.69c0.58,0,1.02-0.14,1.32-0.42l0.16-0.15v-1.96h-1.56v-0.92H14V15.11z" /> + android:pathData="M13.77,12.24l-0.22,0.27c-0.63,0.73-1.55,1.1-2.76,1.1c-1.08,0-1.92-0.36-2.53-1.07c-0.61-0.71-0.93-1.72-0.94-3.02V7.56 c0-1.39,0.28-2.44,0.84-3.13s1.39-1.04,2.51-1.04c0.95,0,1.69,0.26,2.23,0.79s0.83,1.28,0.89,2.26h-1.25 C12.47,5.82,12.3,5.33,12,4.98s-0.74-0.52-1.34-0.52c-0.72,0-1.24,0.23-1.57,0.7S8.59,6.37,8.58,7.4v2.03c0,1,0.19,1.77,0.57,2.31 c0.38,0.54,0.93,0.8,1.65,0.8c0.67,0,1.19-0.16,1.54-0.49l0.18-0.17V9.59H10.7V8.52h3.07V12.24z" /> <path - android:pathData="M 0 0 H 14 V 24 H 0 V 0 Z" /> -</vector>
\ No newline at end of file + android:pathData="M 0 0 H 14 V 17 H 0 V 0 Z" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_4g_mobiledata.xml b/packages/SystemUI/res/drawable/ic_4g_mobiledata.xml index b2fab0c80d51..e98560b9c0a2 100644 --- a/packages/SystemUI/res/drawable/ic_4g_mobiledata.xml +++ b/packages/SystemUI/res/drawable/ic_4g_mobiledata.xml @@ -14,17 +14,17 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="14dp" - android:height="24dp" - android:viewportWidth="14" - android:viewportHeight="24"> + android:width="14dp" + android:height="17dp" + android:viewportWidth="14" + android:viewportHeight="17"> <path android:fillColor="#FFFFFFFF" - android:pathData="M6.42,13.3h0.95v0.88H6.42v1.98H5.38v-1.98H2.16v-0.64l3.17-5.91h1.09C6.42,7.63,6.42,13.3,6.42,13.3z M3.31,13.3h2.07 V9.25L3.31,13.3z" /> + android:pathData="M5.07,10.13h1.11v1.03H5.07v2.31H3.86v-2.31H0.1v-0.75l3.7-6.9h1.27V10.13z M1.44,10.13h2.42V5.4L1.44,10.13z" /> <path android:fillColor="#FFFFFFFF" - android:pathData="M13.99,15.11l-0.19,0.23c-0.54,0.63-1.33,0.94-2.37,0.94c-0.92,0-1.65-0.31-2.17-0.92s-0.79-1.48-0.8-2.59V11.1 c0-1.2,0.24-2.09,0.72-2.69s1.2-0.89,2.15-0.89c0.81,0,1.45,0.23,1.91,0.68s0.71,1.1,0.76,1.94h-1.07 c-0.04-0.53-0.19-0.95-0.44-1.25s-0.63-0.45-1.14-0.45c-0.61,0-1.06,0.2-1.35,0.6s-0.43,1.04-0.45,1.92v1.74 c0,0.86,0.16,1.52,0.49,1.98s0.8,0.69,1.41,0.69c0.58,0,1.02-0.14,1.32-0.42l0.16-0.15v-1.96h-1.56v-0.92h2.62V15.11z" /> + android:pathData="M13.9,12.24l-0.22,0.27c-0.63,0.73-1.55,1.1-2.76,1.1c-1.08,0-1.92-0.36-2.53-1.07c-0.61-0.71-0.93-1.72-0.94-3.02V7.56 c0-1.39,0.28-2.44,0.84-3.13s1.39-1.04,2.51-1.04c0.95,0,1.69,0.26,2.23,0.79s0.83,1.28,0.89,2.26h-1.25 c-0.05-0.62-0.22-1.1-0.52-1.45s-0.74-0.52-1.34-0.52c-0.72,0-1.24,0.23-1.57,0.7S8.72,6.37,8.71,7.4v2.03 c0,1,0.19,1.77,0.57,2.31c0.38,0.54,0.93,0.8,1.65,0.8c0.67,0,1.19-0.16,1.54-0.49l0.18-0.17V9.59h-1.82V8.52h3.07V12.24z" /> <path - android:pathData="M 0 0 H 14 V 24 H 0 V 0 Z" /> + android:pathData="M 0 0 H 14 V 17 H 0 V 0 Z" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_4g_plus_mobiledata.xml b/packages/SystemUI/res/drawable/ic_4g_plus_mobiledata.xml index bdbb2df3ce0e..bf39ea2798b3 100644 --- a/packages/SystemUI/res/drawable/ic_4g_plus_mobiledata.xml +++ b/packages/SystemUI/res/drawable/ic_4g_plus_mobiledata.xml @@ -14,20 +14,20 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="20dp" - android:height="24dp" - android:viewportWidth="20" - android:viewportHeight="24"> + android:width="22dp" + android:height="17dp" + android:viewportWidth="22" + android:viewportHeight="17"> <path android:fillColor="#FFFFFFFF" - android:pathData="M6.18,13.3h0.95v0.88H6.18v1.98H5.14v-1.98H1.92v-0.64l3.17-5.91h1.09V13.3z M3.07,13.3h2.07V9.25L3.07,13.3z" /> + android:pathData="M5.32,10.13h1.11v1.03H5.32v2.31H4.11v-2.31H0.35v-0.75l3.7-6.9h1.27V10.13z M1.69,10.13h2.42V5.4L1.69,10.13z" /> <path android:fillColor="#FFFFFFFF" - android:pathData="M13.75,15.11l-0.19,0.23c-0.54,0.63-1.33,0.94-2.37,0.94c-0.92,0-1.65-0.31-2.17-0.92s-0.79-1.48-0.8-2.59V11.1 c0-1.2,0.24-2.09,0.72-2.69s1.2-0.89,2.15-0.89c0.81,0,1.45,0.23,1.91,0.68s0.71,1.1,0.76,1.94h-1.07 c-0.04-0.53-0.19-0.95-0.44-1.25s-0.63-0.45-1.14-0.45c-0.61,0-1.06,0.2-1.35,0.6s-0.43,1.04-0.45,1.92v1.74 c0,0.86,0.16,1.52,0.49,1.98s0.8,0.69,1.41,0.69c0.58,0,1.02-0.14,1.32-0.42l0.16-0.15v-1.96h-1.56v-0.92h2.63V15.11z" /> + android:pathData="M14.15,12.24l-0.22,0.27c-0.63,0.73-1.55,1.1-2.76,1.1c-1.08,0-1.92-0.36-2.53-1.07c-0.61-0.71-0.93-1.72-0.94-3.02V7.56 c0-1.39,0.28-2.44,0.84-3.13s1.39-1.04,2.51-1.04c0.95,0,1.69,0.26,2.23,0.79s0.83,1.28,0.89,2.26H12.9 c-0.05-0.62-0.22-1.1-0.52-1.45s-0.74-0.52-1.34-0.52c-0.72,0-1.24,0.23-1.57,0.7S8.97,6.37,8.96,7.4v2.03 c0,1,0.19,1.77,0.57,2.31c0.38,0.54,0.93,0.8,1.65,0.8c0.67,0,1.19-0.16,1.54-0.49l0.18-0.17V9.59h-1.82V8.52h3.07V12.24z" /> <path android:fillColor="#FFFFFFFF" - android:pathData="M 20 9.64 L 18 9.64 L 18 7.64 L 17 7.64 L 17 9.64 L 15 9.64 L 15 10.64 L 17 10.64 L 17 12.64 L 18 12.64 L 18 10.64 L 20 10.64 Z" /> + android:pathData="M 19.3 5.74 L 19.3 3.39 L 18 3.39 L 18 5.74 L 15.65 5.74 L 15.65 7.04 L 18 7.04 L 18 9.39 L 19.3 9.39 L 19.3 7.04 L 21.65 7.04 L 21.65 5.74 Z" /> <path - android:pathData="M 0 0 H 20 V 24 H 0 V 0 Z" /> + android:pathData="M 0 0 H 22 V 17 H 0 V 0 Z" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_e_mobiledata.xml b/packages/SystemUI/res/drawable/ic_e_mobiledata.xml index 1a4a2e362b47..ca601d6c36ab 100644 --- a/packages/SystemUI/res/drawable/ic_e_mobiledata.xml +++ b/packages/SystemUI/res/drawable/ic_e_mobiledata.xml @@ -14,14 +14,14 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="7dp" - android:height="24dp" - android:viewportWidth="7" - android:viewportHeight="24"> + android:width="6dp" + android:height="17dp" + android:viewportWidth="6" + android:viewportHeight="17"> <path android:fillColor="#FFFFFFFF" - android:pathData="M6.5,12.32H3.48v3.02H7v0.92H2.41V7.73h4.53v0.92H3.48v2.75H6.5V12.32z" /> + android:pathData="M5.1,8.88H1.57v3.53h4.1v1.07H0.32V3.52h5.28V4.6H1.57V7.8H5.1V8.88z" /> <path - android:pathData="M 0 0 H 7 V 24 H 0 V 0 Z" /> + android:pathData="M 0 0 H 6 V 17 H 0 V 0 Z" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_g_mobiledata.xml b/packages/SystemUI/res/drawable/ic_g_mobiledata.xml index d6a0488544fd..8ff6d7a5cc88 100644 --- a/packages/SystemUI/res/drawable/ic_g_mobiledata.xml +++ b/packages/SystemUI/res/drawable/ic_g_mobiledata.xml @@ -14,14 +14,14 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="8dp" - android:height="24dp" - android:viewportWidth="8" - android:viewportHeight="24"> + android:width="7dp" + android:height="17dp" + android:viewportWidth="7" + android:viewportHeight="17"> <path android:fillColor="#FFFFFFFF" - android:pathData="M8,15.21l-0.19,0.23c-0.54,0.63-1.33,0.94-2.37,0.94c-0.92,0-1.65-0.31-2.17-0.92s-0.79-1.48-0.81-2.59V11.2 c0-1.2,0.24-2.09,0.72-2.69s1.19-0.89,2.15-0.89c0.81,0,1.45,0.23,1.91,0.68S7.95,9.39,8,10.23H6.93C6.88,9.7,6.74,9.28,6.49,8.99 S5.85,8.54,5.34,8.54c-0.61,0-1.06,0.2-1.35,0.6s-0.43,1.04-0.45,1.92v1.74c0,0.86,0.16,1.52,0.49,1.98s0.8,0.69,1.41,0.69 c0.58,0,1.02-0.14,1.32-0.42l0.16-0.15v-1.96H5.37v-0.92H8V15.21z" /> + android:pathData="M6.73,12.24l-0.22,0.27c-0.63,0.73-1.55,1.1-2.76,1.1c-1.08,0-1.92-0.36-2.53-1.07c-0.61-0.71-0.93-1.72-0.94-3.02V7.56 c0-1.39,0.28-2.44,0.84-3.13S2.5,3.39,3.62,3.39c0.95,0,1.69,0.26,2.23,0.79s0.83,1.28,0.89,2.26H5.48 c-0.05-0.62-0.22-1.1-0.52-1.45S4.22,4.46,3.62,4.46c-0.72,0-1.24,0.23-1.57,0.7S1.54,6.37,1.53,7.4v2.03c0,1,0.19,1.77,0.57,2.31 c0.38,0.54,0.93,0.8,1.65,0.8c0.67,0,1.19-0.16,1.54-0.49l0.18-0.17V9.59H3.66V8.52h3.07V12.24z" /> <path - android:pathData="M 0 0 H 8 V 24 H 0 V 0 Z" /> + android:pathData="M 0 0 H 7 V 17 H 0 V 0 Z" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_h_mobiledata.xml b/packages/SystemUI/res/drawable/ic_h_mobiledata.xml index be85bbb4f79b..68ea58e33f31 100644 --- a/packages/SystemUI/res/drawable/ic_h_mobiledata.xml +++ b/packages/SystemUI/res/drawable/ic_h_mobiledata.xml @@ -14,14 +14,14 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="8dp" - android:height="24dp" - android:viewportWidth="8" - android:viewportHeight="24"> + android:width="7dp" + android:height="17dp" + android:viewportWidth="7" + android:viewportHeight="17"> <path android:fillColor="#FFFFFFFF" - android:pathData="M8,16.27H6.92v-3.94H3.49v3.94H2.42V7.73h1.07v3.67h3.43V7.73H8V16.27z" /> + android:pathData="M6.76,13.48H5.5v-4.6H1.49v4.6H0.24V3.52h1.25V7.8H5.5V3.52h1.26V13.48z" /> <path - android:pathData="M 0 0 H 8 V 24 H 0 V 0 Z" /> + android:pathData="M 0 0 H 7 V 17 H 0 V 0 Z" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_h_plus_mobiledata.xml b/packages/SystemUI/res/drawable/ic_h_plus_mobiledata.xml index f31f83c51412..4212800217ec 100644 --- a/packages/SystemUI/res/drawable/ic_h_plus_mobiledata.xml +++ b/packages/SystemUI/res/drawable/ic_h_plus_mobiledata.xml @@ -14,17 +14,17 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="14dp" - android:height="24dp" - android:viewportWidth="14" - android:viewportHeight="24"> + android:width="15dp" + android:height="17dp" + android:viewportWidth="15" + android:viewportHeight="17"> <path android:fillColor="#FFFFFFFF" - android:pathData="M7.64,16.27H6.56v-3.94H3.13v3.94H2.06V7.73h1.07v3.67h3.43V7.73h1.08V16.27z" /> + android:pathData="M7.01,13.48H5.75v-4.6H1.74v4.6H0.49V3.52h1.25V7.8h4.01V3.52h1.26V13.48z" /> <path android:fillColor="#FFFFFFFF" - android:pathData="M 14 9.73 L 12 9.73 L 12 7.73 L 11 7.73 L 11 9.73 L 9 9.73 L 9 10.73 L 11 10.73 L 11 12.73 L 12 12.73 L 12 10.73 L 14 10.73 Z" /> + android:pathData="M 12.16 5.74 L 12.16 3.39 L 10.86 3.39 L 10.86 5.74 L 8.51 5.74 L 8.51 7.04 L 10.86 7.04 L 10.86 9.39 L 12.16 9.39 L 12.16 7.04 L 14.51 7.04 L 14.51 5.74 Z" /> <path - android:pathData="M 0 0 H 14 V 24 H 0 V 0 Z" /> + android:pathData="M 0 0 H 15 V 17 H 0 V 0 Z" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_lte_mobiledata.xml b/packages/SystemUI/res/drawable/ic_lte_mobiledata.xml index e45b5e05aff0..7536f5134cbe 100644 --- a/packages/SystemUI/res/drawable/ic_lte_mobiledata.xml +++ b/packages/SystemUI/res/drawable/ic_lte_mobiledata.xml @@ -14,20 +14,20 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="18dp" - android:height="24dp" - android:viewportWidth="18" - android:viewportHeight="24"> + android:width="18dp" + android:height="17dp" + android:viewportWidth="18" + android:viewportHeight="17"> <path android:fillColor="#FFFFFFFF" - android:pathData="M3.79,15.35h3.35v0.92H2.71V7.73h1.08V15.35z" /> + android:pathData="M1.34,12.4h3.9v1.07H0.08V3.52h1.26V12.4z" /> <path android:fillColor="#FFFFFFFF" - android:pathData="M12.15,8.65H9.91v7.61H8.84V8.65H6.6V7.73h5.55V8.65z" /> + android:pathData="M11.1,4.6H8.48v8.88H7.23V4.6H4.62V3.52h6.48V4.6z" /> <path android:fillColor="#FFFFFFFF" - android:pathData="M17.5,12.32h-3.02v3.02H18v0.92h-4.59V7.73h4.53v0.92h-3.46v2.75h3.02V12.32z" /> + android:pathData="M17.34,8.88h-3.52v3.53h4.1v1.07h-5.35V3.52h5.28V4.6h-4.03V7.8h3.52V8.88z" /> <path - android:pathData="M 0 0 H 18 V 24 H 0 V 0 Z" /> + android:pathData="M 0 0 H 18 V 17 H 0 V 0 Z" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_lte_plus_mobiledata.xml b/packages/SystemUI/res/drawable/ic_lte_plus_mobiledata.xml index 553a5bdc021e..302e3bdc5287 100644 --- a/packages/SystemUI/res/drawable/ic_lte_plus_mobiledata.xml +++ b/packages/SystemUI/res/drawable/ic_lte_plus_mobiledata.xml @@ -14,23 +14,23 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> + android:width="26dp" + android:height="17dp" + android:viewportWidth="26" + android:viewportHeight="17"> <path android:fillColor="#FFFFFFFF" - android:pathData="M3.91,15.35h3.35v0.92H2.84V7.73h1.08V15.35z" /> + android:pathData="M1.59,12.4h3.9v1.07H0.33V3.52h1.26V12.4z" /> <path android:fillColor="#FFFFFFFF" - android:pathData="M12.28,8.65h-2.24v7.61H8.96V8.65H6.73V7.73h5.55V8.65z" /> + android:pathData="M11.35,4.6H8.73v8.88H7.48V4.6H4.87V3.52h6.48V4.6z" /> <path android:fillColor="#FFFFFFFF" - android:pathData="M17.63,12.32h-3.02v3.02h3.52v0.92h-4.59V7.73h4.53v0.92h-3.46v2.75h3.02V12.32z" /> + android:pathData="M17.59,8.88h-3.52v3.53h4.1v1.07h-5.35V3.52h5.28V4.6h-4.03V7.8h3.52V8.88z" /> <path android:fillColor="#FFFFFFFF" - android:pathData="M 24 9.76 L 22 9.76 L 22 7.76 L 21 7.76 L 21 9.76 L 19 9.76 L 19 10.76 L 21 10.76 L 21 12.76 L 22 12.76 L 22 10.76 L 24 10.76 Z" /> + android:pathData="M 23.32 5.74 L 23.32 3.39 L 22.02 3.39 L 22.02 5.74 L 19.67 5.74 L 19.67 7.04 L 22.02 7.04 L 22.02 9.39 L 23.32 9.39 L 23.32 7.04 L 25.67 7.04 L 25.67 5.74 Z" /> <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> + android:pathData="M 0 0 H 26 V 17 H 0 V 0 Z" /> </vector> diff --git a/packages/SystemUI/res/drawable/qs_customizer_background.xml b/packages/SystemUI/res/drawable/qs_customizer_background.xml index 12d8016bf6a0..e15a734b0e05 100644 --- a/packages/SystemUI/res/drawable/qs_customizer_background.xml +++ b/packages/SystemUI/res/drawable/qs_customizer_background.xml @@ -14,6 +14,6 @@ limitations under the License. --> <transition xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:drawable="@color/qs_detail_transition" /> - <item android:drawable="?android:attr/colorPrimary" /> + <item android:drawable="@drawable/qs_customizer_background_transition" /> + <item android:drawable="@drawable/qs_customizer_background_primary" /> </transition> diff --git a/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml b/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml new file mode 100644 index 000000000000..abe1429697ac --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<inset xmlns:android="http://schemas.android.com/apk/res/android"> + <shape> + <solid android:color="?android:attr/colorPrimary"/> + <corners android:radius="?android:attr/dialogCornerRadius" /> + </shape> +</inset> diff --git a/packages/SystemUI/res/drawable/qs_header_status_dot.xml b/packages/SystemUI/res/drawable/qs_customizer_background_transition.xml index 69bfd49255fc..ed8f61a97c2a 100644 --- a/packages/SystemUI/res/drawable/qs_header_status_dot.xml +++ b/packages/SystemUI/res/drawable/qs_customizer_background_transition.xml @@ -13,7 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. --> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="oval"> - <solid android:color="@android:color/white"/> -</shape> +<inset xmlns:android="http://schemas.android.com/apk/res/android"> + <shape> + <solid android:color="@color/qs_detail_transition"/> + <corners android:radius="?android:attr/dialogCornerRadius" /> + </shape> +</inset> diff --git a/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml b/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml new file mode 100644 index 000000000000..557cae150303 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<inset xmlns:android="http://schemas.android.com/apk/res/android"> + <shape> + <solid android:color="?android:attr/colorSecondary"/> + <corners + android:topLeftRadius="?android:attr/dialogCornerRadius" + android:topRightRadius="?android:attr/dialogCornerRadius" /> + </shape> +</inset> diff --git a/packages/SystemUI/res/drawable/rounded_full_bg_bottom.xml b/packages/SystemUI/res/drawable/rounded_full_bg_bottom.xml index c3e36f211845..a4b3c99f7ec6 100644 --- a/packages/SystemUI/res/drawable/rounded_full_bg_bottom.xml +++ b/packages/SystemUI/res/drawable/rounded_full_bg_bottom.xml @@ -1,4 +1,18 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="?android:attr/colorPrimaryDark" /> diff --git a/packages/SystemUI/res/drawable/rounded_ripple.xml b/packages/SystemUI/res/drawable/rounded_ripple.xml new file mode 100644 index 000000000000..5588eb21ad8e --- /dev/null +++ b/packages/SystemUI/res/drawable/rounded_ripple.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="?android:attr/colorControlHighlight"> + <item android:id="@android:id/mask"> + <shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="#FFFFFFFF"/> + <corners android:radius="8dp"/> + </shape> + </item> + <item android:id="@android:id/background"> + <shape android:shape="rectangle"> + <solid android:color="#FFFFFFFF"/> + <corners android:radius="8dp"/> + </shape> + </item> +</ripple>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/stat_sys_roaming.xml b/packages/SystemUI/res/drawable/stat_sys_roaming.xml index bd2edf39d929..0dd9f5a39f91 100644 --- a/packages/SystemUI/res/drawable/stat_sys_roaming.xml +++ b/packages/SystemUI/res/drawable/stat_sys_roaming.xml @@ -1,28 +1,27 @@ <!-- -Copyright (C) 2014 The Android Open Source Project + 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 + 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. + 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="@dimen/signal_icon_size" android:height="@dimen/signal_icon_size" - android:viewportWidth="24" - android:viewportHeight="24"> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M7.8,7.2L9,10H7L5.87,7.33H4V10H2V2h5c1.13,0,2,0.87,2,2v1.33C9,6.13,8.47,6.87,7.8,7.2z M7,4H4v1.33h3V4z" /> - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:pathData="M0,0h24v24H0V0z" /> -</vector>
\ No newline at end of file + android:viewportWidth="17" + android:viewportHeight="17"> + +<path + android:fillColor="#FFFFFFFF" + android:pathData="M2.93,4.81H1.81V7.4H1V1h1.79c0.63,0,1.1,0.16,1.42,0.49S4.7,2.29,4.7,2.92c0,0.4-0.09,0.74-0.26,1.04 C4.26,4.25,4.02,4.48,3.7,4.63l1.24,2.72V7.4H4.07L2.93,4.81z M1.81,4.12h0.98c0.34,0,0.61-0.11,0.81-0.33 c0.2-0.22,0.3-0.51,0.3-0.87c0-0.82-0.37-1.23-1.12-1.23H1.81V4.12z" /> +<path + android:pathData="M 0 0 H 17 V 17 H 0 V 0 Z" /> +</vector> diff --git a/packages/SystemUI/res/layout/car_volume_dialog.xml b/packages/SystemUI/res/layout/car_volume_dialog.xml index e45c0f9f5e4e..94cc001ca01a 100644 --- a/packages/SystemUI/res/layout/car_volume_dialog.xml +++ b/packages/SystemUI/res/layout/car_volume_dialog.xml @@ -38,7 +38,7 @@ android:orientation="vertical" android:clipChildren="false" android:clipToPadding="false" - android:elevation="@dimen/volume_panel_elevation" > + android:elevation="@dimen/volume_dialog_elevation" > <LinearLayout android:id="@+id/car_volume_dialog_rows" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/mobile_signal_group.xml b/packages/SystemUI/res/layout/mobile_signal_group.xml index cc6e3bfbebaa..5ecd380c6950 100644 --- a/packages/SystemUI/res/layout/mobile_signal_group.xml +++ b/packages/SystemUI/res/layout/mobile_signal_group.xml @@ -50,7 +50,8 @@ android:layout_height="wrap_content" android:layout_width="wrap_content" android:layout_gravity="center_vertical" - android:paddingEnd="1dp" + android:paddingStart="1dp" + android:paddingEnd="2dp" android:visibility="gone" /> <Space android:id="@+id/mobile_roaming_space" diff --git a/packages/SystemUI/res/layout/qs_customize_divider.xml b/packages/SystemUI/res/layout/qs_customize_divider.xml index 71ad85bf3a96..51febc78e23e 100644 --- a/packages/SystemUI/res/layout/qs_customize_divider.xml +++ b/packages/SystemUI/res/layout/qs_customize_divider.xml @@ -20,9 +20,8 @@ android:id="@android:id/title" android:layout_width="match_parent" android:layout_height="wrap_content" + android:gravity="center" android:paddingTop="20dp" - android:paddingStart="16dp" - android:paddingEnd="8dp" android:paddingBottom="13dp" android:textAppearance="@android:style/TextAppearance.Material.Body2" android:textColor="?android:attr/colorAccent" diff --git a/packages/SystemUI/res/layout/qs_customize_panel.xml b/packages/SystemUI/res/layout/qs_customize_panel.xml index b3b6a0c43a98..506e6c856aff 100644 --- a/packages/SystemUI/res/layout/qs_customize_panel.xml +++ b/packages/SystemUI/res/layout/qs_customize_panel.xml @@ -22,7 +22,6 @@ android:layout_height="0dp" android:elevation="4dp" android:orientation="vertical" - android:background="@drawable/qs_customizer_background" android:gravity="center_horizontal"> </com.android.systemui.qs.customize.QSCustomizer> diff --git a/packages/SystemUI/res/layout/qs_customize_panel_content.xml b/packages/SystemUI/res/layout/qs_customize_panel_content.xml index 04d0e6524eac..d70a37ae15b2 100644 --- a/packages/SystemUI/res/layout/qs_customize_panel_content.xml +++ b/packages/SystemUI/res/layout/qs_customize_panel_content.xml @@ -15,24 +15,44 @@ limitations under the License. --> -<merge xmlns:android="http://schemas.android.com/apk/res/android"> - - <Toolbar - android:id="@*android:id/action_bar" +<merge xmlns:android="http://schemas.android.com/apk/res/android">-> + <View android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="28dp" - android:navigationContentDescription="@*android:string/action_bar_up_description" - style="?android:attr/toolbarStyle" /> + android:layout_height="@*android:dimen/quick_qs_offset_height" + android:background="@android:color/transparent" /> - <android.support.v7.widget.RecyclerView - android:id="@android:id/list" + <com.android.keyguard.AlphaOptimizedLinearLayout + android:id="@+id/customize_container" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" - android:scrollIndicators="top" - android:scrollbars="vertical" - android:importantForAccessibility="no" /> + android:layout_marginLeft="@dimen/notification_side_paddings" + android:layout_marginRight="@dimen/notification_side_paddings" + android:orientation="vertical" + android:background="@drawable/qs_customizer_background"> + <Toolbar + android:id="@*android:id/action_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@drawable/qs_customizer_toolbar" + android:navigationContentDescription="@*android:string/action_bar_up_description" + style="?android:attr/toolbarStyle" /> + + <android.support.v7.widget.RecyclerView + android:id="@android:id/list" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:paddingTop="28dp" + android:paddingLeft="@dimen/qs_tile_layout_margin_side" + android:paddingRight="@dimen/qs_tile_layout_margin_side" + android:paddingBottom="28dp" + android:clipToPadding="false" + android:scrollIndicators="top" + android:scrollbars="vertical" + android:scrollbarStyle="outsideOverlay" + android:importantForAccessibility="no" /> + </com.android.keyguard.AlphaOptimizedLinearLayout> <View android:id="@+id/nav_bar_background" diff --git a/packages/SystemUI/res/layout/qs_customize_tile_frame.xml b/packages/SystemUI/res/layout/qs_customize_tile_frame.xml index ff55f990857a..a2250b1975a3 100644 --- a/packages/SystemUI/res/layout/qs_customize_tile_frame.xml +++ b/packages/SystemUI/res/layout/qs_customize_tile_frame.xml @@ -17,9 +17,8 @@ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_height="wrap_content" + android:layout_height="@dimen/qs_tile_height" android:layout_width="match_parent" - android:paddingStart="8dp" - android:paddingEnd="8dp" - android:paddingTop="8dp" + android:layout_marginTop="@dimen/qs_tile_margin_top_bottom" + android:layout_marginBottom="@dimen/qs_tile_margin_top_bottom" android:gravity="center" /> diff --git a/packages/SystemUI/res/layout/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml index cd3271fa5374..c03f25c75101 100644 --- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml +++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml @@ -21,7 +21,7 @@ android:layout_marginTop="8dp" android:layout_marginBottom="14dp" android:layout_below="@id/quick_status_bar_system_icons" - > + android:paddingEnd="@dimen/signal_cluster_battery_padding" > <com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons" @@ -29,9 +29,4 @@ android:layout_height="match_parent" android:layout_weight="1" /> - <include layout="@layout/signal_cluster_view" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_marginStart="@dimen/signal_cluster_margin_start" /> - </LinearLayout> diff --git a/packages/SystemUI/res/layout/quick_settings_header_info.xml b/packages/SystemUI/res/layout/quick_settings_header_info.xml index 03e8451e59dc..54baa4a82a0b 100644 --- a/packages/SystemUI/res/layout/quick_settings_header_info.xml +++ b/packages/SystemUI/res/layout/quick_settings_header_info.xml @@ -54,15 +54,14 @@ android:layout_marginStart="@dimen/qs_header_alarm_text_margin_start" android:textAppearance="@style/TextAppearance.QS.TileLabel" /> - <ImageView + <View android:id="@+id/status_separator" - android:layout_width="2dp" - android:layout_height="2dp" - android:layout_marginStart="8dp" - android:layout_marginEnd="8dp" - android:layout_gravity="center_vertical" - android:src="@drawable/qs_header_status_dot" - android:tint="?android:attr/textColorPrimary" /> + android:layout_width="1dp" + android:layout_height="match_parent" + android:layout_marginStart="10dp" + android:layout_marginEnd="10dp" + android:background="@android:color/white" + android:backgroundTint="?android:attr/textColorPrimary" /> <ImageView android:id="@+id/ringer_mode_icon" diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml index ca8fcba017b1..ed18dc728402 100644 --- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml +++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml @@ -22,6 +22,7 @@ android:layout_width="match_parent" android:layout_height="@*android:dimen/quick_qs_total_height" android:layout_gravity="@integer/notification_panel_layout_gravity" + android:background="@android:color/transparent" android:baselineAligned="false" android:clickable="false" android:clipChildren="false" diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml index 388b6333c719..f38129f0dffa 100644 --- a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml +++ b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml @@ -31,23 +31,25 @@ <com.android.systemui.statusbar.policy.Clock android:id="@+id/clock" + android:textAppearance="@style/TextAppearance.StatusBar.Clock" android:layout_width="wrap_content" android:layout_height="match_parent" - android:gravity="center_vertical|start" + android:singleLine="true" android:paddingStart="@dimen/status_bar_left_clock_starting_padding" android:paddingEnd="@dimen/status_bar_left_clock_end_padding" - android:singleLine="true" - android:textAppearance="@style/TextAppearance.StatusBar.Clock" - systemui:showDark="false" /> + android:gravity="center_vertical|start" + systemui:showDark="false" + /> <com.android.systemui.statusbar.policy.DateView android:id="@+id/date" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:gravity="center_vertical" + android:padding="4dp" android:singleLine="true" android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date" android:textSize="@dimen/qs_time_collapsed_size" + android:gravity="center_vertical" systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" /> <android.widget.Space @@ -55,11 +57,12 @@ android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" - android:gravity="center_vertical|center_horizontal" /> + android:gravity="center_vertical|center_horizontal" + /> - <com.android.systemui.BatteryMeterView - android:id="@+id/battery" + <com.android.systemui.BatteryMeterView android:id="@+id/battery" android:layout_height="match_parent" android:layout_width="wrap_content" - android:gravity="center_vertical|end" /> + android:gravity="center_vertical|end" + /> </LinearLayout> diff --git a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml new file mode 100644 index 000000000000..d607c8c29ead --- /dev/null +++ b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** 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. +*/ +--> +<com.android.systemui.statusbar.StatusBarMobileView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:systemui="http://schemas.android.com/apk/res-auto" + android:id="@+id/mobile_combo" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:orientation="horizontal"> + <FrameLayout + android:id="@+id/inout_container" + android:layout_height="17dp" + android:layout_width="wrap_content" + android:layout_gravity="center_vertical"> + <ImageView + android:id="@+id/mobile_in" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:src="@drawable/ic_activity_down" + android:visibility="gone" + android:paddingEnd="2dp" + /> + <ImageView + android:id="@+id/mobile_out" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:src="@drawable/ic_activity_up" + android:paddingEnd="2dp" + android:visibility="gone" + /> + </FrameLayout> + <ImageView + android:id="@+id/mobile_type" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="center_vertical" + android:paddingEnd="1dp" + android:visibility="gone" /> + <Space + android:id="@+id/mobile_roaming_space" + android:layout_height="match_parent" + android:layout_width="@dimen/roaming_icon_start_padding" + android:visibility="gone" + /> + <FrameLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical"> + <com.android.systemui.statusbar.AnimatedImageView + android:id="@+id/mobile_signal" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + systemui:hasOverlappingRendering="false" + /> + <ImageView + android:id="@+id/mobile_roaming" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/stat_sys_roaming" + android:contentDescription="@string/data_connection_roaming" + android:visibility="gone" /> + </FrameLayout> +</com.android.systemui.statusbar.StatusBarMobileView> + diff --git a/packages/SystemUI/res/layout/status_bar_wifi_group.xml b/packages/SystemUI/res/layout/status_bar_wifi_group.xml new file mode 100644 index 000000000000..08cef55e0393 --- /dev/null +++ b/packages/SystemUI/res/layout/status_bar_wifi_group.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** 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. +*/ +--> +<com.android.systemui.statusbar.StatusBarWifiView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:systemui="http://schemas.android.com/apk/res-auto" + android:id="@+id/wifi_combo" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:paddingStart="4dp" + android:gravity="center_vertical" + android:orientation="horizontal" > + + <FrameLayout + android:id="@+id/inout_container" + android:layout_height="17dp" + android:layout_width="wrap_content" + android:gravity="center_vertical" > + <ImageView + android:id="@+id/wifi_in" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:src="@drawable/ic_activity_down" + android:visibility="gone" + android:paddingEnd="2dp" + /> + <ImageView + android:id="@+id/wifi_out" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:src="@drawable/ic_activity_up" + android:paddingEnd="2dp" + android:visibility="gone" + /> + </FrameLayout> + <FrameLayout + android:id="@+id/wifi_combo" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:gravity="center_vertical" > + <com.android.systemui.statusbar.AlphaOptimizedImageView + android:theme="?attr/lightIconTheme" + android:id="@+id/wifi_signal" + android:layout_height="wrap_content" + android:layout_width="wrap_content" /> + </FrameLayout> + + <View + android:id="@+id/wifi_signal_spacer" + android:layout_width="@dimen/status_bar_wifi_signal_spacer_width" + android:layout_height="4dp" + android:visibility="gone" /> + + <!-- Looks like CarStatusBar uses this... --> + <ViewStub + android:id="@+id/connected_device_signals_stub" + android:layout="@layout/connected_device_signal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <View + android:id="@+id/wifi_airplane_spacer" + android:layout_width="@dimen/status_bar_airplane_spacer_width" + android:layout_height="4dp" + android:visibility="gone" + /> +</com.android.systemui.statusbar.StatusBarWifiView> diff --git a/packages/SystemUI/res/layout/system_icons.xml b/packages/SystemUI/res/layout/system_icons.xml index 1fafb2fc72d6..258b82aacb2e 100644 --- a/packages/SystemUI/res/layout/system_icons.xml +++ b/packages/SystemUI/res/layout/system_icons.xml @@ -24,14 +24,10 @@ android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" + android:paddingEnd="4dp" android:gravity="center_vertical" android:orientation="horizontal"/> - <include layout="@layout/signal_cluster_view" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/signal_cluster_margin_start"/> - <com.android.systemui.BatteryMeterView android:id="@+id/battery" android:layout_height="match_parent" android:layout_width="wrap_content" diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index 0a3f4eb690d5..d4fe1c6df9a9 100644 --- a/packages/SystemUI/res/layout/volume_dialog.xml +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -17,9 +17,11 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:padding="@dimen/volume_dialog_panel_transparent_padding" android:background="@android:color/transparent" android:theme="@style/qs_theme" - android:clipChildren="false" > + android:clipChildren="false" + android:clipToPadding="false"> <!-- right-aligned to be physically near volume button --> <LinearLayout android:id="@+id/volume_dialog" @@ -30,22 +32,25 @@ android:background="@android:color/transparent" android:layout_margin="@dimen/volume_dialog_base_margin" android:orientation="vertical" - android:clipChildren="false" > + android:clipChildren="false" + android:clipToPadding="false" > <FrameLayout android:id="@+id/ringer" android:layout_width="@dimen/volume_dialog_ringer_size" android:layout_height="@dimen/volume_dialog_ringer_size" android:layout_marginBottom="@dimen/volume_dialog_spacer" - android:elevation="@dimen/volume_panel_elevation" + android:translationZ="@dimen/volume_dialog_elevation" android:layout_gravity="right" + android:clipChildren="false" + android:clipToPadding="false" android:background="@drawable/rounded_bg_full"> <com.android.keyguard.AlphaOptimizedImageButton android:id="@+id/ringer_icon" style="@style/VolumeButtons" - android:background="?android:selectableItemBackgroundBorderless" - android:layout_width="@dimen/volume_dialog_tap_target_size" - android:layout_height="@dimen/volume_dialog_tap_target_size" + android:background="@drawable/rounded_ripple" + android:layout_width="match_parent" + android:layout_height="match_parent" android:tint="@color/accent_tint_color_selector" android:layout_gravity="center" android:soundEffectsEnabled="false" /> @@ -59,10 +64,10 @@ android:minWidth="@dimen/volume_dialog_panel_width" android:layout_height="wrap_content" android:orientation="vertical" + android:translationZ="@dimen/volume_dialog_elevation" android:clipChildren="false" android:clipToPadding="false" - android:background="@drawable/rounded_bg_full" - android:elevation="@dimen/volume_panel_elevation" > + android:background="@drawable/rounded_bg_full" > <LinearLayout android:id="@+id/volume_dialog_rows" android:layout_width="wrap_content" @@ -84,7 +89,7 @@ android:layout_height="@dimen/volume_dialog_tap_target_size" android:layout_gravity="center" android:contentDescription="@string/accessibility_volume_settings" - android:background="?android:selectableItemBackgroundBorderless" + android:background="@drawable/ripple_drawable_20dp" android:tint="?android:attr/colorControlNormal" android:soundEffectsEnabled="false" /> </FrameLayout> diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml index bcc369252c29..6128da8627a9 100644 --- a/packages/SystemUI/res/layout/volume_dialog_row.xml +++ b/packages/SystemUI/res/layout/volume_dialog_row.xml @@ -60,7 +60,7 @@ style="@style/VolumeButtons" android:layout_width="@dimen/volume_dialog_tap_target_size" android:layout_height="@dimen/volume_dialog_tap_target_size" - android:background="?android:selectableItemBackgroundBorderless" + android:background="@drawable/ripple_drawable_20dp" android:layout_marginBottom="@dimen/volume_dialog_row_margin_bottom" android:tint="@color/accent_tint_color_selector" android:soundEffectsEnabled="false" /> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 91c872495c9d..84ca657f3da5 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -270,13 +270,15 @@ <!-- 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_transparent_padding">8dp</dimen> + <!-- the amount the volume panel should be offset at the end from the view next to it (or the screen edge, in portrait--> <dimen name="volume_dialog_base_margin">8dp</dimen> <dimen name="volume_dialog_panel_width">64dp</dimen> - <dimen name="volume_dialog_slider_height">108dp</dimen> + <dimen name="volume_dialog_slider_height">116dp</dimen> <dimen name="volume_dialog_row_height">252dp</dimen> @@ -286,7 +288,7 @@ <dimen name="volume_dialog_spacer">4dp</dimen> - <dimen name="volume_dialog_slider_margin_top">22dp</dimen> + <dimen name="volume_dialog_slider_margin_top">14dp</dimen> <dimen name="volume_dialog_slider_margin_bottom">-2dp</dimen> @@ -294,6 +296,8 @@ <dimen name="volume_dialog_settings_icon_size">16dp</dimen> + <dimen name="volume_dialog_elevation">9dp</dimen> + <!-- Gravity for the notification panel --> <integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top --> @@ -313,8 +317,10 @@ <dimen name="pull_span_min">25dp</dimen> <dimen name="qs_tile_height">106dp</dimen> + <dimen name="qs_tile_layout_margin_side">9dp</dimen> <dimen name="qs_tile_margin_horizontal">18dp</dimen> <dimen name="qs_tile_margin_vertical">24dp</dimen> + <dimen name="qs_tile_margin_top_bottom">12dp</dimen> <dimen name="qs_tile_margin_top">18dp</dimen> <dimen name="qs_quick_tile_size">48dp</dimen> <!-- Width for the spacer, used between QS tiles. --> @@ -734,8 +740,6 @@ <dimen name="volume_expander_margin_end">2dp</dimen> <dimen name="volume_expander_margin_top">6dp</dimen> - <dimen name="volume_panel_elevation">8dp</dimen> - <!-- Padding between icon and text for managed profile toast --> <dimen name="managed_profile_toast_padding">4dp</dimen> diff --git a/packages/SystemUI/res/values/dimens_car.xml b/packages/SystemUI/res/values/dimens_car.xml index 5679dd2f1fb0..6caed61f11a7 100644 --- a/packages/SystemUI/res/values/dimens_car.xml +++ b/packages/SystemUI/res/values/dimens_car.xml @@ -32,6 +32,8 @@ <dimen name="car_navigation_button_width">64dp</dimen> <dimen name="car_navigation_bar_width">760dp</dimen> + <dimen name="car_left_navigation_bar_width">96dp</dimen> + <dimen name="car_right_navigation_bar_width">96dp</dimen> <dimen name="car_page_indicator_dot_diameter">12dp</dimen> <dimen name="car_page_indicator_margin_bottom">24dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 419e9d28fa4e..4074042d3615 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1350,7 +1350,7 @@ <string name="volume_dialog_title">%s volume controls</string> - <string name="volume_dialog_ringer_guidance_ring">Calls and notifications will ring</string> + <string name="volume_dialog_ringer_guidance_ring">Calls and notifications will ring (<xliff:g id="volume level" example="56">%1$s</xliff:g>)</string> <string name="output_title">Media output</string> <string name="output_calls_title">Phone call output</string> @@ -1748,6 +1748,10 @@ <!-- Name of the headset in status bar [CHAR LIMIT=30] --> <string name="headset">Headset</string> + <!-- Accessibility description for long click on a quick settings tile - this is used in the + context of the sentence "double tap and hold to _Open settings_" [CHAR LIMIT=NONE] --> + <string name="accessibility_long_click_tile">Open settings</string> + <!-- Accessibility description of headphones icon [CHAR LIMIT=NONE] --> <string name="accessibility_status_bar_headphones">Headphones connected</string> diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index 0683514f6f2a..1ae06d751255 100644 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -81,14 +81,6 @@ public class BatteryMeterView extends LinearLayout implements private float mDarkIntensity; private int mUser; - /** - * Whether we should use colors that adapt based on wallpaper/the scrim behind quick settings. - */ - private boolean mUseWallpaperTextColors; - - private int mNonAdaptedForegroundColor; - private int mNonAdaptedBackgroundColor; - public BatteryMeterView(Context context) { this(context, null, 0); } @@ -148,29 +140,6 @@ public class BatteryMeterView extends LinearLayout implements updateShowPercent(); } - /** - * Sets whether the battery meter view uses the wallpaperTextColor. If we're not using it, we'll - * revert back to dark-mode-based/tinted colors. - * - * @param shouldUseWallpaperTextColor whether we should use wallpaperTextColor for all - * components - */ - public void useWallpaperTextColor(boolean shouldUseWallpaperTextColor) { - if (shouldUseWallpaperTextColor == mUseWallpaperTextColors) { - return; - } - - mUseWallpaperTextColors = shouldUseWallpaperTextColor; - - if (mUseWallpaperTextColors) { - updateColors( - Utils.getColorAttr(mContext, R.attr.wallpaperTextColor), - Utils.getColorAttr(mContext, R.attr.wallpaperTextColorSecondary)); - } else { - updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor); - } - } - public void setColorsFromContext(Context context) { if (context == null) { return; @@ -210,8 +179,7 @@ public class BatteryMeterView extends LinearLayout implements getContext().getContentResolver().registerContentObserver( Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mSettingObserver, mUser); updateShowPercent(); - Dependency.get(TunerService.class) - .addTunable(this, StatusBarIconController.ICON_BLACKLIST); + Dependency.get(TunerService.class).addTunable(this, StatusBarIconController.ICON_BLACKLIST); Dependency.get(ConfigurationController.class).addCallback(this); mUserTracker.startTracking(); } @@ -305,23 +273,19 @@ public class BatteryMeterView extends LinearLayout implements @Override public void onDarkChanged(Rect area, float darkIntensity, int tint) { mDarkIntensity = darkIntensity; - float intensity = DarkIconDispatcher.isInArea(area, this) ? darkIntensity : 0; - mNonAdaptedForegroundColor = getColorForDarkIntensity( - intensity, mLightModeFillColor, mDarkModeFillColor); - mNonAdaptedBackgroundColor = getColorForDarkIntensity( - intensity, mLightModeBackgroundColor,mDarkModeBackgroundColor); - - if (!mUseWallpaperTextColors) { - updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor); - } + int foreground = getColorForDarkIntensity(intensity, mLightModeFillColor, + mDarkModeFillColor); + int background = getColorForDarkIntensity(intensity, mLightModeBackgroundColor, + mDarkModeBackgroundColor); + mDrawable.setColors(foreground, background); + setTextColor(foreground); } - private void updateColors(int foregroundColor, int backgroundColor) { - mDrawable.setColors(foregroundColor, backgroundColor); - mTextColor = foregroundColor; + public void setTextColor(int color) { + mTextColor = color; if (mBatteryPercentView != null) { - mBatteryPercentView.setTextColor(foregroundColor); + mBatteryPercentView.setTextColor(color); } } diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java index 816c598daf4f..8cff56df1730 100644 --- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java @@ -228,9 +228,14 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mHandler.removeCallbacks(mConnectionRunnable); Intent launcherServiceIntent = new Intent(ACTION_QUICKSTEP) .setPackage(mRecentsComponentName.getPackageName()); - boolean bound = mContext.bindServiceAsUser(launcherServiceIntent, - mOverviewServiceConnection, Context.BIND_AUTO_CREATE, - UserHandle.of(mDeviceProvisionedController.getCurrentUser())); + boolean bound = false; + try { + bound = mContext.bindServiceAsUser(launcherServiceIntent, + mOverviewServiceConnection, Context.BIND_AUTO_CREATE, + UserHandle.of(mDeviceProvisionedController.getCurrentUser())); + } catch (SecurityException e) { + Log.e(TAG_OPS, "Unable to bind because of security error", e); + } if (!bound) { // Retry after exponential backoff timeout final long timeoutMs = (long) Math.scalb(BACKOFF_MILLIS, mConnectionBackoffAttempts); diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java index 1a9655ef62cd..2a2714735951 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -53,7 +53,8 @@ public final class Prefs { Key.NUM_APPS_LAUNCHED, Key.HAS_SEEN_RECENTS_ONBOARDING, Key.SEEN_RINGER_GUIDANCE_COUNT, - Key.QS_HAS_TURNED_OFF_MOBILE_DATA + Key.QS_HAS_TURNED_OFF_MOBILE_DATA, + Key.TOUCHED_RINGER_TOGGLE }) public @interface Key { @Deprecated @@ -91,6 +92,7 @@ public final class Prefs { String SEEN_RINGER_GUIDANCE_COUNT = "RingerGuidanceCount"; String QS_TILE_SPECS_REVEALED = "QsTileSpecsRevealed"; String QS_HAS_TURNED_OFF_MOBILE_DATA = "QsHasTurnedOffMobileData"; + String TOUCHED_RINGER_TOGGLE = "TouchedRingerToggle"; } public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) { diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 2c0e95b5af26..a61ce8c291fd 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -29,30 +29,16 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.util.ArraySet; -import android.util.TimingsTraceLog; import android.util.Log; +import android.util.TimingsTraceLog; -import com.android.systemui.globalactions.GlobalActionsComponent; -import com.android.systemui.keyboard.KeyboardUI; -import com.android.systemui.keyguard.KeyguardViewMediator; -import com.android.systemui.media.RingtonePlayer; -import com.android.systemui.pip.PipUI; import com.android.systemui.plugins.OverlayPlugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.PluginManager; -import com.android.systemui.power.PowerUI; -import com.android.systemui.recents.Recents; -import com.android.systemui.shortcut.ShortcutKeyDispatcher; -import com.android.systemui.stackdivider.Divider; -import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarWindowManager; -import com.android.systemui.usb.StorageNotification; import com.android.systemui.util.NotificationChannels; -import com.android.systemui.util.leak.GarbageMonitor; -import com.android.systemui.volume.VolumeUI; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -99,6 +85,10 @@ public class SystemUIApplication extends Application implements SysUiServiceProv mServices[i].onBootCompleted(); } } + + IntentFilter localeChangedFilter = new IntentFilter( + Intent.ACTION_LOCALE_CHANGED); + registerReceiver(mLocaleChangeReceiver, localeChangedFilter); } }, filter); } else { @@ -249,4 +239,14 @@ public class SystemUIApplication extends Application implements SysUiServiceProv public SystemUI[] getServices() { return mServices; } + + private final BroadcastReceiver mLocaleChangeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) { + // Update names of SystemUi notification channels + NotificationChannels.createAll(context); + } + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 3ba3d0eb4974..5c0576d0de2f 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -284,14 +284,19 @@ public class PipTouchHandler { // Defer the update of the current movement bounds until after the user finishes // touching the screen } else { - final Rect toMovementBounds = mMenuState == MENU_STATE_FULL - ? expandedMovementBounds - : normalMovementBounds; - animateToOffset(animatingBounds, toMovementBounds, - fromImeAdjustment, - fromImeAdjustment ? mIsImeShowing : mIsShelfShowing, - // Shelf height serves as an offset, but does not change movement bounds. - fromImeAdjustment ? mImeOffset : mShelfHeight); + final int adjustedOffset = Math.max(mIsImeShowing ? mImeHeight + mImeOffset : 0, + mIsShelfShowing ? mShelfHeight : 0); + Rect normalAdjustedBounds = new Rect(); + mSnapAlgorithm.getMovementBounds(mNormalBounds, insetBounds, normalAdjustedBounds, + adjustedOffset); + Rect expandedAdjustedBounds = new Rect(); + mSnapAlgorithm.getMovementBounds(mExpandedBounds, insetBounds, + expandedAdjustedBounds, adjustedOffset); + final Rect toAdjustedBounds = mMenuState == MENU_STATE_FULL + ? expandedAdjustedBounds + : normalAdjustedBounds; + + animateToOffset(animatingBounds, toAdjustedBounds); } } @@ -313,23 +318,13 @@ public class PipTouchHandler { } } - private void animateToOffset(Rect animatingBounds, Rect toMovementBounds, - boolean fromImeAdjustment, boolean showing, int offset) { + private void animateToOffset(Rect animatingBounds, Rect toAdjustedBounds) { final Rect bounds = new Rect(animatingBounds); - if (showing) { - // IME/shelf visible, apply the IME/shelf offset if the space allows for it - final int calculatedOffset = toMovementBounds.bottom - Math.max(toMovementBounds.top, - toMovementBounds.bottom - offset); - bounds.offset(0, - Math.min(0, toMovementBounds.bottom - calculatedOffset - bounds.top)); - } else { - // IME/shelf hidden - if (bounds.top >= (mMovementBounds.bottom - offset)) { - bounds.offset(0, toMovementBounds.bottom - bounds.top - - // Counter going back home from search where keyboard is up. - (fromImeAdjustment ? mShelfHeight : 0)); - } + if (toAdjustedBounds.bottom < mMovementBounds.bottom + && bounds.top < toAdjustedBounds.bottom) { + return; } + bounds.offset(0, toAdjustedBounds.bottom - bounds.top); mMotionHelper.animateToOffset(bounds); } diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index f08219a8f742..c409f738ec2a 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.time.Duration; import java.util.Arrays; public class PowerUI extends SystemUI { @@ -61,6 +62,8 @@ public class PowerUI extends SystemUI { 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 static final int CHARGE_CYCLE_PERCENT_RESET = 45; + private static final long SIX_HOURS_MILLIS = Duration.ofHours(6).toMillis(); private final Handler mHandler = new Handler(); private final Receiver mReceiver = new Receiver(); @@ -69,7 +72,6 @@ public class PowerUI extends SystemUI { private HardwarePropertiesManager mHardwarePropertiesManager; private WarningsUI mWarnings; private final Configuration mLastConfiguration = new Configuration(); - private int mBatteryLevel = 100; private long mTimeRemaining = Long.MAX_VALUE; private int mPlugType = 0; private int mInvalidCharger = 0; @@ -88,6 +90,7 @@ public class PowerUI extends SystemUI { private long mNextLogTime; private IThermalService mThermalService; + @VisibleForTesting int mBatteryLevel = 100; @VisibleForTesting int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN; // by using the same instance (method references are not guaranteed to be the same object @@ -205,12 +208,6 @@ public class PowerUI extends SystemUI { final boolean plugged = mPlugType != 0; final boolean oldPlugged = oldPlugType != 0; - // if we are now unplugged but we were previously plugged in we should allow the - // time based trigger again. - if (!plugged && plugged != oldPlugged) { - mLowWarningShownThisChargeCycle = false; - mSevereWarningShownThisChargeCycle = false; - } int oldBucket = findBatteryLevelBucket(oldBatteryLevel); int bucket = findBatteryLevelBucket(mBatteryLevel); @@ -261,7 +258,8 @@ public class PowerUI extends SystemUI { boolean isPowerSaver = mPowerManager.isPowerSaveMode(); // only play SFX when the dialog comes up or the bucket changes final boolean playSound = bucket != oldBucket || oldPlugged; - if (mEnhancedEstimates.isHybridNotificationEnabled()) { + final boolean hybridEnabled = mEnhancedEstimates.isHybridNotificationEnabled(); + if (hybridEnabled) { 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 @@ -270,6 +268,14 @@ public class PowerUI extends SystemUI { mWarnings.updateEstimate(estimate); mWarnings.updateThresholds(mEnhancedEstimates.getLowWarningThreshold(), mEnhancedEstimates.getSevereWarningThreshold()); + + // if we are now over 45% battery & 6 hours remaining we can trigger hybrid + // notification again + if (mBatteryLevel >= CHARGE_CYCLE_PERCENT_RESET + && mTimeRemaining > SIX_HOURS_MILLIS) { + mLowWarningShownThisChargeCycle = false; + mSevereWarningShownThisChargeCycle = false; + } } } @@ -277,13 +283,15 @@ public class PowerUI extends SystemUI { mTimeRemaining, isPowerSaver, mBatteryStatus)) { mWarnings.showLowBatteryWarning(playSound); - // mark if we've already shown a warning this cycle. This will prevent the time based - // trigger from spamming users since the time remaining can vary based on current - // device usage. - if (mTimeRemaining < mEnhancedEstimates.getSevereWarningThreshold()) { - mSevereWarningShownThisChargeCycle = true; - } else { - mLowWarningShownThisChargeCycle = true; + // mark if we've already shown a warning this cycle. This will prevent the notification + // trigger from spamming users by only showing low/critical warnings once per cycle + if (hybridEnabled) { + if (mTimeRemaining < mEnhancedEstimates.getSevereWarningThreshold() + || mBatteryLevel < mLowBatteryReminderLevels[1]) { + mSevereWarningShownThisChargeCycle = true; + } else { + mLowWarningShownThisChargeCycle = true; + } } } else if (shouldDismissLowBatteryWarning(plugged, oldBucket, bucket, mTimeRemaining, isPowerSaver)) { @@ -295,12 +303,16 @@ public class PowerUI extends SystemUI { @VisibleForTesting boolean shouldShowLowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket, - int bucket, long timeRemaining, boolean isPowerSaver, int mBatteryStatus) { + int bucket, long timeRemaining, boolean isPowerSaver, int batteryStatus) { + if (mEnhancedEstimates.isHybridNotificationEnabled()) { + // triggering logic when enhanced estimate is available + return isEnhancedTrigger(plugged, timeRemaining, isPowerSaver, batteryStatus); + } + // legacy triggering logic return !plugged && !isPowerSaver - && (((bucket < oldBucket || oldPlugged) && bucket < 0) - || isTimeBasedTrigger(timeRemaining)) - && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN; + && (((bucket < oldBucket || oldPlugged) && bucket < 0)) + && batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN; } @VisibleForTesting @@ -315,19 +327,23 @@ public class PowerUI extends SystemUI { || hybridWouldDismiss)); } - private boolean isTimeBasedTrigger(long timeRemaining) { - if (!mEnhancedEstimates.isHybridNotificationEnabled()) { + private boolean isEnhancedTrigger(boolean plugged, long timeRemaining, boolean isPowerSaver, + int batteryStatus) { + if (plugged || isPowerSaver || batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) { return false; } - - // Only show the time based warning once per charge cycle - final boolean canShowWarning = timeRemaining < mEnhancedEstimates.getLowWarningThreshold() - && !mLowWarningShownThisChargeCycle; - - // Only show the severe time based warning once per charge cycle - final boolean canShowSevereWarning = - timeRemaining < mEnhancedEstimates.getSevereWarningThreshold() - && !mSevereWarningShownThisChargeCycle; + int warnLevel = mLowBatteryReminderLevels[0]; + int critLevel = mLowBatteryReminderLevels[1]; + + // Only show the low warning once per charge cycle + final boolean canShowWarning = !mLowWarningShownThisChargeCycle + && (timeRemaining < mEnhancedEstimates.getLowWarningThreshold() + || mBatteryLevel <= warnLevel); + + // Only show the severe warning once per charge cycle + final boolean canShowSevereWarning = !mSevereWarningShownThisChargeCycle + && (timeRemaining < mEnhancedEstimates.getSevereWarningThreshold() + || mBatteryLevel <= critLevel); return canShowWarning || canShowSevereWarning; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index 892395222d1f..c548cf6a6519 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -167,6 +167,13 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } } + @Override + public void setExpansion(float expansion) { + for (TileRecord tr : mTiles) { + tr.tileView.setExpansion(expansion); + } + } + public void setPageListener(PageListener listener) { mPageListener = listener; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 71614634cd00..bfbfbf6fe813 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -18,15 +18,17 @@ package com.android.systemui.qs; import android.content.Context; import android.content.res.Configuration; +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} @@ -42,11 +44,7 @@ public class QSContainerImpl extends FrameLayout { protected float mQsExpansion; private QSCustomizer mQSCustomizer; private View mQSFooter; - private View mBackground; - private View mBackgroundGradient; - private View mStatusBarBackground; - private int mSideMargins; public QSContainerImpl(Context context, AttributeSet attrs) { @@ -62,8 +60,6 @@ public class QSContainerImpl extends FrameLayout { mQSCustomizer = findViewById(R.id.qs_customize); mQSFooter = findViewById(R.id.qs_footer); mBackground = findViewById(R.id.quick_settings_background); - mStatusBarBackground = findViewById(R.id.quick_settings_status_bar_background); - mBackgroundGradient = findViewById(R.id.quick_settings_gradient_view); mSideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings); setClickable(true); @@ -72,22 +68,6 @@ public class QSContainerImpl extends FrameLayout { } @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - - // Hide the backgrounds when in landscape mode. - if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { - mBackgroundGradient.setVisibility(View.INVISIBLE); - mStatusBarBackground.setVisibility(View.INVISIBLE); - } else { - mBackgroundGradient.setVisibility(View.VISIBLE); - mStatusBarBackground.setVisibility(View.VISIBLE); - } - - updateResources(); - } - - @Override public boolean performClick() { // Want to receive clicks so missing QQS tiles doesn't cause collapse, but // don't want to do anything with them. @@ -121,14 +101,6 @@ public class QSContainerImpl extends FrameLayout { updateExpansion(); } - private void updateResources() { - LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams(); - layoutParams.topMargin = mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.quick_qs_offset_height); - - mQSPanel.setLayoutParams(layoutParams); - } - /** * 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. diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 29f3c43a1fa4..018a63560429 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -291,6 +291,7 @@ public class QSFragment extends Fragment implements QS { mHeader.setExpansion(mKeyguardShowing, expansion, panelTranslationY); mFooter.setExpansion(mKeyguardShowing ? 1 : expansion); mQSPanel.getQsTileRevealController().setExpansion(expansion); + mQSPanel.getTileLayout().setExpansion(expansion); mQSPanel.setTranslationY(translationScaleY * heightDiff); mQSDetail.setFullyExpanded(fullyExpanded); @@ -359,7 +360,6 @@ public class QSFragment extends Fragment implements QS { // The customize state changed, so our height changed. mContainer.updateExpansion(); mQSPanel.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE); - mHeader.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE); mFooter.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE); // Let the panel know the position changed and it needs to update where notifications // and whatnot are. diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 61e3065fd4a3..6368a6b32787 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -616,5 +616,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne boolean updateResources(); void setListening(boolean listening); + + default void setExpansion(float expansion) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index ca88d704fd87..df65d1fb82cd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -26,7 +26,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; -import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; import android.media.AudioManager; @@ -48,7 +47,6 @@ import com.android.systemui.BatteryMeterView; import com.android.systemui.Dependency; import com.android.systemui.Prefs; import com.android.systemui.R; -import com.android.systemui.R.id; import com.android.systemui.SysUiServiceProvider; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.qs.QSDetail.Callback; @@ -56,10 +54,8 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.PhoneStatusBarView; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager; -import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.policy.DarkIconDispatcher; import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver; -import com.android.systemui.statusbar.policy.DateView; import com.android.systemui.statusbar.policy.NextAlarmController; import java.util.Locale; @@ -94,7 +90,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue private TouchAnimator mStatusIconsAlphaAnimator; private TouchAnimator mHeaderTextContainerAlphaAnimator; - private View mSystemIconsView; private View mQuickQsStatusIcons; private View mDate; private View mHeaderTextContainerView; @@ -112,9 +107,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue private View mStatusSeparator; private ImageView mRingerModeIcon; private TextView mRingerModeTextView; - private BatteryMeterView mBatteryMeterView; - private Clock mClockView; - private DateView mDateView; private NextAlarmController mAlarmController; /** Counts how many times the long press tooltip has been shown to the user. */ @@ -146,7 +138,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue mHeaderQsPanel = findViewById(R.id.quick_qs_panel); mDate = findViewById(R.id.date); mDate.setOnClickListener(this); - mSystemIconsView = findViewById(R.id.quick_status_bar_system_icons); mQuickQsStatusIcons = findViewById(R.id.quick_qs_status_icons); mIconManager = new TintedIconManager(findViewById(R.id.statusIcons)); @@ -169,15 +160,12 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue // Set light text on the header icons because they will always be on a black background applyDarkness(R.id.clock, tintArea, 0, DarkIconDispatcher.DEFAULT_ICON_TINT); - applyDarkness(id.signal_cluster, tintArea, intensity, colorForeground); // Set the correct tint for the status icons so they contrast mIconManager.setTint(fillColor); - mBatteryMeterView = findViewById(R.id.battery); - mBatteryMeterView.setForceShowPercent(true); - mClockView = findViewById(R.id.clock); - mDateView = findViewById(R.id.date); + BatteryMeterView battery = findViewById(R.id.battery); + battery.setForceShowPercent(true); } private void updateStatusText() { @@ -224,13 +212,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); updateResources(); - - // Update color schemes in landscape to use wallpaperTextColor - boolean shouldUseWallpaperTextColor = - newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE; - mBatteryMeterView.useWallpaperTextColor(shouldUseWallpaperTextColor); - mClockView.useWallpaperTextColor(shouldUseWallpaperTextColor); - mDateView.useWallpaperTextColor(shouldUseWallpaperTextColor); } @Override @@ -240,21 +221,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue } private void updateResources() { - Resources resources = mContext.getResources(); - - // Update height for a few views, especially due to landscape mode restricting space. + // Update height, especially due to landscape mode restricting space. mHeaderTextContainerView.getLayoutParams().height = - resources.getDimensionPixelSize(R.dimen.qs_header_tooltip_height); + mContext.getResources().getDimensionPixelSize(R.dimen.qs_header_tooltip_height); mHeaderTextContainerView.setLayoutParams(mHeaderTextContainerView.getLayoutParams()); - mSystemIconsView.getLayoutParams().height = resources.getDimensionPixelSize( - com.android.internal.R.dimen.quick_qs_offset_height); - mSystemIconsView.setLayoutParams(mSystemIconsView.getLayoutParams()); - - getLayoutParams().height = - resources.getDimensionPixelSize(com.android.internal.R.dimen.quick_qs_total_height); - setLayoutParams(getLayoutParams()); - updateStatusIconAlphaAnimator(); updateHeaderTextContainerAlphaAnimator(); } @@ -526,8 +497,9 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue mHeaderQsPanel.setHost(host, null /* No customization in header */); // Use SystemUI context to get battery meter colors, and let it use the default tint (white) - mBatteryMeterView.setColorsFromContext(mHost.getContext()); - mBatteryMeterView.onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT); + BatteryMeterView battery = findViewById(R.id.battery); + battery.setColorsFromContext(mHost.getContext()); + battery.onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT); } public void setCallback(Callback qsPanelCallback) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index 1cb89c472dbb..64e7a6353485 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -23,6 +23,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout { protected int mCellHeight; protected int mCellMarginHorizontal; protected int mCellMarginVertical; + protected int mSidePadding; protected final ArrayList<TileRecord> mRecords = new ArrayList<>(); private int mCellMarginTop; @@ -80,6 +81,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout { mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal); mCellMarginVertical= res.getDimensionPixelSize(R.dimen.qs_tile_margin_vertical); mCellMarginTop = res.getDimensionPixelSize(R.dimen.qs_tile_margin_top); + mSidePadding = res.getDimensionPixelOffset(R.dimen.qs_tile_layout_margin_side); if (mColumns != columns) { mColumns = columns; requestLayout(); @@ -93,7 +95,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout { final int numTiles = mRecords.size(); final int width = MeasureSpec.getSize(widthMeasureSpec); final int numRows = (numTiles + mColumns - 1) / mColumns; - mCellWidth = (width - (mCellMarginHorizontal * (mColumns + 1))) / mColumns; + mCellWidth = (width - mSidePadding * 2 - (mCellMarginHorizontal * mColumns)) / mColumns; // Measure each QS tile. View previousView = this; diff --git a/packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java index ec183769c763..31c455d880d6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java @@ -44,7 +44,6 @@ public class CarStatusBarHeader extends LinearLayout { float intensity = colorForeground == Color.WHITE ? 0f : 1f; Rect tintArea = new Rect(0, 0, 0, 0); - applyDarkness(R.id.signal_cluster, tintArea, intensity, colorForeground); applyDarkness(R.id.battery, tintArea, intensity, colorForeground); applyDarkness(R.id.clock, tintArea, intensity, colorForeground); diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java index a3d6c6cff283..4aa83d0d5f15 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java @@ -20,6 +20,7 @@ import android.animation.Animator.AnimatorListener; import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Point; import android.os.Bundle; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.GridLayoutManager; @@ -37,6 +38,7 @@ import android.widget.Toolbar.OnMenuItemClickListener; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; +import com.android.settingslib.Utils; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.qs.QS; @@ -81,10 +83,9 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene public QSCustomizer(Context context, AttributeSet attrs) { super(new ContextThemeWrapper(context, R.style.edit_theme), attrs); - mClipper = new QSDetailClipper(this); LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this); - + mClipper = new QSDetailClipper(findViewById(R.id.customize_container)); mToolbar = findViewById(com.android.internal.R.id.action_bar); TypedValue value = new TypedValue(); mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true); @@ -100,7 +101,10 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, mContext.getString(com.android.internal.R.string.reset)); mToolbar.setTitle(R.string.qs_edit); - + int accentColor = Utils.getColorAttr(context, android.R.attr.colorAccent); + mToolbar.setTitleTextColor(accentColor); + mToolbar.getNavigationIcon().setTint(accentColor); + mToolbar.getOverflowIcon().setTint(accentColor); mRecyclerView = findViewById(android.R.id.list); mTileAdapter = new TileAdapter(getContext()); mTileQueryHelper = new TileQueryHelper(context, mTileAdapter); diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index 943a17644b55..6f664d7debc0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -102,7 +102,9 @@ public class TileServices extends IQSService.Stub { mTokenMap.remove(service.getToken()); mTiles.remove(tile.getComponent()); final String slot = tile.getComponent().getClassName(); - mMainHandler.post(() -> mHost.getIconController().removeIcon(slot)); + // TileServices doesn't know how to add more than 1 icon per slot, so remove all + mMainHandler.post(() -> mHost.getIconController() + .removeAllIconsForSlot(slot)); } } 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 a9defc8b75a0..09d928fd3657 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java @@ -194,6 +194,7 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { setClickable(state.state != Tile.STATE_UNAVAILABLE); mIcon.setIcon(state); setContentDescription(state.contentDescription); + mAccessibilityClass = state.expandedAccessibilityClassName; if (state instanceof QSTile.BooleanState) { boolean newState = ((BooleanState) state).value; @@ -269,6 +270,10 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { info.setText(label); info.setChecked(b); info.setCheckable(true); + info.addAction( + new AccessibilityNodeInfo.AccessibilityAction( + AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.getId(), + getResources().getString(R.string.accessibility_long_click_tile))); } } } 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 3cb4c71fc98c..d21b06f02f16 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java @@ -32,12 +32,10 @@ import com.android.systemui.R; import com.android.systemui.plugins.qs.QSIconView; import com.android.systemui.plugins.qs.QSTile; - import java.util.Objects; /** View that represents a standard quick settings tile. **/ public class QSTileView extends QSTileBaseView { - private static final int DEFAULT_MAX_LINES = 2; private static final boolean DUAL_TARGET_ALLOWED = false; private View mDivider; protected TextView mLabel; @@ -87,22 +85,17 @@ public class QSTileView extends QSTileBaseView { mLabelContainer.setClipChildren(false); mLabelContainer.setClipToPadding(false); mLabel = mLabelContainer.findViewById(R.id.tile_label); - mLabel.setSelected(true); // Allow marquee to work. mPadLock = mLabelContainer.findViewById(R.id.restricted_padlock); 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); - mSecondLine.setSelected(true); // Allow marquee to work. addView(mLabelContainer); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (mLabel.getMaxLines() != DEFAULT_MAX_LINES) { - mLabel.setMaxLines(DEFAULT_MAX_LINES); - } super.onMeasure(widthMeasureSpec, heightMeasureSpec); // Remeasure view if the secondary label text will be cut off. @@ -114,6 +107,15 @@ public class QSTileView extends QSTileBaseView { } @Override + public void setExpansion(float expansion) { + // Start the marquee when fully expanded and stop when fully collapsed. Leave as is for + // other expansion ratios since there is no way way to pause the marquee. + boolean selected = expansion == 1f ? true : expansion == 0f ? false : mLabel.isSelected(); + mLabel.setSelected(selected); + mSecondLine.setSelected(selected); + } + + @Override protected void handleStateChanged(QSTile.State state) { super.handleStateChanged(state); if (!Objects.equals(mLabel.getText(), state.label) || mState != state.state) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index 674ccd8c3471..0f85c5b37f65 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -492,6 +492,13 @@ public class Recents extends SystemUI } } mDraggingInRecentsCurrentUser = currentUser; + + if (mOverviewProxyService.getProxy() != null) { + // The overview service is handling split screen, so just skip the wait for the + // first draw and notify the divider to start animating now + EventBus.getDefault().post(new RecentsDrawnEvent()); + } + return true; } else { EventBus.getDefault().send(new ShowUserToastEvent( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 03b263d2ae95..3ece2f958100 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar; -import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback; @@ -25,7 +24,10 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.annotation.Nullable; +import android.app.NotificationChannel; import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Path; @@ -39,6 +41,7 @@ import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.AttributeSet; import android.util.FloatProperty; +import android.util.Log; import android.util.MathUtils; import android.util.Property; import android.view.KeyEvent; @@ -90,12 +93,17 @@ import java.util.List; import java.util.function.BooleanSupplier; import java.util.function.Consumer; +/** + * View representing a notification item - this can be either the individual child notification or + * the group summary (which contains 1 or more child notifications). + */ public class ExpandableNotificationRow extends ActivatableNotificationView implements PluginListener<NotificationMenuRowPlugin> { private static final int DEFAULT_DIVIDER_ALPHA = 0x29; private static final int COLORED_DIVIDER_ALPHA = 0x7B; private static final int MENU_VIEW_INDEX = 0; + private static final String TAG = "ExpandableNotifRow"; public interface LayoutListener { public void onLayout(); @@ -166,6 +174,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private NotificationGuts mGuts; private NotificationData.Entry mEntry; private StatusBarNotification mStatusBarNotification; + private PackageManager mCachedPackageManager; + private PackageInfo mCachedPackageInfo; private String mAppName; private boolean mIsHeadsUp; private boolean mLastChronometerRunning = true; @@ -372,6 +382,53 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mEntry = entry; mStatusBarNotification = entry.notification; mNotificationInflater.inflateNotificationViews(); + + perhapsCachePackageInfo(); + } + + /** + * Caches the package manager and info objects which are expensive to obtain. + */ + private void perhapsCachePackageInfo() { + if (mCachedPackageInfo == null) { + mCachedPackageManager = StatusBar.getPackageManagerForUser( + mContext, mStatusBarNotification.getUser().getIdentifier()); + try { + mCachedPackageInfo = mCachedPackageManager.getPackageInfo( + mStatusBarNotification.getPackageName(), PackageManager.GET_SIGNATURES); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "perhapsCachePackageInfo: Could not find package info"); + } + } + } + + /** + * Returns whether this row is considered non-blockable (e.g. it's a non-blockable system notif, + * covers multiple channels, or is in a whitelist). + */ + public boolean getIsNonblockable() { + boolean isNonblockable; + + isNonblockable = Dependency.get(NotificationBlockingHelperManager.class) + .isNonblockablePackage(mStatusBarNotification.getPackageName()); + + // Only bother with going through the children if the row is still blockable based on the + // number of unique channels. + if (!isNonblockable) { + isNonblockable = getNumUniqueChannels() > 1; + } + + // Only bother with IPC if the package is still blockable. + if (!isNonblockable && mCachedPackageManager != null && mCachedPackageInfo != null) { + if (com.android.settingslib.Utils.isSystemPackage( + mContext.getResources(), mCachedPackageManager, mCachedPackageInfo)) { + if (mEntry.channel != null + && !mEntry.channel.isBlockableSystem()) { + isNonblockable = true; + } + } + } + return isNonblockable; } public void onNotificationUpdated() { @@ -2019,6 +2076,32 @@ public class ExpandableNotificationRow extends ActivatableNotificationView updateChildrenVisibility(); applyChildrenRoundness(); } + /** + * Returns the number of channels covered by the notification row (including its children if + * it's a summary notification). + */ + public int getNumUniqueChannels() { + ArraySet<NotificationChannel> channels = new ArraySet<>(); + + channels.add(mEntry.channel); + + // If this is a summary, then add in the children notification channels for the + // same user and pkg. + if (mIsSummaryWithChildren) { + final List<ExpandableNotificationRow> childrenRows = getNotificationChildren(); + final int numChildren = childrenRows.size(); + for (int i = 0; i < numChildren; i++) { + final ExpandableNotificationRow childRow = childrenRows.get(i); + final NotificationChannel childChannel = childRow.getEntry().channel; + final StatusBarNotification childSbn = childRow.getStatusBarNotification(); + if (childSbn.getUser().equals(mStatusBarNotification.getUser()) && + childSbn.getPackageName().equals(mStatusBarNotification.getPackageName())) { + channels.add(childChannel); + } + } + } + return channels.size(); + } public void updateChildrenHeaderAppearance() { if (mIsSummaryWithChildren) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NeutralGoodDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/NeutralGoodDrawable.java new file mode 100644 index 000000000000..cdb0514a2686 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NeutralGoodDrawable.java @@ -0,0 +1,78 @@ +/* + * 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.statusbar; + +import android.content.Context; +import android.content.res.Resources.Theme; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.view.ContextThemeWrapper; +import android.view.Gravity; +import com.android.settingslib.Utils; +import com.android.systemui.R; + +/** + * NeutralGoodDrawable implements a drawable that will load 2 underlying drawable resources, one + * with each the DualToneDarkTheme and DualToneLightTheme, choosing which one based on what + * DarkIconDispatcher tells us about darkness + */ +public class NeutralGoodDrawable extends LayerDrawable { + + public static NeutralGoodDrawable create(Context context, int resId) { + int dualToneLightTheme = Utils.getThemeAttr(context, R.attr.lightIconTheme); + int dualToneDarkTheme = Utils.getThemeAttr(context, R.attr.darkIconTheme); + ContextThemeWrapper light = new ContextThemeWrapper(context, dualToneLightTheme); + ContextThemeWrapper dark = new ContextThemeWrapper(context, dualToneDarkTheme); + + return create(light, dark, resId); + } + + /** + * For the on-the-go young entrepreneurial who wants to cache contexts + * @param light - a context using the R.attr.lightIconTheme + * @param dark - a context using the R.attr.darkIconTheme + * @param resId - the resId for our drawable + */ + public static NeutralGoodDrawable create(Context light, Context dark, int resId) { + return new NeutralGoodDrawable( + new Drawable[] { + light.getDrawable(resId).mutate(), + dark.getDrawable(resId).mutate() }); + } + + protected NeutralGoodDrawable(Drawable []drawables) { + super(drawables); + + for (int i = 0; i < drawables.length; i++) { + setLayerGravity(i, Gravity.CENTER); + } + + mutate(); + setDarkIntensity(0); + } + + public void setDarkIntensity(float intensity) { + + getDrawable(0).setAlpha((int) ((1 - intensity) * 255f)); + getDrawable(1).setAlpha((int) (intensity * 255f)); + + invalidateSelf(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBlockingHelperManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBlockingHelperManager.java index c9c1bc6fdc39..20e5f86ee097 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBlockingHelperManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBlockingHelperManager.java @@ -17,11 +17,19 @@ package com.android.systemui.statusbar; import android.content.Context; +import android.content.pm.PackageManager; +import android.os.UserHandle; +import android.service.notification.StatusBarNotification; import android.support.annotation.VisibleForTesting; import android.util.Log; import com.android.systemui.Dependency; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; +import com.android.systemui.statusbar.phone.StatusBar; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; @@ -37,6 +45,7 @@ public class NotificationBlockingHelperManager { private final Context mContext; /** Row that the blocking helper will be shown in (via {@link NotificationGuts}. */ private ExpandableNotificationRow mBlockingHelperRow; + private Set<String> mNonBlockablePkgs; /** * Whether the notification shade/stack is expanded - used to determine blocking helper @@ -46,6 +55,9 @@ public class NotificationBlockingHelperManager { public NotificationBlockingHelperManager(Context context) { mContext = context; + mNonBlockablePkgs = new HashSet<>(); + Collections.addAll(mNonBlockablePkgs, mContext.getResources().getStringArray( + com.android.internal.R.array.config_nonBlockableNotificationPackages)); } /** @@ -59,15 +71,14 @@ public class NotificationBlockingHelperManager { */ boolean perhapsShowBlockingHelper( ExpandableNotificationRow row, NotificationMenuRowPlugin menuRow) { - int numChildren = row.getNumberOfNotificationChildren(); - // We only show the blocking helper if: - // - The dismissed row is a valid group (>1 or 0 children) or the only child in the group + // - User sentiment is negative (DEBUG flag can bypass) // - The notification shade is fully expanded (guarantees we're not touching a HUN). - // - User sentiment is negative - if (DEBUG - || row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE + // - The row is blockable (i.e. not non-blockable) + // - The dismissed row is a valid group (>1 or 0 children) or the only child in the group + if ((row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE || DEBUG) && mIsShadeExpanded + && !row.getIsNonblockable() && (!row.isChildInGroup() || row.isOnlyChildInGroup())) { // Dismiss any current blocking helper before continuing forward (only one can be shown // at a given time). @@ -125,6 +136,13 @@ public class NotificationBlockingHelperManager { mIsShadeExpanded = expandedHeight > 0.0f; } + /** + * Returns whether the given package name is in the list of non-blockable packages. + */ + public boolean isNonblockablePackage(String packageName) { + return mNonBlockablePkgs.contains(packageName); + } + @VisibleForTesting boolean isBlockingHelperRowNull() { return mBlockingHelperRow == null; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 775faee7fbc3..4b6ab64ab624 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -18,25 +18,21 @@ package com.android.systemui.statusbar; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; -import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; +import android.Manifest; import android.app.AppGlobals; -import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; +import android.content.Context; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; -import android.content.Context; import android.graphics.drawable.Icon; -import android.os.AsyncTask; -import android.os.Bundle; import android.os.RemoteException; import android.os.SystemClock; -import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.SnoozeCriterion; @@ -46,10 +42,8 @@ import android.util.ArraySet; import android.view.View; import android.widget.ImageView; import android.widget.RemoteViews; -import android.Manifest; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.util.NotificationColorUtil; import com.android.systemui.Dependency; @@ -454,47 +448,44 @@ public class NotificationData { return Ranking.VISIBILITY_NO_OVERRIDE; } - public boolean shouldSuppressFullScreenIntent(String key) { - if (mRankingMap != null) { - getRanking(key, mTmpRanking); - return (mTmpRanking.getSuppressedVisualEffects() - & SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) != 0; - } - return false; + public boolean shouldSuppressFullScreenIntent(StatusBarNotification sbn) { + return shouldSuppressVisualEffect(sbn, SUPPRESSED_EFFECT_FULL_SCREEN_INTENT); } - public boolean shouldSuppressPeek(String key) { - if (mRankingMap != null) { - getRanking(key, mTmpRanking); - return (mTmpRanking.getSuppressedVisualEffects() - & SUPPRESSED_EFFECT_PEEK) != 0; - } - return false; + public boolean shouldSuppressPeek(StatusBarNotification sbn) { + return shouldSuppressVisualEffect(sbn, SUPPRESSED_EFFECT_PEEK); } - public boolean shouldSuppressStatusBar(String key) { - if (mRankingMap != null) { - getRanking(key, mTmpRanking); - return (mTmpRanking.getSuppressedVisualEffects() - & SUPPRESSED_EFFECT_STATUS_BAR) != 0; - } - return false; + public boolean shouldSuppressStatusBar(StatusBarNotification sbn) { + return shouldSuppressVisualEffect(sbn, SUPPRESSED_EFFECT_STATUS_BAR); + } + + public boolean shouldSuppressAmbient(StatusBarNotification sbn) { + return shouldSuppressVisualEffect(sbn, SUPPRESSED_EFFECT_AMBIENT); } - public boolean shouldSuppressAmbient(String key) { + public boolean shouldSuppressNotificationList(StatusBarNotification sbn) { + return shouldSuppressVisualEffect(sbn, SUPPRESSED_EFFECT_NOTIFICATION_LIST); + } + + private boolean shouldSuppressVisualEffect(StatusBarNotification sbn, int effect) { + if (isExemptFromDndVisualSuppression(sbn)) { + return false; + } + String key = sbn.getKey(); if (mRankingMap != null) { getRanking(key, mTmpRanking); - return (mTmpRanking.getSuppressedVisualEffects() - & SUPPRESSED_EFFECT_AMBIENT) != 0; + return (mTmpRanking.getSuppressedVisualEffects() & effect) != 0; } return false; } - public boolean shouldSuppressNotificationList(String key) { - if (mRankingMap != null) { - getRanking(key, mTmpRanking); - return (mTmpRanking.getSuppressedVisualEffects() - & SUPPRESSED_EFFECT_NOTIFICATION_LIST) != 0; + protected boolean isExemptFromDndVisualSuppression(StatusBarNotification sbn) { + if ((sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) { + return true; + } + if (sbn.getNotification().isMediaNotification()) { + return true; } return false; } @@ -620,11 +611,11 @@ public class NotificationData { return true; } - if (mEnvironment.isDozing() && shouldSuppressAmbient(sbn.getKey())) { + if (mEnvironment.isDozing() && shouldSuppressAmbient(sbn)) { return true; } - if (!mEnvironment.isDozing() && shouldSuppressNotificationList(sbn.getKey())) { + if (!mEnvironment.isDozing() && shouldSuppressNotificationList(sbn)) { return true; } @@ -641,9 +632,14 @@ public class NotificationData { // this is a foreground-service disclosure for a user that does not need to show one return true; } - if (mFsc.isSystemAlertNotification(sbn) && !mFsc.isSystemAlertWarningNeeded( - sbn.getUserId(), sbn.getPackageName())) { - return true; + if (mFsc.isSystemAlertNotification(sbn)) { + final String[] apps = sbn.getNotification().extras.getStringArray( + Notification.EXTRA_FOREGROUND_APPS); + if (apps != null && apps.length >= 1) { + if (!mFsc.isSystemAlertWarningNeeded(sbn.getUserId(), apps[0])) { + return true; + } + } } return false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java index 7a7cc99eb44d..45df4505fac2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java @@ -299,12 +299,12 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. updateNotifications(); } - private boolean shouldSuppressFullScreenIntent(String key) { + private boolean shouldSuppressFullScreenIntent(StatusBarNotification sbn) { if (mPresenter.isDeviceInVrMode()) { return true; } - return mNotificationData.shouldSuppressFullScreenIntent(key); + return mNotificationData.shouldSuppressFullScreenIntent(sbn); } private void inflateViews(NotificationData.Entry entry, ViewGroup parent) { @@ -690,7 +690,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. NotificationData.Entry shadeEntry = createNotificationViews(notification); boolean isHeadsUped = shouldPeek(shadeEntry); if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) { - if (shouldSuppressFullScreenIntent(key)) { + if (shouldSuppressFullScreenIntent(notification)) { if (DEBUG) { Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key); } @@ -846,13 +846,13 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. return false; } - if (!mPresenter.isDozing() && mNotificationData.shouldSuppressPeek(sbn.getKey())) { + if (!mPresenter.isDozing() && mNotificationData.shouldSuppressPeek(sbn)) { if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey()); return false; } // Peeking triggers an ambient display pulse, so disable peek is ambient is active - if (mPresenter.isDozing() && mNotificationData.shouldSuppressAmbient(sbn.getKey())) { + if (mPresenter.isDozing() && mNotificationData.shouldSuppressAmbient(sbn)) { if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey()); return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java index 75204d9513fe..dff5f3814f68 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java @@ -65,7 +65,6 @@ public class NotificationGutsManager implements Dumpable { private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); - private final Set<String> mNonBlockablePkgs; private final Context mContext; private final AccessibilityManager mAccessibilityManager; @@ -87,10 +86,6 @@ public class NotificationGutsManager implements Dumpable { mContext = context; Resources res = context.getResources(); - mNonBlockablePkgs = new HashSet<>(); - Collections.addAll(mNonBlockablePkgs, res.getStringArray( - com.android.internal.R.array.config_nonBlockableNotificationPackages)); - mAccessibilityManager = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); } @@ -279,12 +274,12 @@ public class NotificationGutsManager implements Dumpable { iNotificationManager, packageName, row.getEntry().channel, - getNumNotificationChannels(row, packageName, userHandle), + row.getNumUniqueChannels(), sbn, mCheckSaveListener, onSettingsClick, onAppSettingsClick, - mNonBlockablePkgs, + row.getIsNonblockable(), isForBlockingHelper, row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE); } catch (RemoteException e) { @@ -293,34 +288,6 @@ public class NotificationGutsManager implements Dumpable { } /** - * @return the number of channels covered by the notification row (including its children if - * it's a summary notification). - */ - private int getNumNotificationChannels( - ExpandableNotificationRow row, String packageName, UserHandle userHandle) { - ArraySet<NotificationChannel> channels = new ArraySet<>(); - - channels.add(row.getEntry().channel); - - // If this is a summary, then add in the children notification channels for the - // same user and pkg. - if (row.isSummaryWithChildren()) { - final List<ExpandableNotificationRow> childrenRows = row.getNotificationChildren(); - final int numChildren = childrenRows.size(); - for (int i = 0; i < numChildren; i++) { - final ExpandableNotificationRow childRow = childrenRows.get(i); - final NotificationChannel childChannel = childRow.getEntry().channel; - final StatusBarNotification childSbn = childRow.getStatusBarNotification(); - if (childSbn.getUser().equals(userHandle) && - childSbn.getPackageName().equals(packageName)) { - channels.add(childChannel); - } - } - } - return channels.size(); - } - - /** * Closes guts or notification menus that might be visible and saves any changes. * * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java index a2f336eb6398..a93be00ba080 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java @@ -31,7 +31,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; @@ -49,13 +48,11 @@ import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.settingslib.Utils; import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; import java.util.List; -import java.util.Set; /** * The guts of a notification revealed when performing a long press. @@ -74,7 +71,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private int mStartingUserImportance; private int mChosenImportance; private boolean mIsSingleDefaultChannel; - private boolean mNonblockable; + private boolean mIsNonblockable; private StatusBarNotification mSbn; private AnimatorSet mExpandAnimation; private boolean mIsForeground; @@ -128,10 +125,10 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G final CheckSaveListener checkSaveListener, final OnSettingsClickListener onSettingsClick, final OnAppSettingsClickListener onAppSettingsClick, - final Set<String> nonBlockablePkgs) + boolean isNonblockable) throws RemoteException { bindNotification(pm, iNotificationManager, pkg, notificationChannel, numChannels, sbn, - checkSaveListener, onSettingsClick, onAppSettingsClick, nonBlockablePkgs, + checkSaveListener, onSettingsClick, onAppSettingsClick, isNonblockable, false /* isBlockingHelper */, false /* isUserSentimentNegative */); } @@ -146,7 +143,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G CheckSaveListener checkSaveListener, OnSettingsClickListener onSettingsClick, OnAppSettingsClickListener onAppSettingsClick, - Set<String> nonBlockablePkgs, + boolean isNonblockable, boolean isForBlockingHelper, boolean isUserSentimentNegative) throws RemoteException { @@ -162,9 +159,11 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G mSingleNotificationChannel = notificationChannel; mStartingUserImportance = mChosenImportance = mSingleNotificationChannel.getImportance(); mNegativeUserSentiment = isUserSentimentNegative; + mIsNonblockable = isNonblockable; mIsForeground = (mSbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0; mIsForBlockingHelper = isForBlockingHelper; + mAppUid = mSbn.getUid(); int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage( pkg, mAppUid, false /* includeDeleted */); @@ -173,27 +172,11 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G } else { // Special behavior for the Default channel if no other channels have been defined. mIsSingleDefaultChannel = mNumNotificationChannels == 1 - && mSingleNotificationChannel.getId() - .equals(NotificationChannel.DEFAULT_CHANNEL_ID) - && numTotalChannels <= 1; + && mSingleNotificationChannel.getId().equals( + NotificationChannel.DEFAULT_CHANNEL_ID) + && numTotalChannels == 1; } - try { - final PackageInfo pkgInfo = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES); - if (Utils.isSystemPackage(getResources(), pm, pkgInfo)) { - if (mSingleNotificationChannel != null - && !mSingleNotificationChannel.isBlockableSystem()) { - mNonblockable = true; - } - } - } catch (PackageManager.NameNotFoundException e) { - // unlikely. - } - if (nonBlockablePkgs != null) { - mNonblockable |= nonBlockablePkgs.contains(pkg); - } - mNonblockable |= (mNumNotificationChannels > 1); - bindHeader(); bindPrompt(); bindButtons(); @@ -210,7 +193,6 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DIRECT_BOOT_AWARE); if (info != null) { - mAppUid = mSbn.getUid(); mAppName = String.valueOf(mPm.getApplicationLabel(info)); pkgicon = mPm.getApplicationIcon(info); } @@ -261,7 +243,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private void bindPrompt() { final TextView blockPrompt = findViewById(R.id.block_prompt); bindName(); - if (mNonblockable) { + if (mIsNonblockable) { blockPrompt.setText(R.string.notification_unblockable_desc); } else { if (mNegativeUserSentiment) { @@ -288,7 +270,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G } private void saveImportance() { - if (mNonblockable) { + if (mIsNonblockable) { return; } MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE, @@ -314,7 +296,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G keep.setOnClickListener(mOnKeepShowing); minimize.setOnClickListener(mOnStopMinNotifications); - if (mNonblockable) { + if (mIsNonblockable) { keep.setText(R.string.notification_done); block.setVisibility(GONE); minimize.setVisibility(GONE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java new file mode 100644 index 000000000000..56f78f419275 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import com.android.internal.statusbar.StatusBarIcon; +import java.util.ArrayList; +import java.util.List; + +/** + * Holds an array of {@link com.android.internal.statusbar.StatusBarIcon}s and draws them + * in a linear layout + */ +public class StatusBarIconContainer { + private final List<StatusBarIcon> mIcons = new ArrayList<>(); + + public StatusBarIconContainer(List<StatusBarIcon> icons) { + mIcons.addAll(icons); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 603902a4507d..bd6bd12a5e9b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar; +import static com.android.systemui.statusbar.policy.DarkIconDispatcher.getTint; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; @@ -23,6 +25,7 @@ import android.animation.ValueAnimator; import android.app.Notification; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Canvas; @@ -56,7 +59,7 @@ import com.android.systemui.statusbar.notification.NotificationUtils; import java.text.NumberFormat; import java.util.Arrays; -public class StatusBarIconView extends AnimatedImageView { +public class StatusBarIconView extends AnimatedImageView implements StatusIconDisplayable { public static final int NO_COLOR = 0; /** @@ -867,6 +870,21 @@ public class StatusBarIconView extends AnimatedImageView { mOnDismissListener = onDismissListener; } + @Override + public void onDarkChanged(Rect area, float darkIntensity, int tint) { + setImageTintList(ColorStateList.valueOf(getTint(area, this, tint))); + } + + @Override + public boolean isIconVisible() { + return mIcon != null && mIcon.visible; + } + + @Override + public boolean isIconBlocked() { + return mBlocked; + } + public interface OnVisibilityChangedListener { void onVisibilityChanged(int newVisibility); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java new file mode 100644 index 000000000000..b7620f30d742 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java @@ -0,0 +1,207 @@ +/* + * 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; + +import static com.android.systemui.statusbar.policy.DarkIconDispatcher.getTint; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.keyguard.AlphaOptimizedLinearLayout; +import com.android.settingslib.graph.SignalDrawable; +import com.android.systemui.R; +import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; +import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver; + +public class StatusBarMobileView extends AlphaOptimizedLinearLayout implements DarkReceiver, + StatusIconDisplayable { + private static final String TAG = "StatusBarMobileView"; + + private String mSlot; + private MobileIconState mState; + private SignalDrawable mMobileDrawable; + private View mInoutContainer; + private ImageView mIn; + private ImageView mOut; + private ImageView mMobile, mMobileType, mMobileRoaming; + private View mMobileRoamingSpace; + + public static StatusBarMobileView fromContext(Context context) { + LayoutInflater inflater = LayoutInflater.from(context); + + return (StatusBarMobileView) + inflater.inflate(R.layout.status_bar_mobile_signal_group, null); + } + + public StatusBarMobileView(Context context) { + super(context); + } + + public StatusBarMobileView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public StatusBarMobileView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public StatusBarMobileView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + init(); + } + + private void init() { + mMobile = findViewById(R.id.mobile_signal); + mMobileType = findViewById(R.id.mobile_type); + mMobileRoaming = findViewById(R.id.mobile_roaming); + mMobileRoamingSpace = findViewById(R.id.mobile_roaming_space); + mIn = findViewById(R.id.mobile_in); + mOut = findViewById(R.id.mobile_out); + mInoutContainer = findViewById(R.id.inout_container); + + mMobileDrawable = new SignalDrawable(getContext()); + mMobile.setImageDrawable(mMobileDrawable); + } + + public void applyMobileState(MobileIconState state) { + if (state == null) { + setVisibility(View.GONE); + mState = null; + return; + } + + if (mState == null) { + mState = state; + initViewState(); + return; + } + + if (!mState.equals(state)) { + updateState(state); + } + } + + private void initViewState() { + setContentDescription(mState.contentDescription); + if (!mState.visible) { + setVisibility(View.GONE); + } else { + setVisibility(View.VISIBLE); + } + mMobileDrawable.setLevel(mState.strengthId); + if (mState.typeId > 0) { + mMobileType.setContentDescription(mState.typeContentDescription); + mMobileType.setImageResource(mState.typeId); + mMobileType.setVisibility(View.VISIBLE); + } else { + mMobileType.setVisibility(View.GONE); + } + + mMobileRoaming.setVisibility(mState.roaming ? View.VISIBLE : View.GONE); + mMobileRoamingSpace.setVisibility(mState.roaming ? View.VISIBLE : View.GONE); + mIn.setVisibility(mState.activityIn ? View.VISIBLE : View.GONE); + mOut.setVisibility(mState.activityIn ? View.VISIBLE : View.GONE); + mInoutContainer.setVisibility((mState.activityIn || mState.activityOut) + ? View.VISIBLE : View.GONE); + } + + private void updateState(MobileIconState state) { + setContentDescription(state.contentDescription); + if (mState.visible != state.visible) { + setVisibility(state.visible ? View.VISIBLE : View.GONE); + } + if (mState.strengthId != state.strengthId) { + mMobileDrawable.setLevel(state.strengthId); + } + if (mState.typeId != state.typeId && state.typeId != 0) { + mMobileType.setContentDescription(state.typeContentDescription); + mMobileType.setImageResource(state.typeId); + mMobileType.setVisibility(View.VISIBLE); + } else { + mMobileType.setVisibility(View.GONE); + } + + mMobileRoaming.setVisibility(state.roaming ? View.VISIBLE : View.GONE); + mMobileRoamingSpace.setVisibility(state.roaming ? View.VISIBLE : View.GONE); + mIn.setVisibility(state.activityIn ? View.VISIBLE : View.GONE); + mOut.setVisibility(state.activityIn ? View.VISIBLE : View.GONE); + mInoutContainer.setVisibility((state.activityIn || state.activityOut) + ? View.VISIBLE : View.GONE); + + mState = state; + } + + @Override + public void onDarkChanged(Rect area, float darkIntensity, int tint) { + mMobileDrawable.setDarkIntensity(darkIntensity); + ColorStateList color = ColorStateList.valueOf(getTint(area, this, tint)); + mIn.setImageTintList(color); + mOut.setImageTintList(color); + mMobileType.setImageTintList(color); + mMobileRoaming.setImageTintList(color); + } + + @Override + public String getSlot() { + return mSlot; + } + + public void setSlot(String slot) { + mSlot = slot; + } + + @Override + public void setStaticDrawableColor(int color) { + ColorStateList list = ColorStateList.valueOf(color); + float intensity = color == Color.WHITE ? 0 : 1; + mMobileDrawable.setDarkIntensity(intensity); + + mIn.setImageTintList(list); + mOut.setImageTintList(list); + mMobileType.setImageTintList(list); + mMobileRoaming.setImageTintList(list); + } + + @Override + public boolean isIconVisible() { + return mState.visible; + } + + @VisibleForTesting + public MobileIconState getState() { + return mState; + } + + @Override + public String toString() { + return "StatusBarMobileView(slot=" + mSlot + " state=" + mState + ")"; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java new file mode 100644 index 000000000000..afd373ed6321 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java @@ -0,0 +1,192 @@ +/* + * 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; + +import static com.android.systemui.statusbar.policy.DarkIconDispatcher.getTint; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; + +import com.android.keyguard.AlphaOptimizedLinearLayout; +import com.android.settingslib.Utils; +import com.android.systemui.R; +import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; +import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver; + +/** + * Start small: StatusBarWifiView will be able to layout from a WifiIconState + */ +public class StatusBarWifiView extends AlphaOptimizedLinearLayout implements DarkReceiver, + StatusIconDisplayable { + private static final String TAG = "StatusBarWifiView"; + + private ImageView mWifiIcon; + private ImageView mIn; + private ImageView mOut; + private View mInoutContainer; + private View mSignalSpacer; + private View mAirplaneSpacer; + private WifiIconState mState; + private String mSlot; + private float mDarkIntensity = 0; + + private ContextThemeWrapper mDarkContext; + private ContextThemeWrapper mLightContext; + + public static StatusBarWifiView fromContext(Context context) { + LayoutInflater inflater = LayoutInflater.from(context); + return (StatusBarWifiView) inflater.inflate(R.layout.status_bar_wifi_group, null); + } + + public StatusBarWifiView(Context context) { + super(context); + } + + public StatusBarWifiView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public StatusBarWifiView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public StatusBarWifiView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + init(); + } + + public void setSlot(String slot) { + mSlot = slot; + } + + @Override + public void setStaticDrawableColor(int color) { + ColorStateList list = ColorStateList.valueOf(color); + mWifiIcon.setImageTintList(list); + mIn.setImageTintList(list); + mOut.setImageTintList(list); + } + + @Override + public String getSlot() { + return mSlot; + } + + @Override + public boolean isIconVisible() { + return mState != null && mState.visible; + } + + private void init() { + int dualToneLightTheme = Utils.getThemeAttr(mContext, R.attr.lightIconTheme); + int dualToneDarkTheme = Utils.getThemeAttr(mContext, R.attr.darkIconTheme); + mLightContext = new ContextThemeWrapper(mContext, dualToneLightTheme); + mDarkContext = new ContextThemeWrapper(mContext, dualToneDarkTheme); + + mWifiIcon = findViewById(R.id.wifi_signal); + mIn = findViewById(R.id.wifi_in); + mOut = findViewById(R.id.wifi_out); + mSignalSpacer = findViewById(R.id.wifi_signal_spacer); + mAirplaneSpacer = findViewById(R.id.wifi_airplane_spacer); + mInoutContainer = findViewById(R.id.inout_container); + } + + public void applyWifiState(WifiIconState state) { + if (state == null) { + setVisibility(View.GONE); + mState = null; + return; + } + + if (mState == null) { + mState = state; + initViewState(); + } + + if (!mState.equals(state)) { + updateState(state); + } + } + + private void updateState(WifiIconState state) { + if (mState.resId != state.resId && state.resId >= 0) { + NeutralGoodDrawable drawable = NeutralGoodDrawable + .create(mLightContext, mDarkContext, state.resId); + drawable.setDarkIntensity(mDarkIntensity); + mWifiIcon.setImageDrawable(drawable); + } + + mIn.setVisibility(state.activityIn ? View.VISIBLE : View.GONE); + mOut.setVisibility(state.activityOut ? View.VISIBLE : View.GONE); + mInoutContainer.setVisibility( + (state.activityIn || state.activityOut) ? View.VISIBLE : View.GONE); + mAirplaneSpacer.setVisibility(state.airplaneSpacerVisible ? View.VISIBLE : View.GONE); + mSignalSpacer.setVisibility(state.signalSpacerVisible ? View.VISIBLE : View.GONE); + if (mState.visible != state.visible) { + setVisibility(state.visible ? View.VISIBLE : View.GONE); + } + + mState = state; + } + + private void initViewState() { + if (mState.resId >= 0) { + NeutralGoodDrawable drawable = NeutralGoodDrawable.create( + mLightContext, mDarkContext, mState.resId); + drawable.setDarkIntensity(mDarkIntensity); + mWifiIcon.setImageDrawable(drawable); + } + + mIn.setVisibility(mState.activityIn ? View.VISIBLE : View.GONE); + mOut.setVisibility(mState.activityOut ? View.VISIBLE : View.GONE); + mInoutContainer.setVisibility( + (mState.activityIn || mState.activityOut) ? View.VISIBLE : View.GONE); + mAirplaneSpacer.setVisibility(mState.airplaneSpacerVisible ? View.VISIBLE : View.GONE); + mSignalSpacer.setVisibility(mState.signalSpacerVisible ? View.VISIBLE : View.GONE); + setVisibility(mState.visible ? View.VISIBLE : View.GONE); + } + + @Override + public void onDarkChanged(Rect area, float darkIntensity, int tint) { + mDarkIntensity = darkIntensity; + Drawable d = mWifiIcon.getDrawable(); + if (d instanceof NeutralGoodDrawable) { + ((NeutralGoodDrawable)d).setDarkIntensity(darkIntensity); + } + mIn.setImageTintList(ColorStateList.valueOf(getTint(area, this, tint))); + mOut.setImageTintList(ColorStateList.valueOf(getTint(area, this, tint))); + } + + + @Override + public String toString() { + return "StatusBarWifiView(slot=" + mSlot + " state=" + mState + ")"; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java new file mode 100644 index 000000000000..ccab0d6fa44b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java @@ -0,0 +1,29 @@ +/* + * 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.statusbar; + +import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver; + +public interface StatusIconDisplayable extends DarkReceiver { + String getSlot(); + void setStaticDrawableColor(int color); + boolean isIconVisible(); + default boolean isIconBlocked() { + return false; + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java index 284113624cc1..b7d501e7f745 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java @@ -46,6 +46,12 @@ public class CarFacetButtonController { } } + public void removeAll() { + mButtonsByCategory.clear(); + mButtonsByPackage.clear(); + mSelectedFacetButton = null; + } + /** * This will unselect the currently selected CarFacetButton and determine which one should be * selected next. It does this by reading the properties on the CarFacetButton and seeing if diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index a95d0a4dc7b8..3530e0b84664 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.car.hvac.HvacController; import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.UserSwitcherController; import java.io.FileDescriptor; @@ -80,12 +81,16 @@ public class CarStatusBar extends StatusBar implements private boolean mShowRight; private boolean mShowBottom; private CarFacetButtonController mCarFacetButtonController; + private ActivityManagerWrapper mActivityManagerWrapper; + private DeviceProvisionedController mDeviceProvisionedController; + private boolean mDeviceIsProvisioned = true; @Override public void start() { super.start(); mTaskStackListener = new TaskStackListenerImpl(); - ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); + mActivityManagerWrapper = ActivityManagerWrapper.getInstance(); + mActivityManagerWrapper.registerTaskStackListener(mTaskStackListener); mStackScroller.setScrollingEnabled(true); @@ -96,12 +101,54 @@ public class CarStatusBar extends StatusBar implements Log.d(TAG, "Connecting to HVAC service"); Dependency.get(HvacController.class).connectToCarService(); } + mCarFacetButtonController = Dependency.get(CarFacetButtonController.class); + mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); + mDeviceIsProvisioned = mDeviceProvisionedController.isDeviceProvisioned(); + if (!mDeviceIsProvisioned) { + mDeviceProvisionedController.addCallback( + new DeviceProvisionedController.DeviceProvisionedListener() { + @Override + public void onDeviceProvisionedChanged() { + mDeviceIsProvisioned = + mDeviceProvisionedController.isDeviceProvisioned(); + restartNavBars(); + } + }); + } + } + + /** + * Remove all content from navbars and rebuild them. Used to allow for different nav bars + * before and after the device is provisioned + */ + private void restartNavBars() { + mCarFacetButtonController.removeAll(); + if (ENABLE_HVAC_CONNECTION) { + Dependency.get(HvacController.class).removeAllComponents(); + } + if (mNavigationBarWindow != null) { + mNavigationBarWindow.removeAllViews(); + mNavigationBarView = null; + } + + if (mLeftNavigationBarWindow != null) { + mLeftNavigationBarWindow.removeAllViews(); + mLeftNavigationBarView = null; + } + + if (mRightNavigationBarWindow != null) { + mRightNavigationBarWindow.removeAllViews(); + mRightNavigationBarView = null; + } + buildNavBarContent(); } + @Override public void destroy() { mCarBatteryController.stopListening(); mConnectedDeviceSignalController.stopListening(); + mActivityManagerWrapper.unregisterTaskStackListener(mTaskStackListener); if (mNavigationBarWindow != null) { mWindowManager.removeViewImmediate(mNavigationBarWindow); @@ -117,10 +164,10 @@ public class CarStatusBar extends StatusBar implements mWindowManager.removeViewImmediate(mRightNavigationBarWindow); mRightNavigationBarView = null; } - super.destroy(); } + @Override protected void makeStatusBarView() { super.makeStatusBarView(); @@ -167,129 +214,132 @@ public class CarStatusBar extends StatusBar implements @Override protected void createNavigationBar() { - mCarFacetButtonController = Dependency.get(CarFacetButtonController.class); - if (mNavigationBarView != null) { - return; - } - mShowBottom = mContext.getResources().getBoolean(R.bool.config_enableBottomNavigationBar); + mShowLeft = mContext.getResources().getBoolean(R.bool.config_enableLeftNavigationBar); + mShowRight = mContext.getResources().getBoolean(R.bool.config_enableRightNavigationBar); + + buildNavBarWindows(); + buildNavBarContent(); + attachNavBarWindows(); + } + + private void buildNavBarContent() { if (mShowBottom) { - buildBottomBar(); + buildBottomBar((mDeviceIsProvisioned) ? R.layout.car_navigation_bar : + R.layout.car_navigation_bar_unprovisioned); } - int widthForSides = mContext.getResources().getDimensionPixelSize( - R.dimen.navigation_bar_height_car_mode); + if (mShowLeft) { + buildLeft((mDeviceIsProvisioned) ? R.layout.car_left_navigation_bar : + R.layout.car_left_navigation_bar_unprovisioned); + } + if (mShowRight) { + buildRight((mDeviceIsProvisioned) ? R.layout.car_right_navigation_bar : + R.layout.car_right_navigation_bar_unprovisioned); + } + } - mShowLeft = mContext.getResources().getBoolean(R.bool.config_enableLeftNavigationBar); + private void buildNavBarWindows() { + if (mShowBottom) { + mNavigationBarWindow = (ViewGroup) View.inflate(mContext, + R.layout.navigation_bar_window, null); + } if (mShowLeft) { - buildLeft(widthForSides); + mLeftNavigationBarWindow = (ViewGroup) View.inflate(mContext, + R.layout.navigation_bar_window, null); + } + if (mShowRight) { + mRightNavigationBarWindow = (ViewGroup) View.inflate(mContext, + R.layout.navigation_bar_window, null); } - mShowRight = mContext.getResources().getBoolean(R.bool.config_enableRightNavigationBar); + } + + private void attachNavBarWindows() { + if (mShowBottom) { + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, + PixelFormat.TRANSLUCENT); + lp.setTitle("CarNavigationBar"); + lp.windowAnimations = 0; + mWindowManager.addView(mNavigationBarWindow, lp); + } + if (mShowLeft) { + int width = mContext.getResources().getDimensionPixelSize( + R.dimen.car_left_navigation_bar_width); + WindowManager.LayoutParams leftlp = new WindowManager.LayoutParams( + width, LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, + PixelFormat.TRANSLUCENT); + leftlp.setTitle("LeftCarNavigationBar"); + leftlp.windowAnimations = 0; + leftlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR; + leftlp.gravity = Gravity.LEFT; + mWindowManager.addView(mLeftNavigationBarWindow, leftlp); + } if (mShowRight) { - buildRight(widthForSides); + int width = mContext.getResources().getDimensionPixelSize( + R.dimen.car_right_navigation_bar_width); + WindowManager.LayoutParams rightlp = new WindowManager.LayoutParams( + width, LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, + PixelFormat.TRANSLUCENT); + rightlp.setTitle("RightCarNavigationBar"); + rightlp.windowAnimations = 0; + rightlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR; + rightlp.gravity = Gravity.RIGHT; + mWindowManager.addView(mRightNavigationBarWindow, rightlp); } } - - private void buildBottomBar() { + private void buildBottomBar(int layout) { // SystemUI requires that the navigation bar view have a parent. Since the regular // StatusBar inflates navigation_bar_window as this parent view, use the same view for the // CarNavigationBarView. - mNavigationBarWindow = (ViewGroup) View.inflate(mContext, - R.layout.navigation_bar_window, null); - if (mNavigationBarWindow == null) { - Log.e(TAG, "CarStatusBar failed inflate for R.layout.navigation_bar_window"); - } - - - View.inflate(mContext, R.layout.car_navigation_bar, mNavigationBarWindow); + View.inflate(mContext, layout, mNavigationBarWindow); mNavigationBarView = (CarNavigationBarView) mNavigationBarWindow.getChildAt(0); if (mNavigationBarView == null) { Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar"); throw new RuntimeException("Unable to build botom nav bar due to missing layout"); } mNavigationBarView.setStatusBar(this); - - - WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_NAVIGATION_BAR, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH - | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, - PixelFormat.TRANSLUCENT); - lp.setTitle("CarNavigationBar"); - lp.windowAnimations = 0; - - - mWindowManager.addView(mNavigationBarWindow, lp); } - private void buildLeft(int widthForSides) { - mLeftNavigationBarWindow = (ViewGroup) View.inflate(mContext, - R.layout.navigation_bar_window, null); - if (mLeftNavigationBarWindow == null) { - Log.e(TAG, "CarStatusBar failed inflate for R.layout.navigation_bar_window"); - } - - View.inflate(mContext, R.layout.car_left_navigation_bar, mLeftNavigationBarWindow); + private void buildLeft(int layout) { + View.inflate(mContext, layout, mLeftNavigationBarWindow); mLeftNavigationBarView = (CarNavigationBarView) mLeftNavigationBarWindow.getChildAt(0); if (mLeftNavigationBarView == null) { Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar"); throw new RuntimeException("Unable to build left nav bar due to missing layout"); } mLeftNavigationBarView.setStatusBar(this); - - WindowManager.LayoutParams leftlp = new WindowManager.LayoutParams( - widthForSides, LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH - | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, - PixelFormat.TRANSLUCENT); - leftlp.setTitle("LeftCarNavigationBar"); - leftlp.windowAnimations = 0; - leftlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR; - leftlp.gravity = Gravity.LEFT; - mWindowManager.addView(mLeftNavigationBarWindow, leftlp); } - private void buildRight(int widthForSides) { - mRightNavigationBarWindow = (ViewGroup) View.inflate(mContext, - R.layout.navigation_bar_window, null); - if (mRightNavigationBarWindow == null) { - Log.e(TAG, "CarStatusBar failed inflate for R.layout.navigation_bar_window"); - } - - View.inflate(mContext, R.layout.car_right_navigation_bar, mRightNavigationBarWindow); + private void buildRight(int layout) { + View.inflate(mContext, layout, mRightNavigationBarWindow); mRightNavigationBarView = (CarNavigationBarView) mRightNavigationBarWindow.getChildAt(0); if (mRightNavigationBarView == null) { Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar"); throw new RuntimeException("Unable to build right nav bar due to missing layout"); } - mRightNavigationBarView.setStatusBar(this); - - WindowManager.LayoutParams rightlp = new WindowManager.LayoutParams( - widthForSides, LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH - | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, - PixelFormat.TRANSLUCENT); - rightlp.setTitle("RightCarNavigationBar"); - rightlp.windowAnimations = 0; - rightlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR; - rightlp.gravity = Gravity.RIGHT; - mWindowManager.addView(mRightNavigationBarWindow, rightlp); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/hvac/HvacController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/hvac/HvacController.java index 23bf88796da3..7d283d9fde9d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/hvac/HvacController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/hvac/HvacController.java @@ -176,6 +176,14 @@ public class HvacController { }; /** + * Removes all registered components. This is useful if you need to rebuild the UI since + * components self register. + */ + public void removeAllComponents() { + mTempComponents.clear(); + } + + /** * Key for storing {@link TemperatureView}s in a hash map */ private static class HvacKey { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java index f42473d4cddf..75b31c5a1edd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java @@ -17,8 +17,6 @@ package com.android.systemui.statusbar.phone; import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS; import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO; -import static com.android.systemui.statusbar.phone.StatusBar.reinflateSignalCluster; - import android.annotation.Nullable; import android.app.Fragment; import android.app.StatusBarManager; @@ -34,7 +32,6 @@ import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.SysUiServiceProvider; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.SignalClusterView; import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager; import com.android.systemui.statusbar.policy.DarkIconDispatcher; import com.android.systemui.statusbar.policy.EncryptionHelper; @@ -63,7 +60,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private int mDisabled1; private StatusBar mStatusBarComponent; private DarkIconManager mDarkIconManager; - private SignalClusterView mSignalClusterView; private View mOperatorNameFrame; private SignalCallback mSignalCallback = new SignalCallback() { @@ -99,9 +95,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager); mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area); mClockView = mStatusBar.findViewById(R.id.clock); - mSignalClusterView = mStatusBar.findViewById(R.id.signal_cluster); - Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mSignalClusterView); - // Default to showing until we know otherwise. showSystemIconArea(false); initEmergencyCryptkeeperText(); initOperatorName(); @@ -128,7 +121,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue @Override public void onDestroyView() { super.onDestroyView(); - Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mSignalClusterView); Dependency.get(StatusBarIconController.class).removeIconGroup(mDarkIconManager); if (mNetworkController.hasEmergencyCryptKeeperText()) { mNetworkController.removeCallback(mSignalCallback); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java index 3f9ae8023149..80c4eb043d24 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java @@ -69,7 +69,7 @@ public class DarkIconDispatcherImpl implements DarkIconDispatcher { mReceivers.remove(object); } - public void applyDark(ImageView object) { + public void applyDark(DarkReceiver object) { mReceivers.get(object).onDarkChanged(mTintArea, mDarkIntensity, mIconTint); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java index edfd02bdfb26..48540b111609 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java @@ -20,25 +20,33 @@ import android.graphics.Rect; import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.UserHandle; +import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import com.android.internal.statusbar.StatusBarIcon; -import com.android.settingslib.Utils; import com.android.systemui.DemoMode; import com.android.systemui.R; +import com.android.systemui.statusbar.StatusIconDisplayable; import com.android.systemui.statusbar.StatusBarIconView; +import com.android.systemui.statusbar.StatusBarMobileView; +import com.android.systemui.statusbar.StatusBarWifiView; +import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; +import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; import com.android.systemui.statusbar.policy.DarkIconDispatcher; import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver; -import com.android.systemui.statusbar.policy.LocationControllerImpl; -import com.android.systemui.util.leak.LeakDetector; +import java.util.ArrayList; public class DemoStatusIcons extends StatusIconContainer implements DemoMode, DarkReceiver { + private static final String TAG = "DemoStatusIcons"; + private final LinearLayout mStatusIcons; + private final ArrayList<StatusBarMobileView> mMobileViews = new ArrayList<>(); private final int mIconSize; + private StatusBarWifiView mWifiView; private boolean mDemoMode; private int mColor; @@ -56,6 +64,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da } public void remove() { + mMobileViews.clear(); ((ViewGroup) getParent()).removeView(this); } @@ -66,7 +75,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da private void updateColors() { for (int i = 0; i < getChildCount(); i++) { - StatusBarIconView child = (StatusBarIconView) getChildAt(i); + StatusIconDisplayable child = (StatusIconDisplayable) getChildAt(i); child.setStaticDrawableColor(mColor); } } @@ -145,6 +154,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da } } + /// Can only be used to update non-signal related slots private void updateSlot(String slot, String iconPkg, int iconId) { if (!mDemoMode) return; if (iconPkg == null) { @@ -152,7 +162,11 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da } int removeIndex = -1; for (int i = 0; i < getChildCount(); i++) { - StatusBarIconView v = (StatusBarIconView) getChildAt(i); + View child = getChildAt(i); + if (!(child instanceof StatusBarIconView)) { + continue; + } + StatusBarIconView v = (StatusBarIconView) child; if (slot.equals(v.getTag())) { if (iconId == 0) { removeIndex = i; @@ -182,8 +196,101 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da addView(v, 0, new LinearLayout.LayoutParams(mIconSize, mIconSize)); } + public void addDemoWifiView(WifiIconState state) { + Log.d(TAG, "addDemoWifiView: "); + StatusBarWifiView view = StatusBarWifiView.fromContext(mContext); + view.setSlot(state.slot); + + int viewIndex = getChildCount(); + // If we have mobile views, put wifi before them + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child instanceof StatusBarMobileView) { + viewIndex = i; + break; + } + } + + mWifiView = view; + mWifiView.applyWifiState(state); + mWifiView.setStaticDrawableColor(mColor); + addView(view, viewIndex); + } + + public void updateWifiState(WifiIconState state) { + Log.d(TAG, "updateWifiState: "); + if (mWifiView == null) { + addDemoWifiView(state); + } else { + mWifiView.applyWifiState(state); + } + } + + public void addMobileView(MobileIconState state) { + Log.d(TAG, "addMobileView: "); + StatusBarMobileView view = StatusBarMobileView.fromContext(mContext); + + view.setSlot(state.slot); + view.applyMobileState(state); + view.setStaticDrawableColor(mColor); + + // mobile always goes at the end + mMobileViews.add(view); + addView(view, getChildCount()); + } + + public void updateMobileState(MobileIconState state) { + Log.d(TAG, "updateMobileState: "); + // If the view for this subId exists already, use it + for (int i = 0; i < mMobileViews.size(); i++) { + StatusBarMobileView view = mMobileViews.get(i); + if (view.getState().subId == state.subId) { + view.applyMobileState(state); + return; + } + } + + // Else we have to add it + addMobileView(state); + } + + public void onRemoveIcon(StatusIconDisplayable view) { + if (view.getSlot().equals("wifi")) { + removeView(mWifiView); + mWifiView = null; + } else { + StatusBarMobileView mobileView = matchingMobileView(view); + if (mobileView != null) { + removeView(mobileView); + mMobileViews.remove(mobileView); + } + } + } + + private StatusBarMobileView matchingMobileView(StatusIconDisplayable otherView) { + if (!(otherView instanceof StatusBarMobileView)) { + return null; + } + + StatusBarMobileView v = (StatusBarMobileView) otherView; + for (StatusBarMobileView view : mMobileViews) { + if (view.getState().subId == v.getState().subId) { + return view; + } + } + + return null; + } + @Override public void onDarkChanged(Rect area, float darkIntensity, int tint) { setColor(DarkIconDispatcher.getTint(area, mStatusIcons, tint)); + + if (mWifiView != null) { + mWifiView.onDarkChanged(area, darkIntensity, tint); + } + for (StatusBarMobileView view : mMobileViews) { + view.onDarkChanged(area, darkIntensity, tint); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index fcd4e8f3669f..df2b817101ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -334,6 +334,20 @@ public class KeyguardBouncer { return mKeyguardView != null && mKeyguardView.hasDismissActions(); } + public int getTop() { + if (mKeyguardView == null) { + return 0; + } + + int top = mKeyguardView.getTop(); + // The password view has an extra top padding that should be ignored. + if (mKeyguardView.getCurrentSecurityMode() == SecurityMode.Password) { + View messageArea = mKeyguardView.findViewById(R.id.keyguard_message_area); + top += messageArea.getTop(); + } + return top; + } + protected void ensureView() { // Removal of the view might be deferred to reduce unlock latency, // in this case we need to force the removal, otherwise we'll diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index 19e829592f79..3d7067d1d799 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -114,6 +114,11 @@ public class KeyguardClockPositionAlgorithm { private boolean mTracking; /** + * Distance in pixels between the top of the screen and the first view of the bouncer. + */ + private int mBouncerTop; + + /** * Refreshes the dimension values. */ public void loadDimens(Resources res) { @@ -129,7 +134,7 @@ public class KeyguardClockPositionAlgorithm { public void setup(int minTopMargin, int maxShadeBottom, int notificationStackHeight, float expandedHeight, float maxPanelHeight, int parentHeight, int keyguardStatusHeight, - float dark, boolean secure, boolean tracking) { + float dark, boolean secure, boolean tracking, int bouncerTop) { mMinTopMargin = minTopMargin + mContainerTopPadding; mMaxShadeBottom = maxShadeBottom; mNotificationStackHeight = notificationStackHeight; @@ -140,6 +145,7 @@ public class KeyguardClockPositionAlgorithm { mDarkAmount = dark; mCurrentlySecure = secure; mTracking = tracking; + mBouncerTop = bouncerTop; } public void run(Result result) { @@ -189,8 +195,10 @@ public class KeyguardClockPositionAlgorithm { private int getClockY() { // Dark: Align the bottom edge of the clock at about half of the screen: final float clockYDark = getMaxClockY() + burnInPreventionOffsetY(); - float clockYRegular = getExpandedClockPosition(); - float clockYTarget = mCurrentlySecure ? mMinTopMargin : -mKeyguardStatusHeight; + final float clockYRegular = getExpandedClockPosition(); + final boolean hasEnoughSpace = mMinTopMargin + mKeyguardStatusHeight < mBouncerTop; + float clockYTarget = mCurrentlySecure && hasEnoughSpace ? + mMinTopMargin : -mKeyguardStatusHeight; // Move clock up while collapsing the shade float shadeExpansion = mExpandedHeight / mMaxPanelHeight; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 994c0abcf70b..b81780962f08 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -207,7 +207,6 @@ public class KeyguardStatusBarView extends RelativeLayout @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { updateLayoutConsideringCutout(); - setSignalClusterLayoutWidth(); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @@ -296,17 +295,6 @@ public class KeyguardStatusBarView extends RelativeLayout return true; } - //TODO: Something is setting signal_cluster to MATCH_PARENT. Why? - private void setSignalClusterLayoutWidth() { - View signalCluster = findViewById(R.id.signal_cluster); - if (signalCluster == null) { - return; - } - - LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) signalCluster.getLayoutParams(); - lp.width = LinearLayout.LayoutParams.WRAP_CONTENT; - } - public void setListening(boolean listening) { if (listening == mBatteryListening) { return; @@ -459,7 +447,6 @@ public class KeyguardStatusBarView extends RelativeLayout mIconManager.setTint(iconColor); Rect tintArea = new Rect(0, 0, 0, 0); - applyDarkness(R.id.signal_cluster, tintArea, intensity, iconColor); applyDarkness(R.id.battery, tintArea, intensity, iconColor); applyDarkness(R.id.clock, tintArea, intensity, iconColor); // Reload user avatar diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index 9063dea584b1..b6a11f71f251 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -150,7 +150,7 @@ public class NotificationIconAreaController implements DarkReceiver { // showAmbient == show in shade but not shelf if (!showAmbient && mEntryManager.getNotificationData().shouldSuppressStatusBar( - entry.key)) { + entry.notification)) { return false; } 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 cccda90c20a8..27ca0d11ee28 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -160,6 +160,7 @@ public class NotificationPanelView extends PanelView implements protected int mQsMinExpansionHeight; protected int mQsMaxExpansionHeight; private int mQsPeekHeight; + private int mBouncerTop; private boolean mStackScrollerOverscrolling; private boolean mQsExpansionFromOverscroll; private float mLastOverscroll; @@ -476,7 +477,8 @@ public class NotificationPanelView extends PanelView implements mKeyguardStatusView.getHeight(), mDarkAmount, mStatusBar.isKeyguardCurrentlySecure(), - mTracking); + mTracking, + mBouncerTop); mClockPositionAlgorithm.run(mClockPositionResult); if (animate || mClockAnimator != null) { startClockAnimation(mClockPositionResult.clockX, mClockPositionResult.clockY); @@ -550,6 +552,11 @@ public class NotificationPanelView extends PanelView implements return count; } + public void setBouncerTop(int bouncerTop) { + mBouncerTop = bouncerTop; + positionClockAndNotifications(); + } + private void startClockAnimation(int x, int y) { if (mClockAnimationTargetX == x && mClockAnimationTargetY == y) { return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index c326feeb3d52..3e7b0d9f62d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -297,7 +297,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, mIconController.setIcon(mSlotLocation, LOCATION_STATUS_ICON_ID, mContext.getString(R.string.accessibility_location_active)); } else { - mIconController.removeIcon(mSlotLocation); + mIconController.removeAllIconsForSlot(mSlotLocation); } } @@ -363,16 +363,16 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, zenDescription = mContext.getString(R.string.interruption_level_priority); } - if (DndTile.isVisible(mContext) && !DndTile.isCombinedIcon(mContext) - && audioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) { - volumeVisible = true; - volumeIconId = R.drawable.stat_sys_ringer_silent; - volumeDescription = mContext.getString(R.string.accessibility_ringer_silent); - } else if (zen != Global.ZEN_MODE_NO_INTERRUPTIONS && zen != Global.ZEN_MODE_ALARMS && + if (zen != Global.ZEN_MODE_NO_INTERRUPTIONS && zen != Global.ZEN_MODE_ALARMS && audioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) { volumeVisible = true; volumeIconId = R.drawable.stat_sys_ringer_vibrate; volumeDescription = mContext.getString(R.string.accessibility_ringer_vibrate); + } else if (zen != Global.ZEN_MODE_NO_INTERRUPTIONS && zen != Global.ZEN_MODE_ALARMS && + audioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) { + volumeVisible = true; + volumeIconId = R.drawable.stat_sys_ringer_silent; + volumeDescription = mContext.getString(R.string.accessibility_ringer_silent); } if (zenVisible) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java index fb1addfeb1c3..12bdfc674f04 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java @@ -32,7 +32,7 @@ public final class PhoneStatusBarTransitions extends BarTransitions { private final PhoneStatusBarView mView; private final float mIconAlphaWhenOpaque; - private View mLeftSide, mStatusIcons, mSignalCluster, mBattery, mClock; + private View mLeftSide, mStatusIcons, mBattery, mClock; private Animator mCurrentAnimation; public PhoneStatusBarTransitions(PhoneStatusBarView view) { @@ -45,7 +45,6 @@ public final class PhoneStatusBarTransitions extends BarTransitions { public void init() { mLeftSide = mView.findViewById(R.id.notification_icon_area); mStatusIcons = mView.findViewById(R.id.statusIcons); - mSignalCluster = mView.findViewById(R.id.signal_cluster); mBattery = mView.findViewById(R.id.battery); mClock = mView.findViewById(R.id.clock); applyModeBackground(-1, getMode(), false /*animate*/); @@ -90,7 +89,6 @@ public final class PhoneStatusBarTransitions extends BarTransitions { anims.playTogether( animateTransitionTo(mLeftSide, newAlpha), animateTransitionTo(mStatusIcons, newAlpha), - animateTransitionTo(mSignalCluster, newAlpha), animateTransitionTo(mBattery, newAlphaBC), animateTransitionTo(mClock, newAlphaBC) ); @@ -102,7 +100,6 @@ public final class PhoneStatusBarTransitions extends BarTransitions { } else { mLeftSide.setAlpha(newAlpha); mStatusIcons.setAlpha(newAlpha); - mSignalCluster.setAlpha(newAlpha); mBattery.setAlpha(newAlphaBC); mClock.setAlpha(newAlphaBC); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 4e12936ff47a..2c025b5e2392 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -85,7 +85,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo /** * Default alpha value for most scrims. */ - public static final float GRADIENT_SCRIM_ALPHA = 0.70f; + public static final float GRADIENT_SCRIM_ALPHA = 0.45f; /** * A scrim varies its opacity based on a busyness factor, for example * how many notifications are currently visible. 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 c03ecb306226..750d2a5b3d50 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -348,6 +348,7 @@ public class StatusBar extends SystemUI implements DemoMode, protected boolean mBouncerShowing; private PhoneStatusBarPolicy mIconPolicy; + private StatusBarSignalPolicy mSignalPolicy; private VolumeComponent mVolumeComponent; private BrightnessMirrorController mBrightnessMirrorController; @@ -562,7 +563,7 @@ public class StatusBar extends SystemUI implements DemoMode, }; private KeyguardUserSwitcher mKeyguardUserSwitcher; - private UserSwitcherController mUserSwitcherController; + protected UserSwitcherController mUserSwitcherController; private NetworkController mNetworkController; private KeyguardMonitorImpl mKeyguardMonitor = (KeyguardMonitorImpl) Dependency.get(KeyguardMonitor.class); @@ -588,7 +589,7 @@ public class StatusBar extends SystemUI implements DemoMode, } }; private boolean mNoAnimationOnNextBarModeChange; - private FalsingManager mFalsingManager; + protected FalsingManager mFalsingManager; private final KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { @@ -747,6 +748,7 @@ public class StatusBar extends SystemUI implements DemoMode, // Lastly, call to the icon policy to install/update all the icons. mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController); + mSignalPolicy = new StatusBarSignalPolicy(mContext, mIconController); mUnlockMethodCache = UnlockMethodCache.getInstance(mContext); mUnlockMethodCache.addListener(this); @@ -1131,7 +1133,6 @@ public class StatusBar extends SystemUI implements DemoMode, } protected void reevaluateStyles() { - inflateSignalClusters(); inflateFooterView(); updateFooter(); inflateEmptyShadeView(); @@ -1145,36 +1146,6 @@ public class StatusBar extends SystemUI implements DemoMode, } } - private void inflateSignalClusters() { - if (mKeyguardStatusBar != null) reinflateSignalCluster(mKeyguardStatusBar); - } - - public static SignalClusterView reinflateSignalCluster(View view) { - Context context = view.getContext(); - SignalClusterView signalCluster = view.findViewById(R.id.signal_cluster); - if (signalCluster != null) { - ViewParent parent = signalCluster.getParent(); - if (parent instanceof ViewGroup) { - ViewGroup viewParent = (ViewGroup) parent; - int index = viewParent.indexOfChild(signalCluster); - viewParent.removeView(signalCluster); - SignalClusterView newCluster = (SignalClusterView) LayoutInflater.from(context) - .inflate(R.layout.signal_cluster_view, viewParent, false); - ViewGroup.MarginLayoutParams layoutParams = - (ViewGroup.MarginLayoutParams) viewParent.getLayoutParams(); - layoutParams.setMarginsRelative( - context.getResources().getDimensionPixelSize( - R.dimen.signal_cluster_margin_start), - 0, 0, 0); - newCluster.setLayoutParams(layoutParams); - viewParent.addView(newCluster, index); - return newCluster; - } - return signalCluster; - } - return null; - } - private void inflateEmptyShadeView() { if (mStackScroller == null) { return; @@ -3448,6 +3419,13 @@ public class StatusBar extends SystemUI implements DemoMode, return updateIsKeyguard(); } + /** + * @return True if StatusBar state is FULLSCREEN_USER_SWITCHER. + */ + public boolean isFullScreenUserSwitcherState() { + return mState == StatusBarState.FULLSCREEN_USER_SWITCHER; + } + private boolean updateIsKeyguard() { boolean wakeAndUnlocking = mFingerprintUnlockController.getMode() == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index 956bebb6ca41..94e004bd1b47 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -16,12 +16,16 @@ package com.android.systemui.statusbar.phone; import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS; import static android.app.StatusBarManager.DISABLE_NONE; +import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON; +import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE; +import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI; import android.content.Context; import android.os.Bundle; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.ArraySet; +import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; @@ -33,19 +37,39 @@ import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.DemoMode; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.statusbar.StatusIconDisplayable; import com.android.systemui.statusbar.StatusBarIconView; +import com.android.systemui.statusbar.StatusBarMobileView; +import com.android.systemui.statusbar.StatusBarWifiView; +import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; +import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; import com.android.systemui.statusbar.policy.DarkIconDispatcher; +import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver; import com.android.systemui.util.Utils.DisableStateTracker; +import java.util.List; public interface StatusBarIconController { + /** + * When an icon is added with TAG_PRIMARY, it will be treated as the primary icon + * in that slot and not added as a sub slot. + */ + public static final int TAG_PRIMARY = 0; + public void addIconGroup(IconManager iconManager); public void removeIconGroup(IconManager iconManager); public void setExternalIcon(String slot); public void setIcon(String slot, int resourceId, CharSequence contentDescription); public void setIcon(String slot, StatusBarIcon icon); - public void setIconVisibility(String slotTty, boolean b); - public void removeIcon(String slot); + public void setSignalIcon(String slot, WifiIconState state); + public void setMobileIcons(String slot, List<MobileIconState> states); + public void setIconVisibility(String slot, boolean b); + /** + * If you don't know what to pass for `tag`, either remove all icons for slot, or use + * TAG_PRIMARY to refer to the first icon at a given slot. + */ + public void removeIcon(String slot, int tag); + public void removeAllIconsForSlot(String slot); public static final String ICON_BLACKLIST = "icon_blacklist"; @@ -79,9 +103,9 @@ public interface StatusBarIconController { @Override protected void onIconAdded(int index, String slot, boolean blocked, - StatusBarIcon icon) { - StatusBarIconView v = addIcon(index, slot, blocked, icon); - mDarkIconDispatcher.addDarkReceiver(v); + StatusBarIconHolder holder) { + StatusIconDisplayable view = addHolder(index, slot, blocked, holder); + mDarkIconDispatcher.addDarkReceiver((DarkReceiver) view); } @Override @@ -95,21 +119,21 @@ public interface StatusBarIconController { @Override protected void destroy() { for (int i = 0; i < mGroup.getChildCount(); i++) { - mDarkIconDispatcher.removeDarkReceiver((ImageView) mGroup.getChildAt(i)); + mDarkIconDispatcher.removeDarkReceiver((DarkReceiver) mGroup.getChildAt(i)); } mGroup.removeAllViews(); } @Override protected void onRemoveIcon(int viewIndex) { - mDarkIconDispatcher.removeDarkReceiver((ImageView) mGroup.getChildAt(viewIndex)); + mDarkIconDispatcher.removeDarkReceiver((DarkReceiver) mGroup.getChildAt(viewIndex)); super.onRemoveIcon(viewIndex); } @Override public void onSetIcon(int viewIndex, StatusBarIcon icon) { super.onSetIcon(viewIndex, icon); - mDarkIconDispatcher.applyDark((ImageView) mGroup.getChildAt(viewIndex)); + mDarkIconDispatcher.applyDark((DarkReceiver) mGroup.getChildAt(viewIndex)); } @Override @@ -135,17 +159,18 @@ public interface StatusBarIconController { } @Override - protected void onIconAdded(int index, String slot, boolean blocked, StatusBarIcon icon) { - StatusBarIconView v = addIcon(index, slot, blocked, icon); - v.setStaticDrawableColor(mColor); + protected void onIconAdded(int index, String slot, boolean blocked, + StatusBarIconHolder holder) { + StatusIconDisplayable view = addHolder(index, slot, blocked, holder); + view.setStaticDrawableColor(mColor); } public void setTint(int color) { mColor = color; for (int i = 0; i < mGroup.getChildCount(); i++) { View child = mGroup.getChildAt(i); - if (child instanceof StatusBarIconView) { - StatusBarIconView icon = (StatusBarIconView) child; + if (child instanceof StatusIconDisplayable) { + StatusIconDisplayable icon = (StatusIconDisplayable) child; icon.setStaticDrawableColor(mColor); } } @@ -171,6 +196,7 @@ public interface StatusBarIconController { // Enables SystemUI demo mode to take effect in this group protected boolean mDemoable = true; + private boolean mIsInDemoMode; protected DemoStatusIcons mDemoStatusIcons; public IconManager(ViewGroup group) { @@ -205,10 +231,27 @@ public interface StatusBarIconController { } protected void onIconAdded(int index, String slot, boolean blocked, - StatusBarIcon icon) { - addIcon(index, slot, blocked, icon); + StatusBarIconHolder holder) { + addHolder(index, slot, blocked, holder); + } + + protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked, + StatusBarIconHolder holder) { + switch (holder.getType()) { + case TYPE_ICON: + return addIcon(index, slot, blocked, holder.getIcon()); + + case TYPE_WIFI: + return addSignalIcon(index, slot, holder.getWifiState()); + + case TYPE_MOBILE: + return addMobileIcon(index, slot, holder.getMobileState()); + } + + return null; } + @VisibleForTesting protected StatusBarIconView addIcon(int index, String slot, boolean blocked, StatusBarIcon icon) { StatusBarIconView view = onCreateStatusBarIconView(slot, blocked); @@ -218,10 +261,45 @@ public interface StatusBarIconController { } @VisibleForTesting - protected StatusBarIconView onCreateStatusBarIconView(String slot, boolean blocked) { + protected StatusBarWifiView addSignalIcon(int index, String slot, WifiIconState state) { + StatusBarWifiView view = onCreateStatusBarWifiView(slot); + view.applyWifiState(state); + mGroup.addView(view, index, onCreateLayoutParams()); + + if (mIsInDemoMode) { + mDemoStatusIcons.addDemoWifiView(state); + } + return view; + } + + @VisibleForTesting + protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) { + StatusBarMobileView view = onCreateStatusBarMobileView(slot); + view.applyMobileState(state); + mGroup.addView(view, index, onCreateLayoutParams()); + + if (mIsInDemoMode) { + mDemoStatusIcons.addMobileView(state); + } + return view; + } + + private StatusBarIconView onCreateStatusBarIconView(String slot, boolean blocked) { return new StatusBarIconView(mContext, slot, null, blocked); } + private StatusBarWifiView onCreateStatusBarWifiView(String slot) { + StatusBarWifiView view = StatusBarWifiView.fromContext(mContext); + view.setSlot(slot); + return view; + } + + private StatusBarMobileView onCreateStatusBarMobileView(String slot) { + StatusBarMobileView view = StatusBarMobileView.fromContext(mContext); + view.setSlot(slot); + return view; + } + protected LinearLayout.LayoutParams onCreateLayoutParams() { return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize); } @@ -256,6 +334,9 @@ public interface StatusBarIconController { } protected void onRemoveIcon(int viewIndex) { + if (mIsInDemoMode) { + mDemoStatusIcons.onRemoveIcon((StatusIconDisplayable) mGroup.getChildAt(viewIndex)); + } mGroup.removeViewAt(viewIndex); } @@ -264,17 +345,59 @@ public interface StatusBarIconController { view.set(icon); } + public void onSetIconHolder(int viewIndex, StatusBarIconHolder holder) { + switch (holder.getType()) { + case TYPE_ICON: + onSetIcon(viewIndex, holder.getIcon()); + return; + case TYPE_WIFI: + onSetSignalIcon(viewIndex, holder.getWifiState()); + return; + + case TYPE_MOBILE: + onSetMobileIcon(viewIndex, holder.getMobileState()); + default: + break; + } + } + + public void onSetSignalIcon(int viewIndex, WifiIconState state) { + StatusBarWifiView wifiView = (StatusBarWifiView) mGroup.getChildAt(viewIndex); + if (wifiView != null) { + wifiView.applyWifiState(state); + } + + if (mIsInDemoMode) { + mDemoStatusIcons.updateWifiState(state); + } + } + + public void onSetMobileIcon(int viewIndex, MobileIconState state) { + StatusBarMobileView view = (StatusBarMobileView) mGroup.getChildAt(viewIndex); + if (view != null) { + view.applyMobileState(state); + } + + if (mIsInDemoMode) { + mDemoStatusIcons.updateMobileState(state); + } + } + @Override public void dispatchDemoCommand(String command, Bundle args) { if (!mDemoable) { return; } - if (mDemoStatusIcons != null && command.equals(COMMAND_EXIT)) { - mDemoStatusIcons.dispatchDemoCommand(command, args); - exitDemoMode(); + if (command.equals(COMMAND_EXIT)) { + if (mDemoStatusIcons != null) { + mDemoStatusIcons.dispatchDemoCommand(command, args); + exitDemoMode(); + } + mIsInDemoMode = false; } else { if (mDemoStatusIcons == null) { + mIsInDemoMode = true; mDemoStatusIcons = createDemoStatusIcons(); } mDemoStatusIcons.dispatchDemoCommand(command, args); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java index 8f5e705ff2a4..510af03e6f28 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java @@ -16,13 +16,14 @@ package com.android.systemui.statusbar.phone; +import android.annotation.NonNull; import android.content.Context; import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.UserHandle; +import android.util.ArrayMap; import android.util.ArraySet; import android.view.ViewGroup; -import android.widget.LinearLayout; import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.Dependency; @@ -30,7 +31,9 @@ import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.SysUiServiceProvider; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.StatusBarIconView; +import com.android.systemui.statusbar.StatusIconDisplayable; +import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; +import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.statusbar.policy.IconLogger; @@ -40,8 +43,9 @@ import com.android.systemui.tuner.TunerService.Tunable; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.List; -import static com.android.systemui.statusbar.phone.CollapsedStatusBarFragment.STATUS_BAR_ICON_MANAGER_TAG; +import static com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY; /** * Receives the callbacks from CommandQueue related to icons and tracks the state of @@ -50,20 +54,25 @@ import static com.android.systemui.statusbar.phone.CollapsedStatusBarFragment.ST */ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tunable, ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController { + private static final String TAG = "StatusBarIconController"; private final ArrayList<IconManager> mIconGroups = new ArrayList<>(); private final ArraySet<String> mIconBlacklist = new ArraySet<>(); private final IconLogger mIconLogger = Dependency.get(IconLogger.class); + // Points to light or dark context depending on the... context? private Context mContext; - private DemoStatusIcons mDemoStatusIcons; - private IconManager mStatusBarIconManager; + private Context mLightContext; + private Context mDarkContext; + + private boolean mIsDark = false; public StatusBarIconControllerImpl(Context context) { super(context.getResources().getStringArray( com.android.internal.R.array.config_statusBarIcons)); Dependency.get(ConfigurationController.class).addCallback(this); + mContext = context; loadDimens(); @@ -76,12 +85,16 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu @Override public void addIconGroup(IconManager group) { mIconGroups.add(group); - for (int i = 0; i < mIcons.size(); i++) { - StatusBarIcon icon = mIcons.get(i); - if (icon != null) { - String slot = mSlots.get(i); - boolean blocked = mIconBlacklist.contains(slot); - group.onIconAdded(getViewIndex(getSlotIndex(slot)), slot, blocked, icon); + List<Slot> allSlots = getSlots(); + for (int i = 0; i < allSlots.size(); i++) { + Slot slot = allSlots.get(i); + List<StatusBarIconHolder> holders = slot.getHolderList(); + boolean blocked = mIconBlacklist.contains(slot.getName()); + + for (StatusBarIconHolder holder : holders) { + int tag = holder.getTag(); + int viewIndex = getViewIndex(getSlotIndex(slot.getName()), holder.getTag()); + group.onIconAdded(viewIndex, slot.getName(), blocked, holder); } } } @@ -99,104 +112,209 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu } mIconBlacklist.clear(); mIconBlacklist.addAll(StatusBarIconController.getIconBlacklist(newValue)); - ArrayList<StatusBarIcon> current = new ArrayList<>(mIcons); - ArrayList<String> currentSlots = new ArrayList<>(mSlots); + ArrayList<Slot> currentSlots = getSlots(); + ArrayMap<Slot, List<StatusBarIconHolder>> slotsToReAdd = new ArrayMap<>(); + + // This is a little hacky... Peel off all of the holders on all of the slots + // but keep them around so they can be re-added + // Remove all the icons. - for (int i = current.size() - 1; i >= 0; i--) { - removeIcon(currentSlots.get(i)); + for (int i = currentSlots.size() - 1; i >= 0; i--) { + Slot s = currentSlots.get(i); + slotsToReAdd.put(s, s.getHolderList()); + removeAllIconsForSlot(s.getName()); } + // Add them all back - for (int i = 0; i < current.size(); i++) { - setIcon(currentSlots.get(i), current.get(i)); + for (int i = 0; i < currentSlots.size(); i++) { + Slot item = currentSlots.get(i); + List<StatusBarIconHolder> iconsForSlot = slotsToReAdd.get(item); + if (iconsForSlot == null) continue; + for (StatusBarIconHolder holder : iconsForSlot) { + setIcon(getSlotIndex(item.getName()), holder); + } } } private void loadDimens() { } - private void addSystemIcon(int index, StatusBarIcon icon) { - String slot = getSlot(index); - int viewIndex = getViewIndex(index); + private void addSystemIcon(int index, StatusBarIconHolder holder) { + String slot = getSlotName(index); + int viewIndex = getViewIndex(index, holder.getTag()); boolean blocked = mIconBlacklist.contains(slot); - mIconLogger.onIconVisibility(getSlot(index), icon.visible); - mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, blocked, icon)); + mIconLogger.onIconVisibility(getSlotName(index), holder.isVisible()); + mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, blocked, holder)); } @Override public void setIcon(String slot, int resourceId, CharSequence contentDescription) { int index = getSlotIndex(slot); - StatusBarIcon icon = getIcon(index); - if (icon == null) { - icon = new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(), - Icon.createWithResource(mContext, resourceId), 0, 0, contentDescription); - setIcon(slot, icon); + StatusBarIconHolder holder = getIcon(index, 0); + if (holder == null) { + StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(), + Icon.createWithResource( + mContext, resourceId), 0, 0, contentDescription); + holder = StatusBarIconHolder.fromIcon(icon); + setIcon(index, holder); } else { - icon.icon = Icon.createWithResource(mContext, resourceId); - icon.contentDescription = contentDescription; - handleSet(index, icon); + holder.getIcon().icon = Icon.createWithResource(mContext, resourceId); + holder.getIcon().contentDescription = contentDescription; + handleSet(index, holder); + } + } + + /** + * Signal icons need to be handled differently, because they can be + * composite views + */ + @Override + public void setSignalIcon(String slot, WifiIconState state) { + + int index = getSlotIndex(slot); + + if (state == null) { + removeIcon(index, 0); + return; + } + + StatusBarIconHolder holder = getIcon(index, 0); + if (holder == null) { + holder = StatusBarIconHolder.fromWifiIconState(state); + setIcon(index, holder); + } else { + holder.setWifiState(state); + handleSet(index, holder); + } + } + + /** + * Accept a list of MobileIconStates, which all live in the same slot(?!), and then are sorted + * by subId. Don't worry this definitely makes sense and works. + * @param slot da slot + * @param iconStates All of the mobile icon states + */ + @Override + public void setMobileIcons(String slot, List<MobileIconState> iconStates) { + Slot mobileSlot = getSlot(slot); + int slotIndex = getSlotIndex(slot); + + for (MobileIconState state : iconStates) { + StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId); + if (holder == null) { + holder = StatusBarIconHolder.fromMobileIconState(state); + setIcon(slotIndex, holder); + } else { + holder.setMobileState(state); + handleSet(slotIndex, holder); + } } } @Override public void setExternalIcon(String slot) { - int viewIndex = getViewIndex(getSlotIndex(slot)); + int viewIndex = getViewIndex(getSlotIndex(slot), 0); int height = mContext.getResources().getDimensionPixelSize( R.dimen.status_bar_icon_drawing_size); mIconGroups.forEach(l -> l.onIconExternal(viewIndex, height)); } + //TODO: remove this (used in command queue and for 3rd party tiles?) @Override public void setIcon(String slot, StatusBarIcon icon) { setIcon(getSlotIndex(slot), icon); } + /** + * For backwards compatibility, in the event that someone gives us a slot and a status bar icon + */ + private void setIcon(int index, StatusBarIcon icon) { + if (icon == null) { + removeAllIconsForSlot(getSlotName(index)); + return; + } + + StatusBarIconHolder holder = StatusBarIconHolder.fromIcon(icon); + setIcon(index, holder); + } + @Override - public void removeIcon(String slot) { - int index = getSlotIndex(slot); - removeIcon(index); + public void setIcon(int index, @NonNull StatusBarIconHolder holder) { + boolean isNew = getIcon(index, holder.getTag()) == null; + super.setIcon(index, holder); + + if (isNew) { + addSystemIcon(index, holder); + } else { + handleSet(index, holder); + } } public void setIconVisibility(String slot, boolean visibility) { int index = getSlotIndex(slot); - StatusBarIcon icon = getIcon(index); - if (icon == null || icon.visible == visibility) { + StatusBarIconHolder holder = getIcon(index, 0); + if (holder == null || holder.isVisible() == visibility) { return; } - icon.visible = visibility; - handleSet(index, icon); + + holder.setVisible(visibility); + handleSet(index, holder); + } + + public void removeIcon(String slot) { + removeAllIconsForSlot(slot); + } + + @Override + public void removeIcon(String slot, int tag) { + removeIcon(getSlotIndex(slot), tag); } @Override - public void removeIcon(int index) { - if (getIcon(index) == null) { + public void removeAllIconsForSlot(String slotName) { + Slot slot = getSlot(slotName); + if (!slot.hasIconsInSlot()) { return; } - mIconLogger.onIconHidden(getSlot(index)); - super.removeIcon(index); - int viewIndex = getViewIndex(index); - mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex)); + + mIconLogger.onIconHidden(slotName); + + int slotIndex = getSlotIndex(slotName); + List<StatusBarIconHolder> iconsToRemove = slot.getHolderList(); + for (StatusBarIconHolder holder : iconsToRemove) { + int viewIndex = getViewIndex(slotIndex, holder.getTag()); + slot.removeForTag(holder.getTag()); + mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex)); + } } @Override - public void setIcon(int index, StatusBarIcon icon) { - if (icon == null) { - removeIcon(index); + public void removeIcon(int index, int tag) { + if (getIcon(index, tag) == null) { return; } - boolean isNew = getIcon(index) == null; - super.setIcon(index, icon); - if (isNew) { - addSystemIcon(index, icon); - } else { - handleSet(index, icon); - } + mIconLogger.onIconHidden(getSlotName(index)); + super.removeIcon(index, tag); + int viewIndex = getViewIndex(index, 0); + mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex)); } - private void handleSet(int index, StatusBarIcon icon) { - int viewIndex = getViewIndex(index); - mIconLogger.onIconVisibility(getSlot(index), icon.visible); - mIconGroups.forEach(l -> l.onSetIcon(viewIndex, icon)); + private void handleSet(int index, StatusBarIconHolder holder) { + int viewIndex = getViewIndex(index, holder.getTag()); + mIconLogger.onIconVisibility(getSlotName(index), holder.isVisible()); + mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder)); + } + + /** + * For mobile essentially (an array of holders in one slot) + */ + private void handleSet(int slotIndex, List<StatusBarIconHolder> holders) { + for (StatusBarIconHolder holder : holders) { + int viewIndex = getViewIndex(slotIndex, holder.getTag()); + mIconLogger.onIconVisibility(getSlotName(slotIndex), holder.isVisible()); + mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder)); + } } @Override @@ -208,7 +326,7 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu int N = group.getChildCount(); pw.println(" icon views: " + N); for (int i = 0; i < N; i++) { - StatusBarIconView ic = (StatusBarIconView) group.getChildAt(i); + StatusIconDisplayable ic = (StatusIconDisplayable) group.getChildAt(i); pw.println(" [" + i + "] icon=" + ic); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java new file mode 100644 index 000000000000..e854dd0cd737 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java @@ -0,0 +1,131 @@ +/* + * 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.statusbar.phone; + +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.drawable.Icon; +import android.os.UserHandle; +import com.android.internal.statusbar.StatusBarIcon; +import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; +import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; + +/** + * Wraps {@link com.android.internal.statusbar.StatusBarIcon} so we can still have a uniform list + */ +public class StatusBarIconHolder { + public static final int TYPE_ICON = 0; + public static final int TYPE_WIFI = 1; + public static final int TYPE_MOBILE = 2; + + private StatusBarIcon mIcon; + private WifiIconState mWifiState; + private MobileIconState mMobileState; + private int mType = TYPE_ICON; + private int mTag = 0; + private boolean mVisible = true; + + public static StatusBarIconHolder fromIcon(StatusBarIcon icon) { + StatusBarIconHolder wrapper = new StatusBarIconHolder(); + wrapper.mIcon = icon; + + return wrapper; + } + + public static StatusBarIconHolder fromResId(Context context, int resId, + CharSequence contentDescription) { + StatusBarIconHolder holder = new StatusBarIconHolder(); + holder.mIcon = new StatusBarIcon(UserHandle.SYSTEM, context.getPackageName(), + Icon.createWithResource( context, resId), 0, 0, contentDescription); + return holder; + } + + public static StatusBarIconHolder fromWifiIconState(WifiIconState state) { + StatusBarIconHolder holder = new StatusBarIconHolder(); + holder.mWifiState = state; + holder.mType = TYPE_WIFI; + return holder; + } + + public static StatusBarIconHolder fromMobileIconState(MobileIconState state) { + StatusBarIconHolder holder = new StatusBarIconHolder(); + holder.mMobileState = state; + holder.mType = TYPE_MOBILE; + holder.mTag = state.subId; + return holder; + } + + public int getType() { + return mType; + } + + @Nullable + public StatusBarIcon getIcon() { + return mIcon; + } + + @Nullable + public WifiIconState getWifiState() { + return mWifiState; + } + + public void setWifiState(WifiIconState state) { + mWifiState = state; + } + + @Nullable + public MobileIconState getMobileState() { + return mMobileState; + } + + public void setMobileState(MobileIconState state) { + mMobileState = state; + } + + public boolean isVisible() { + switch (mType) { + case TYPE_ICON: + return mIcon.visible; + case TYPE_WIFI: + return mWifiState.visible; + case TYPE_MOBILE: + return mMobileState.visible; + + default: return true; + } + } + + public void setVisible(boolean visible) { + switch (mType) { + case TYPE_ICON: + mIcon.visible = visible; + break; + + case TYPE_WIFI: + mWifiState.visible = visible; + break; + + case TYPE_MOBILE: + mMobileState.visible = visible; + break; + } + } + + public int getTag() { + return mTag; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java index 1aa3a4312f8d..c773170a2756 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java @@ -16,64 +16,78 @@ package com.android.systemui.statusbar.phone; +import android.annotation.NonNull; +import android.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.StatusBarIcon; - import java.io.PrintWriter; import java.util.ArrayList; +import java.util.List; + +import static com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY; public class StatusBarIconList { - protected ArrayList<String> mSlots = new ArrayList<>(); - protected ArrayList<StatusBarIcon> mIcons = new ArrayList<>(); + private ArrayList<Slot> mSlots = new ArrayList<>(); public StatusBarIconList(String[] slots) { final int N = slots.length; for (int i=0; i < N; i++) { - mSlots.add(slots[i]); - mIcons.add(null); + mSlots.add(new Slot(slots[i], null)); } } public int getSlotIndex(String slot) { final int N = mSlots.size(); for (int i=0; i<N; i++) { - if (slot.equals(mSlots.get(i))) { + Slot item = mSlots.get(i); + if (item.getName().equals(slot)) { return i; } } // Auto insert new items at the beginning. - mSlots.add(0, slot); - mIcons.add(0, null); + mSlots.add(0, new Slot(slot, null)); return 0; } + protected ArrayList<Slot> getSlots() { + return new ArrayList<>(mSlots); + } + + protected Slot getSlot(String name) { + return mSlots.get(getSlotIndex(name)); + } + public int size() { return mSlots.size(); } - public void setIcon(int index, StatusBarIcon icon) { - mIcons.set(index, icon); + public void setIcon(int index, @NonNull StatusBarIconHolder holder) { + mSlots.get(index).addHolder(holder); } - public void removeIcon(int index) { - mIcons.set(index, null); + public void removeIcon(int index, int tag) { + mSlots.get(index).removeForTag(tag); } - public String getSlot(int index) { - return mSlots.get(index); + public String getSlotName(int index) { + return mSlots.get(index).getName(); } - public StatusBarIcon getIcon(int index) { - return mIcons.get(index); + public StatusBarIconHolder getIcon(int index, int tag) { + return mSlots.get(index).getHolderForTag(tag); } - public int getViewIndex(int index) { + public int getViewIndex(int slotIndex, int tag) { int count = 0; - for (int i = 0; i < index; i++) { - if (mIcons.get(i) != null) { - count++; + for (int i = 0; i < slotIndex; i++) { + Slot item = mSlots.get(i); + if (item.hasIconsInSlot()) { + count += item.numberOfIcons(); } } - return count; + + Slot viewItem = mSlots.get(slotIndex); + return count + viewItem.viewIndexOffsetForTag(tag); } public void dump(PrintWriter pw) { @@ -81,7 +95,163 @@ public class StatusBarIconList { final int N = mSlots.size(); pw.println(" icon slots: " + N); for (int i=0; i<N; i++) { - pw.printf(" %2d: (%s) %s\n", i, mSlots.get(i), mIcons.get(i)); + pw.printf(" %2d:%s\n", i, mSlots.get(i).toString()); + } + } + + public static class Slot { + private final String mName; + private StatusBarIconHolder mHolder; + /** + * Only used if multiple icons are added to the same slot. + * + * If there are mSubSlots, then these are structured like: + * [ First item | (the rest) ] + * + * The tricky thing to keep in mind here is that the list [mHolder, mSubSlots] is ordered + * ascending, but for view logic we should go backwards through the list. I.e., the first + * element (mHolder) should be the highest index, because higher priority items go to the + * right of lower priority items + */ + private ArrayList<StatusBarIconHolder> mSubSlots; + + public Slot(String name, StatusBarIconHolder iconHolder) { + mName = name; + mHolder = iconHolder; + } + + public String getName() { + return mName; + } + + @Nullable + public StatusBarIconHolder getHolderForTag(int tag) { + if (tag == TAG_PRIMARY) { + return mHolder; + } + + if (mSubSlots != null) { + for (StatusBarIconHolder holder : mSubSlots) { + if (holder.getTag() == tag) { + return holder; + } + } + } + + return null; + } + + public void addHolder(StatusBarIconHolder holder) { + int tag = holder.getTag(); + if (tag == TAG_PRIMARY) { + mHolder = holder; + } else { + setSubSlot(holder, tag); + } + } + + public void removeForTag(int tag) { + if (tag == TAG_PRIMARY) { + mHolder = null; + } else { + int index = getIndexForTag(tag); + if (index != -1) { + mSubSlots.remove(index); + } + } + } + + @VisibleForTesting + public void clear() { + mHolder = null; + if (mSubSlots != null) { + mSubSlots = null; + } + } + + private void setSubSlot(StatusBarIconHolder holder, int tag) { + if (mSubSlots == null) { + mSubSlots = new ArrayList<>(); + mSubSlots.add(holder); + return; + } + + if (getIndexForTag(tag) != -1) { + // Holder exists for tag; no-op + return; + } + + // These holders get added to the end. Confused yet? + mSubSlots.add(holder); + } + + private int getIndexForTag(int tag) { + for (int i = 0; i < mSubSlots.size(); i++) { + StatusBarIconHolder h = mSubSlots.get(i); + if (h.getTag() == tag) { + return i; + } + } + + return -1; + } + + public boolean hasIconsInSlot() { + if (mHolder != null) return true; + if (mSubSlots == null) return false; + + return mSubSlots.size() > 0; + } + + public int numberOfIcons() { + int num = mHolder == null ? 0 : 1; + if (mSubSlots == null) return num; + + return num + mSubSlots.size(); + } + + /** + * View index is backwards from regular index + * @param tag the tag of the holder being viewed + * @return (1 + mSubSlots.size() - indexOfTag) + */ + public int viewIndexOffsetForTag(int tag) { + if (mSubSlots == null) { + return 0; + } + + int subSlots = mSubSlots.size(); + if (tag == TAG_PRIMARY) { + return subSlots; + } + + return subSlots - getIndexForTag(tag) - 1; + } + + public List<StatusBarIconHolder> getHolderList() { + ArrayList<StatusBarIconHolder> holders = new ArrayList<>(); + if (mHolder != null) { + holders.add(mHolder); + } + + if (mSubSlots != null) { + holders.addAll(mSubSlots); + } + + return holders; + } + + @Override + public String toString() { + return String.format("(%s) %s", mName, subSlotsString()); + } + + private String subSlotsString() { + if (mSubSlots == null) { + return ""; + } + + return "" + mSubSlots.size() + " subSlots"; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 597560823824..6b6ea10bfa63 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -135,26 +135,34 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mFingerprintUnlockController = fingerprintUnlockController; mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext, mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry); + mContainer.addOnLayoutChangeListener(this::onContainerLayout); mNotificationPanelView = notificationPanelView; notificationPanelView.setExpansionListener(this::onPanelExpansionChanged); } + private void onContainerLayout(View v, int left, int top, int right, int bottom, + int oldLeft, int oldTop, int oldRight, int oldBottom) { + mNotificationPanelView.setBouncerTop(mBouncer.getTop()); + } + private void onPanelExpansionChanged(float expansion, boolean tracking) { // We don't want to translate the bounce when: // • Keyguard is occluded, because we're in a FLAG_SHOW_WHEN_LOCKED activity and need to // conserve the original animation. // • The user quickly taps on the display and we show "swipe up to unlock." // • Keyguard will be dismissed by an action. a.k.a: FLAG_DISMISS_KEYGUARD_ACTIVITY + // • Full-screen user switcher is displayed. final boolean noLongerTracking = mLastTracking != tracking && !tracking; if (mOccluded || mNotificationPanelView.isUnlockHintRunning() - || mBouncer.willDismissWithAction()) { + || mBouncer.willDismissWithAction() + || mStatusBar.isFullScreenUserSwitcherState()) { mBouncer.setExpansion(0); } else if (mShowing && mStatusBar.isKeyguardCurrentlySecure() && !mDozing) { mBouncer.setExpansion(expansion); if (expansion == 1) { mBouncer.onFullyHidden(); } else if (!mBouncer.isShowing() && !mBouncer.isAnimatingAway()) { - mBouncer.show(false /* resetSecuritySelection */, false /* notifyFalsing */); + mBouncer.show(false /* resetSecuritySelection */, false /* animated */); } else if (noLongerTracking) { // Notify that falsing manager should stop its session when user stops touching, // even before the animation ends, to guarantee that we're not recording sensitive diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java new file mode 100644 index 000000000000..c5a3a0d33cd3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java @@ -0,0 +1,420 @@ +/* + * 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.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Rect; +import android.os.Handler; +import android.os.Looper; +import android.telephony.SubscriptionInfo; +import android.util.ArraySet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.widget.ImageView; +import com.android.settingslib.graph.SignalDrawable; +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.statusbar.phone.StatusBarIconController; +import com.android.systemui.statusbar.policy.DarkIconDispatcher; +import com.android.systemui.statusbar.policy.NetworkController; +import com.android.systemui.statusbar.policy.NetworkController.IconState; +import com.android.systemui.statusbar.policy.NetworkControllerImpl; +import com.android.systemui.statusbar.policy.SecurityController; +import com.android.systemui.tuner.TunerService.Tunable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + + +public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallback, + SecurityController.SecurityControllerCallback, Tunable { + private static final String TAG = "StatusBarSignalPolicy"; + + private final String mSlotAirplane; + private final String mSlotMobile; + private final String mSlotWifi; + private final String mSlotEthernet; + private final String mSlotVpn; + + private final Context mContext; + private final StatusBarIconController mIconController; + private final NetworkController mNetworkController; + private final SecurityController mSecurityController; + private final Handler mHandler = Handler.getMain(); + + private boolean mBlockAirplane; + private boolean mBlockMobile; + private boolean mBlockWifi; + private boolean mBlockEthernet; + private boolean mActivityEnabled; + private boolean mForceBlockWifi; + + // Track as little state as possible, and only for padding purposes + private boolean mIsAirplaneMode = false; + private boolean mWifiVisible = false; + + private ArrayList<MobileIconState> mMobileStates = new ArrayList<MobileIconState>(); + private WifiIconState mWifiIconState = new WifiIconState(); + + public StatusBarSignalPolicy(Context context, StatusBarIconController iconController) { + mContext = context; + + mSlotAirplane = mContext.getString(com.android.internal.R.string.status_bar_airplane); + mSlotMobile = mContext.getString(com.android.internal.R.string.status_bar_mobile); + mSlotWifi = mContext.getString(com.android.internal.R.string.status_bar_wifi); + mSlotEthernet = mContext.getString(com.android.internal.R.string.status_bar_ethernet); + mSlotVpn = mContext.getString(com.android.internal.R.string.status_bar_vpn); + + mIconController = iconController; + mNetworkController = Dependency.get(NetworkController.class); + mSecurityController = Dependency.get(SecurityController.class); + + mNetworkController.addCallback(this); + mSecurityController.addCallback(this); + } + + public void destroy() { + mNetworkController.removeCallback(this); + mSecurityController.removeCallback(this); + } + + private void updateVpn() { + boolean vpnVisible = mSecurityController.isVpnEnabled(); + int vpnIconId = currentVpnIconId(mSecurityController.isVpnBranded()); + + mIconController.setIcon(mSlotVpn, vpnIconId, null); + mIconController.setIconVisibility(mSlotVpn, vpnVisible); + } + + private int currentVpnIconId(boolean isBranded) { + return isBranded ? R.drawable.stat_sys_branded_vpn : R.drawable.stat_sys_vpn_ic; + } + + private void updateActivityEnabled() { + mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity); + } + + /** + * From SecurityController + */ + @Override + public void onStateChanged() { + mHandler.post(this::updateVpn); + } + + @Override + public void onTuningChanged(String key, String newValue) { + if (!StatusBarIconController.ICON_BLACKLIST.equals(key)) { + return; + } + ArraySet<String> blockList = StatusBarIconController.getIconBlacklist(newValue); + boolean blockAirplane = blockList.contains(mSlotAirplane); + boolean blockMobile = blockList.contains(mSlotMobile); + boolean blockWifi = blockList.contains(mSlotWifi); + boolean blockEthernet = blockList.contains(mSlotEthernet); + + if (blockAirplane != mBlockAirplane || blockMobile != mBlockMobile + || blockEthernet != mBlockEthernet || blockWifi != mBlockWifi) { + mBlockAirplane = blockAirplane; + mBlockMobile = blockMobile; + mBlockEthernet = blockEthernet; + mBlockWifi = blockWifi || mForceBlockWifi; + // Re-register to get new callbacks. + mNetworkController.removeCallback(this); + } + } + + @Override + public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon, + boolean activityIn, boolean activityOut, String description, boolean isTransient, + String statusLabel) { + + boolean visible = statusIcon.visible && !mBlockWifi; + boolean in = activityIn && mActivityEnabled && visible; + boolean out = activityOut && mActivityEnabled && visible; + + mWifiIconState.visible = visible; + mWifiIconState.resId = statusIcon.icon; + mWifiIconState.activityIn = in; + mWifiIconState.activityOut = out; + mWifiIconState.slot = mSlotWifi; + mWifiIconState.airplaneSpacerVisible = mIsAirplaneMode; + mWifiIconState.contentDescription = statusIcon.contentDescription; + + if (mWifiIconState.visible && mWifiIconState.resId > 0) { + mIconController.setSignalIcon(mSlotWifi, mWifiIconState.copy()); + mIconController.setIconVisibility(mSlotWifi, true); + } else { + mIconController.setIconVisibility(mSlotWifi, false); + } + } + + @Override + public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType, + int qsType, boolean activityIn, boolean activityOut, String typeContentDescription, + String description, boolean isWide, int subId, boolean roaming) { + MobileIconState state = getState(subId); + if (state == null) { + return; + } + + state.visible = statusIcon.visible && !mBlockMobile; + state.strengthId = statusIcon.icon; + state.typeId = statusType; + state.contentDescription = statusIcon.contentDescription; + state.typeContentDescription = typeContentDescription; + state.roaming = roaming; + state.activityIn = activityIn && mActivityEnabled; + state.activityOut = activityOut && mActivityEnabled; + + // Always send a copy to maintain value type semantics + mIconController.setMobileIcons(mSlotMobile, MobileIconState.copyStates(mMobileStates)); + } + + private MobileIconState getState(int subId) { + for (MobileIconState state : mMobileStates) { + if (state.subId == subId) { + return state; + } + } + Log.e(TAG, "Unexpected subscription " + subId); + return null; + } + + + /** + * It is expected that a call to setSubs will be immediately followed by setMobileDataIndicators + * so we don't have to update the icon manager at this point, just remove the old ones + * @param subs list of mobile subscriptions, displayed as mobile data indicators (max 8) + */ + @Override + public void setSubs(List<SubscriptionInfo> subs) { + if (hasCorrectSubs(subs)) { + return; + } + + mIconController.removeAllIconsForSlot(mSlotMobile); + mMobileStates.clear(); + final int n = subs.size(); + for (int i = 0; i < n; i++) { + mMobileStates.add(new MobileIconState(subs.get(i).getSubscriptionId())); + } + } + + private boolean hasCorrectSubs(List<SubscriptionInfo> subs) { + final int N = subs.size(); + if (N != mMobileStates.size()) { + return false; + } + for (int i = 0; i < N; i++) { + if (mMobileStates.get(i).subId != subs.get(i).getSubscriptionId()) { + return false; + } + } + return true; + } + + @Override + public void setNoSims(boolean show, boolean simDetected) { + // Noop yay! + } + + + @Override + public void setEthernetIndicators(IconState state) { + boolean visible = state.visible && !mBlockEthernet; + int resId = state.icon; + String description = state.contentDescription; + + if (resId > 0) { + mIconController.setIcon(mSlotEthernet, resId, description); + mIconController.setIconVisibility(mSlotEthernet, true); + } else { + mIconController.setIconVisibility(mSlotEthernet, false); + } + } + + @Override + public void setIsAirplaneMode(IconState icon) { + mIsAirplaneMode = icon.visible && !mBlockAirplane; + int resId = icon.icon; + String description = icon.contentDescription; + + if (mIsAirplaneMode && resId > 0) { + mIconController.setIcon(mSlotAirplane, resId, description); + mIconController.setIconVisibility(mSlotAirplane, true); + } else { + mIconController.setIconVisibility(mSlotAirplane, false); + } + } + + @Override + public void setMobileDataEnabled(boolean enabled) { + // Don't care. + } + + private static abstract class SignalIconState { + public boolean visible; + public boolean activityOut; + public boolean activityIn; + public String slot; + public String contentDescription; + + @Override + public boolean equals(Object o) { + // Skipping reference equality bc this should be more of a value type + if (o == null || getClass() != o.getClass()) { + return false; + } + SignalIconState that = (SignalIconState) o; + return visible == that.visible && + activityOut == that.activityOut && + activityIn == that.activityIn && + Objects.equals(contentDescription, that.contentDescription) && + Objects.equals(slot, that.slot); + } + + @Override + public int hashCode() { + return Objects.hash(visible, activityOut, slot); + } + + protected void copyTo(SignalIconState other) { + other.visible = visible; + other.activityIn = activityIn; + other.activityOut = activityOut; + other.slot = slot; + other.contentDescription = contentDescription; + } + } + + public static class WifiIconState extends SignalIconState{ + public int resId; + public boolean airplaneSpacerVisible; + public boolean signalSpacerVisible; + + @Override + public boolean equals(Object o) { + // Skipping reference equality bc this should be more of a value type + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + WifiIconState that = (WifiIconState) o; + return resId == that.resId && + airplaneSpacerVisible == that.airplaneSpacerVisible && + signalSpacerVisible == that.signalSpacerVisible; + } + + public void copyTo(WifiIconState other) { + super.copyTo(other); + other.resId = resId; + other.airplaneSpacerVisible = airplaneSpacerVisible; + other.signalSpacerVisible = signalSpacerVisible; + } + + public WifiIconState copy() { + WifiIconState newState = new WifiIconState(); + copyTo(newState); + return newState; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), + resId, airplaneSpacerVisible, signalSpacerVisible); + } + + @Override public String toString() { + return "WifiIconState(resId=" + resId + ", visible=" + visible + ")"; + } + } + + /** + * A little different. This one delegates to SignalDrawable instead of a specific resId + */ + public static class MobileIconState extends SignalIconState { + public int subId; + public int strengthId; + public int typeId; + public boolean roaming; + public boolean needsLeadingPadding; + public String typeContentDescription; + + private MobileIconState(int subId) { + super(); + this.subId = subId; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + MobileIconState that = (MobileIconState) o; + return subId == that.subId && + strengthId == that.strengthId && + typeId == that.typeId && + roaming == that.roaming && + needsLeadingPadding == that.needsLeadingPadding && + Objects.equals(typeContentDescription, that.typeContentDescription); + } + + @Override + public int hashCode() { + + return Objects + .hash(super.hashCode(), subId, strengthId, typeId, roaming, needsLeadingPadding, + typeContentDescription); + } + + public void copyTo(MobileIconState other) { + super.copyTo(other); + other.subId = subId; + other.strengthId = strengthId; + other.typeId = typeId; + other.roaming = roaming; + other.needsLeadingPadding = needsLeadingPadding; + other.typeContentDescription = typeContentDescription; + } + + private static List<MobileIconState> copyStates(List<MobileIconState> inStates) { + ArrayList<MobileIconState> outStates = new ArrayList<>(); + for (MobileIconState state : inStates) { + MobileIconState copy = new MobileIconState(state.subId); + state.copyTo(copy); + outStates.add(copy); + } + + return outStates; + } + + @Override public String toString() { + return "MobileIconState(subId=" + subId + ", strengthId=" + strengthId + ", roaming=" + + roaming + ", visible=" + visible + ")"; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java index dab28d65a5a9..255e10e2680d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java @@ -18,13 +18,13 @@ package com.android.systemui.statusbar.phone; import android.annotation.Nullable; import android.content.Context; -import android.util.ArrayMap; import android.util.AttributeSet; +import android.util.Log; import android.view.View; import com.android.keyguard.AlphaOptimizedLinearLayout; import com.android.systemui.R; -import com.android.systemui.statusbar.StatusBarIconView; +import com.android.systemui.statusbar.StatusIconDisplayable; import com.android.systemui.statusbar.stack.ViewState; /** @@ -97,7 +97,7 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { * Layout is happening from end -> start */ private void calculateIconTranslations() { - float width = getWidth(); + float width = getWidth() - getPaddingEnd(); float translationX = width; float contentStart = getPaddingStart(); int childCount = getChildCount(); @@ -109,18 +109,22 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { //TODO: Dots for (int i = childCount - 1; i >= 0; i--) { View child = getChildAt(i); - if (!(child instanceof StatusBarIconView)) { + if (!(child instanceof StatusIconDisplayable)) { + if (DEBUG) Log.d(TAG, "skipping child (wrong type)"); continue; } + StatusIconDisplayable iconView = (StatusIconDisplayable) child; + ViewState childState = getViewStateFromChild(child); if (childState == null ) { + if (DEBUG) Log.d(TAG, "skipping child (" + iconView.getSlot() + ") no ViewState"); continue; } - // Rely on StatusBarIcon for truth about visibility - if (!((StatusBarIconView) child).getStatusBarIcon().visible) { + if (!iconView.isIconVisible() || iconView.isIconBlocked()) { childState.hidden = true; + if (DEBUG) Log.d(TAG, "skipping child (" + iconView.getSlot() + ") not visible"); continue; } @@ -175,8 +179,8 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { vs.initFrom(child); vs.alpha = 1.0f; - if (child instanceof StatusBarIconView) { - vs.hidden = !((StatusBarIconView)child).getStatusBarIcon().visible; + if (child instanceof StatusIconDisplayable) { + vs.hidden = !((StatusIconDisplayable)child).isIconVisible(); } else { vs.hidden = false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java index baeaaadf419f..4c92d01eae4c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java @@ -40,7 +40,6 @@ import android.view.Display; import android.view.View; import android.widget.TextView; -import com.android.settingslib.Utils; import com.android.systemui.DemoMode; import com.android.systemui.Dependency; import com.android.systemui.FontSizeUtils; @@ -85,17 +84,6 @@ public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.C private boolean mShowSeconds; private Handler mSecondsHandler; - /** - * Whether we should use colors that adapt based on wallpaper/the scrim behind quick settings - * for text. - */ - private boolean mUseWallpaperTextColor; - - /** - * Color to be set on this {@link TextView}, when wallpaperTextColor is <b>not</b> utilized. - */ - private int mNonAdaptedColor; - public Clock(Context context) { this(context, null); } @@ -113,7 +101,6 @@ public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.C try { mAmPmStyle = a.getInt(R.styleable.Clock_amPmStyle, AM_PM_STYLE_GONE); mShowDark = a.getBoolean(R.styleable.Clock_showDark, true); - mNonAdaptedColor = getCurrentTextColor(); } finally { a.recycle(); } @@ -240,10 +227,7 @@ public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.C @Override public void onDarkChanged(Rect area, float darkIntensity, int tint) { - mNonAdaptedColor = DarkIconDispatcher.getTint(area, this, tint); - if (!mUseWallpaperTextColor) { - setTextColor(mNonAdaptedColor); - } + setTextColor(DarkIconDispatcher.getTint(area, this, tint)); } @Override @@ -258,25 +242,6 @@ public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.C 0); } - /** - * Sets whether the clock uses the wallpaperTextColor. If we're not using it, we'll revert back - * to dark-mode-based/tinted colors. - * - * @param shouldUseWallpaperTextColor whether we should use wallpaperTextColor for text color - */ - public void useWallpaperTextColor(boolean shouldUseWallpaperTextColor) { - if (shouldUseWallpaperTextColor == mUseWallpaperTextColor) { - return; - } - mUseWallpaperTextColor = shouldUseWallpaperTextColor; - - if (mUseWallpaperTextColor) { - setTextColor(Utils.getColorAttr(mContext, R.attr.wallpaperTextColor)); - } else { - setTextColor(mNonAdaptedColor); - } - } - private void updateShowSeconds() { if (mShowSeconds) { // Wait until we have a display to start trying to show seconds. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DarkIconDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DarkIconDispatcher.java index 58944c6ad571..945ed761c2b4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DarkIconDispatcher.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DarkIconDispatcher.java @@ -35,7 +35,7 @@ public interface DarkIconDispatcher { // Used to reapply darkness on an object, must have previously been added through // addDarkReceiver. - void applyDark(ImageView object); + void applyDark(DarkReceiver object); int DEFAULT_ICON_TINT = Color.WHITE; Rect sTmpRect = new Rect(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java index ef630c7205e1..74a30fa8094f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java @@ -27,7 +27,6 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.widget.TextView; -import com.android.settingslib.Utils; import com.android.systemui.Dependency; import com.android.systemui.R; @@ -43,17 +42,6 @@ public class DateView extends TextView { private String mLastText; private String mDatePattern; - /** - * Whether we should use colors that adapt based on wallpaper/the scrim behind quick settings - * for text. - */ - private boolean mUseWallpaperTextColor; - - /** - * Color to be set on this {@link TextView}, when wallpaperTextColor is <b>not</b> utilized. - */ - private int mNonAdaptedTextColor; - private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -74,7 +62,6 @@ public class DateView extends TextView { public DateView(Context context, AttributeSet attrs) { super(context, attrs); - mNonAdaptedTextColor = getCurrentTextColor(); TypedArray a = context.getTheme().obtainStyledAttributes( attrs, R.styleable.DateView, @@ -130,25 +117,6 @@ public class DateView extends TextView { } } - /** - * Sets whether the date view uses the wallpaperTextColor. If we're not using it, we'll revert - * back to dark-mode-based/tinted colors. - * - * @param shouldUseWallpaperTextColor whether we should use wallpaperTextColor for text color - */ - public void useWallpaperTextColor(boolean shouldUseWallpaperTextColor) { - if (shouldUseWallpaperTextColor == mUseWallpaperTextColor) { - return; - } - mUseWallpaperTextColor = shouldUseWallpaperTextColor; - - if (mUseWallpaperTextColor) { - setTextColor(Utils.getColorAttr(mContext, R.attr.wallpaperTextColor)); - } else { - setTextColor(mNonAdaptedTextColor); - } - } - public void setDatePattern(String pattern) { if (TextUtils.equals(pattern, mDatePattern)) { return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 4533aa5e2f76..4c100cdb99ff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -54,6 +54,7 @@ import com.android.systemui.R; import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; +import com.android.systemui.statusbar.policy.MobileSignalController.MobileState; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; @@ -848,6 +849,11 @@ public class NetworkControllerImpl extends BroadcastReceiver subs.add(addSignalController(i, i)); } mCallbackHandler.setSubs(subs); + for (int i = 0; i < mMobileSignalControllers.size(); i++) { + int key = mMobileSignalControllers.keyAt(i); + MobileSignalController controller = mMobileSignalControllers.get(key); + controller.notifyListeners(); + } } } String nosim = args.getString("nosim"); diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java index 7ffca173be86..d52746599aa1 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java +++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java @@ -323,10 +323,10 @@ public class StorageNotification extends SystemUI { if (notif != null) { mNotificationManager.notifyAsUser(vol.getId(), SystemMessage.NOTE_STORAGE_PUBLIC, - notif, UserHandle.ALL); + notif, UserHandle.of(vol.getMountUserId())); } else { mNotificationManager.cancelAsUser(vol.getId(), SystemMessage.NOTE_STORAGE_PUBLIC, - UserHandle.ALL); + UserHandle.of(vol.getMountUserId())); } } diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java index 1cbdfe81869d..7a9cdfd85645 100644 --- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java +++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java @@ -39,8 +39,7 @@ public class NotificationChannels extends SystemUI { public static String BATTERY = "BAT"; public static String HINTS = "HNT"; - @VisibleForTesting - static void createAll(Context context) { + public static void createAll(Context context) { final NotificationManager nm = context.getSystemService(NotificationManager.class); final NotificationChannel batteryChannel = new NotificationChannel(BATTERY, context.getString(R.string.notification_channel_battery), diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index ee6748eee2f8..a97effd3a023 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -16,6 +16,8 @@ package com.android.systemui.volume; +import static android.media.AudioManager.RINGER_MODE_NORMAL; + import android.app.NotificationManager; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -29,6 +31,7 @@ import android.database.ContentObserver; import android.media.AudioAttributes; import android.media.AudioManager; import android.media.AudioSystem; +import android.media.IAudioService; import android.media.IVolumeController; import android.media.VolumePolicy; import android.media.session.MediaController.PlaybackInfo; @@ -39,6 +42,7 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.Settings; @@ -73,9 +77,11 @@ import java.util.Objects; public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpable { private static final String TAG = Util.logTag(VolumeDialogControllerImpl.class); + + private static final int TOUCH_FEEDBACK_TIMEOUT_MS = 1000; private static final int DYNAMIC_STREAM_START_INDEX = 100; private static final int VIBRATE_HINT_DURATION = 50; - private static final AudioAttributes SONFICIATION_VIBRATION_ATTRIBUTES = + private static final AudioAttributes SONIFICIATION_VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) @@ -100,6 +106,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa private final W mWorker; private final Context mContext; private AudioManager mAudio; + private IAudioService mAudioService; protected StatusBar mStatusBar; private final NotificationManager mNoMan; private final SettingObserver mObserver; @@ -113,6 +120,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa private boolean mShowA11yStream; private boolean mShowVolumeDialog; private boolean mShowSafetyWarning; + private long mLastToggledRingerOn; private final NotificationManager mNotificationManager; private boolean mDestroyed; @@ -140,6 +148,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa mReceiver.init(); mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); mHasVibrator = mVibrator != null && mVibrator.hasVibrator(); + mAudioService = IAudioService.Stub.asInterface( + ServiceManager.getService(Context.AUDIO_SERVICE)); updateStatusBar(); boolean accessibilityVolumeStreamActive = context.getSystemService( @@ -300,10 +310,24 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa mShowSafetyWarning = safetyWarning; } - public void vibrate() { + @Override + public void scheduleTouchFeedback() { + mLastToggledRingerOn = System.currentTimeMillis(); + } + + private void playTouchFeedback() { + if (System.currentTimeMillis() - mLastToggledRingerOn < TOUCH_FEEDBACK_TIMEOUT_MS) { + try { + mAudioService.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD); + } catch (RemoteException e) { + // ignore + } + } + } + + public void vibrate(VibrationEffect effect) { if (mHasVibrator) { - mVibrator.vibrate(VibrationEffect.createOneShot(VIBRATE_HINT_DURATION, - VibrationEffect.DEFAULT_AMPLITUDE), SONFICIATION_VIBRATION_ATTRIBUTES); + mVibrator.vibrate(effect, SONIFICIATION_VIBRATION_ATTRIBUTES); } } @@ -425,7 +449,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa for (int stream : STREAMS.keySet()) { updateStreamLevelW(stream, getAudioManagerStreamVolume(stream)); streamStateW(stream).levelMin = getAudioManagerStreamMinVolume(stream); - streamStateW(stream).levelMax = getAudioManagerStreamMaxVolume(stream); + streamStateW(stream).levelMax = Math.max(1, getAudioManagerStreamMaxVolume(stream)); updateStreamMuteW(stream, mAudio.isStreamMute(stream)); final StreamState ss = streamStateW(stream); ss.muteSupported = mAudio.isStreamAffectedByMute(stream); @@ -556,6 +580,11 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa if (rm == mState.ringerModeInternal) return false; mState.ringerModeInternal = rm; Events.writeEvent(mContext, Events.EVENT_INTERNAL_RINGER_MODE_CHANGED, rm); + + if (mState.ringerModeInternal == RINGER_MODE_NORMAL) { + playTouchFeedback(); + } + return true; } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 71c7f80ef659..6f71e55b0d3c 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -22,6 +22,10 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL; import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; import static android.media.AudioManager.STREAM_ACCESSIBILITY; +import static android.media.AudioManager.STREAM_ALARM; +import static android.media.AudioManager.STREAM_MUSIC; +import static android.media.AudioManager.STREAM_RING; +import static android.media.AudioManager.STREAM_VOICE_CALL; import static android.view.View.GONE; import static android.view.View.VISIBLE; @@ -41,13 +45,17 @@ import android.content.res.Resources; import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.drawable.ColorDrawable; +import android.media.AudioAttributes; import android.media.AudioManager; import android.media.AudioSystem; +import android.media.MediaPlayer; import android.os.Debug; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.SystemClock; +import android.os.VibrationEffect; +import android.os.Vibrator; import android.provider.Settings; import android.provider.Settings.Global; import android.text.InputFilter; @@ -126,6 +134,7 @@ public class VolumeDialogImpl implements VolumeDialog { private final Accessibility mAccessibility = new Accessibility(); private final ColorStateList mActiveSliderTint; private final ColorStateList mInactiveSliderTint; + private final Vibrator mVibrator; private boolean mShowing; private boolean mShowA11yStream; @@ -146,6 +155,7 @@ public class VolumeDialogImpl implements VolumeDialog { mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext)); mInactiveSliderTint = loadColorStateList(R.color.volume_slider_inactive); mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); + mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); } public void init(int windowType, Callback callback) { @@ -203,6 +213,9 @@ public class VolumeDialogImpl implements VolumeDialog { .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator()) .withEndAction(() -> { mWindow.getDecorView().requestAccessibilityFocus(); + if (!Prefs.getBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, true)) { + mRingerIcon.postOnAnimationDelayed(mSinglePress, 1500); + } }) .start(); }); @@ -223,12 +236,16 @@ public class VolumeDialogImpl implements VolumeDialog { mSettingsIcon = mDialog.findViewById(R.id.settings); if (mRows.isEmpty()) { + if (!AudioSystem.isSingleVolume(mContext)) { + addRow(STREAM_ACCESSIBILITY, R.drawable.ic_volume_accessibility, + R.drawable.ic_volume_accessibility, true, false); + } addRow(AudioManager.STREAM_MUSIC, R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true); if (!AudioSystem.isSingleVolume(mContext)) { addRow(AudioManager.STREAM_RING, R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true, false); - addRow(AudioManager.STREAM_ALARM, + addRow(STREAM_ALARM, R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, true, false); addRow(AudioManager.STREAM_VOICE_CALL, R.drawable.ic_volume_voice, R.drawable.ic_volume_voice, false, false); @@ -236,8 +253,6 @@ public class VolumeDialogImpl implements VolumeDialog { R.drawable.ic_volume_bt_sco, R.drawable.ic_volume_bt_sco, false, false); addRow(AudioManager.STREAM_SYSTEM, R.drawable.ic_volume_system, R.drawable.ic_volume_system_mute, false, false); - addRow(STREAM_ACCESSIBILITY, R.drawable.ic_volume_accessibility, - R.drawable.ic_volume_accessibility, true, false); } } else { addExistingRows(); @@ -287,11 +302,11 @@ public class VolumeDialogImpl implements VolumeDialog { if (D.BUG) Slog.d(TAG, "Adding row for stream " + stream); VolumeRow row = new VolumeRow(); initRow(row, stream, iconRes, iconMuteRes, important, defaultStream); - int rowSize; - 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); + if (dynamic && mRows.size() > 2) { + // Dynamic Streams should be the first in the list, so they're shown to start of + // everything except a11y + mDialogRowsView.addView(row.view, 1); + mRows.add(1, row); } else { mDialogRowsView.addView(row.view); mRows.add(row); @@ -315,6 +330,11 @@ public class VolumeDialogImpl implements VolumeDialog { return row; } } + for (VolumeRow row : mRows) { + if (row.stream == STREAM_MUSIC) { + return row; + } + } return mRows.get(0); } @@ -414,6 +434,7 @@ public class VolumeDialogImpl implements VolumeDialog { mRingerIcon.setOnClickListener(v -> { Events.writeEvent(mContext, Events.EVENT_ICON_CLICK, AudioManager.STREAM_RING, mRingerIcon.getTag()); + Prefs.putBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, true); final StreamState ss = mState.states.get(AudioManager.STREAM_RING); if (ss == null) { return; @@ -424,7 +445,6 @@ public class VolumeDialogImpl implements VolumeDialog { final boolean hasVibrator = mController.hasVibrator(); if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) { if (hasVibrator) { - mController.vibrate(); newRingerMode = AudioManager.RINGER_MODE_VIBRATE; } else { newRingerMode = AudioManager.RINGER_MODE_SILENT; @@ -438,30 +458,56 @@ public class VolumeDialogImpl implements VolumeDialog { } } updateRingerH(); - + provideTouchFeedbackH(newRingerMode); mController.setRingerMode(newRingerMode, false); maybeShowToastH(newRingerMode); }); updateRingerH(); } + + private void provideTouchFeedbackH(int newRingerMode) { + VibrationEffect effect = null; + switch (newRingerMode) { + case RINGER_MODE_NORMAL: + mController.scheduleTouchFeedback(); + break; + case RINGER_MODE_SILENT: + effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); + break; + case RINGER_MODE_VIBRATE: + default: + effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK); + } + if (effect != null) { + mController.vibrate(effect); + } + } + private void maybeShowToastH(int newRingerMode) { int seenToastCount = Prefs.getInt(mContext, Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT, 0); if (seenToastCount > VolumePrefs.SHOW_RINGER_TOAST_COUNT) { return; } - int toastText; + CharSequence toastText = null; switch (newRingerMode) { case RINGER_MODE_NORMAL: - toastText = R.string.volume_dialog_ringer_guidance_ring; + final StreamState ss = mState.states.get(AudioManager.STREAM_RING); + if (ss != null) { + toastText = mContext.getString( + R.string.volume_dialog_ringer_guidance_ring, + Utils.formatPercentage(ss.level, ss.levelMax)); + } break; case RINGER_MODE_SILENT: - toastText = com.android.internal.R.string.volume_dialog_ringer_guidance_silent; + toastText = mContext.getString( + com.android.internal.R.string.volume_dialog_ringer_guidance_silent); break; case RINGER_MODE_VIBRATE: default: - toastText = com.android.internal.R.string.volume_dialog_ringer_guidance_vibrate; + toastText = mContext.getString( + com.android.internal.R.string.volume_dialog_ringer_guidance_vibrate); } Toast.makeText(mContext, toastText, Toast.LENGTH_SHORT).show(); @@ -538,7 +584,7 @@ public class VolumeDialogImpl implements VolumeDialog { } private boolean shouldBeVisibleH(VolumeRow row, VolumeRow activeRow) { - boolean isActive = row == activeRow; + boolean isActive = row.stream == activeRow.stream; if (row.stream == AudioSystem.STREAM_ACCESSIBILITY) { return mShowA11yStream; } @@ -550,7 +596,18 @@ public class VolumeDialogImpl implements VolumeDialog { return true; } - return row.defaultStream || isActive; + if (isActive) { + return true; + } + + if (row.defaultStream) { + return activeRow.stream == STREAM_RING + || activeRow.stream == STREAM_ALARM + || activeRow.stream == STREAM_VOICE_CALL + || activeRow.stream == STREAM_ACCESSIBILITY; + } + + return false; } private void updateRowsH(final VolumeRow activeRow) { @@ -670,7 +727,8 @@ public class VolumeDialogImpl implements VolumeDialog { if (mActiveStream != state.activeStream) { mPrevActiveStream = mActiveStream; mActiveStream = state.activeStream; - updateRowsH(getActiveRow()); + VolumeRow activeRow = getActiveRow(); + updateRowsH(activeRow); rescheduleTimeoutH(); } for (VolumeRow row : mRows) { @@ -696,7 +754,7 @@ public class VolumeDialogImpl implements VolumeDialog { final boolean isA11yStream = row.stream == STREAM_ACCESSIBILITY; final boolean isRingStream = row.stream == AudioManager.STREAM_RING; final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM; - final boolean isAlarmStream = row.stream == AudioManager.STREAM_ALARM; + final boolean isAlarmStream = row.stream == STREAM_ALARM; final boolean isMusicStream = row.stream == AudioManager.STREAM_MUSIC; final boolean isRingVibrate = isRingStream && mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE; @@ -921,6 +979,21 @@ public class VolumeDialogImpl implements VolumeDialog { } } + private Runnable mSinglePress = new Runnable() { + @Override + public void run() { + mRingerIcon.setPressed(true); + mRingerIcon.postOnAnimationDelayed(mSingleUnpress, 200); + } + }; + + private Runnable mSingleUnpress = new Runnable() { + @Override + public void run() { + mRingerIcon.setPressed(false); + } + }; + private final VolumeDialogController.Callbacks mControllerCallbackH = new VolumeDialogController.Callbacks() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePrefs.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePrefs.java index 173400f5e31a..434327cd491c 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumePrefs.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePrefs.java @@ -43,7 +43,7 @@ public class VolumePrefs { public static final String PREF_ADJUST_ALARMS = "pref_adjust_alarms"; public static final String PREF_ADJUST_NOTIFICATION = "pref_adjust_notification"; - public static final int SHOW_RINGER_TOAST_COUNT = 9; + public static final int SHOW_RINGER_TOAST_COUNT = 12; public static final boolean DEFAULT_SHOW_HEADERS = true; public static final boolean DEFAULT_ENABLE_AUTOMUTE = true; 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 4b455baee56b..149f2de06be4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java @@ -58,6 +58,7 @@ public class PowerUITest extends SysuiTestCase { 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 static final long ABOVE_CHARGE_CYCLE_THRESHOLD = Duration.ofHours(8).toMillis(); private HardwarePropertiesManager mHardProps; private WarningsUI mMockWarnings; private PowerUI mPowerUI; @@ -198,6 +199,7 @@ public class PowerUITest extends SysuiTestCase { when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); + mPowerUI.mBatteryLevel = 10; mPowerUI.start(); // unplugged device that would show the non-hybrid notification and the hybrid @@ -213,6 +215,7 @@ public class PowerUITest extends SysuiTestCase { when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); + mPowerUI.mBatteryLevel = 10; mPowerUI.start(); // unplugged device that would show the non-hybrid but not the hybrid @@ -254,13 +257,14 @@ public class PowerUITest extends SysuiTestCase { } @Test - public void testShouldShowLowBatteryWarning_deviceBatteryStatusUnkown_returnsNoShow() { + public void testShouldShowLowBatteryWarning_deviceBatteryStatusUnknown_returnsNoShow() { when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); mPowerUI.start(); - // Unknown battery status device that would show the neither due + // Unknown battery status device that would show the neither due to the battery status being + // unknown boolean shouldShow = mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, @@ -295,6 +299,9 @@ public class PowerUITest extends SysuiTestCase { mPowerUI.maybeShowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET); + + // reduce battery level to handle time based trigger -> level trigger interactions + mPowerUI.mBatteryLevel = 10; boolean shouldShow = mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java index 23a34051bd7a..ab042d4ce491 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -29,6 +31,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.AppOpsManager; +import android.app.NotificationChannel; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.util.ArraySet; @@ -50,6 +53,7 @@ import org.mockito.Spy; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.List; import java.util.function.Consumer; @SmallTest @@ -274,4 +278,24 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { mGroupRow.setBlockingHelperShowing(false); assertFalse(mGroupRow.isBlockingHelperShowing()); } + + @Test + public void testGetNumUniqueChildren_defaultChannel() { + assertEquals(1, mGroupRow.getNumUniqueChannels()); + } + + @Test + public void testGetNumUniqueChildren_multiChannel() { + List<ExpandableNotificationRow> childRows = + mGroupRow.getChildrenContainer().getNotificationChildren(); + // Give each child a unique channel id/name. + int i = 0; + for (ExpandableNotificationRow childRow : childRows) { + childRow.getEntry().channel = + new NotificationChannel("id" + i, "dinnertime" + i, IMPORTANCE_DEFAULT); + i++; + } + + assertEquals(3, mGroupRow.getNumUniqueChannels()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationBlockingHelperManagerTest.java index 64f34e009e7f..78cceeb3ff53 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationBlockingHelperManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationBlockingHelperManagerTest.java @@ -87,7 +87,7 @@ public class NotificationBlockingHelperManagerTest extends SysuiTestCase { @Test public void testDismissCurrentBlockingHelper_withDetachedBlockingHelperRow() throws Exception { - ExpandableNotificationRow row = spy(mHelper.createRow()); + ExpandableNotificationRow row = spy(createBlockableRowSpy()); row.setBlockingHelperShowing(true); when(row.isAttachedToWindow()).thenReturn(false); mBlockingHelperManager.setBlockingHelperRowForTest(row); @@ -100,7 +100,7 @@ public class NotificationBlockingHelperManagerTest extends SysuiTestCase { @Test public void testDismissCurrentBlockingHelper_withAttachedBlockingHelperRow() throws Exception { - ExpandableNotificationRow row = spy(mHelper.createRow()); + ExpandableNotificationRow row = spy(createBlockableRowSpy()); row.setBlockingHelperShowing(true); when(row.isAttachedToWindow()).thenReturn(true); mBlockingHelperManager.setBlockingHelperRowForTest(row); @@ -113,7 +113,7 @@ public class NotificationBlockingHelperManagerTest extends SysuiTestCase { @Test public void testPerhapsShowBlockingHelper_shown() throws Exception { - ExpandableNotificationRow row = mHelper.createRow(); + ExpandableNotificationRow row = createBlockableRowSpy(); row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE; mBlockingHelperManager.setNotificationShadeExpanded(1f); @@ -125,7 +125,7 @@ public class NotificationBlockingHelperManagerTest extends SysuiTestCase { @Test public void testPerhapsShowBlockingHelper_shownForLargeGroup() throws Exception { - ExpandableNotificationRow groupRow = mHelper.createGroup(10); + ExpandableNotificationRow groupRow = createBlockableGroupRowSpy(10); groupRow.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE; mBlockingHelperManager.setNotificationShadeExpanded(1f); @@ -137,7 +137,7 @@ public class NotificationBlockingHelperManagerTest extends SysuiTestCase { @Test public void testPerhapsShowBlockingHelper_shownForOnlyChildNotification() throws Exception { - ExpandableNotificationRow groupRow = mHelper.createGroup(1); + ExpandableNotificationRow groupRow = createBlockableGroupRowSpy(1); // Explicitly get the children container & call getViewAtPosition on it instead of the row // as other factors such as view expansion may cause us to get the parent row back instead // of the child row. @@ -152,7 +152,7 @@ public class NotificationBlockingHelperManagerTest extends SysuiTestCase { @Test public void testPerhapsShowBlockingHelper_notShownDueToNeutralUserSentiment() throws Exception { - ExpandableNotificationRow row = mHelper.createRow(); + ExpandableNotificationRow row = createBlockableRowSpy(); row.getEntry().userSentiment = USER_SENTIMENT_NEUTRAL; mBlockingHelperManager.setNotificationShadeExpanded(1f); @@ -162,7 +162,7 @@ public class NotificationBlockingHelperManagerTest extends SysuiTestCase { @Test public void testPerhapsShowBlockingHelper_notShownDueToPositiveUserSentiment() throws Exception { - ExpandableNotificationRow row = mHelper.createRow(); + ExpandableNotificationRow row = createBlockableRowSpy(); row.getEntry().userSentiment = USER_SENTIMENT_POSITIVE; mBlockingHelperManager.setNotificationShadeExpanded(1f); @@ -171,7 +171,7 @@ public class NotificationBlockingHelperManagerTest extends SysuiTestCase { @Test public void testPerhapsShowBlockingHelper_notShownDueToShadeVisibility() throws Exception { - ExpandableNotificationRow row = mHelper.createRow(); + ExpandableNotificationRow row = createBlockableRowSpy(); row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE; // Hide the shade mBlockingHelperManager.setNotificationShadeExpanded(0f); @@ -180,9 +180,19 @@ public class NotificationBlockingHelperManagerTest extends SysuiTestCase { } @Test + public void testPerhapsShowBlockingHelper_notShownDueToNonblockability() throws Exception { + ExpandableNotificationRow row = createBlockableRowSpy(); + when(row.getIsNonblockable()).thenReturn(true); + row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE; + mBlockingHelperManager.setNotificationShadeExpanded(1f); + + assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(row, mMenuRow)); + } + + @Test public void testPerhapsShowBlockingHelper_notShownAsNotificationIsInMultipleChildGroup() throws Exception { - ExpandableNotificationRow groupRow = mHelper.createGroup(2); + ExpandableNotificationRow groupRow = createBlockableGroupRowSpy(2); // Explicitly get the children container & call getViewAtPosition on it instead of the row // as other factors such as view expansion may cause us to get the parent row back instead // of the child row. @@ -195,7 +205,7 @@ public class NotificationBlockingHelperManagerTest extends SysuiTestCase { @Test public void testBlockingHelperShowAndDismiss() throws Exception{ - ExpandableNotificationRow row = spy(mHelper.createRow()); + ExpandableNotificationRow row = spy(createBlockableRowSpy()); row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE; when(row.isAttachedToWindow()).thenReturn(true); mBlockingHelperManager.setNotificationShadeExpanded(1f); @@ -211,4 +221,18 @@ public class NotificationBlockingHelperManagerTest extends SysuiTestCase { verify(mEntryManager).updateNotifications(); } + + private ExpandableNotificationRow createBlockableRowSpy() throws Exception { + ExpandableNotificationRow row = spy(mHelper.createRow()); + when(row.getIsNonblockable()).thenReturn(false); + return row; + } + + private ExpandableNotificationRow createBlockableGroupRowSpy(int numChildren) throws Exception { + ExpandableNotificationRow row = spy(mHelper.createGroup(numChildren)); + when(row.getIsNonblockable()).thenReturn(false); + return row; + } + + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java index 2000bff7e99a..5e27fde04441 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java @@ -35,6 +35,7 @@ import android.app.Notification; import android.app.NotificationChannel; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.media.session.MediaSession; import android.os.Bundle; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; @@ -61,6 +62,7 @@ public class NotificationDataTest extends SysuiTestCase { private static final int UID_NORMAL = 123; private static final int UID_ALLOW_DURING_SETUP = 456; private static final String TEST_HIDDEN_NOTIFICATION_KEY = "testHiddenNotificationKey"; + private static final String TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY = "exempt"; private final StatusBarNotification mMockStatusBarNotification = mock(StatusBarNotification.class); @@ -226,12 +228,21 @@ public class NotificationDataTest extends SysuiTestCase { public void testSuppressSystemAlertNotification() { when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false); when(mFsc.isSystemAlertNotification(any())).thenReturn(true); + StatusBarNotification sbn = mRow.getEntry().notification; + Bundle bundle = new Bundle(); + bundle.putStringArray(Notification.EXTRA_FOREGROUND_APPS, new String[] {"something"}); + sbn.getNotification().extras = bundle; assertTrue(mNotificationData.shouldFilterOut(mRow.getEntry().notification)); } @Test public void testDoNotSuppressSystemAlertNotification() { + StatusBarNotification sbn = mRow.getEntry().notification; + Bundle bundle = new Bundle(); + bundle.putStringArray(Notification.EXTRA_FOREGROUND_APPS, new String[] {"something"}); + sbn.getNotification().extras = bundle; + when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true); when(mFsc.isSystemAlertNotification(any())).thenReturn(true); @@ -249,7 +260,24 @@ public class NotificationDataTest extends SysuiTestCase { } @Test + public void testDoNotSuppressMalformedSystemAlertNotification() { + when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true); + + // missing extra + assertFalse(mNotificationData.shouldFilterOut(mRow.getEntry().notification)); + + StatusBarNotification sbn = mRow.getEntry().notification; + Bundle bundle = new Bundle(); + bundle.putStringArray(Notification.EXTRA_FOREGROUND_APPS, new String[] {}); + sbn.getNotification().extras = bundle; + + // extra missing values + assertFalse(mNotificationData.shouldFilterOut(mRow.getEntry().notification)); + } + + @Test public void testShouldFilterHiddenNotifications() { + initStatusBarNotification(false); // setup when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false); when(mFsc.isSystemAlertNotification(any())).thenReturn(false); @@ -264,6 +292,33 @@ public class NotificationDataTest extends SysuiTestCase { assertFalse(mNotificationData.shouldFilterOut(mMockStatusBarNotification)); } + @Test + public void testIsExemptFromDndVisualSuppression_foreground() { + initStatusBarNotification(false); + when(mMockStatusBarNotification.getKey()).thenReturn( + TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY); + Notification n = mMockStatusBarNotification.getNotification(); + n.flags = Notification.FLAG_FOREGROUND_SERVICE; + + assertTrue(mNotificationData.isExemptFromDndVisualSuppression(mMockStatusBarNotification)); + assertFalse(mNotificationData.shouldSuppressAmbient(mMockStatusBarNotification)); + } + + @Test + public void testIsExemptFromDndVisualSuppression_media() { + initStatusBarNotification(false); + when(mMockStatusBarNotification.getKey()).thenReturn( + TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY); + Notification n = mMockStatusBarNotification.getNotification(); + Notification.Builder nb = Notification.Builder.recoverBuilder(mContext, n); + nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class))); + n = nb.build(); + when(mMockStatusBarNotification.getNotification()).thenReturn(n); + + assertTrue(mNotificationData.isExemptFromDndVisualSuppression(mMockStatusBarNotification)); + assertFalse(mNotificationData.shouldSuppressAmbient(mMockStatusBarNotification)); + } + private void initStatusBarNotification(boolean allowDuringSetup) { Bundle bundle = new Bundle(); bundle.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, allowDuringSetup); @@ -293,6 +348,13 @@ public class NotificationDataTest extends SysuiTestCase { outRanking.getImportance(), outRanking.getImportanceExplanation(), outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null, outRanking.canShowBadge(), outRanking.getUserSentiment(), true); + } else if (key.equals(TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY)) { + outRanking.populate(key, outRanking.getRank(), + outRanking.matchesInterruptionFilter(), + outRanking.getVisibilityOverride(), 255, + outRanking.getImportance(), outRanking.getImportanceExplanation(), + outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null, + outRanking.canShowBadge(), outRanking.getUserSentiment(), true); } else { outRanking.populate(key, outRanking.getRank(), outRanking.matchesInterruptionFilter(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsManagerTest.java index 21f6750c0cdb..0ef2d051e4aa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsManagerTest.java @@ -268,9 +268,10 @@ public class NotificationGutsManagerTest extends SysuiTestCase { @Test public void testInitializeNotificationInfoView_showBlockingHelper() throws Exception { NotificationInfo notificationInfoView = mock(NotificationInfo.class); - ExpandableNotificationRow row = mHelper.createRow(); + ExpandableNotificationRow row = spy(mHelper.createRow()); row.setBlockingHelperShowing(true); row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE; + when(row.getIsNonblockable()).thenReturn(false); StatusBarNotification statusBarNotification = row.getStatusBarNotification(); mGutsManager.initializeNotificationInfo(row, notificationInfoView); @@ -285,7 +286,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { any(NotificationInfo.CheckSaveListener.class), any(NotificationInfo.OnSettingsClickListener.class), any(NotificationInfo.OnAppSettingsClickListener.class), - any(), + eq(false), eq(true) /* isForBlockingHelper */, eq(true) /* isUserSentimentNegative */); } @@ -293,9 +294,10 @@ public class NotificationGutsManagerTest extends SysuiTestCase { @Test public void testInitializeNotificationInfoView_dontShowBlockingHelper() throws Exception { NotificationInfo notificationInfoView = mock(NotificationInfo.class); - ExpandableNotificationRow row = mHelper.createRow(); + ExpandableNotificationRow row = spy(mHelper.createRow()); row.setBlockingHelperShowing(false); row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE; + when(row.getIsNonblockable()).thenReturn(false); StatusBarNotification statusBarNotification = row.getStatusBarNotification(); mGutsManager.initializeNotificationInfo(row, notificationInfoView); @@ -310,7 +312,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { any(NotificationInfo.CheckSaveListener.class), any(NotificationInfo.OnSettingsClickListener.class), any(NotificationInfo.OnAppSettingsClickListener.class), - any(), + eq(false), eq(false) /* isForBlockingHelper */, eq(true) /* isUserSentimentNegative */); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java index 7dda8b268b3e..d86e947cdfcc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java @@ -76,7 +76,6 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -87,6 +86,7 @@ public class NotificationInfoTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test_package"; private static final String TEST_SYSTEM_PACKAGE_NAME = PRINT_SPOOLER_PACKAGE_NAME; private static final int TEST_UID = 1; + private static final int MULTIPLE_CHANNEL_COUNT = 2; private static final String TEST_CHANNEL = "test_channel"; private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME"; @@ -157,7 +157,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_SetsTextApplicationName() throws Exception { when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name"); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false); final TextView textView = mNotificationInfo.findViewById(R.id.pkgname); assertTrue(textView.getText().toString().contains("App Name")); assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility()); @@ -169,7 +169,7 @@ public class NotificationInfoTest extends SysuiTestCase { when(mMockPackageManager.getApplicationIcon(any(ApplicationInfo.class))) .thenReturn(iconDrawable); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false); final ImageView iconView = mNotificationInfo.findViewById(R.id.pkgicon); assertEquals(iconDrawable, iconView.getDrawable()); } @@ -177,7 +177,7 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false); final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name); assertEquals(GONE, groupNameView.getVisibility()); final TextView groupDividerView = mNotificationInfo.findViewById(R.id.pkg_group_divider); @@ -193,7 +193,7 @@ public class NotificationInfoTest extends SysuiTestCase { eq("test_group_id"), eq(TEST_PACKAGE_NAME), eq(TEST_UID))) .thenReturn(notificationChannelGroup); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false); final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name); assertEquals(View.VISIBLE, groupNameView.getVisibility()); assertEquals("Test Group Name", groupNameView.getText()); @@ -204,7 +204,7 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_SetsTextChannelName() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(TEST_CHANNEL_NAME, textView.getText()); } @@ -212,16 +212,27 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_DefaultChannelDoesNotUseChannelName() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mDefaultNotificationChannel, 1, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mDefaultNotificationChannel, 1, mSbn, null, null, null, false); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(GONE, textView.getVisibility()); } @Test + public void testBindNotification_DefaultChannelUsesChannelNameIfMoreChannelsExist() + throws Exception { + // Package has one channel by default. + when(mMockINotificationManager.getNumNotificationChannelsForPackage( + eq(TEST_PACKAGE_NAME), eq(TEST_UID), anyBoolean())).thenReturn(10); + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, mDefaultNotificationChannel, 1, mSbn, null, null, null, false); + final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); + assertEquals(VISIBLE, textView.getVisibility()); + } + + @Test public void testBindNotification_UnblockablePackageUsesChannelName() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, - Collections.singleton(TEST_PACKAGE_NAME)); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(VISIBLE, textView.getVisibility()); } @@ -229,7 +240,7 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_BlockButton() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false); final View block = mNotificationInfo.findViewById(R.id.block); final View minimize = mNotificationInfo.findViewById(R.id.minimize); assertEquals(VISIBLE, block.getVisibility()); @@ -240,7 +251,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_MinButton() throws Exception { mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false); final View block = mNotificationInfo.findViewById(R.id.block); final View minimize = mNotificationInfo.findViewById(R.id.minimize); assertEquals(GONE, block.getVisibility()); @@ -255,7 +266,7 @@ public class NotificationInfoTest extends SysuiTestCase { (View v, NotificationChannel c, int appUid) -> { assertEquals(mNotificationChannel, c); latch.countDown(); - }, null, null); + }, null, false); final View settingsButton = mNotificationInfo.findViewById(R.id.info); settingsButton.performClick(); @@ -266,7 +277,7 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertTrue(settingsButton.getVisibility() != View.VISIBLE); } @@ -274,11 +285,11 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_SettingsButtonReappearsAfterSecondBind() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, (View v, NotificationChannel c, int appUid) -> { - }, null, null); + }, null, false); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertEquals(View.VISIBLE, settingsButton.getVisibility()); } @@ -287,11 +298,11 @@ public class NotificationInfoTest extends SysuiTestCase { public void testOnClickListenerPassesNullChannelForBundle() throws Exception { final CountDownLatch latch = new CountDownLatch(1); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 2, mSbn, null, + TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, (View v, NotificationChannel c, int appUid) -> { assertEquals(null, c); latch.countDown(); - }, null, null); + }, null, true); mNotificationInfo.findViewById(R.id.info).performClick(); // Verify that listener was triggered. @@ -303,7 +314,8 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_ChannelNameInvisibleWhenBundleFromDifferentChannels() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 2, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, null, + null, true); final TextView channelNameView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(GONE, channelNameView.getVisibility()); @@ -313,7 +325,8 @@ public class NotificationInfoTest extends SysuiTestCase { @UiThreadTest public void testStopInvisibleIfBundleFromDifferentChannels() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 2, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, null, + null, true); final TextView blockView = mNotificationInfo.findViewById(R.id.block); assertEquals(GONE, blockView.getVisibility()); } @@ -321,8 +334,8 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testbindNotification_BlockingHelper() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, - null, null, false, true); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false, false, + true); final TextView view = mNotificationInfo.findViewById(R.id.block_prompt); assertEquals(View.VISIBLE, view.getVisibility()); assertEquals(mContext.getString(R.string.inline_blocking_helper), view.getText()); @@ -331,8 +344,7 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testbindNotification_UnblockableTextVisibleWhenAppUnblockable() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, - null, Collections.singleton(TEST_PACKAGE_NAME)); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true); final TextView view = mNotificationInfo.findViewById(R.id.block_prompt); assertEquals(View.VISIBLE, view.getVisibility()); assertEquals(mContext.getString(R.string.notification_unblockable_desc), @@ -342,7 +354,7 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( anyString(), eq(TEST_UID), any()); } @@ -351,7 +363,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testDoesNotUpdateNotificationChannelAfterImportanceChanged() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false); mNotificationInfo.findViewById(R.id.block).performClick(); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( @@ -363,7 +375,7 @@ public class NotificationInfoTest extends SysuiTestCase { throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false); mNotificationInfo.findViewById(R.id.minimize).performClick(); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( @@ -375,7 +387,7 @@ public class NotificationInfoTest extends SysuiTestCase { throws Exception { int originalImportance = mNotificationChannel.getImportance(); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false); mNotificationInfo.handleCloseControls(true, false); verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( @@ -388,7 +400,7 @@ public class NotificationInfoTest extends SysuiTestCase { throws Exception { mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false); mNotificationInfo.handleCloseControls(true, false); verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( @@ -408,7 +420,7 @@ public class NotificationInfoTest extends SysuiTestCase { null /* checkSaveListener */, null /* onSettingsClick */, null /* onAppSettingsClick */, - null /* nonBlockablePkgs */, + false /* isNonblockable */, true /* isForBlockingHelper */, false /* isUserSentimentNegative */); @@ -421,8 +433,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testNonBlockableAppDoesNotBecomeBlocked() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, - null, Collections.singleton(TEST_PACKAGE_NAME)); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( @@ -433,7 +444,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBlockChangedCallsUpdateNotificationChannel() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); @@ -452,8 +463,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testNonBlockableAppDoesNotBecomeMin() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, - null, Collections.singleton(TEST_PACKAGE_NAME)); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true); mNotificationInfo.findViewById(R.id.minimize).performClick(); waitForUndoButton(); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( @@ -465,7 +475,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false); mNotificationInfo.findViewById(R.id.minimize).performClick(); waitForUndoButton(); @@ -484,7 +494,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testKeepUpdatesNotificationChannel() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false); mNotificationInfo.handleCloseControls(true, false); @@ -500,7 +510,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBlockUndoDoesNotBlockNotificationChannel() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); @@ -520,7 +530,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testMinUndoDoesNotMinNotificationChannel() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false); mNotificationInfo.findViewById(R.id.minimize).performClick(); waitForUndoButton(); @@ -540,8 +550,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testCloseControlsDoesNotUpdateiMinIfSaveIsFalse() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, - Collections.singleton(TEST_PACKAGE_NAME)); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true); mNotificationInfo.findViewById(R.id.minimize).performClick(); waitForUndoButton(); @@ -554,8 +563,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testCloseControlsDoesNotUpdateIfSaveIsFalse() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, - Collections.singleton(TEST_PACKAGE_NAME)); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); @@ -570,7 +578,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, (Runnable saveImportance, StatusBarNotification sbn) -> { - }, null, null, Collections.singleton(TEST_PACKAGE_NAME)); + }, null, null, true); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); @@ -586,7 +594,7 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, (Runnable saveImportance, StatusBarNotification sbn) -> { saveImportance.run(); - }, null, null, null); + }, null, null, false); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); @@ -616,7 +624,7 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel, 1, sbn, null, null, (View v, Intent intent) -> { latch.countDown(); - }, null); + }, false); final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings); assertEquals(View.VISIBLE, settingsLink.getVisibility()); settingsLink.performClick(); @@ -641,10 +649,10 @@ public class NotificationInfoTest extends SysuiTestCase { 0, null, 0, 0, n, UserHandle.CURRENT, null, 0); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 2, sbn, null, null, + TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, sbn, null, null, (View v, Intent intent) -> { latch.countDown(); - }, null); + }, false); final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings); assertEquals(View.VISIBLE, settingsLink.getVisibility()); settingsLink.performClick(); @@ -662,7 +670,8 @@ public class NotificationInfoTest extends SysuiTestCase { 0, null, 0, 0, n, UserHandle.CURRENT, null, 0); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 2, sbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, sbn, null, null, + null, false); final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings); assertEquals(GONE, settingsLink.getVisibility()); } @@ -682,7 +691,7 @@ public class NotificationInfoTest extends SysuiTestCase { 0, null, 0, 0, n, UserHandle.CURRENT, null, 0); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, sbn, null, null, null, null); + TEST_PACKAGE_NAME, mNotificationChannel, 1, sbn, null, null, null, false); final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings); assertEquals(GONE, settingsLink.getVisibility()); } @@ -698,8 +707,7 @@ public class NotificationInfoTest extends SysuiTestCase { mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, - Collections.singleton(TEST_PACKAGE_NAME)); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true); mNotificationInfo.findViewById(R.id.minimize).performClick(); waitForUndoButton(); @@ -711,8 +719,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testUndoText_block() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, - Collections.singleton(TEST_PACKAGE_NAME)); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); @@ -724,8 +731,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testNoHeaderOnConfirmation() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, - Collections.singleton(TEST_PACKAGE_NAME)); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); @@ -736,8 +742,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testHeaderOnUndo() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, - Collections.singleton(TEST_PACKAGE_NAME)); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java index 2792d8c45865..07ac11b82e8c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java @@ -1,17 +1,23 @@ package com.android.systemui.statusbar; +import static com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.phone.StatusBarIconHolder; import com.android.systemui.statusbar.phone.StatusBarIconList; +import com.android.systemui.statusbar.phone.StatusBarIconList.Slot; +import java.util.ArrayList; +import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; @@ -41,45 +47,125 @@ public class StatusBarIconListTest extends SysuiTestCase { @Test public void testAddSlotSlidesIcons() { StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS); - StatusBarIcon sbIcon = mock(StatusBarIcon.class); - statusBarIconList.setIcon(0, sbIcon); + StatusBarIconHolder sbHolder = mock(StatusBarIconHolder.class); + statusBarIconList.setIcon(0, sbHolder); statusBarIconList.getSlotIndex("zzz"); // new content added in front - assertNull(statusBarIconList.getIcon(0)); - assertEquals(sbIcon, statusBarIconList.getIcon(1)); + assertNull(statusBarIconList.getIcon(0, TAG_PRIMARY)); + assertEquals(sbHolder, statusBarIconList.getIcon(1, TAG_PRIMARY)); } @Test public void testGetAndSetIcon() { StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS); - StatusBarIcon sbIconA = mock(StatusBarIcon.class); - StatusBarIcon sbIconB = mock(StatusBarIcon.class); - statusBarIconList.setIcon(0, sbIconA); - statusBarIconList.setIcon(1, sbIconB); - assertEquals(sbIconA, statusBarIconList.getIcon(0)); - assertEquals(sbIconB, statusBarIconList.getIcon(1)); - assertNull(statusBarIconList.getIcon(2)); // icon not set + StatusBarIconHolder sbHolderA = mock(StatusBarIconHolder.class); + StatusBarIconHolder sbHolderB = mock(StatusBarIconHolder.class); + statusBarIconList.setIcon(0, sbHolderA); + statusBarIconList.setIcon(1, sbHolderB); + assertEquals(sbHolderA, statusBarIconList.getIcon(0, TAG_PRIMARY)); + assertEquals(sbHolderB, statusBarIconList.getIcon(1, TAG_PRIMARY)); + assertNull(statusBarIconList.getIcon(2, TAG_PRIMARY)); // icon not set } @Test public void testRemoveIcon() { StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS); - StatusBarIcon sbIconA = mock(StatusBarIcon.class); - StatusBarIcon sbIconB = mock(StatusBarIcon.class); - statusBarIconList.setIcon(0, sbIconA); - statusBarIconList.setIcon(1, sbIconB); - statusBarIconList.removeIcon(0); - assertNull(statusBarIconList.getIcon(0)); // icon not set + StatusBarIconHolder sbHolderA = mock(StatusBarIconHolder.class); + StatusBarIconHolder sbHolderB = mock(StatusBarIconHolder.class); + statusBarIconList.setIcon(0, sbHolderA); + statusBarIconList.setIcon(1, sbHolderB); + statusBarIconList.removeIcon(0, TAG_PRIMARY); + assertNull(statusBarIconList.getIcon(0, TAG_PRIMARY)); // icon not set } @Test - public void testGetViewIndex() { + public void testGetViewIndex_NoMultiples() { StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS); - StatusBarIcon sbIcon = mock(StatusBarIcon.class); - statusBarIconList.setIcon(2, sbIcon); - assertEquals(0, statusBarIconList.getViewIndex(2)); // Icon for item 2 is 0th child view. - statusBarIconList.setIcon(0, sbIcon); - assertEquals(0, statusBarIconList.getViewIndex(0)); // Icon for item 0 is 0th child view, - assertEquals(1, statusBarIconList.getViewIndex(2)); // and item 2 is now 1st child view. + StatusBarIconHolder sbHolder = mock(StatusBarIconHolder.class); + statusBarIconList.setIcon(2, sbHolder); + // Icon for item 2 is 0th child view. + assertEquals(0, statusBarIconList.getViewIndex(2, TAG_PRIMARY)); + statusBarIconList.setIcon(0, sbHolder); + // Icon for item 0 is 0th child view, + assertEquals(0, statusBarIconList.getViewIndex(0, TAG_PRIMARY)); + // and item 2 is now 1st child view. + assertEquals(1, statusBarIconList.getViewIndex(2, TAG_PRIMARY)); } + @Test + public void testGetViewIndex_MultipleIconsPerSlot() { + StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS); + StatusBarIconHolder sbHolder = mock(StatusBarIconHolder.class); + + statusBarIconList.setIcon(2, sbHolder); // item 2, one icon 0th child + + // All of these can be added to the same slot + // no tag bc it defaults to 0 + StatusBarIconHolder sbHolder2 = mock(StatusBarIconHolder.class); + StatusBarIconHolder sbHolder3 = mock(StatusBarIconHolder.class); + int sb3Tag = 1; + when(sbHolder3.getTag()).thenReturn(sb3Tag); + StatusBarIconHolder sbHolder4 = mock(StatusBarIconHolder.class); + int sb4Tag = 2; + when(sbHolder4.getTag()).thenReturn(sb4Tag); + + // Put a holder at slot 1, verify that it is first + statusBarIconList.setIcon(1, sbHolder2); + assertEquals(0, statusBarIconList.getViewIndex(1, TAG_PRIMARY)); + + // Put another holder at slot 1, verify it's index 0 and the rest come after + statusBarIconList.setIcon(1, sbHolder3); + assertEquals(0, statusBarIconList.getViewIndex(1, sb3Tag)); + assertEquals(1, statusBarIconList.getViewIndex(1, TAG_PRIMARY)); + // First icon should be at the end + assertEquals(2, statusBarIconList.getViewIndex(2, TAG_PRIMARY)); + + // Put another one in there just for good measure + statusBarIconList.setIcon(1, sbHolder4); + assertEquals(0, statusBarIconList.getViewIndex(1, sb4Tag)); + assertEquals(1, statusBarIconList.getViewIndex(1, sb3Tag)); + assertEquals(2, statusBarIconList.getViewIndex(1, TAG_PRIMARY)); + assertEquals(3, statusBarIconList.getViewIndex(2, TAG_PRIMARY)); + } + + /** + * StatusBarIconList.Slot tests + */ + + @Test + public void testSlot_OrderIsPreserved() { + Slot testSlot = new Slot("test_name", null); + + // no tag bc it defaults to 0 + StatusBarIconHolder sbHolder1 = mock(StatusBarIconHolder.class); + StatusBarIconHolder sbHolder2 = mock(StatusBarIconHolder.class); + int sb2Tag = 1; + when(sbHolder2.getTag()).thenReturn(sb2Tag); + StatusBarIconHolder sbHolder3 = mock(StatusBarIconHolder.class); + int sb3Tag = 2; + when(sbHolder3.getTag()).thenReturn(sb3Tag); + + ArrayList<StatusBarIconHolder> expected = new ArrayList<>(); + expected.add(sbHolder1); + expected.add(sbHolder2); + expected.add(sbHolder3); + + + // Add 3 icons in the same slot, and verify that the list we get is equal to what we gave + for (StatusBarIconHolder holder : expected) { + testSlot.addHolder(holder); + } + assertTrue(listsEqual(expected, testSlot.getHolderList())); + } + + private boolean listsEqual(List<StatusBarIconHolder> list1, List<StatusBarIconHolder> list2) { + if (list1.size() != list2.size()) return false; + + for (int i = 0; i < list1.size(); i++) { + if (!list1.get(i).equals(list2.get(i))) { + return false; + } + } + + return true; + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java index 5db77925b25e..72b0156d25f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java @@ -14,10 +14,17 @@ package com.android.systemui.statusbar.phone; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; + +import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON; +import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE; +import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import android.graphics.Rect; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; @@ -25,9 +32,14 @@ import android.view.ViewGroup; import android.widget.LinearLayout; import com.android.internal.statusbar.StatusBarIcon; +import com.android.systemui.statusbar.StatusIconDisplayable; import com.android.systemui.statusbar.StatusBarIconView; +import com.android.systemui.statusbar.StatusBarMobileView; +import com.android.systemui.statusbar.StatusBarWifiView; import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager; import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager; +import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; +import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; import com.android.systemui.statusbar.policy.DarkIconDispatcher; import com.android.systemui.utils.leaks.LeakCheckedTest; @@ -50,50 +62,119 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { public void testSetCalledOnAdd_IconManager() { LinearLayout layout = new LinearLayout(mContext); TestIconManager manager = new TestIconManager(layout); - StatusBarIcon icon = mock(StatusBarIcon.class); - - manager.onIconAdded(0, "test_slot", false, icon); - verify(manager.mMock).set(eq(icon)); + testCallOnAdd_forManager(manager); } @Test public void testSetCalledOnAdd_DarkIconManager() { LinearLayout layout = new LinearLayout(mContext); TestDarkIconManager manager = new TestDarkIconManager(layout); - StatusBarIcon icon = mock(StatusBarIcon.class); + testCallOnAdd_forManager(manager); + } + + + private <T extends IconManager & TestableIconManager> void testCallOnAdd_forManager(T manager) { + StatusBarIconHolder holder = holderForType(TYPE_ICON); + manager.onIconAdded(0, "test_slot", false, holder); + assertTrue("Expected StatusBarIconView", + (manager.getViewAt(0) instanceof StatusBarIconView)); - manager.onIconAdded(0, "test_slot", false, icon); - verify(manager.mMock).set(eq(icon)); + holder = holderForType(TYPE_WIFI); + manager.onIconAdded(1, "test_wifi", false, holder); + assertTrue(manager.getViewAt(1) instanceof StatusBarWifiView); + + holder = holderForType(TYPE_MOBILE); + manager.onIconAdded(2, "test_mobile", false, holder); + assertTrue(manager.getViewAt(2) instanceof StatusBarMobileView); } - private static class TestDarkIconManager extends DarkIconManager { + private StatusBarIconHolder holderForType(int type) { + switch (type) { + case TYPE_MOBILE: + return StatusBarIconHolder.fromMobileIconState(mock(MobileIconState.class)); + + case TYPE_WIFI: + return StatusBarIconHolder.fromWifiIconState(mock(WifiIconState.class)); - private final StatusBarIconView mMock; + case TYPE_ICON: + default: + return StatusBarIconHolder.fromIcon(mock(StatusBarIcon.class)); + } + } + + private static class TestDarkIconManager extends DarkIconManager + implements TestableIconManager { public TestDarkIconManager(LinearLayout group) { super(group); - mMock = mock(StatusBarIconView.class); } @Override - protected StatusBarIconView onCreateStatusBarIconView(String slot, boolean blocked) { - return mMock; + public StatusIconDisplayable getViewAt(int index) { + return (StatusIconDisplayable) mGroup.getChildAt(index); } - } - private static class TestIconManager extends IconManager { + @Override + protected StatusBarIconView addIcon(int index, String slot, boolean blocked, + StatusBarIcon icon) { + StatusBarIconView mock = mock(StatusBarIconView.class); + mGroup.addView(mock, index); + + return mock; + } - private final StatusBarIconView mMock; + @Override + protected StatusBarWifiView addSignalIcon(int index, String slot, WifiIconState state) { + StatusBarWifiView mock = mock(StatusBarWifiView.class); + mGroup.addView(mock, index); + return mock; + } + @Override + protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) { + StatusBarMobileView mock = mock(StatusBarMobileView.class); + mGroup.addView(mock, index); + + return mock; + } + } + + private static class TestIconManager extends IconManager implements TestableIconManager { public TestIconManager(ViewGroup group) { super(group); - mMock = mock(StatusBarIconView.class); } @Override - protected StatusBarIconView onCreateStatusBarIconView(String slot, boolean blocked) { - return mMock; + public StatusIconDisplayable getViewAt(int index) { + return (StatusIconDisplayable) mGroup.getChildAt(index); + } + + @Override + protected StatusBarIconView addIcon(int index, String slot, boolean blocked, + StatusBarIcon icon) { + StatusBarIconView mock = mock(StatusBarIconView.class); + mGroup.addView(mock, index); + + return mock; + } + + @Override + protected StatusBarWifiView addSignalIcon(int index, String slot, WifiIconState state) { + StatusBarWifiView mock = mock(StatusBarWifiView.class); + mGroup.addView(mock, index); + return mock; + } + + @Override + protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) { + StatusBarMobileView mock = mock(StatusBarMobileView.class); + mGroup.addView(mock, index); + + return mock; } } + private interface TestableIconManager { + StatusIconDisplayable getViewAt(int index); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 4a66bb7deca0..b31a2dc1af04 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -41,6 +41,7 @@ import android.app.Notification; import android.app.StatusBarManager; import android.app.trust.TrustManager; import android.content.Context; +import android.content.pm.UserInfo; import android.hardware.fingerprint.FingerprintManager; import android.metrics.LogMaker; import android.os.Binder; @@ -71,6 +72,7 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.assist.AssistManager; import com.android.systemui.keyguard.KeyguardViewMediator; +import com.android.systemui.classifier.FalsingManager; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.statusbar.ActivatableNotificationView; @@ -91,6 +93,7 @@ import com.android.systemui.statusbar.NotificationLogger; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; @@ -98,6 +101,7 @@ import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.KeyguardMonitorImpl; +import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import org.junit.Before; @@ -134,6 +138,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private VisualStabilityManager mVisualStabilityManager; @Mock private NotificationListener mNotificationListener; @Mock private KeyguardViewMediator mKeyguardViewMediator; + @Mock private NotificationLockscreenUserManager mLockscreenUserManager; private TestableStatusBar mStatusBar; private FakeMetricsLogger mMetricsLogger; @@ -146,9 +151,7 @@ public class StatusBarTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mDependency.injectMockDependency(AssistManager.class); mDependency.injectMockDependency(DeviceProvisionedController.class); - mDependency.injectMockDependency(NotificationGroupManager.class); mDependency.injectMockDependency(NotificationGutsManager.class); - mDependency.injectMockDependency(NotificationRemoteInputManager.class); mDependency.injectMockDependency(NotificationMediaManager.class); mDependency.injectMockDependency(ForegroundServiceController.class); mDependency.injectTestDependency(NotificationViewHierarchyManager.class, @@ -202,7 +205,12 @@ public class StatusBarTest extends SysuiTestCase { mPowerManager, mNotificationPanelView, mBarService, mNotificationListener, mNotificationLogger, mVisualStabilityManager, mViewHierarchyManager, mEntryManager, mScrimController, mFingerprintUnlockController, - mock(ActivityLaunchAnimator.class), mKeyguardViewMediator); + mock(ActivityLaunchAnimator.class), mKeyguardViewMediator, + mock(NotificationRemoteInputManager.class), mock(NotificationGroupManager.class), + mock(FalsingManager.class), mock(StatusBarWindowManager.class), + mock(NotificationIconAreaController.class), mock(DozeScrimController.class), + mock(NotificationShelf.class), mLockscreenUserManager, + mock(CommandQueue.class)); mStatusBar.mContext = mContext; mStatusBar.mComponents = mContext.getComponents(); mEntryManager.setUpForTest(mStatusBar, mStackScroller, mStatusBar, mHeadsUpManager, @@ -529,11 +537,7 @@ public class StatusBarTest extends SysuiTestCase { @Test @RunWithLooper(setAsMainLooper = true) public void testUpdateKeyguardState_DoesNotCrash() { - mStatusBar.mStatusBarWindow = mock(StatusBarWindowView.class); mStatusBar.mState = StatusBarState.KEYGUARD; - mStatusBar.mDozeScrimController = mock(DozeScrimController.class); - mStatusBar.mNotificationIconAreaController = mock(NotificationIconAreaController.class); - mStatusBar.mLockscreenUserManager = mock(NotificationLockscreenUserManager.class); when(mStatusBar.mLockscreenUserManager.getCurrentProfiles()).thenReturn( new SparseArray<>()); mStatusBar.updateKeyguardState(false, false); @@ -541,9 +545,6 @@ public class StatusBarTest extends SysuiTestCase { @Test public void testFingerprintNotification_UpdatesScrims() { - mStatusBar.mStatusBarWindowManager = mock(StatusBarWindowManager.class); - mStatusBar.mDozeScrimController = mock(DozeScrimController.class); - mStatusBar.mNotificationIconAreaController = mock(NotificationIconAreaController.class); mStatusBar.notifyFpAuthModeChanged(); verify(mScrimController).transitionTo(any(), any()); } @@ -629,6 +630,32 @@ public class StatusBarTest extends SysuiTestCase { verify(mStackScroller).changeViewPosition(any(FooterView.class), eq(-1 /* end */)); } + @Test + public void testSetState_changesIsFullScreenUserSwitcherState() { + mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD); + assertFalse(mStatusBar.isFullScreenUserSwitcherState()); + + mStatusBar.setBarStateForTest(StatusBarState.FULLSCREEN_USER_SWITCHER); + assertTrue(mStatusBar.isFullScreenUserSwitcherState()); + } + + @Test + public void testShowKeyguardImplementation_setsState() { + when(mLockscreenUserManager.getCurrentProfiles()).thenReturn(new SparseArray<>()); + + mStatusBar.setBarStateForTest(StatusBarState.SHADE); + + // By default, showKeyguardImpl sets state to KEYGUARD. + mStatusBar.showKeyguardImpl(); + assertTrue(mStatusBar.mState == StatusBarState.KEYGUARD); + + // If useFullscreenUserSwitcher is true, state is set to FULLSCREEN_USER_SWITCHER. + mStatusBar.mUserSwitcherController = mock(UserSwitcherController.class); + when(mStatusBar.mUserSwitcherController.useFullscreenUserSwitcher()).thenReturn(true); + mStatusBar.showKeyguardImpl(); + assertTrue(mStatusBar.mState == StatusBarState.FULLSCREEN_USER_SWITCHER); + } + static class TestableStatusBar extends StatusBar { public TestableStatusBar(StatusBarKeyguardViewManager man, UnlockMethodCache unlock, KeyguardIndicationController key, @@ -640,7 +667,16 @@ public class StatusBarTest extends SysuiTestCase { NotificationViewHierarchyManager viewHierarchyManager, TestableNotificationEntryManager entryManager, ScrimController scrimController, FingerprintUnlockController fingerprintUnlockController, - ActivityLaunchAnimator launchAnimator, KeyguardViewMediator keyguardViewMediator) { + ActivityLaunchAnimator launchAnimator, KeyguardViewMediator keyguardViewMediator, + NotificationRemoteInputManager notificationRemoteInputManager, + NotificationGroupManager notificationGroupManager, + FalsingManager falsingManager, + StatusBarWindowManager statusBarWindowManager, + NotificationIconAreaController notificationIconAreaController, + DozeScrimController dozeScrimController, + NotificationShelf notificationShelf, + NotificationLockscreenUserManager notificationLockscreenUserManager, + CommandQueue commandQueue) { mStatusBarKeyguardViewManager = man; mUnlockMethodCache = unlock; mKeyguardIndicationController = key; @@ -660,6 +696,15 @@ public class StatusBarTest extends SysuiTestCase { mActivityLaunchAnimator = launchAnimator; mKeyguardViewMediator = keyguardViewMediator; mClearAllEnabled = true; + mRemoteInputManager = notificationRemoteInputManager; + mGroupManager = notificationGroupManager; + mFalsingManager = falsingManager; + mStatusBarWindowManager = statusBarWindowManager; + mNotificationIconAreaController = notificationIconAreaController; + mDozeScrimController = dozeScrimController; + mNotificationShelf = notificationShelf; + mLockscreenUserManager = notificationLockscreenUserManager; + mCommandQueue = commandQueue; } private WakefulnessLifecycle createAwakeWakefulnessLifecycle() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java index 6b501af95097..8e34685cceec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java @@ -19,6 +19,9 @@ import android.testing.LeakCheck; import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager; +import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; +import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; +import java.util.List; public class FakeStatusBarIconController extends BaseLeakChecker<IconManager> implements StatusBarIconController { @@ -53,12 +56,23 @@ public class FakeStatusBarIconController extends BaseLeakChecker<IconManager> } @Override - public void setIconVisibility(String slotTty, boolean b) { + public void setSignalIcon(String slot, WifiIconState state) { + } + + @Override + public void setMobileIcons(String slot, List<MobileIconState> states) { + } + @Override + public void setIconVisibility(String slotTty, boolean b) { } @Override - public void removeIcon(String slot) { + public void removeIcon(String slot, int tag) { + } + @Override + public void removeAllIconsForSlot(String slot) { } + } diff --git a/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-land/config.xml b/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-land/config.xml deleted file mode 100644 index 1aa1af3b76fc..000000000000 --- a/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-land/config.xml +++ /dev/null @@ -1,21 +0,0 @@ -<!-- - ~ Copyright (C) 2018 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License - --> - -<resources> - <dimen name="quick_qs_offset_height">@dimen/status_bar_height_landscape</dimen> - <!-- Total height of QQS in landscape, this is effectively status_bar_height_landscape + 128 --> - <dimen name="quick_qs_total_height">156dp</dimen> -</resources>
\ No newline at end of file diff --git a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-land/config.xml b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-land/config.xml deleted file mode 100644 index 1aa1af3b76fc..000000000000 --- a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-land/config.xml +++ /dev/null @@ -1,21 +0,0 @@ -<!-- - ~ Copyright (C) 2018 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License - --> - -<resources> - <dimen name="quick_qs_offset_height">@dimen/status_bar_height_landscape</dimen> - <!-- Total height of QQS in landscape, this is effectively status_bar_height_landscape + 128 --> - <dimen name="quick_qs_total_height">156dp</dimen> -</resources>
\ No newline at end of file diff --git a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values-land/config.xml b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values-land/config.xml deleted file mode 100644 index 1aa1af3b76fc..000000000000 --- a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values-land/config.xml +++ /dev/null @@ -1,21 +0,0 @@ -<!-- - ~ Copyright (C) 2018 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License - --> - -<resources> - <dimen name="quick_qs_offset_height">@dimen/status_bar_height_landscape</dimen> - <!-- Total height of QQS in landscape, this is effectively status_bar_height_landscape + 128 --> - <dimen name="quick_qs_total_height">156dp</dimen> -</resources>
\ No newline at end of file diff --git a/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-land/config.xml b/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-land/config.xml deleted file mode 100644 index 1aa1af3b76fc..000000000000 --- a/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-land/config.xml +++ /dev/null @@ -1,21 +0,0 @@ -<!-- - ~ Copyright (C) 2018 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License - --> - -<resources> - <dimen name="quick_qs_offset_height">@dimen/status_bar_height_landscape</dimen> - <!-- Total height of QQS in landscape, this is effectively status_bar_height_landscape + 128 --> - <dimen name="quick_qs_total_height">156dp</dimen> -</resources>
\ No newline at end of file diff --git a/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values-land/config.xml b/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values-land/config.xml deleted file mode 100644 index 1aa1af3b76fc..000000000000 --- a/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values-land/config.xml +++ /dev/null @@ -1,21 +0,0 @@ -<!-- - ~ Copyright (C) 2018 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License - --> - -<resources> - <dimen name="quick_qs_offset_height">@dimen/status_bar_height_landscape</dimen> - <!-- Total height of QQS in landscape, this is effectively status_bar_height_landscape + 128 --> - <dimen name="quick_qs_total_height">156dp</dimen> -</resources>
\ No newline at end of file diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto index 9c74188d671e..934ad882a635 100644 --- a/proto/src/wifi.proto +++ b/proto/src/wifi.proto @@ -249,12 +249,12 @@ message WifiLog { optional int32 num_wificond_crashes = 54; // Indicates the number of times an error was encountered in - // Wifi HAL when wifi was turned on. - optional int32 num_wifi_on_failure_due_to_hal = 55; + // Wifi HAL on |WifiNative.setupInterfaceForClientMode|. + optional int32 num_setup_client_interface_failure_due_to_hal = 55; // Indicates the number of times an error was encountered in - // Wificond when wifi was turned on. - optional int32 num_wifi_on_failure_due_to_wificond = 56; + // Wificond on |WifiNative.setupInterfaceForClientMode|. + optional int32 num_setup_client_interface_failure_due_to_wificond = 56; // Wi-Fi Aware metrics optional WifiAwareLog wifi_aware_log = 57; @@ -385,6 +385,43 @@ message WifiLog { // Histogram counting instances of scans with N many 802.11mc (RTT) supporting APs repeated NumConnectableNetworksBucket observed_80211mc_supporting_aps_in_scan_histogram = 95; + + // Total number of times supplicant crashed. + optional int32 num_supplicant_crashes = 96; + + // Total number of times hostapd crashed. + optional int32 num_hostapd_crashes = 97; + + // Indicates the number of times an error was encountered in + // supplicant on |WifiNative.setupInterfaceForClientMode|. + optional int32 num_setup_client_interface_failure_due_to_supplicant = 98; + + // Indicates the number of times an error was encountered in + // Wifi HAL on |WifiNative.setupInterfaceForSoftApMode|. + optional int32 num_setup_soft_ap_interface_failure_due_to_hal = 99; + + // Indicates the number of times an error was encountered in + // Wifi HAL on |WifiNative.setupInterfaceForSoftApMode|. + optional int32 num_setup_soft_ap_interface_failure_due_to_wificond = 100; + + // Indicates the number of times an error was encountered in + // Wifi HAL on |WifiNative.setupInterfaceForSoftApMode|. + optional int32 num_setup_soft_ap_interface_failure_due_to_hostapd = 101; + + // Indicates the number of times we got an interface down in client mode. + optional int32 num_client_interface_down = 102; + + // Indicates the number of times we got an interface down in softap mode. + optional int32 num_soft_ap_interface_down = 103; + + // Indicates the number of scan requests from external apps. + optional int32 num_external_app_oneshot_scan_requests = 104; + + // Indicates the number of times a scan request from an external foreground app was throttled. + optional int32 num_external_foreground_app_oneshot_scan_requests_throttled = 105; + + // Indicates the number of times a scan request from an external background app was throttled. + optional int32 num_external_background_app_oneshot_scan_requests_throttled = 106; } // Information that gets logged for every WiFi connection. diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 8941b4926584..de112f94f743 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -853,11 +853,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (mSecurityPolicy.findA11yWindowInfoById(windowId) == null) { return null; } - IBinder token = mGlobalWindowTokens.get(windowId); - if (token != null) { - return token; - } - return getCurrentUserStateLocked().mWindowTokens.get(windowId); + return findWindowTokenLocked(windowId); } } @@ -2527,6 +2523,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub getInteractionBridge().clearAccessibilityFocusNotLocked(windowId); } + private IBinder findWindowTokenLocked(int windowId) { + IBinder token = mGlobalWindowTokens.get(windowId); + if (token != null) { + return token; + } + return getCurrentUserStateLocked().mWindowTokens.get(windowId); + } + private int findWindowIdLocked(IBinder token) { final int globalIndex = mGlobalWindowTokens.indexOfValue(token); if (globalIndex >= 0) { @@ -2986,7 +2990,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // the accessibility layer reports which are windows // that a sighted user can touch. default: { - return isRetrievalAllowingWindow(event.getWindowId()); + return isRetrievalAllowingWindowLocked(event.getWindowId()); } } } @@ -3438,7 +3442,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public boolean canGetAccessibilityNodeInfoLocked( AbstractAccessibilityServiceConnection service, int windowId) { - return canRetrieveWindowContentLocked(service) && isRetrievalAllowingWindow(windowId); + return canRetrieveWindowContentLocked(service) + && isRetrievalAllowingWindowLocked(windowId); } public boolean canRetrieveWindowsLocked(AbstractAccessibilityServiceConnection service) { @@ -3523,17 +3528,40 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub || userId == UserHandle.USER_CURRENT_OR_SELF); } - private boolean isRetrievalAllowingWindow(int windowId) { + private boolean isRetrievalAllowingWindowLocked(int windowId) { // The system gets to interact with any window it wants. if (Binder.getCallingUid() == Process.SYSTEM_UID) { return true; } + if (Binder.getCallingUid() == Process.SHELL_UID) { + if (!isShellAllowedToRetrieveWindowLocked(windowId)) { + return false; + } + } if (windowId == mActiveWindowId) { return true; } return findA11yWindowInfoById(windowId) != null; } + private boolean isShellAllowedToRetrieveWindowLocked(int windowId) { + long token = Binder.clearCallingIdentity(); + try { + IBinder windowToken = findWindowTokenLocked(windowId); + if (windowToken == null) { + return false; + } + int userId = mWindowManagerService.getWindowOwnerUserId(windowToken); + if (userId == UserHandle.USER_NULL) { + return false; + } + return !mUserManager.hasUserRestriction( + UserManager.DISALLOW_DEBUGGING_FEATURES, UserHandle.of(userId)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + public AccessibilityWindowInfo findA11yWindowInfoById(int windowId) { return mA11yWindowInfoById.get(windowId); } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index d4ecd8b93859..3253f2e40692 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -19,7 +19,6 @@ package com.android.server.autofill; import static android.Manifest.permission.MANAGE_AUTO_FILL; import static android.content.Context.AUTOFILL_MANAGER_SERVICE; -import static com.android.server.autofill.Helper.bundleToString; import static com.android.server.autofill.Helper.sDebug; import static com.android.server.autofill.Helper.sFullScreenMode; import static com.android.server.autofill.Helper.sPartitionMaxCount; @@ -193,8 +192,7 @@ public final class AutofillManagerService extends SystemService { if (disabledBefore == disabledNow) { // Nothing changed, do nothing. if (sDebug) { - Slog.d(TAG, "Autofill restriction did not change for user " + userId + ": " - + bundleToString(newRestrictions)); + Slog.d(TAG, "Autofill restriction did not change for user " + userId); return; } } diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java index a202aafde53b..78526f53cdde 100644 --- a/services/autofill/java/com/android/server/autofill/Helper.java +++ b/services/autofill/java/com/android/server/autofill/Helper.java @@ -79,28 +79,6 @@ public final class Helper { throw new UnsupportedOperationException("contains static members only"); } - static void append(StringBuilder builder, Bundle bundle) { - if (bundle == null || !sVerbose) { - builder.append("null"); - return; - } - final Set<String> keySet = bundle.keySet(); - builder.append("[Bundle with ").append(keySet.size()).append(" extras:"); - for (String key : keySet) { - final Object value = bundle.get(key); - builder.append(' ').append(key).append('='); - builder.append((value instanceof Object[]) - ? Arrays.toString((Objects[]) value) : value); - } - builder.append(']'); - } - - static String bundleToString(Bundle bundle) { - final StringBuilder builder = new StringBuilder(); - append(builder, bundle); - return builder.toString(); - } - @Nullable static AutofillId[] toArray(@Nullable ArraySet<AutofillId> set) { if (set == null) return null; diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 706fb1a72491..e14584f9070b 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -2502,8 +2502,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback); - pw.print(prefix); pw.print("mClientState: "); pw.println( - Helper.bundleToString(mClientState)); + if (mClientState != null) { + pw.print(prefix); pw.print("mClientState: "); pw.print(mClientState.getSize()); pw + .println(" bytes"); + } pw.print(prefix); pw.print("mCompatMode: "); pw.println(mCompatMode); pw.print(prefix); pw.print("mUrlBar: "); if (mUrlBar == null) { diff --git a/services/backup/java/com/android/server/backup/BackupAgentTimeoutParameters.java b/services/backup/java/com/android/server/backup/BackupAgentTimeoutParameters.java index 4de2c9b8e79f..49fa1ccce5fe 100644 --- a/services/backup/java/com/android/server/backup/BackupAgentTimeoutParameters.java +++ b/services/backup/java/com/android/server/backup/BackupAgentTimeoutParameters.java @@ -21,6 +21,7 @@ import android.os.Handler; import android.provider.Settings; import android.util.KeyValueListParser; import android.util.KeyValueSettingObserver; +import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -31,6 +32,8 @@ import com.android.internal.annotations.VisibleForTesting; * are represented as a comma-delimited key value list. */ public class BackupAgentTimeoutParameters extends KeyValueSettingObserver { + private static final String TAG = "BackupAgentTimeout"; + @VisibleForTesting public static final String SETTING = Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS; @@ -120,30 +123,50 @@ public class BackupAgentTimeoutParameters extends KeyValueSettingObserver { public long getKvBackupAgentTimeoutMillis() { synchronized (mLock) { + if (BackupManagerService.DEBUG_SCHEDULING) { + Slog.v(TAG, "getKvBackupAgentTimeoutMillis(): " + mKvBackupAgentTimeoutMillis); + } return mKvBackupAgentTimeoutMillis; } } public long getFullBackupAgentTimeoutMillis() { synchronized (mLock) { + if (BackupManagerService.DEBUG_SCHEDULING) { + Slog.v(TAG, "getFullBackupAgentTimeoutMillis(): " + mFullBackupAgentTimeoutMillis); + } return mFullBackupAgentTimeoutMillis; } } public long getSharedBackupAgentTimeoutMillis() { synchronized (mLock) { + if (BackupManagerService.DEBUG_SCHEDULING) { + Slog.v( + TAG, + "getSharedBackupAgentTimeoutMillis(): " + mSharedBackupAgentTimeoutMillis); + } return mSharedBackupAgentTimeoutMillis; } } public long getRestoreAgentTimeoutMillis() { synchronized (mLock) { + if (BackupManagerService.DEBUG_SCHEDULING) { + Slog.v(TAG, "getRestoreAgentTimeoutMillis(): " + mRestoreAgentTimeoutMillis); + } return mRestoreAgentTimeoutMillis; } } public long getRestoreAgentFinishedTimeoutMillis() { synchronized (mLock) { + if (BackupManagerService.DEBUG_SCHEDULING) { + Slog.v( + TAG, + "getRestoreAgentFinishedTimeoutMillis(): " + + mRestoreAgentFinishedTimeoutMillis); + } return mRestoreAgentFinishedTimeoutMillis; } } diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index d6f6c6cf1fc3..bd51af270dd8 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -215,13 +215,6 @@ public class BackupManagerService implements BackupManagerServiceInterface { // Timeout interval for deciding that a bind or clear-data has taken too long private static final long TIMEOUT_INTERVAL = 10 * 1000; - // Timeout intervals for agent backup & restore operations - public static final long TIMEOUT_BACKUP_INTERVAL = 30 * 1000; - public static final long TIMEOUT_FULL_BACKUP_INTERVAL = 5 * 60 * 1000; - public static final long TIMEOUT_SHARED_BACKUP_INTERVAL = 30 * 60 * 1000; - public static final long TIMEOUT_RESTORE_INTERVAL = 60 * 1000; - public static final long TIMEOUT_RESTORE_FINISHED_INTERVAL = 30 * 1000; - // User confirmation timeout for a full backup/restore operation. It's this long in // order to give them time to enter the backup password. private static final long TIMEOUT_FULL_CONFIRMATION = 60 * 1000; @@ -232,6 +225,7 @@ public class BackupManagerService implements BackupManagerServiceInterface { private static final int BUSY_BACKOFF_FUZZ = 1000 * 60 * 60 * 2; // two hours private BackupManagerConstants mConstants; + private final BackupAgentTimeoutParameters mAgentTimeoutParameters; private Context mContext; private PackageManager mPackageManager; private IPackageManager mPackageManagerBinder; @@ -315,6 +309,10 @@ public class BackupManagerService implements BackupManagerServiceInterface { return mConstants; } + public BackupAgentTimeoutParameters getAgentTimeoutParameters() { + return mAgentTimeoutParameters; + } + public Context getContext() { return mContext; } @@ -799,6 +797,10 @@ public class BackupManagerService implements BackupManagerServiceInterface { mBackupManagerBinder = Trampoline.asInterface(parent.asBinder()); + mAgentTimeoutParameters = new + BackupAgentTimeoutParameters(Handler.getMain(), mContext.getContentResolver()); + mAgentTimeoutParameters.start(); + // spin up the backup/restore handler thread mBackupHandler = new BackupHandler(this, backupThread.getLooper()); @@ -3407,7 +3409,7 @@ public class BackupManagerService implements BackupManagerServiceInterface { } mActiveRestoreSession = new ActiveRestoreSession(this, packageName, transport); mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_SESSION_TIMEOUT, - TIMEOUT_RESTORE_INTERVAL); + mAgentTimeoutParameters.getRestoreAgentTimeoutMillis()); } return mActiveRestoreSession; } diff --git a/services/backup/java/com/android/server/backup/BackupManagerServiceInterface.java b/services/backup/java/com/android/server/backup/BackupManagerServiceInterface.java index 7b021c64eaf8..aabe7f611845 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerServiceInterface.java +++ b/services/backup/java/com/android/server/backup/BackupManagerServiceInterface.java @@ -191,4 +191,7 @@ public interface BackupManagerServiceInterface { void dump(FileDescriptor fd, PrintWriter pw, String[] args); IBackupManager getBackupManagerBinder(); + + // Gets access to the backup/restore agent timeout parameters. + BackupAgentTimeoutParameters getAgentTimeoutParameters(); } diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java index 47558775d19e..7f0030a5064e 100644 --- a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java +++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java @@ -4,8 +4,8 @@ import static android.os.ParcelFileDescriptor.MODE_CREATE; import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; import static android.os.ParcelFileDescriptor.MODE_TRUNCATE; + import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP_WAIT; -import static com.android.server.backup.BackupManagerService.TIMEOUT_BACKUP_INTERVAL; import android.app.ApplicationThreadConstants; import android.app.IBackupAgent; @@ -19,6 +19,7 @@ import android.os.RemoteException; import android.os.SELinux; import android.util.Slog; +import com.android.internal.util.Preconditions; import com.android.server.backup.utils.FullBackupUtils; import libcore.io.IoUtils; @@ -59,6 +60,7 @@ public class KeyValueAdbBackupEngine { private ParcelFileDescriptor mSavedState; private ParcelFileDescriptor mBackupData; private ParcelFileDescriptor mNewState; + private final BackupAgentTimeoutParameters mAgentTimeoutParameters; public KeyValueAdbBackupEngine(OutputStream output, PackageInfo packageInfo, BackupManagerServiceInterface backupManagerService, PackageManager packageManager, @@ -81,6 +83,9 @@ public class KeyValueAdbBackupEngine { pkg + BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX); mManifestFile = new File(mDataDir, BackupManagerService.BACKUP_MANIFEST_FILENAME); + mAgentTimeoutParameters = Preconditions.checkNotNull( + backupManagerService.getAgentTimeoutParameters(), + "Timeout parameters cannot be null"); } public void backupOnePackage() throws IOException { @@ -148,8 +153,9 @@ public class KeyValueAdbBackupEngine { // Return true on backup success, false otherwise private boolean invokeAgentForAdbBackup(String packageName, IBackupAgent agent) { int token = mBackupManagerService.generateRandomIntegerToken(); + long kvBackupAgentTimeoutMillis = mAgentTimeoutParameters.getKvBackupAgentTimeoutMillis(); try { - mBackupManagerService.prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, null, + mBackupManagerService.prepareOperationTimeout(token, kvBackupAgentTimeoutMillis, null, OP_TYPE_BACKUP_WAIT); // Start backup and wait for BackupManagerService to get callback for success or timeout @@ -231,14 +237,14 @@ public class KeyValueAdbBackupEngine { } private void writeBackupData() throws IOException { - int token = mBackupManagerService.generateRandomIntegerToken(); + long kvBackupAgentTimeoutMillis = mAgentTimeoutParameters.getKvBackupAgentTimeoutMillis(); ParcelFileDescriptor[] pipes = null; try { pipes = ParcelFileDescriptor.createPipe(); - mBackupManagerService.prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, null, + mBackupManagerService.prepareOperationTimeout(token, kvBackupAgentTimeoutMillis, null, OP_TYPE_BACKUP_WAIT); // We will have to create a runnable that will read the manifest and backup data we diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java index 0582abac91d4..56946596eea3 100644 --- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java +++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java @@ -25,9 +25,6 @@ import static com.android.server.backup.BackupManagerService.MORE_DEBUG; import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP_WAIT; import static com.android.server.backup.BackupManagerService.SHARED_BACKUP_AGENT_PACKAGE; import static com.android.server.backup.BackupManagerService.TAG; -import static com.android.server.backup.BackupManagerService.TIMEOUT_FULL_BACKUP_INTERVAL; -import static com.android.server.backup.BackupManagerService - .TIMEOUT_SHARED_BACKUP_INTERVAL; import android.app.ApplicationThreadConstants; import android.app.IBackupAgent; @@ -44,9 +41,11 @@ import android.util.Log; import android.util.Slog; import android.util.StringBuilderPrinter; +import com.android.internal.util.Preconditions; import com.android.server.AppWidgetBackupBridge; -import com.android.server.backup.BackupRestoreTask; +import com.android.server.backup.BackupAgentTimeoutParameters; import com.android.server.backup.BackupManagerService; +import com.android.server.backup.BackupRestoreTask; import com.android.server.backup.utils.FullBackupUtils; import java.io.BufferedOutputStream; @@ -75,6 +74,7 @@ public class FullBackupEngine { private final long mQuota; private final int mOpToken; private final int mTransportFlags; + private final BackupAgentTimeoutParameters mAgentTimeoutParameters; class FullBackupRunner implements Runnable { @@ -137,8 +137,8 @@ public class FullBackupEngine { final boolean isSharedStorage = mPackage.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE); final long timeout = isSharedStorage ? - TIMEOUT_SHARED_BACKUP_INTERVAL : - TIMEOUT_FULL_BACKUP_INTERVAL; + mAgentTimeoutParameters.getSharedBackupAgentTimeoutMillis() : + mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis(); if (DEBUG) { Slog.d(TAG, "Calling doFullBackup() on " + mPackage.packageName); @@ -180,6 +180,9 @@ public class FullBackupEngine { mQuota = quota; mOpToken = opToken; mTransportFlags = transportFlags; + mAgentTimeoutParameters = Preconditions.checkNotNull( + backupManagerService.getAgentTimeoutParameters(), + "Timeout parameters cannot be null"); } public int preflightCheck() throws RemoteException { diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java index 40b6967a729a..bc7d9fc691dd 100644 --- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java +++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java @@ -19,7 +19,6 @@ package com.android.server.backup.fullbackup; import static com.android.server.backup.BackupManagerService.MORE_DEBUG; import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP_WAIT; import static com.android.server.backup.BackupManagerService.TAG; -import static com.android.server.backup.BackupManagerService.TIMEOUT_FULL_BACKUP_INTERVAL; import android.app.backup.IBackupManager; import android.content.ComponentName; @@ -33,6 +32,8 @@ import android.os.UserHandle; import android.util.Slog; import com.android.internal.backup.IObbBackupService; +import com.android.internal.util.Preconditions; +import com.android.server.backup.BackupAgentTimeoutParameters; import com.android.server.backup.BackupManagerService; import com.android.server.backup.utils.FullBackupUtils; @@ -46,10 +47,14 @@ public class FullBackupObbConnection implements ServiceConnection { private BackupManagerService backupManagerService; volatile IObbBackupService mService; + private final BackupAgentTimeoutParameters mAgentTimeoutParameters; public FullBackupObbConnection(BackupManagerService backupManagerService) { this.backupManagerService = backupManagerService; mService = null; + mAgentTimeoutParameters = Preconditions.checkNotNull( + backupManagerService.getAgentTimeoutParameters(), + "Timeout parameters cannot be null"); } public void establish() { @@ -75,8 +80,10 @@ public class FullBackupObbConnection implements ServiceConnection { try { pipes = ParcelFileDescriptor.createPipe(); int token = backupManagerService.generateRandomIntegerToken(); + long fullBackupAgentTimeoutMillis = + mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis(); backupManagerService.prepareOperationTimeout( - token, TIMEOUT_FULL_BACKUP_INTERVAL, null, OP_TYPE_BACKUP_WAIT); + token, fullBackupAgentTimeoutMillis, null, OP_TYPE_BACKUP_WAIT); mService.backupObbs(pkg.packageName, pipes[1], token, backupManagerService.getBackupManagerBinder()); FullBackupUtils.routeSocketDataToOutput(pipes[0], out); diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java index 2c2dd8528cb1..a40afc3cfa4d 100644 --- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java +++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java @@ -22,7 +22,6 @@ import static com.android.server.backup.BackupManagerService.MORE_DEBUG; import static com.android.server.backup.BackupManagerService.OP_PENDING; import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP; import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP_WAIT; -import static com.android.server.backup.BackupManagerService.TIMEOUT_FULL_BACKUP_INTERVAL; import android.annotation.Nullable; import android.app.IBackupAgent; @@ -43,7 +42,9 @@ import android.util.Log; import android.util.Slog; import com.android.internal.backup.IBackupTransport; +import com.android.internal.util.Preconditions; import com.android.server.EventLogTags; +import com.android.server.backup.BackupAgentTimeoutParameters; import com.android.server.backup.BackupRestoreTask; import com.android.server.backup.FullBackupJob; import com.android.server.backup.BackupManagerService; @@ -146,6 +147,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba private volatile boolean mIsDoingBackup; private volatile boolean mCancelAll; private final int mCurrentOpToken; + private final BackupAgentTimeoutParameters mAgentTimeoutParameters; public PerformFullTransportBackupTask(BackupManagerService backupManagerService, TransportClient transportClient, @@ -167,6 +169,9 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba mUserInitiated = userInitiated; mCurrentOpToken = backupManagerService.generateRandomIntegerToken(); mBackupRunnerOpToken = backupManagerService.generateRandomIntegerToken(); + mAgentTimeoutParameters = Preconditions.checkNotNull( + backupManagerService.getAgentTimeoutParameters(), + "Timeout parameters cannot be null"); if (backupManagerService.isBackupOperationInProgress()) { if (DEBUG) { @@ -698,9 +703,11 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba @Override public int preflightFullBackup(PackageInfo pkg, IBackupAgent agent) { int result; + long fullBackupAgentTimeoutMillis = + mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis(); try { backupManagerService.prepareOperationTimeout( - mCurrentOpToken, TIMEOUT_FULL_BACKUP_INTERVAL, this, OP_TYPE_BACKUP_WAIT); + mCurrentOpToken, fullBackupAgentTimeoutMillis, this, OP_TYPE_BACKUP_WAIT); backupManagerService.addBackupTrace("preflighting"); if (MORE_DEBUG) { Slog.d(TAG, "Preflighting full payload of " + pkg.packageName); @@ -713,7 +720,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba // timeout had been produced. In case of a real backstop timeout, mResult // will still contain the value it was constructed with, AGENT_ERROR, which // intentionaly falls into the "just report failure" code. - mLatch.await(TIMEOUT_FULL_BACKUP_INTERVAL, TimeUnit.MILLISECONDS); + mLatch.await(fullBackupAgentTimeoutMillis, TimeUnit.MILLISECONDS); long totalSize = mResult.get(); // If preflight timed out, mResult will contain error code as int. @@ -769,8 +776,10 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba @Override public long getExpectedSizeOrErrorCode() { + long fullBackupAgentTimeoutMillis = + mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis(); try { - mLatch.await(TIMEOUT_FULL_BACKUP_INTERVAL, TimeUnit.MILLISECONDS); + mLatch.await(fullBackupAgentTimeoutMillis, TimeUnit.MILLISECONDS); return mResult.get(); } catch (InterruptedException e) { return BackupTransport.NO_MORE_DATA; @@ -863,8 +872,10 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba // If preflight succeeded, returns positive number - preflight size, // otherwise return negative error code. long getPreflightResultBlocking() { + long fullBackupAgentTimeoutMillis = + mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis(); try { - mPreflightLatch.await(TIMEOUT_FULL_BACKUP_INTERVAL, TimeUnit.MILLISECONDS); + mPreflightLatch.await(fullBackupAgentTimeoutMillis, TimeUnit.MILLISECONDS); if (mIsCancelled) { return BackupManager.ERROR_BACKUP_CANCELLED; } @@ -879,8 +890,10 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba } int getBackupResultBlocking() { + long fullBackupAgentTimeoutMillis = + mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis(); try { - mBackupLatch.await(TIMEOUT_FULL_BACKUP_INTERVAL, TimeUnit.MILLISECONDS); + mBackupLatch.await(fullBackupAgentTimeoutMillis, TimeUnit.MILLISECONDS); if (mIsCancelled) { return BackupManager.ERROR_BACKUP_CANCELLED; } diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java index 136fada43b1f..69f08ae49a2e 100644 --- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java +++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java @@ -19,7 +19,6 @@ package com.android.server.backup.internal; import static com.android.server.backup.BackupManagerService.DEBUG; import static com.android.server.backup.BackupManagerService.MORE_DEBUG; import static com.android.server.backup.BackupManagerService.TAG; -import static com.android.server.backup.BackupManagerService.TIMEOUT_RESTORE_INTERVAL; import android.app.backup.RestoreSet; import android.content.Intent; @@ -33,7 +32,9 @@ import android.util.Pair; import android.util.Slog; import com.android.internal.backup.IBackupTransport; +import com.android.internal.util.Preconditions; import com.android.server.EventLogTags; +import com.android.server.backup.BackupAgentTimeoutParameters; import com.android.server.backup.BackupManagerService; import com.android.server.backup.BackupRestoreTask; import com.android.server.backup.DataChangedJournal; @@ -81,10 +82,14 @@ public class BackupHandler extends Handler { public static final int MSG_OP_COMPLETE = 21; private final BackupManagerService backupManagerService; + private final BackupAgentTimeoutParameters mAgentTimeoutParameters; public BackupHandler(BackupManagerService backupManagerService, Looper looper) { super(looper); this.backupManagerService = backupManagerService; + mAgentTimeoutParameters = Preconditions.checkNotNull( + backupManagerService.getAgentTimeoutParameters(), + "Timeout parameters cannot be null"); } public void handleMessage(Message msg) { @@ -322,7 +327,8 @@ public class BackupHandler extends Handler { // Done: reset the session timeout clock removeMessages(MSG_RESTORE_SESSION_TIMEOUT); - sendEmptyMessageDelayed(MSG_RESTORE_SESSION_TIMEOUT, TIMEOUT_RESTORE_INTERVAL); + sendEmptyMessageDelayed(MSG_RESTORE_SESSION_TIMEOUT, + mAgentTimeoutParameters.getRestoreAgentTimeoutMillis()); params.listener.onFinished(callerLogString); } 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 11394e66a0f0..ac605b1ddd69 100644 --- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java +++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java @@ -24,7 +24,6 @@ import static com.android.server.backup.BackupManagerService.OP_PENDING; import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP; import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP_WAIT; import static com.android.server.backup.BackupManagerService.PACKAGE_MANAGER_SENTINEL; -import static com.android.server.backup.BackupManagerService.TIMEOUT_BACKUP_INTERVAL; import static com.android.server.backup.internal.BackupHandler.MSG_BACKUP_OPERATION_TIMEOUT; import static com.android.server.backup.internal.BackupHandler.MSG_BACKUP_RESTORE_STEP; @@ -55,8 +54,10 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.backup.IBackupTransport; +import com.android.internal.util.Preconditions; import com.android.server.AppWidgetBackupBridge; import com.android.server.EventLogTags; +import com.android.server.backup.BackupAgentTimeoutParameters; import com.android.server.backup.BackupRestoreTask; import com.android.server.backup.DataChangedJournal; import com.android.server.backup.KeyValueBackupJob; @@ -142,6 +143,7 @@ public class PerformBackupTask implements BackupRestoreTask { private boolean mFinished; private final boolean mUserInitiated; private final boolean mNonIncremental; + private final BackupAgentTimeoutParameters mAgentTimeoutParameters; private volatile boolean mCancelAll; @@ -162,6 +164,9 @@ public class PerformBackupTask implements BackupRestoreTask { mPendingFullBackups = pendingFullBackups; mUserInitiated = userInitiated; mNonIncremental = nonIncremental; + mAgentTimeoutParameters = Preconditions.checkNotNull( + backupManagerService.getAgentTimeoutParameters(), + "Timeout parameters cannot be null"); mStateDir = new File(backupManagerService.getBaseStateDir(), dirName); mCurrentOpToken = backupManagerService.generateRandomIntegerToken(); @@ -711,8 +716,10 @@ public class PerformBackupTask implements BackupRestoreTask { // Initiate the target's backup pass backupManagerService.addBackupTrace("setting timeout"); + long kvBackupAgentTimeoutMillis = + mAgentTimeoutParameters.getKvBackupAgentTimeoutMillis(); backupManagerService.prepareOperationTimeout( - mEphemeralOpToken, TIMEOUT_BACKUP_INTERVAL, this, OP_TYPE_BACKUP_WAIT); + mEphemeralOpToken, kvBackupAgentTimeoutMillis, this, OP_TYPE_BACKUP_WAIT); backupManagerService.addBackupTrace("calling agent doBackup()"); agent.doBackup( diff --git a/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java b/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java index e4f3a9d5cd0a..a8c7ce6ad832 100644 --- a/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java +++ b/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java @@ -18,10 +18,11 @@ package com.android.server.backup.restore; import static com.android.server.backup.BackupManagerService.DEBUG; import static com.android.server.backup.BackupManagerService.MORE_DEBUG; -import static com.android.server.backup.BackupManagerService.TIMEOUT_FULL_BACKUP_INTERVAL; import android.util.Slog; +import com.android.internal.util.Preconditions; +import com.android.server.backup.BackupAgentTimeoutParameters; import com.android.server.backup.BackupManagerService; import com.android.server.backup.BackupRestoreTask; @@ -37,18 +38,24 @@ public class AdbRestoreFinishedLatch implements BackupRestoreTask { private BackupManagerService backupManagerService; final CountDownLatch mLatch; private final int mCurrentOpToken; + private final BackupAgentTimeoutParameters mAgentTimeoutParameters; public AdbRestoreFinishedLatch(BackupManagerService backupManagerService, int currentOpToken) { this.backupManagerService = backupManagerService; mLatch = new CountDownLatch(1); mCurrentOpToken = currentOpToken; + mAgentTimeoutParameters = Preconditions.checkNotNull( + backupManagerService.getAgentTimeoutParameters(), + "Timeout parameters cannot be null"); } void await() { boolean latched = false; + long fullBackupAgentTimeoutMillis = + mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis(); try { - latched = mLatch.await(TIMEOUT_FULL_BACKUP_INTERVAL, TimeUnit.MILLISECONDS); + latched = mLatch.await(fullBackupAgentTimeoutMillis, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Slog.w(TAG, "Interrupted!"); } diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java index c1a1c1dc10e7..6bc753000837 100644 --- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java +++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java @@ -23,9 +23,6 @@ import static com.android.server.backup.BackupManagerService.MORE_DEBUG; import static com.android.server.backup.BackupManagerService.OP_TYPE_RESTORE_WAIT; import static com.android.server.backup.BackupManagerService.SHARED_BACKUP_AGENT_PACKAGE; import static com.android.server.backup.BackupManagerService.TAG; -import static com.android.server.backup.BackupManagerService.TIMEOUT_RESTORE_INTERVAL; -import static com.android.server.backup.BackupManagerService - .TIMEOUT_SHARED_BACKUP_INTERVAL; import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT; import android.app.ApplicationThreadConstants; @@ -40,13 +37,17 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.Signature; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.provider.Settings; +import android.text.TextUtils; import android.util.Slog; +import com.android.internal.util.Preconditions; import com.android.server.LocalServices; +import com.android.server.backup.BackupAgentTimeoutParameters; +import com.android.server.backup.BackupManagerService; import com.android.server.backup.BackupRestoreTask; import com.android.server.backup.FileMetadata; import com.android.server.backup.KeyValueAdbRestoreEngine; -import com.android.server.backup.BackupManagerService; import com.android.server.backup.fullbackup.FullBackupObbConnection; import com.android.server.backup.utils.BytesReadListener; import com.android.server.backup.utils.FullBackupRestoreObserverUtils; @@ -56,8 +57,11 @@ import com.android.server.backup.utils.TarBackupReader; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.List; /** * Full restore engine, used by both adb restore and transport-based full restore. @@ -121,6 +125,8 @@ public class FullRestoreEngine extends RestoreEngine { final int mEphemeralOpToken; + private final BackupAgentTimeoutParameters mAgentTimeoutParameters; + public FullRestoreEngine(BackupManagerService backupManagerService, BackupRestoreTask monitorTask, IFullBackupRestoreObserver observer, IBackupManagerMonitor monitor, PackageInfo onlyPackage, boolean allowApks, @@ -135,6 +141,9 @@ public class FullRestoreEngine extends RestoreEngine { mAllowObbs = allowObbs; mBuffer = new byte[32 * 1024]; mBytes = 0; + mAgentTimeoutParameters = Preconditions.checkNotNull( + backupManagerService.getAgentTimeoutParameters(), + "Timeout parameters cannot be null"); } public IBackupAgent getAgent() { @@ -320,12 +329,17 @@ public class FullRestoreEngine extends RestoreEngine { pkg, 0); // If we haven't sent any data to this app yet, we probably - // need to clear it first. Check that. + // need to clear it first. Check that. if (!mClearedPackages.contains(pkg)) { - // apps with their own backup agents are - // responsible for coherently managing a full - // restore. - if (mTargetApp.backupAgentName == null) { + // Apps with their own backup agents are responsible for coherently + // managing a full restore. + // In some rare cases they can't, especially in case of deferred + // restore. In this case check whether this app should be forced to + // clear up. + // TODO: Fix this properly with manifest parameter. + boolean forceClear = shouldForceClearAppDataOnFullRestore( + mTargetApp.packageName); + if (mTargetApp.backupAgentName == null || forceClear) { if (DEBUG) { Slog.d(TAG, "Clearing app data preparatory to full restore"); @@ -381,8 +395,8 @@ public class FullRestoreEngine extends RestoreEngine { long toCopy = info.size; final boolean isSharedStorage = pkg.equals(SHARED_BACKUP_AGENT_PACKAGE); final long timeout = isSharedStorage ? - TIMEOUT_SHARED_BACKUP_INTERVAL : - TIMEOUT_RESTORE_INTERVAL; + mAgentTimeoutParameters.getSharedBackupAgentTimeoutMillis() : + mAgentTimeoutParameters.getRestoreAgentTimeoutMillis(); try { mBackupManagerService.prepareOperationTimeout(token, timeout, @@ -623,6 +637,24 @@ public class FullRestoreEngine extends RestoreEngine { return true; } + /** + * Returns whether the package is in the list of the packages for which clear app data should + * be called despite the fact that they have backup agent. + * + * <p>The list is read from {@link Settings.Secure.PACKAGES_TO_CLEAR_DATA_BEFORE_FULL_RESTORE}. + */ + private boolean shouldForceClearAppDataOnFullRestore(String packageName) { + String packageListString = Settings.Secure.getString( + mBackupManagerService.getContext().getContentResolver(), + Settings.Secure.PACKAGES_TO_CLEAR_DATA_BEFORE_FULL_RESTORE); + if (TextUtils.isEmpty(packageListString)) { + return false; + } + + List<String> packages = Arrays.asList(packageListString.split(";")); + return packages.contains(packageName); + } + void sendOnRestorePackage(String name) { if (mObserver != null) { try { diff --git a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java index dacde0b9af68..77163d348e1c 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java @@ -16,8 +16,6 @@ package com.android.server.backup.restore; -import static com.android.server.backup.BackupPasswordManager.PBKDF_CURRENT; -import static com.android.server.backup.BackupPasswordManager.PBKDF_FALLBACK; import static com.android.server.backup.BackupManagerService.BACKUP_FILE_HEADER_MAGIC; import static com.android.server.backup.BackupManagerService.BACKUP_FILE_VERSION; import static com.android.server.backup.BackupManagerService.BACKUP_MANIFEST_FILENAME; @@ -28,8 +26,8 @@ import static com.android.server.backup.BackupManagerService.OP_TYPE_RESTORE_WAI import static com.android.server.backup.BackupManagerService.SETTINGS_PACKAGE; import static com.android.server.backup.BackupManagerService.SHARED_BACKUP_AGENT_PACKAGE; import static com.android.server.backup.BackupManagerService.TAG; -import static com.android.server.backup.BackupManagerService.TIMEOUT_FULL_BACKUP_INTERVAL; -import static com.android.server.backup.BackupManagerService.TIMEOUT_RESTORE_INTERVAL; +import static com.android.server.backup.BackupPasswordManager.PBKDF_CURRENT; +import static com.android.server.backup.BackupPasswordManager.PBKDF_FALLBACK; import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT; import android.app.ApplicationThreadConstants; @@ -48,7 +46,9 @@ import android.os.RemoteException; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; import com.android.server.LocalServices; +import com.android.server.backup.BackupAgentTimeoutParameters; import com.android.server.backup.BackupManagerService; import com.android.server.backup.FileMetadata; import com.android.server.backup.KeyValueAdbRestoreEngine; @@ -101,6 +101,7 @@ public class PerformAdbRestoreTask implements Runnable { private byte[] mWidgetData = null; private long mBytes; + private final BackupAgentTimeoutParameters mAgentTimeoutParameters; // Runner that can be placed on a separate thread to do in-process invocation // of the "restore finished" API asynchronously. Used by adb restore. @@ -155,6 +156,9 @@ public class PerformAdbRestoreTask implements Runnable { mAgentPackage = null; mTargetApp = null; mObbConnection = new FullBackupObbConnection(backupManagerService); + mAgentTimeoutParameters = Preconditions.checkNotNull( + backupManagerService.getAgentTimeoutParameters(), + "Timeout parameters cannot be null"); // Which packages we've already wiped data on. We prepopulate this // with a whitelist of packages known to be unclearable. @@ -643,9 +647,11 @@ public class PerformAdbRestoreTask implements Runnable { if (okay) { boolean agentSuccess = true; long toCopy = info.size; + long restoreAgentTimeoutMillis = + mAgentTimeoutParameters.getRestoreAgentTimeoutMillis(); try { mBackupManagerService.prepareOperationTimeout( - token, TIMEOUT_RESTORE_INTERVAL, null, OP_TYPE_RESTORE_WAIT); + token, restoreAgentTimeoutMillis, null, OP_TYPE_RESTORE_WAIT); if (FullBackup.OBB_TREE_TOKEN.equals(info.domain)) { if (DEBUG) { @@ -820,10 +826,12 @@ public class PerformAdbRestoreTask implements Runnable { // In the adb restore case, we do restore-finished here if (doRestoreFinished) { final int token = mBackupManagerService.generateRandomIntegerToken(); + long fullBackupAgentTimeoutMillis = + mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis(); final AdbRestoreFinishedLatch latch = new AdbRestoreFinishedLatch( mBackupManagerService, token); mBackupManagerService.prepareOperationTimeout( - token, TIMEOUT_FULL_BACKUP_INTERVAL, latch, OP_TYPE_RESTORE_WAIT); + token, fullBackupAgentTimeoutMillis, latch, OP_TYPE_RESTORE_WAIT); if (mTargetApp.processName.equals("system")) { if (MORE_DEBUG) { Slog.d(TAG, "system agent - restoreFinished on thread"); diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java index 4b467e5a0399..12d72d8a4637 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java @@ -23,9 +23,6 @@ import static com.android.server.backup.BackupManagerService.OP_TYPE_RESTORE_WAI import static com.android.server.backup.BackupManagerService.PACKAGE_MANAGER_SENTINEL; import static com.android.server.backup.BackupManagerService.SETTINGS_PACKAGE; import static com.android.server.backup.BackupManagerService.TAG; -import static com.android.server.backup.BackupManagerService - .TIMEOUT_RESTORE_FINISHED_INTERVAL; -import static com.android.server.backup.BackupManagerService.TIMEOUT_RESTORE_INTERVAL; import static com.android.server.backup.internal.BackupHandler.MSG_BACKUP_RESTORE_STEP; import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT; import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_SESSION_TIMEOUT; @@ -56,9 +53,11 @@ import android.util.EventLog; import android.util.Slog; import com.android.internal.backup.IBackupTransport; +import com.android.internal.util.Preconditions; import com.android.server.AppWidgetBackupBridge; import com.android.server.EventLogTags; import com.android.server.LocalServices; +import com.android.server.backup.BackupAgentTimeoutParameters; import com.android.server.backup.BackupRestoreTask; import com.android.server.backup.BackupUtils; import com.android.server.backup.PackageManagerBackupAgent; @@ -160,6 +159,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { ParcelFileDescriptor mNewState; private final int mEphemeralOpToken; + private final BackupAgentTimeoutParameters mAgentTimeoutParameters; // This task can assume that the wakelock is properly held for it and doesn't have to worry // about releasing it. @@ -190,6 +190,9 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { mFinished = false; mDidLaunch = false; mListener = listener; + mAgentTimeoutParameters = Preconditions.checkNotNull( + backupManagerService.getAgentTimeoutParameters(), + "Timeout parameters cannot be null"); if (targetPackage != null) { // Single package restore @@ -760,8 +763,9 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // Kick off the restore, checking for hung agents. The timeout or // the operationComplete() callback will schedule the next step, // so we do not do that here. + long restoreAgentTimeoutMillis = mAgentTimeoutParameters.getRestoreAgentTimeoutMillis(); backupManagerService.prepareOperationTimeout( - mEphemeralOpToken, TIMEOUT_RESTORE_INTERVAL, this, OP_TYPE_RESTORE_WAIT); + mEphemeralOpToken, restoreAgentTimeoutMillis, this, OP_TYPE_RESTORE_WAIT); mAgent.doRestore(mBackupData, appVersionCode, mNewState, mEphemeralOpToken, backupManagerService.getBackupManagerBinder()); } catch (Exception e) { @@ -813,9 +817,11 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { Slog.d(TAG, "restoreFinished packageName=" + mCurrentPackage.packageName); } try { + long restoreAgentFinishedTimeoutMillis = + mAgentTimeoutParameters.getRestoreAgentFinishedTimeoutMillis(); backupManagerService .prepareOperationTimeout(mEphemeralOpToken, - TIMEOUT_RESTORE_FINISHED_INTERVAL, this, + restoreAgentFinishedTimeoutMillis, this, OP_TYPE_RESTORE_WAIT); mAgent.doRestoreFinished(mEphemeralOpToken, backupManagerService.getBackupManagerBinder()); @@ -1109,9 +1115,10 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { } else { // We were invoked via an active restore session, not by the Package // Manager, so start up the session timeout again. + long restoreAgentTimeoutMillis = mAgentTimeoutParameters.getRestoreAgentTimeoutMillis(); backupManagerService.getBackupHandler().sendEmptyMessageDelayed( MSG_RESTORE_SESSION_TIMEOUT, - TIMEOUT_RESTORE_INTERVAL); + restoreAgentTimeoutMillis); } // Kick off any work that may be needed regarding app widget restores diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index f6ff359d8c19..40f947607b1e 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -266,9 +266,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("LOCK") private boolean mValue; - public DebugFlag(String key) { + public DebugFlag(String key, boolean defaultValue) { mKey = key; - refresh(); + mValue = SystemProperties.getBoolean(key, defaultValue); } void refresh() { @@ -290,7 +290,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ private static final class DebugFlags { static final DebugFlag FLAG_OPTIMIZE_START_INPUT = - new DebugFlag("debug.optimize_startinput"); + new DebugFlag("debug.optimize_startinput", false); } diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index d09a161d1ef4..bde6bd8db6fd 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -931,7 +931,7 @@ public class IpSecService extends IIpSecService.Stub { return mPort; } - public FileDescriptor getSocket() { + public FileDescriptor getFileDescriptor() { return mSocket; } @@ -1065,7 +1065,10 @@ public class IpSecService extends IIpSecService.Stub { public synchronized IpSecSpiResponse allocateSecurityParameterIndex( String destinationAddress, int requestedSpi, IBinder binder) throws RemoteException { checkInetAddress(destinationAddress); - /* requestedSpi can be anything in the int range, so no check is needed. */ + // RFC 4303 Section 2.1 - 0=local, 1-255=reserved. + if (requestedSpi > 0 && requestedSpi < 256) { + throw new IllegalArgumentException("ESP SPI must not be in the range of 0-255."); + } checkNotNull(binder, "Null Binder passed to allocateSecurityParameterIndex"); UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e3584e639abc..76d26a8a2d92 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2873,13 +2873,15 @@ public class ActivityManagerService extends IActivityManager.Stub } /** - * Encapsulates the globla setting "hidden_api_blacklist_exemptions", including tracking the + * Encapsulates the global setting "hidden_api_blacklist_exemptions", including tracking the * latest value via a content observer. */ static class HiddenApiBlacklist extends ContentObserver { private final Context mContext; private boolean mBlacklistDisabled; + private String mExemptionsStr; + private List<String> mExemptions = Collections.emptyList(); public HiddenApiBlacklist(Handler handler, Context context) { super(handler); @@ -2895,8 +2897,22 @@ public class ActivityManagerService extends IActivityManager.Stub } private void update() { - mBlacklistDisabled = "*".equals(Settings.Global.getString(mContext.getContentResolver(), - Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS)); + String exemptions = Settings.Global.getString(mContext.getContentResolver(), + Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS); + if (!TextUtils.equals(exemptions, mExemptionsStr)) { + mExemptionsStr = exemptions; + if ("*".equals(exemptions)) { + mBlacklistDisabled = true; + mExemptions = Collections.emptyList(); + } else { + mBlacklistDisabled = false; + mExemptions = TextUtils.isEmpty(exemptions) + ? Collections.emptyList() + : Arrays.asList(exemptions.split(",")); + } + zygoteProcess.setApiBlacklistExemptions(mExemptions); + } + } boolean isDisabled() { @@ -4477,8 +4493,8 @@ public class ActivityManagerService extends IActivityManager.Stub StatsLog.write(StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED, component.app.uid, component.realActivity.getPackageName(), component.realActivity.getShortClassName(), resumed ? - StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED__ACTIVITY__MOVE_TO_FOREGROUND : - StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED__ACTIVITY__MOVE_TO_BACKGROUND); + StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED__STATE__FOREGROUND : + StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED__STATE__BACKGROUND); if (resumed) { if (mUsageStatsService != null) { mUsageStatsService.reportEvent(component.realActivity, component.userId, @@ -13163,10 +13179,6 @@ public class ActivityManagerService extends IActivityManager.Stub + android.Manifest.permission.SHUTDOWN); } - // TODO: Where should the corresponding '1' (start) write go? - StatsLog.write(StatsLog.DEVICE_ON_STATUS_CHANGED, - StatsLog.DEVICE_ON_STATUS_CHANGED__STATE__OFF); - boolean timedout = false; synchronized(this) { @@ -14814,7 +14826,7 @@ public class ActivityManagerService extends IActivityManager.Stub .setPackage("android") .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); broadcastIntent(null, intent, null, null, 0, null, null, null, - OP_NONE, null, true, false, UserHandle.USER_ALL); + OP_NONE, null, false, false, UserHandle.USER_ALL); } finally { Binder.restoreCallingIdentity(ident); } @@ -15181,6 +15193,12 @@ public class ActivityManagerService extends IActivityManager.Stub crashInfo.throwFileName, crashInfo.throwLineNumber); + StatsLog.write(StatsLog.APP_CRASH_OCCURRED, + Binder.getCallingUid(), + eventType, + processName, + Binder.getCallingPid()); + addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo); mAppErrors.crashApplication(r, crashInfo); @@ -15351,6 +15369,9 @@ public class ActivityManagerService extends IActivityManager.Stub EventLog.writeEvent(EventLogTags.AM_WTF, UserHandle.getUserId(callingUid), callingPid, processName, r == null ? -1 : r.info.flags, tag, crashInfo.exceptionMessage); + StatsLog.write(StatsLog.WTF_OCCURRED, callingUid, tag, processName, + callingPid); + addErrorToDropBox("wtf", r, processName, null, null, tag, null, null, crashInfo); return r; @@ -15472,19 +15493,6 @@ public class ActivityManagerService extends IActivityManager.Stub final String dropboxTag = processClass(process) + "_" + eventType; if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return; - // Log to StatsLog before the rate-limiting. - // The logging below is adapated from appendDropboxProcessHeaders. - StatsLog.write(StatsLog.DROPBOX_ERROR_CHANGED, - process != null ? process.uid : -1, - dropboxTag, - processName, - process != null ? process.pid : -1, - (process != null && process.info != null) ? - (process.info.isInstantApp() ? 1 : 0) : -1, - activity != null ? activity.shortComponentName : null, - activity != null ? activity.packageName : null, - process != null ? (process.isInterestingToUserLocked() ? 1 : 0) : -1); - // Rate-limit how often we're willing to do the heavy lifting below to // collect and record logs; currently 5 logs per 10 second period. final long now = SystemClock.elapsedRealtime(); @@ -19764,6 +19772,7 @@ public class ActivityManagerService extends IActivityManager.Stub catPw.flush(); } dropBuilder.append(catSw.toString()); + StatsLog.write(StatsLog.LOW_MEM_REPORTED); addErrorToDropBox("lowmem", null, "system_server", null, null, tag.toString(), dropBuilder.toString(), null, null); //Slog.i(TAG, "Sent to dropbox:"); @@ -20920,8 +20929,16 @@ public class ActivityManagerService extends IActivityManager.Stub // explicitly list each action as a protected broadcast, so we will check for that // one safe case and allow it: an explicit broadcast, only being received by something // that has protected itself. - if (receivers != null && receivers.size() > 0 - && (intent.getPackage() != null || intent.getComponent() != null)) { + if (intent.getPackage() != null || intent.getComponent() != null) { + if (receivers == null || receivers.size() == 0) { + // Intent is explicit and there's no receivers. + // This happens, e.g. , when a system component sends a broadcast to + // its own runtime receiver, and there's no manifest receivers for it, + // because this method is called twice for each broadcast, + // for runtime receivers and manifest receivers and the later check would find + // no receivers. + return; + } boolean allProtected = true; for (int i = receivers.size()-1; i >= 0; i--) { Object target = receivers.get(i); diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java index 724dd3fd9847..47d0423550c4 100644 --- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java @@ -489,7 +489,7 @@ class ActivityMetricsLogger { builder.addTaggedData(FIELD_CLASS_NAME, info.launchedActivity.info.name); mMetricsLogger.write(builder); StatsLog.write( - StatsLog.APP_START_CANCEL_CHANGED, + StatsLog.APP_START_CANCELED, info.launchedActivity.appInfo.uid, info.launchedActivity.packageName, convertAppStartTransitionType(type), @@ -561,7 +561,7 @@ class ActivityMetricsLogger { packageOptimizationInfo.getCompilationFilter()); mMetricsLogger.write(builder); StatsLog.write( - StatsLog.APP_START_CHANGED, + StatsLog.APP_START_OCCURRED, info.applicationInfo.uid, info.packageName, convertAppStartTransitionType(info.type), @@ -582,15 +582,15 @@ class ActivityMetricsLogger { private int convertAppStartTransitionType(int tronType) { if (tronType == TYPE_TRANSITION_COLD_LAUNCH) { - return StatsLog.APP_START_CHANGED__TYPE__COLD; + return StatsLog.APP_START_OCCURRED__TYPE__COLD; } if (tronType == TYPE_TRANSITION_WARM_LAUNCH) { - return StatsLog.APP_START_CHANGED__TYPE__WARM; + return StatsLog.APP_START_OCCURRED__TYPE__WARM; } if (tronType == TYPE_TRANSITION_HOT_LAUNCH) { - return StatsLog.APP_START_CHANGED__TYPE__HOT; + return StatsLog.APP_START_OCCURRED__TYPE__HOT; } - return StatsLog.APP_START_CHANGED__TYPE__APP_START_TRANSITION_TYPE_UNKNOWN; + return StatsLog.APP_START_OCCURRED__TYPE__UNKNOWN; } void logAppTransitionReportedDrawn(ActivityRecord r, boolean restoredFromBundle) { @@ -611,12 +611,12 @@ class ActivityMetricsLogger { info.currentTransitionProcessRunning ? 1 : 0); mMetricsLogger.write(builder); StatsLog.write( - StatsLog.APP_START_FULLY_DRAWN_CHANGED, + StatsLog.APP_START_FULLY_DRAWN, info.launchedActivity.appInfo.uid, info.launchedActivity.packageName, restoredFromBundle - ? StatsLog.APP_START_FULLY_DRAWN_CHANGED__TYPE__WITH_BUNDLE - : StatsLog.APP_START_FULLY_DRAWN_CHANGED__TYPE__WITHOUT_BUNDLE, + ? StatsLog.APP_START_FULLY_DRAWN__TYPE__WITH_BUNDLE + : StatsLog.APP_START_FULLY_DRAWN__TYPE__WITHOUT_BUNDLE, info.launchedActivity.info.name, info.currentTransitionProcessRunning, startupTimeMs); diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index b2872e4d7f5a..7ee20fa2501c 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -47,6 +47,7 @@ import android.util.ArraySet; import android.util.EventLog; import android.util.Log; import android.util.Slog; +import android.util.StatsLog; import android.util.SparseArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -1039,6 +1040,8 @@ class AppErrors { Process.sendSignal(app.pid, Process.SIGNAL_QUIT); } + StatsLog.write(StatsLog.ANR_OCCURRED, app.uid, app.processName, + activity == null ? "unknown": activity.shortComponentName, annotation); mService.addErrorToDropBox("anr", app, app.processName, activity, parent, annotation, cpuInfo, tracesFile, null); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index b338029d4e86..8ecd93e65b08 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -337,7 +337,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub synchronized (mStats) { mStats.noteProcessStartLocked(name, uid); StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, - StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED__EVENT__PROCESS_STARTED); + StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED__STATE__STARTED); } } @@ -345,15 +345,13 @@ public final class BatteryStatsService extends IBatteryStats.Stub synchronized (mStats) { mStats.noteProcessCrashLocked(name, uid); StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, - StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED__EVENT__PROCESS_CRASHED); + StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED__STATE__CRASHED); } } void noteProcessAnr(String name, int uid) { synchronized (mStats) { mStats.noteProcessAnrLocked(name, uid); - StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, - StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED__EVENT__PROCESS_ANRED); } } @@ -361,7 +359,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub synchronized (mStats) { mStats.noteProcessFinishLocked(name, uid); StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, - StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED__EVENT__PROCESS_FINISHED); + StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED__STATE__FINISHED); } } @@ -768,8 +766,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub enforceCallingPermission(); synchronized (mStats) { mStats.noteVideoOnLocked(uid); - StatsLog.write_non_chained(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, uid, null, - StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED__STATE__ON); + StatsLog.write_non_chained(StatsLog.MEDIA_CODEC_STATE_CHANGED, uid, null, + StatsLog.MEDIA_CODEC_STATE_CHANGED__STATE__ON); } } @@ -777,8 +775,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub enforceCallingPermission(); synchronized (mStats) { mStats.noteVideoOffLocked(uid); - StatsLog.write_non_chained(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, uid, - null, StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED__STATE__OFF); + StatsLog.write_non_chained(StatsLog.MEDIA_CODEC_STATE_CHANGED, uid, + null, StatsLog.MEDIA_CODEC_STATE_CHANGED__STATE__OFF); } } @@ -795,8 +793,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub enforceCallingPermission(); synchronized (mStats) { mStats.noteResetVideoLocked(); - StatsLog.write_non_chained(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, -1, null, - StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED__STATE__RESET); + StatsLog.write_non_chained(StatsLog.MEDIA_CODEC_STATE_CHANGED, -1, null, + StatsLog.MEDIA_CODEC_STATE_CHANGED__STATE__RESET); } } diff --git a/services/core/java/com/android/server/am/LockTaskController.java b/services/core/java/com/android/server/am/LockTaskController.java index 106b37f819c3..bef650b90f44 100644 --- a/services/core/java/com/android/server/am/LockTaskController.java +++ b/services/core/java/com/android/server/am/LockTaskController.java @@ -285,6 +285,16 @@ public class LockTaskController { return false; } + /** + * @return the root task of the lock task. + */ + TaskRecord getRootTask() { + if (mLockTaskModeTasks.isEmpty()) { + return null; + } + return mLockTaskModeTasks.get(0); + } + private boolean isLockTaskModeViolationInternal(TaskRecord task, boolean isNewClearTask) { // TODO: Double check what's going on here. If the task is already in lock task mode, it's // likely whitelisted, so will return false below. diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java index fcf00ce79ea9..1d305fb4248f 100644 --- a/services/core/java/com/android/server/am/RecentTasks.java +++ b/services/core/java/com/android/server/am/RecentTasks.java @@ -1155,6 +1155,11 @@ class RecentTasks { } } + // If we're in lock task mode, ignore the root task + if (task == mService.mLockTaskController.getRootTask()) { + return false; + } + return true; } diff --git a/services/core/java/com/android/server/am/RecentsAnimation.java b/services/core/java/com/android/server/am/RecentsAnimation.java index 73a7c3eb32a5..9df321c64c9a 100644 --- a/services/core/java/com/android/server/am/RecentsAnimation.java +++ b/services/core/java/com/android/server/am/RecentsAnimation.java @@ -42,18 +42,13 @@ import com.android.server.wm.WindowManagerService; class RecentsAnimation implements RecentsAnimationCallbacks { private static final String TAG = RecentsAnimation.class.getSimpleName(); - private static final int RECENTS_ANIMATION_TIMEOUT = 10 * 1000; - private final ActivityManagerService mService; private final ActivityStackSupervisor mStackSupervisor; private final ActivityStartController mActivityStartController; private final WindowManagerService mWindowManager; private final UserController mUserController; - private final Handler mHandler; private final int mCallingPid; - private final Runnable mCancelAnimationRunnable; - // The stack to restore the home stack behind when the animation is finished private ActivityStack mRestoreHomeBehindStack; @@ -63,16 +58,9 @@ class RecentsAnimation implements RecentsAnimationCallbacks { mService = am; mStackSupervisor = stackSupervisor; mActivityStartController = activityStartController; - mHandler = new Handler(mStackSupervisor.mLooper); mWindowManager = wm; mUserController = userController; mCallingPid = callingPid; - - mCancelAnimationRunnable = () -> { - // The caller has not finished the animation in a predefined amount of time, so - // force-cancel the animation - mWindowManager.cancelRecentsAnimation(); - }; } void startRecentsActivity(Intent intent, IRecentsAnimationRunner recentsAnimationRunner, @@ -133,10 +121,6 @@ class RecentsAnimation implements RecentsAnimationCallbacks { // duration of the gesture that is driven by the recents component homeActivity.mLaunchTaskBehind = true; - // Post a timeout for the animation. This needs to happen before initializing the - // recents animation on the WM side since we may decide to cancel the animation there - mHandler.postDelayed(mCancelAnimationRunnable, RECENTS_ANIMATION_TIMEOUT); - // Fetch all the surface controls and pass them to the client to get the animation // started mWindowManager.cancelRecentsAnimation(); @@ -157,7 +141,6 @@ class RecentsAnimation implements RecentsAnimationCallbacks { @Override public void onAnimationFinished(boolean moveHomeToTop) { - mHandler.removeCallbacks(mCancelAnimationRunnable); synchronized (mService) { if (mWindowManager.getRecentsAnimationController() == null) return; diff --git a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java index 32b1d1adfe3a..7a6d9647bf43 100644 --- a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java +++ b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java @@ -17,6 +17,7 @@ package com.android.server.broadcastradio.hal1; import android.annotation.NonNull; +import android.annotation.Nullable; import android.hardware.radio.ITuner; import android.hardware.radio.ITunerCallback; import android.hardware.radio.ProgramList; @@ -87,8 +88,9 @@ class TunerCallback implements ITunerCallback { mTuner.close(); } - void startProgramListUpdates(@NonNull ProgramList.Filter filter) { - mProgramListFilter.set(Objects.requireNonNull(filter)); + void startProgramListUpdates(@Nullable ProgramList.Filter filter) { + if (filter == null) filter = new ProgramList.Filter(); + mProgramListFilter.set(filter); sendProgramListUpdate(); } diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java index 6919282054ed..9730c9a1a380 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java @@ -232,6 +232,7 @@ class Convert { * HAL implementation instance. */ 1, // numTuners 1, // numAudioSources + false, // isInitializationRequired false, // isCaptureSupported amfmConfigToBands(amfmConfig), @@ -374,7 +375,9 @@ class Convert { ); } - static @NonNull ProgramFilter programFilterToHal(@NonNull ProgramList.Filter filter) { + static @NonNull ProgramFilter programFilterToHal(@Nullable ProgramList.Filter filter) { + if (filter == null) filter = new ProgramList.Filter(); + ProgramFilter hwFilter = new ProgramFilter(); filter.getIdentifierTypes().stream().forEachOrdered(hwFilter.identifierTypes::add); diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index eee830f3299b..1bc74238436f 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -56,6 +56,7 @@ import android.net.NetworkInfo; import android.net.NetworkState; import android.net.NetworkUtils; import android.net.RouteInfo; +import android.net.util.InterfaceSet; import android.net.util.PrefixUtils; import android.net.util.SharedLog; import android.net.util.VersionedBroadcastListener; @@ -98,6 +99,7 @@ import com.android.server.connectivity.tethering.SimChangeListener; import com.android.server.connectivity.tethering.TetherInterfaceStateMachine; import com.android.server.connectivity.tethering.TetheringConfiguration; import com.android.server.connectivity.tethering.TetheringDependencies; +import com.android.server.connectivity.tethering.TetheringInterfaceUtils; import com.android.server.connectivity.tethering.UpstreamNetworkMonitor; import com.android.server.net.BaseNetworkObserver; @@ -181,9 +183,10 @@ public class Tethering extends BaseNetworkObserver { private final VersionedBroadcastListener mCarrierConfigChange; // TODO: Delete SimChangeListener; it's obsolete. private final SimChangeListener mSimChange; + private final TetheringDependencies mDeps; private volatile TetheringConfiguration mConfig; - private String mCurrentUpstreamIface; + private InterfaceSet mCurrentUpstreamIfaceSet; private Notification.Builder mTetheredNotificationBuilder; private int mLastNotificationId; @@ -202,12 +205,13 @@ public class Tethering extends BaseNetworkObserver { mPolicyManager = policyManager; mLooper = looper; mSystemProperties = systemProperties; + mDeps = deps; mPublicSync = new Object(); mTetherStates = new ArrayMap<>(); - mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper); + mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper, deps); mTetherMasterSM.start(); final Handler smHandler = mTetherMasterSM.getHandler(); @@ -215,8 +219,8 @@ public class Tethering extends BaseNetworkObserver { deps.getOffloadHardwareInterface(smHandler, mLog), mContext.getContentResolver(), mNMService, mLog); - mUpstreamNetworkMonitor = new UpstreamNetworkMonitor( - mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK); + mUpstreamNetworkMonitor = deps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog, + TetherMasterSM.EVENT_UPSTREAM_CALLBACK); mForwardedDownstreams = new HashSet<>(); IntentFilter filter = new IntentFilter(); @@ -1160,12 +1164,11 @@ public class Tethering extends BaseNetworkObserver { } // Needed because the canonical source of upstream truth is just the - // upstream interface name, |mCurrentUpstreamIface|. This is ripe for - // future simplification, once the upstream Network is canonical. + // upstream interface set, |mCurrentUpstreamIfaceSet|. private boolean pertainsToCurrentUpstream(NetworkState ns) { - if (ns != null && ns.linkProperties != null && mCurrentUpstreamIface != null) { + if (ns != null && ns.linkProperties != null && mCurrentUpstreamIfaceSet != null) { for (String ifname : ns.linkProperties.getAllInterfaceNames()) { - if (mCurrentUpstreamIface.equals(ifname)) { + if (mCurrentUpstreamIfaceSet.ifnames.contains(ifname)) { return true; } } @@ -1241,7 +1244,7 @@ public class Tethering extends BaseNetworkObserver { private static final int UPSTREAM_SETTLE_TIME_MS = 10000; - TetherMasterSM(String name, Looper looper) { + TetherMasterSM(String name, Looper looper, TetheringDependencies deps) { super(name, looper); mInitialState = new InitialState(); @@ -1261,7 +1264,7 @@ public class Tethering extends BaseNetworkObserver { addState(mSetDnsForwardersErrorState); mNotifyList = new ArrayList<>(); - mIPv6TetheringCoordinator = new IPv6TetheringCoordinator(mNotifyList, mLog); + mIPv6TetheringCoordinator = deps.getIPv6TetheringCoordinator(mNotifyList, mLog); mOffload = new OffloadWrapper(); setInitialState(mInitialState); @@ -1360,31 +1363,27 @@ public class Tethering extends BaseNetworkObserver { } protected void setUpstreamNetwork(NetworkState ns) { - String iface = null; + InterfaceSet ifaces = null; if (ns != null) { // Find the interface with the default IPv4 route. It may be the // interface described by linkProperties, or one of the interfaces // stacked on top of it. mLog.i("Looking for default routes on: " + ns.linkProperties); - final String iface4 = getIPv4DefaultRouteInterface(ns); - final String iface6 = getIPv6DefaultRouteInterface(ns); - mLog.i("IPv4/IPv6 upstream interface(s): " + iface4 + "/" + iface6); - - iface = (iface4 != null) ? iface4 : null /* TODO: iface6 */; + ifaces = TetheringInterfaceUtils.getTetheringInterfaces(ns); + mLog.i("Found upstream interface(s): " + ifaces); } - if (iface != null) { + if (ifaces != null) { setDnsForwarders(ns.network, ns.linkProperties); } - notifyDownstreamsOfNewUpstreamIface(iface); + notifyDownstreamsOfNewUpstreamIface(ifaces); if (ns != null && pertainsToCurrentUpstream(ns)) { // If we already have NetworkState for this network examine // it immediately, because there likely will be no second // EVENT_ON_AVAILABLE (it was already received). handleNewUpstreamNetworkState(ns); - } else if (mCurrentUpstreamIface == null) { - // There are no available upstream networks, or none that - // have an IPv4 default route (current metric for success). + } else if (mCurrentUpstreamIfaceSet == null) { + // There are no available upstream networks. handleNewUpstreamNetworkState(null); } } @@ -1411,12 +1410,10 @@ public class Tethering extends BaseNetworkObserver { } } - protected void notifyDownstreamsOfNewUpstreamIface(String ifaceName) { - mLog.log("Notifying downstreams of upstream=" + ifaceName); - mCurrentUpstreamIface = ifaceName; + protected void notifyDownstreamsOfNewUpstreamIface(InterfaceSet ifaces) { + mCurrentUpstreamIfaceSet = ifaces; for (TetherInterfaceStateMachine sm : mNotifyList) { - sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED, - ifaceName); + sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED, ifaces); } } @@ -1488,7 +1485,7 @@ public class Tethering extends BaseNetworkObserver { // For example, after CONNECTIVITY_ACTION listening is removed, here // is where we could observe a Wi-Fi network becoming available and // passing validation. - if (mCurrentUpstreamIface == null) { + if (mCurrentUpstreamIfaceSet == null) { // If we have no upstream interface, try to run through upstream // selection again. If, for example, IPv4 connectivity has shown up // after IPv6 (e.g., 464xlat became available) we want the chance to @@ -1512,8 +1509,7 @@ public class Tethering extends BaseNetworkObserver { handleNewUpstreamNetworkState(ns); break; case UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES: - setDnsForwarders(ns.network, ns.linkProperties); - handleNewUpstreamNetworkState(ns); + chooseUpstreamType(false); break; case UpstreamNetworkMonitor.EVENT_ON_LOST: // TODO: Re-evaluate possible upstreams. Currently upstream @@ -1586,7 +1582,7 @@ public class Tethering extends BaseNetworkObserver { if (VDBG) Log.d(TAG, "Tether Mode requested by " + who); handleInterfaceServingStateActive(message.arg1, who); who.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED, - mCurrentUpstreamIface); + mCurrentUpstreamIfaceSet); // If there has been a change and an upstream is now // desired, kick off the selection process. final boolean previousUpstreamWanted = updateUpstreamWanted(); @@ -1864,7 +1860,7 @@ public class Tethering extends BaseNetworkObserver { pw.println(" - lastError = " + tetherState.lastError); } pw.println("Upstream wanted: " + upstreamWanted()); - pw.println("Current upstream interface: " + mCurrentUpstreamIface); + pw.println("Current upstream interface(s): " + mCurrentUpstreamIfaceSet); pw.decreaseIndent(); } @@ -1997,7 +1993,7 @@ public class Tethering extends BaseNetworkObserver { final TetherState tetherState = new TetherState( new TetherInterfaceStateMachine( iface, mLooper, interfaceType, mLog, mNMService, mStatsService, - makeControlCallback(iface))); + makeControlCallback(iface), mDeps)); mTetherStates.put(iface, tetherState); tetherState.stateMachine.start(); } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index c9bdcf1a73b5..2fda08e2a575 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -151,6 +151,13 @@ public class Vpn { .multiply(BigInteger.valueOf(howManyPercentIsMost)) .divide(BigInteger.valueOf(100)); } + // How many routes to evaluate before bailing and declaring this Vpn should provide + // the INTERNET capability. This is necessary because computing the adress space is + // O(n²) and this is running in the system service, so a limit is needed to alleviate + // the risk of attack. + // This is taken as a total of IPv4 + IPV6 routes for simplicity, but the algorithm + // is actually O(n²)+O(n²). + private static final int MAX_ROUTES_TO_EVALUATE = 150; // TODO: create separate trackers for each unique VPN to support // automated reconnection @@ -862,10 +869,12 @@ public class Vpn { */ @VisibleForTesting static boolean providesRoutesToMostDestinations(LinkProperties lp) { + final List<RouteInfo> routes = lp.getAllRoutes(); + if (routes.size() > MAX_ROUTES_TO_EVALUATE) return true; final Comparator<IpPrefix> prefixLengthComparator = IpPrefix.lengthComparator(); TreeSet<IpPrefix> ipv4Prefixes = new TreeSet<>(prefixLengthComparator); TreeSet<IpPrefix> ipv6Prefixes = new TreeSet<>(prefixLengthComparator); - for (final RouteInfo route : lp.getAllRoutes()) { + for (final RouteInfo route : routes) { IpPrefix destination = route.getDestination(); if (destination.isIPv4()) { ipv4Prefixes.add(destination); diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java index 518f6c1440cc..ba67c94d1cd4 100644 --- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java +++ b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java @@ -30,10 +30,8 @@ import android.util.Log; import java.net.Inet6Address; import java.net.InetAddress; -import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.LinkedList; import java.util.Random; @@ -119,7 +117,7 @@ public class IPv6TetheringCoordinator { if (VDBG) { Log.d(TAG, "updateUpstreamNetworkState: " + toDebugString(ns)); } - if (!canTetherIPv6(ns, mLog)) { + if (TetheringInterfaceUtils.getIPv6Interface(ns) == null) { stopIPv6TetheringOnAllInterfaces(); setUpstreamNetworkState(null); return; @@ -208,70 +206,6 @@ public class IPv6TetheringCoordinator { return null; } - private static boolean canTetherIPv6(NetworkState ns, SharedLog sharedLog) { - // Broadly speaking: - // - // [1] does the upstream have an IPv6 default route? - // - // and - // - // [2] does the upstream have one or more global IPv6 /64s - // dedicated to this device? - // - // In lieu of Prefix Delegation and other evaluation of whether a - // prefix may or may not be dedicated to this device, for now just - // check whether the upstream is TRANSPORT_CELLULAR. This works - // because "[t]he 3GPP network allocates each default bearer a unique - // /64 prefix", per RFC 6459, Section 5.2. - - final boolean canTether = - (ns != null) && (ns.network != null) && - (ns.linkProperties != null) && (ns.networkCapabilities != null) && - // At least one upstream DNS server: - ns.linkProperties.isProvisioned() && - // Minimal amount of IPv6 provisioning: - ns.linkProperties.hasIPv6DefaultRoute() && - ns.linkProperties.hasGlobalIPv6Address() && - // Temporary approximation of "dedicated prefix": - ns.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR); - - // For now, we do not support separate IPv4 and IPv6 upstreams (e.g. - // tethering with 464xlat involved). TODO: Rectify this shortcoming, - // likely by calling NetworkManagementService#startInterfaceForwarding() - // for all upstream interfaces. - RouteInfo v4default = null; - RouteInfo v6default = null; - if (canTether) { - for (RouteInfo r : ns.linkProperties.getAllRoutes()) { - if (r.isIPv4Default()) { - v4default = r; - } else if (r.isIPv6Default()) { - v6default = r; - } - - if (v4default != null && v6default != null) { - break; - } - } - } - - final boolean supportedConfiguration = - (v4default != null) && (v6default != null) && - (v4default.getInterface() != null) && - v4default.getInterface().equals(v6default.getInterface()); - - final boolean outcome = canTether && supportedConfiguration; - - if (ns == null) { - sharedLog.log("No available upstream."); - } else { - sharedLog.log(String.format("IPv6 tethering is %s for upstream: %s", - (outcome ? "available" : "not available"), toDebugString(ns))); - } - - return outcome; - } - private static LinkProperties getIPv6OnlyLinkProperties(LinkProperties lp) { final LinkProperties v6only = new LinkProperties(); if (lp == null) { diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java index 2224913b2cf6..5ed14a07cb6c 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java @@ -31,7 +31,7 @@ import android.net.ip.InterfaceController; import android.net.ip.RouterAdvertisementDaemon; import android.net.ip.RouterAdvertisementDaemon.RaParams; import android.net.util.InterfaceParams; -import android.net.util.NetdService; +import android.net.util.InterfaceSet; import android.net.util.SharedLog; import android.os.INetworkManagementService; import android.os.Looper; @@ -49,12 +49,12 @@ import com.android.internal.util.StateMachine; import java.net.Inet6Address; import java.net.InetAddress; -import java.net.SocketException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashSet; import java.util.Objects; import java.util.Random; +import java.util.Set; /** * Provides the interface to IP-layer serving functionality for a given network @@ -117,9 +117,11 @@ public class TetherInterfaceStateMachine extends StateMachine { private final int mInterfaceType; private final LinkProperties mLinkProperties; + private final TetheringDependencies mDeps; + private int mLastError; private int mServingMode; - private String mMyUpstreamIfaceName; // may change over time + private InterfaceSet mUpstreamIfaceSet; // may change over time private InterfaceParams mInterfaceParams; // TODO: De-duplicate this with mLinkProperties above. Currently, these link // properties are those selected by the IPv6TetheringCoordinator and relayed @@ -134,18 +136,19 @@ public class TetherInterfaceStateMachine extends StateMachine { public TetherInterfaceStateMachine( String ifaceName, Looper looper, int interfaceType, SharedLog log, INetworkManagementService nMService, INetworkStatsService statsService, - IControlsTethering tetherController) { + IControlsTethering tetherController, + TetheringDependencies deps) { super(ifaceName, looper); mLog = log.forSubComponent(ifaceName); mNMService = nMService; - // TODO: This should be passed in for testability. - mNetd = NetdService.getInstance(); + mNetd = deps.getNetdService(); mStatsService = statsService; mTetherController = tetherController; mInterfaceCtrl = new InterfaceController(ifaceName, nMService, mNetd, mLog); mIfaceName = ifaceName; mInterfaceType = interfaceType; mLinkProperties = new LinkProperties(); + mDeps = deps; resetLinkProperties(); mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR; mServingMode = IControlsTethering.STATE_AVAILABLE; @@ -246,16 +249,14 @@ public class TetherInterfaceStateMachine extends StateMachine { } private boolean startIPv6() { - // TODO: Refactor for better testability. This is one of the things - // that prohibits unittesting IPv6 tethering setup. - mInterfaceParams = InterfaceParams.getByName(mIfaceName); + mInterfaceParams = mDeps.getInterfaceParams(mIfaceName); if (mInterfaceParams == null) { mLog.e("Failed to find InterfaceParams"); stopIPv6(); return false; } - mRaDaemon = new RouterAdvertisementDaemon(mInterfaceParams); + mRaDaemon = mDeps.getRouterAdvertisementDaemon(mInterfaceParams); if (!mRaDaemon.start()) { stopIPv6(); return false; @@ -621,10 +622,10 @@ public class TetherInterfaceStateMachine extends StateMachine { } private void cleanupUpstream() { - if (mMyUpstreamIfaceName == null) return; + if (mUpstreamIfaceSet == null) return; - cleanupUpstreamInterface(mMyUpstreamIfaceName); - mMyUpstreamIfaceName = null; + for (String ifname : mUpstreamIfaceSet.ifnames) cleanupUpstreamInterface(ifname); + mUpstreamIfaceSet = null; } private void cleanupUpstreamInterface(String upstreamIface) { @@ -660,34 +661,66 @@ public class TetherInterfaceStateMachine extends StateMachine { mLog.e("CMD_TETHER_REQUESTED while already tethering."); break; case CMD_TETHER_CONNECTION_CHANGED: - String newUpstreamIfaceName = (String)(message.obj); - if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) || - (mMyUpstreamIfaceName != null && - mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) { + final InterfaceSet newUpstreamIfaceSet = (InterfaceSet) message.obj; + if (noChangeInUpstreamIfaceSet(newUpstreamIfaceSet)) { if (VDBG) Log.d(TAG, "Connection changed noop - dropping"); break; } - cleanupUpstream(); - if (newUpstreamIfaceName != null) { + + if (newUpstreamIfaceSet == null) { + cleanupUpstream(); + break; + } + + for (String removed : upstreamInterfacesRemoved(newUpstreamIfaceSet)) { + cleanupUpstreamInterface(removed); + } + + final Set<String> added = upstreamInterfacesAdd(newUpstreamIfaceSet); + // This makes the call to cleanupUpstream() in the error + // path for any interface neatly cleanup all the interfaces. + mUpstreamIfaceSet = newUpstreamIfaceSet; + + for (String ifname : added) { try { - mNMService.enableNat(mIfaceName, newUpstreamIfaceName); - mNMService.startInterfaceForwarding(mIfaceName, - newUpstreamIfaceName); + mNMService.enableNat(mIfaceName, ifname); + mNMService.startInterfaceForwarding(mIfaceName, ifname); } catch (Exception e) { mLog.e("Exception enabling NAT: " + e); - cleanupUpstreamInterface(newUpstreamIfaceName); + cleanupUpstream(); mLastError = ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR; transitionTo(mInitialState); return true; } } - mMyUpstreamIfaceName = newUpstreamIfaceName; break; default: return false; } return true; } + + private boolean noChangeInUpstreamIfaceSet(InterfaceSet newIfaces) { + if (mUpstreamIfaceSet == null && newIfaces == null) return true; + if (mUpstreamIfaceSet != null && newIfaces != null) { + return mUpstreamIfaceSet.equals(newIfaces); + } + return false; + } + + private Set<String> upstreamInterfacesRemoved(InterfaceSet newIfaces) { + if (mUpstreamIfaceSet == null) return new HashSet<>(); + + final HashSet<String> removed = new HashSet<>(mUpstreamIfaceSet.ifnames); + removed.removeAll(newIfaces.ifnames); + return removed; + } + + private Set<String> upstreamInterfacesAdd(InterfaceSet newIfaces) { + final HashSet<String> added = new HashSet<>(newIfaces.ifnames); + if (mUpstreamIfaceSet != null) added.removeAll(mUpstreamIfaceSet.ifnames); + return added; + } } /** diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java index b8174b6c8d2b..66afb0f2cb14 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java @@ -16,9 +16,18 @@ package com.android.server.connectivity.tethering; +import android.content.Context; +import android.net.INetd; +import android.net.ip.RouterAdvertisementDaemon; +import android.net.util.InterfaceParams; +import android.net.util.NetdService; import android.os.Handler; import android.net.util.SharedLog; +import com.android.internal.util.StateMachine; + +import java.util.ArrayList; + /** * Capture tethering dependencies, for injection. @@ -29,4 +38,26 @@ public class TetheringDependencies { public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) { return new OffloadHardwareInterface(h, log); } + + public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, StateMachine target, + SharedLog log, int what) { + return new UpstreamNetworkMonitor(ctx, target, log, what); + } + + public IPv6TetheringCoordinator getIPv6TetheringCoordinator( + ArrayList<TetherInterfaceStateMachine> notifyList, SharedLog log) { + return new IPv6TetheringCoordinator(notifyList, log); + } + + public RouterAdvertisementDaemon getRouterAdvertisementDaemon(InterfaceParams ifParams) { + return new RouterAdvertisementDaemon(ifParams); + } + + public InterfaceParams getInterfaceParams(String ifName) { + return InterfaceParams.getByName(ifName); + } + + public INetd getNetdService() { + return NetdService.getInstance(); + } } diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java b/services/core/java/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java new file mode 100644 index 000000000000..6c7ff91761ac --- /dev/null +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java @@ -0,0 +1,90 @@ +/* + * 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.connectivity.tethering; + +import android.annotation.Nullable; +import android.net.LinkProperties; +import android.net.NetworkCapabilities; +import android.net.NetworkState; +import android.net.RouteInfo; +import android.net.util.InterfaceSet; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; + +/** + * @hide + */ +public final class TetheringInterfaceUtils { + /** + * Get upstream interfaces for tethering based on default routes for IPv4/IPv6. + * @return null if there is no usable interface, or a set of at least one interface otherwise. + */ + public static @Nullable InterfaceSet getTetheringInterfaces(NetworkState ns) { + if (ns == null) { + return null; + } + + final LinkProperties lp = ns.linkProperties; + final String if4 = getInterfaceForDestination(lp, Inet4Address.ANY); + final String if6 = getIPv6Interface(ns); + + return (if4 == null && if6 == null) ? null : new InterfaceSet(if4, if6); + } + + /** + * Get the upstream interface for IPv6 tethering. + * @return null if there is no usable interface, or the interface name otherwise. + */ + public static @Nullable String getIPv6Interface(NetworkState ns) { + // Broadly speaking: + // + // [1] does the upstream have an IPv6 default route? + // + // and + // + // [2] does the upstream have one or more global IPv6 /64s + // dedicated to this device? + // + // In lieu of Prefix Delegation and other evaluation of whether a + // prefix may or may not be dedicated to this device, for now just + // check whether the upstream is TRANSPORT_CELLULAR. This works + // because "[t]he 3GPP network allocates each default bearer a unique + // /64 prefix", per RFC 6459, Section 5.2. + final boolean canTether = + (ns != null) && (ns.network != null) && + (ns.linkProperties != null) && (ns.networkCapabilities != null) && + // At least one upstream DNS server: + ns.linkProperties.hasIPv6DnsServer() && + // Minimal amount of IPv6 provisioning: + ns.linkProperties.hasGlobalIPv6Address() && + // Temporary approximation of "dedicated prefix": + ns.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR); + + return canTether + ? getInterfaceForDestination(ns.linkProperties, Inet6Address.ANY) + : null; + } + + private static String getInterfaceForDestination(LinkProperties lp, InetAddress dst) { + final RouteInfo ri = (lp != null) + ? RouteInfo.selectBestRoute(lp.getAllRoutes(), dst) + : null; + return (ri != null) ? ri.getInterface() : null; + } +} diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index 63308f894d09..ec404feeceb7 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -26,6 +26,7 @@ import android.app.job.JobInfo; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; +import android.content.ContentResolver.SyncExemption; import android.content.Context; import android.content.IContentService; import android.content.ISyncStatusObserver; @@ -78,7 +79,7 @@ import java.util.List; */ public final class ContentService extends IContentService.Stub { static final String TAG = "ContentService"; - static final boolean DEBUG = false; + static final boolean DEBUG = true; public static class Lifecycle extends SystemService { private ContentService mService; @@ -451,7 +452,7 @@ public final class ContentService extends IContentService.Stub { SyncManager syncManager = getSyncManager(); if (syncManager != null) { syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle, uid, - uri.getAuthority(), /*isAppStandbyExempted=*/ isUidInForeground(uid)); + uri.getAuthority(), getSyncExemptionForCaller(uid)); } } @@ -508,7 +509,7 @@ public final class ContentService extends IContentService.Stub { int uId = Binder.getCallingUid(); validateExtras(uId, extras); - final boolean isForegroundSyncRequest = isForegroundSyncRequest(uId, extras); + final int syncExemption = getSyncExemptionAndCleanUpExtrasForCaller(uId, extras); // This makes it so that future permission checks will be in the context of this // process rather than the caller's process. We will restore this before returning. @@ -518,7 +519,7 @@ public final class ContentService extends IContentService.Stub { if (syncManager != null) { syncManager.scheduleSync(account, userId, uId, authority, extras, SyncStorageEngine.AuthorityInfo.UNDEFINED, - /*isAppStandbyExempted=*/ isForegroundSyncRequest); + syncExemption); } } finally { restoreCallingIdentity(identityToken); @@ -561,7 +562,7 @@ public final class ContentService extends IContentService.Stub { final Bundle extras = request.getBundle(); validateExtras(callerUid, extras); - final boolean isForegroundSyncRequest = isForegroundSyncRequest(callerUid, extras); + final int syncExemption = getSyncExemptionAndCleanUpExtrasForCaller(callerUid, extras); // This makes it so that future permission checks will be in the context of this // process rather than the caller's process. We will restore this before returning. @@ -589,7 +590,7 @@ public final class ContentService extends IContentService.Stub { syncManager.scheduleSync( request.getAccount(), userId, callerUid, request.getProvider(), extras, SyncStorageEngine.AuthorityInfo.UNDEFINED, - /*isAppStandbyExempted=*/ isForegroundSyncRequest); + syncExemption); } } finally { restoreCallingIdentity(identityToken); @@ -777,13 +778,15 @@ public final class ContentService extends IContentService.Stub { "no permission to write the sync settings"); enforceCrossUserPermission(userId, "no permission to modify the sync settings for user " + userId); + final int callingUid = Binder.getCallingUid(); + final int syncExemptionFlag = getSyncExemptionForCaller(callingUid); long identityToken = clearCallingIdentity(); try { SyncManager syncManager = getSyncManager(); if (syncManager != null) { syncManager.getSyncStorageEngine().setSyncAutomatically(account, userId, - providerName, sync); + providerName, sync, syncExemptionFlag); } } finally { restoreCallingIdentity(identityToken); @@ -964,11 +967,14 @@ public final class ContentService extends IContentService.Stub { mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, "no permission to write the sync settings"); + final int callingUid = Binder.getCallingUid(); + long identityToken = clearCallingIdentity(); try { SyncManager syncManager = getSyncManager(); if (syncManager != null) { - syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag, userId); + syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag, userId, + getSyncExemptionForCaller(callingUid)); } } finally { restoreCallingIdentity(identityToken); @@ -1263,9 +1269,7 @@ public final class ContentService extends IContentService.Stub { } private void validateExtras(int callingUid, Bundle extras) { - if (extras.containsKey(ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC) - || extras.containsKey(ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC) - ) { + if (extras.containsKey(ContentResolver.SYNC_VIRTUAL_EXTRAS_EXEMPTION_FLAG)) { switch (callingUid) { case Process.ROOT_UID: case Process.SHELL_UID: @@ -1277,39 +1281,36 @@ public final class ContentService extends IContentService.Stub { } } - private boolean isForegroundSyncRequest(int callingUid, Bundle extras) { - final boolean isForegroundRequest; - if (extras.getBoolean(ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC)) { - isForegroundRequest = true; - } else if (extras.getBoolean(ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_BG_SYNC)) { - isForegroundRequest = false; - } else { - isForegroundRequest = isUidInForeground(callingUid); - } - extras.remove(ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC); - extras.remove(ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_BG_SYNC); - - return isForegroundRequest; + @SyncExemption + private int getSyncExemptionForCaller(int callingUid) { + return getSyncExemptionAndCleanUpExtrasForCaller(callingUid, null); } - private boolean isUidInForeground(int uid) { - // If the caller is ADB, we assume it's a background request by default, because - // that's also the default of requests from the requestsync command. - // The requestsync command will always set either SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC or - // SYNC_VIRTUAL_EXTRAS_FORCE_BG_SYNC (for non-periodic sync requests), - // so it shouldn't matter in practice. - switch (uid) { - case Process.SHELL_UID: - case Process.ROOT_UID: - return false; + @SyncExemption + private int getSyncExemptionAndCleanUpExtrasForCaller(int callingUid, Bundle extras) { + if (extras != null) { + final int exemption = + extras.getInt(ContentResolver.SYNC_VIRTUAL_EXTRAS_EXEMPTION_FLAG, -1); + + // Need to remove the virtual extra. + extras.remove(ContentResolver.SYNC_VIRTUAL_EXTRAS_EXEMPTION_FLAG); + if (exemption != -1) { + return exemption; + } } final ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class); - if (ami != null) { - return ami.getUidProcessState(uid) - <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; + final int procState = (ami != null) + ? ami.getUidProcessState(callingUid) + : ActivityManager.PROCESS_STATE_NONEXISTENT; + + if (procState <= ActivityManager.PROCESS_STATE_TOP) { + return ContentResolver.SYNC_EXEMPTION_ACTIVE_WITH_TEMP; } - return false; + if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { + return ContentResolver.SYNC_EXEMPTION_ACTIVE; + } + return ContentResolver.SYNC_EXEMPTION_NONE; } /** diff --git a/services/core/java/com/android/server/content/SyncAdapterStateFetcher.java b/services/core/java/com/android/server/content/SyncAdapterStateFetcher.java new file mode 100644 index 000000000000..62fb75107755 --- /dev/null +++ b/services/core/java/com/android/server/content/SyncAdapterStateFetcher.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.content; + +import android.app.usage.UsageStatsManagerInternal; +import android.os.SystemClock; +import android.util.Pair; + +import com.android.server.AppStateTracker; +import com.android.server.LocalServices; + +import java.util.HashMap; + +class SyncAdapterStateFetcher { + + private final HashMap<Pair<Integer, String>, Integer> mBucketCache = + new HashMap<>(); + + public SyncAdapterStateFetcher() { + } + + /** + * Return sync adapter state with a cache. + */ + public int getStandbyBucket(int userId, String packageName) { + final Pair<Integer, String> key = Pair.create(userId, packageName); + final Integer cached = mBucketCache.get(key); + if (cached != null) { + return cached; + } + final UsageStatsManagerInternal usmi = + LocalServices.getService(UsageStatsManagerInternal.class); + if (usmi == null) { + return -1; // Unknown. + } + + final int value = usmi.getAppStandbyBucket(packageName, userId, + SystemClock.elapsedRealtime()); + mBucketCache.put(key, value); + return value; + } + + /** + * Return UID active state. + */ + public boolean isAppActive(int uid) { + final AppStateTracker ast = + LocalServices.getService(AppStateTracker.class); + if (ast == null) { + return false; + } + + return ast.isUidActive(uid); + } +} diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 70892685d8b4..d1f50b733894 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -29,9 +29,11 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.job.JobInfo; import android.app.job.JobScheduler; +import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; +import android.content.ContentResolver.SyncExemption; import android.content.Context; import android.content.ISyncAdapter; import android.content.ISyncAdapterUnsyncableAccountCallback; @@ -70,6 +72,7 @@ import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.PowerManager; +import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; @@ -88,6 +91,8 @@ import android.util.Slog; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.util.ArrayUtils; +import com.android.server.DeviceIdleController; +import com.android.server.DeviceIdleController.LocalService; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.job.JobSchedulerInternal; @@ -550,10 +555,6 @@ public class SyncManager { return mJobScheduler; } - /** - * Should only be created after {@link ContentService#systemReady()} so that - * {@link PackageManager} is ready to query. - */ public SyncManager(Context context, boolean factoryTest) { // Initialize the SyncStorageEngine first, before registering observers // and creating threads and so on; it may fail if the disk is full. @@ -566,9 +567,9 @@ public class SyncManager { mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() { @Override public void onSyncRequest(SyncStorageEngine.EndPoint info, int reason, Bundle extras, - boolean isAppStandbyExempted) { + @SyncExemption int syncExemptionFlag) { scheduleSync(info.account, info.userId, reason, info.provider, extras, - AuthorityInfo.UNDEFINED, isAppStandbyExempted); + AuthorityInfo.UNDEFINED, syncExemptionFlag); } }); @@ -599,7 +600,7 @@ public class SyncManager { scheduleSync(null, UserHandle.USER_ALL, SyncOperation.REASON_SERVICE_CHANGED, type.authority, null, AuthorityInfo.UNDEFINED, - /*isAppStandbyExempted=*/ false); + ContentResolver.SYNC_EXEMPTION_NONE); } } }, mSyncHandler); @@ -649,7 +650,7 @@ public class SyncManager { scheduleSync(account, UserHandle.getUserId(uid), SyncOperation.REASON_ACCOUNTS_UPDATED, null, null, AuthorityInfo.SYNCABLE_NO_ACCOUNT_ACCESS, - /*isAppStandbyExempted=*/ false); + ContentResolver.SYNC_EXEMPTION_NONE); } }); @@ -883,9 +884,9 @@ public class SyncManager { */ public void scheduleSync(Account requestedAccount, int userId, int reason, String requestedAuthority, Bundle extras, int targetSyncState, - boolean isAppStandbyExempted) { + @SyncExemption int syncExemptionFlag) { scheduleSync(requestedAccount, userId, reason, requestedAuthority, extras, targetSyncState, - 0 /* min delay */, true /* checkIfAccountReady */, isAppStandbyExempted); + 0 /* min delay */, true /* checkIfAccountReady */, syncExemptionFlag); } /** @@ -894,7 +895,7 @@ public class SyncManager { private void scheduleSync(Account requestedAccount, int userId, int reason, String requestedAuthority, Bundle extras, int targetSyncState, final long minDelayMillis, boolean checkIfAccountReady, - boolean isAppStandbyExempted) { + @SyncExemption int syncExemptionFlag) { final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); if (extras == null) { extras = new Bundle(); @@ -904,7 +905,7 @@ public class SyncManager { + requestedAuthority + " reason=" + reason + " checkIfAccountReady=" + checkIfAccountReady - + " isAppStandbyExempted=" + isAppStandbyExempted); + + " syncExemptionFlag=" + syncExemptionFlag); } AccountAndUser[] accounts = null; @@ -1016,7 +1017,7 @@ public class SyncManager { scheduleSync(account.account, userId, reason, authority, finalExtras, targetSyncState, minDelayMillis, true /* checkIfAccountReady */, - isAppStandbyExempted); + syncExemptionFlag); } } )); @@ -1067,7 +1068,7 @@ public class SyncManager { sendOnUnsyncableAccount(mContext, syncAdapterInfo, account.userId, () -> scheduleSync(account.account, account.userId, reason, authority, finalExtras, targetSyncState, minDelayMillis, - false, isAppStandbyExempted)); + false, syncExemptionFlag)); } else { // Initialisation sync. Bundle newExtras = new Bundle(); @@ -1086,7 +1087,7 @@ public class SyncManager { new SyncOperation(account.account, account.userId, owningUid, owningPackage, reason, source, authority, newExtras, allowParallelSyncs, - isAppStandbyExempted), + syncExemptionFlag), minDelayMillis ); } @@ -1103,7 +1104,7 @@ public class SyncManager { postScheduleSyncMessage( new SyncOperation(account.account, account.userId, owningUid, owningPackage, reason, source, - authority, extras, allowParallelSyncs, isAppStandbyExempted), + authority, extras, allowParallelSyncs, syncExemptionFlag), minDelayMillis ); } @@ -1217,12 +1218,12 @@ public class SyncManager { * ms to batch syncs. */ public void scheduleLocalSync(Account account, int userId, int reason, String authority, - boolean isAppStandbyExempted) { + @SyncExemption int syncExemptionFlag) { final Bundle extras = new Bundle(); extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true); scheduleSync(account, userId, reason, authority, extras, AuthorityInfo.UNDEFINED, LOCAL_SYNC_DELAY, true /* checkIfAccountReady */, - isAppStandbyExempted); + syncExemptionFlag); } public SyncAdapterType[] getSyncAdapterTypes(int userId) { @@ -1493,7 +1494,7 @@ public class SyncManager { // If any of the duplicate ones has exemption, then we inherit it. if (!syncOperation.isPeriodic) { - boolean inheritAppStandbyExemption = false; + int inheritedSyncExemptionFlag = ContentResolver.SYNC_EXEMPTION_NONE; // Check currently running syncs for (ActiveSyncContext asc: mActiveSyncContexts) { @@ -1534,10 +1535,11 @@ public class SyncManager { // This means the duplicate one has a negative expected run time, but it hasn't // been executed possibly because of app-standby. - if (syncOperation.isAppStandbyExempted - && (minDelay == 0) - && !syncToRun.isAppStandbyExempted) { + if ((minDelay == 0) + && (syncToRun.syncExemptionFlag < syncOperation.syncExemptionFlag)) { syncToRun = syncOperation; + inheritedSyncExemptionFlag = + Math.max(inheritedSyncExemptionFlag, syncToRun.syncExemptionFlag); } } @@ -1551,9 +1553,8 @@ public class SyncManager { if (isLoggable) { Slog.v(TAG, "Cancelling duplicate sync " + op); } - if (op.isAppStandbyExempted) { - inheritAppStandbyExemption = true; - } + inheritedSyncExemptionFlag = + Math.max(inheritedSyncExemptionFlag, op.syncExemptionFlag); cancelJob(op, "scheduleSyncOperationH-duplicate"); } } @@ -1570,8 +1571,9 @@ public class SyncManager { } // If any of the duplicates had exemption, we exempt the current one. - if (inheritAppStandbyExemption) { - syncOperation.isAppStandbyExempted = true; + // + if (inheritedSyncExemptionFlag > ContentResolver.SYNC_EXEMPTION_NONE) { + syncOperation.syncExemptionFlag = inheritedSyncExemptionFlag; } } @@ -1591,7 +1593,7 @@ public class SyncManager { // Note this logic means when an exempted sync fails, // the back-off one will inherit it too, and will be exempted from app-standby. - final int jobFlags = syncOperation.isAppStandbyExempted + final int jobFlags = syncOperation.isAppStandbyExempted() ? JobInfo.FLAG_EXEMPT_FROM_APP_STANDBY : 0; JobInfo.Builder b = new JobInfo.Builder(syncOperation.jobId, @@ -1615,6 +1617,19 @@ public class SyncManager { b.setRequiresCharging(true); } + if (syncOperation.syncExemptionFlag + == ContentResolver.SYNC_EXEMPTION_ACTIVE_WITH_TEMP) { + DeviceIdleController.LocalService dic = + LocalServices.getService(DeviceIdleController.LocalService.class); + if (dic != null) { + dic.addPowerSaveTempWhitelistApp(Process.SYSTEM_UID, + syncOperation.owningPackage, + mConstants.getKeyExemptionTempWhitelistDurationInSeconds() * 1000, + UserHandle.getUserId(syncOperation.owningUid), + /* sync=*/ false, "sync by top app"); + } + } + getJobScheduler().scheduleAsPackage(b.build(), syncOperation.owningPackage, syncOperation.target.userId, syncOperation.wakeLockName()); } @@ -1736,7 +1751,7 @@ public class SyncManager { mContext.getOpPackageName()); for (Account account : accounts) { scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null, - AuthorityInfo.NOT_INITIALIZED, /*isAppStandbyExempted=*/ false); + AuthorityInfo.NOT_INITIALIZED, ContentResolver.SYNC_EXEMPTION_NONE); } } @@ -1930,7 +1945,10 @@ public class SyncManager { protected void dump(FileDescriptor fd, PrintWriter pw, boolean dumpAll) { final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); - dumpSyncState(ipw); + + final SyncAdapterStateFetcher buckets = new SyncAdapterStateFetcher(); + + dumpSyncState(ipw, buckets); mConstants.dump(pw, ""); dumpSyncAdapters(ipw); @@ -1991,7 +2009,7 @@ public class SyncManager { return ret; } - protected void dumpPendingSyncs(PrintWriter pw) { + protected void dumpPendingSyncs(PrintWriter pw, SyncAdapterStateFetcher buckets) { List<SyncOperation> pendingSyncs = getAllPendingSyncs(); pw.print("Pending Syncs: "); @@ -2001,14 +2019,14 @@ public class SyncManager { int count = 0; for (SyncOperation op: pendingSyncs) { if (!op.isPeriodic) { - pw.println(op.dump(null, false)); + pw.println(op.dump(null, false, buckets)); count++; } } pw.println(); } - protected void dumpPeriodicSyncs(PrintWriter pw) { + protected void dumpPeriodicSyncs(PrintWriter pw, SyncAdapterStateFetcher buckets) { List<SyncOperation> pendingSyncs = getAllPendingSyncs(); pw.print("Periodic Syncs: "); @@ -2018,7 +2036,7 @@ public class SyncManager { int count = 0; for (SyncOperation op: pendingSyncs) { if (op.isPeriodic) { - pw.println(op.dump(null, false)); + pw.println(op.dump(null, false, buckets)); count++; } } @@ -2075,7 +2093,7 @@ public class SyncManager { return true; } - protected void dumpSyncState(PrintWriter pw) { + protected void dumpSyncState(PrintWriter pw, SyncAdapterStateFetcher buckets) { final StringBuilder sb = new StringBuilder(); pw.print("Data connected: "); pw.println(mDataConnectionIsConnected); @@ -2150,13 +2168,13 @@ public class SyncManager { sb.setLength(0); pw.print(formatDurationHMS(sb, durationInSeconds)); pw.print(" - "); - pw.print(activeSyncContext.mSyncOperation.dump(pm, false)); + pw.print(activeSyncContext.mSyncOperation.dump(pm, false, buckets)); pw.println(); } pw.println(); - dumpPendingSyncs(pw); - dumpPeriodicSyncs(pw); + dumpPendingSyncs(pw, buckets); + dumpPeriodicSyncs(pw, buckets); // Join the installed sync adapter with the accounts list and emit for everything. pw.println("Sync Status"); @@ -3219,7 +3237,7 @@ public class SyncManager { scheduleSync(syncTargets.account, syncTargets.userId, SyncOperation.REASON_ACCOUNTS_UPDATED, syncTargets.provider, null, AuthorityInfo.NOT_INITIALIZED, - /*isAppStandbyExempted=*/ false); + ContentResolver.SYNC_EXEMPTION_NONE); } } @@ -3286,7 +3304,7 @@ public class SyncManager { syncAdapterInfo.componentName.getPackageName(), SyncOperation.REASON_PERIODIC, SyncStorageEngine.SOURCE_PERIODIC, extras, syncAdapterInfo.type.allowParallelSyncs(), true, SyncOperation.NO_JOB_ID, - pollFrequencyMillis, flexMillis, /*isAppStandbyExempted=*/ false); + pollFrequencyMillis, flexMillis, ContentResolver.SYNC_EXEMPTION_NONE); final int syncOpState = computeSyncOpState(op); switch (syncOpState) { @@ -3431,6 +3449,15 @@ public class SyncManager { Slog.v(TAG, syncContext.toString()); } } + if (op.isAppStandbyExempted()) { + final UsageStatsManagerInternal usmi = LocalServices.getService( + UsageStatsManagerInternal.class); + if (usmi != null) { + usmi.reportExemptedSyncStart(op.owningPackage, + UserHandle.getUserId(op.owningUid)); + } + } + // Connect to the sync adapter. int targetUid; ComponentName targetComponent; @@ -3604,7 +3631,7 @@ public class SyncManager { syncOperation.retries++; if (syncOperation.retries > mConstants.getMaxRetriesWithAppStandbyExemption()) { - syncOperation.isAppStandbyExempted = false; + syncOperation.syncExemptionFlag = ContentResolver.SYNC_EXEMPTION_NONE; } // the operation failed so increase the backoff time @@ -3672,7 +3699,7 @@ public class SyncManager { syncOperation.reason, syncOperation.syncSource, info.provider, new Bundle(), syncOperation.allowParallelSyncs, - syncOperation.isAppStandbyExempted)); + syncOperation.syncExemptionFlag)); } } diff --git a/services/core/java/com/android/server/content/SyncManagerConstants.java b/services/core/java/com/android/server/content/SyncManagerConstants.java index 061e4ca02d2d..2a5858c3e182 100644 --- a/services/core/java/com/android/server/content/SyncManagerConstants.java +++ b/services/core/java/com/android/server/content/SyncManagerConstants.java @@ -52,6 +52,12 @@ public class SyncManagerConstants extends ContentObserver { private static final int DEF_MAX_RETRIES_WITH_APP_STANDBY_EXEMPTION = 5; private int mMaxRetriesWithAppStandbyExemption = DEF_MAX_RETRIES_WITH_APP_STANDBY_EXEMPTION; + private static final String KEY_EXEMPTION_TEMP_WHITELIST_DURATION_IN_SECONDS = + "exemption_temp_whitelist_duration_in_seconds"; + private static final int DEF_EXEMPTION_TEMP_WHITELIST_DURATION_IN_SECONDS = 10 * 60; + private int mKeyExemptionTempWhitelistDurationInSeconds + = DEF_EXEMPTION_TEMP_WHITELIST_DURATION_IN_SECONDS; + protected SyncManagerConstants(Context context) { super(null); mContext = context; @@ -97,6 +103,11 @@ public class SyncManagerConstants extends ContentObserver { mMaxRetriesWithAppStandbyExemption = parser.getInt( KEY_MAX_RETRIES_WITH_APP_STANDBY_EXEMPTION, DEF_MAX_RETRIES_WITH_APP_STANDBY_EXEMPTION); + + mKeyExemptionTempWhitelistDurationInSeconds = parser.getInt( + KEY_EXEMPTION_TEMP_WHITELIST_DURATION_IN_SECONDS, + DEF_EXEMPTION_TEMP_WHITELIST_DURATION_IN_SECONDS); + } } @@ -124,6 +135,12 @@ public class SyncManagerConstants extends ContentObserver { } } + public int getKeyExemptionTempWhitelistDurationInSeconds() { + synchronized (mLock) { + return mKeyExemptionTempWhitelistDurationInSeconds; + } + } + public void dump(PrintWriter pw, String prefix) { synchronized (mLock) { pw.print(prefix); @@ -144,6 +161,10 @@ public class SyncManagerConstants extends ContentObserver { pw.print(prefix); pw.print(" mMaxRetriesWithAppStandbyExemption="); pw.println(mMaxRetriesWithAppStandbyExemption); + + pw.print(prefix); + pw.print(" mKeyExemptionTempWhitelistDurationInSeconds="); + pw.println(mKeyExemptionTempWhitelistDurationInSeconds); } } } diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java index 96bdaeabf17c..d0975637e686 100644 --- a/services/core/java/com/android/server/content/SyncOperation.java +++ b/services/core/java/com/android/server/content/SyncOperation.java @@ -19,6 +19,7 @@ package com.android.server.content; import android.accounts.Account; import android.app.job.JobInfo; import android.content.ContentResolver; +import android.content.ContentResolver.SyncExemption; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.PersistableBundle; @@ -98,33 +99,33 @@ public class SyncOperation { /** jobId of the JobScheduler job corresponding to this sync */ public int jobId; - /** Whether this operation should be exempted from the app-standby throttling. */ - public boolean isAppStandbyExempted; + @SyncExemption + public int syncExemptionFlag; public SyncOperation(Account account, int userId, int owningUid, String owningPackage, int reason, int source, String provider, Bundle extras, - boolean allowParallelSyncs, boolean isAppStandbyExempted) { + boolean allowParallelSyncs, @SyncExemption int syncExemptionFlag) { this(new SyncStorageEngine.EndPoint(account, provider, userId), owningUid, owningPackage, - reason, source, extras, allowParallelSyncs, isAppStandbyExempted); + reason, source, extras, allowParallelSyncs, syncExemptionFlag); } private SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage, int reason, int source, Bundle extras, boolean allowParallelSyncs, - boolean isAppStandbyExempted) { + @SyncExemption int syncExemptionFlag) { this(info, owningUid, owningPackage, reason, source, extras, allowParallelSyncs, false, - NO_JOB_ID, 0, 0, isAppStandbyExempted); + NO_JOB_ID, 0, 0, syncExemptionFlag); } public SyncOperation(SyncOperation op, long periodMillis, long flexMillis) { this(op.target, op.owningUid, op.owningPackage, op.reason, op.syncSource, new Bundle(op.extras), op.allowParallelSyncs, op.isPeriodic, op.sourcePeriodicId, - periodMillis, flexMillis, /*isAppStandbyExempted=*/ false); + periodMillis, flexMillis, ContentResolver.SYNC_EXEMPTION_NONE); } public SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage, int reason, int source, Bundle extras, boolean allowParallelSyncs, boolean isPeriodic, int sourcePeriodicId, long periodMillis, - long flexMillis, boolean isAppStandbyExempted) { + long flexMillis, @SyncExemption int syncExemptionFlag) { this.target = info; this.owningUid = owningUid; this.owningPackage = owningPackage; @@ -138,7 +139,7 @@ public class SyncOperation { this.flexMillis = flexMillis; this.jobId = NO_JOB_ID; this.key = toKey(); - this.isAppStandbyExempted = isAppStandbyExempted; + this.syncExemptionFlag = syncExemptionFlag; } /* Get a one off sync operation instance from a periodic sync. */ @@ -148,7 +149,7 @@ public class SyncOperation { } SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, syncSource, new Bundle(extras), allowParallelSyncs, false, jobId /* sourcePeriodicId */, - periodMillis, flexMillis, /*isAppStandbyExempted=*/ false); + periodMillis, flexMillis, ContentResolver.SYNC_EXEMPTION_NONE); return op; } @@ -166,7 +167,7 @@ public class SyncOperation { periodMillis = other.periodMillis; flexMillis = other.flexMillis; this.key = other.key; - isAppStandbyExempted = other.isAppStandbyExempted; + syncExemptionFlag = other.syncExemptionFlag; } /** @@ -235,7 +236,7 @@ public class SyncOperation { jobInfoExtras.putLong("flexMillis", flexMillis); jobInfoExtras.putLong("expectedRuntime", expectedRuntime); jobInfoExtras.putInt("retries", retries); - jobInfoExtras.putBoolean("isAppStandbyExempted", isAppStandbyExempted); + jobInfoExtras.putInt("syncExemptionFlag", syncExemptionFlag); return jobInfoExtras; } @@ -256,7 +257,7 @@ public class SyncOperation { Bundle extras; boolean allowParallelSyncs, isPeriodic; long periodMillis, flexMillis; - boolean isAppStandbyExempted; + int syncExemptionFlag; if (!jobExtras.getBoolean("SyncManagerJob", false)) { return null; @@ -275,7 +276,8 @@ public class SyncOperation { initiatedBy = jobExtras.getInt("sourcePeriodicId", NO_JOB_ID); periodMillis = jobExtras.getLong("periodMillis"); flexMillis = jobExtras.getLong("flexMillis"); - isAppStandbyExempted = jobExtras.getBoolean("isAppStandbyExempted", false); + syncExemptionFlag = jobExtras.getInt("syncExemptionFlag", + ContentResolver.SYNC_EXEMPTION_NONE); extras = new Bundle(); PersistableBundle syncExtras = jobExtras.getPersistableBundle("syncExtras"); @@ -298,7 +300,7 @@ public class SyncOperation { new SyncStorageEngine.EndPoint(account, provider, userId); SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, source, extras, allowParallelSyncs, isPeriodic, initiatedBy, periodMillis, flexMillis, - isAppStandbyExempted); + syncExemptionFlag); op.jobId = jobExtras.getInt("jobId"); op.expectedRuntime = jobExtras.getLong("expectedRuntime"); op.retries = jobExtras.getInt("retries"); @@ -361,10 +363,10 @@ public class SyncOperation { @Override public String toString() { - return dump(null, true); + return dump(null, true, null); } - String dump(PackageManager pm, boolean shorter) { + String dump(PackageManager pm, boolean shorter, SyncAdapterStateFetcher appStates) { StringBuilder sb = new StringBuilder(); sb.append("JobId=").append(jobId) .append(" ") @@ -385,8 +387,18 @@ public class SyncOperation { if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) { sb.append(" EXPEDITED"); } - if (isAppStandbyExempted) { - sb.append(" STANDBY-EXEMPTED"); + switch (syncExemptionFlag) { + case ContentResolver.SYNC_EXEMPTION_NONE: + break; + case ContentResolver.SYNC_EXEMPTION_ACTIVE: + sb.append(" STANDBY-EXEMPTED"); + break; + case ContentResolver.SYNC_EXEMPTION_ACTIVE_WITH_TEMP: + sb.append(" STANDBY-EXEMPTED(TOP)"); + break; + default: + sb.append(" ExemptionFlag=" + syncExemptionFlag); + break; } sb.append(" Reason="); sb.append(reasonToString(pm, reason)); @@ -397,21 +409,31 @@ public class SyncOperation { SyncManager.formatDurationHMS(sb, flexMillis); sb.append(")"); } + if (retries > 0) { + sb.append(" Retries="); + sb.append(retries); + } if (!shorter) { sb.append(" Owner={"); UserHandle.formatUid(sb, owningUid); sb.append(" "); sb.append(owningPackage); + if (appStates != null) { + sb.append(" ["); + sb.append(appStates.getStandbyBucket( + UserHandle.getUserId(owningUid), owningPackage)); + sb.append("]"); + + if (appStates.isAppActive(owningUid)) { + sb.append(" [ACTIVE]"); + } + } sb.append("}"); if (!extras.keySet().isEmpty()) { sb.append(" "); extrasToStringBuilder(extras, sb); } } - if (retries > 0) { - sb.append(" Retries="); - sb.append(retries); - } return sb.toString(); } @@ -464,6 +486,10 @@ public class SyncOperation { return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false); } + boolean isAppStandbyExempted() { + return syncExemptionFlag != ContentResolver.SYNC_EXEMPTION_NONE; + } + static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) { if (bundle == null) { sb.append("null"); diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java index 8b67b7a27e7e..6081af8d6a55 100644 --- a/services/core/java/com/android/server/content/SyncStorageEngine.java +++ b/services/core/java/com/android/server/content/SyncStorageEngine.java @@ -22,6 +22,7 @@ import android.accounts.AccountManager; import android.app.backup.BackupManager; import android.content.ComponentName; import android.content.ContentResolver; +import android.content.ContentResolver.SyncExemption; import android.content.Context; import android.content.ISyncStatusObserver; import android.content.PeriodicSync; @@ -341,7 +342,7 @@ public class SyncStorageEngine { /** Called when a sync is needed on an account(s) due to some change in state. */ public void onSyncRequest(EndPoint info, int reason, Bundle extras, - boolean exemptFromAppStandby); + @SyncExemption int syncExemptionFlag); } interface PeriodicSyncAddedListener { @@ -647,7 +648,7 @@ public class SyncStorageEngine { } public void setSyncAutomatically(Account account, int userId, String providerName, - boolean sync) { + boolean sync, @SyncExemption int syncExemptionFlag) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Slog.d(TAG, "setSyncAutomatically: " + /* account + */" provider " + providerName + ", user " + userId + " -> " + sync); @@ -677,7 +678,7 @@ public class SyncStorageEngine { if (sync) { requestSync(account, userId, SyncOperation.REASON_SYNC_AUTO, providerName, new Bundle(), - /* exemptFromAppStandby=*/ false); + syncExemptionFlag); } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); queueBackup(); @@ -739,7 +740,7 @@ public class SyncStorageEngine { } if (syncable == AuthorityInfo.SYNCABLE) { requestSync(aInfo, SyncOperation.REASON_IS_SYNCABLE, new Bundle(), - /*exemptFromAppStandby=*/ false); // Or the caller FG state? + ContentResolver.SYNC_EXEMPTION_NONE); } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); } @@ -900,7 +901,8 @@ public class SyncStorageEngine { return true; } - public void setMasterSyncAutomatically(boolean flag, int userId) { + public void setMasterSyncAutomatically(boolean flag, int userId, + @SyncExemption int syncExemptionFlag) { synchronized (mAuthorities) { Boolean auto = mMasterSyncAutomatically.get(userId); if (auto != null && auto.equals(flag)) { @@ -912,7 +914,7 @@ public class SyncStorageEngine { if (flag) { requestSync(null, userId, SyncOperation.REASON_MASTER_SYNC_AUTO, null, new Bundle(), - /*exemptFromAppStandby=*/ false); // Or the caller FG state? + syncExemptionFlag); } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); mContext.sendBroadcast(ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED); @@ -2046,7 +2048,8 @@ public class SyncStorageEngine { String value = c.getString(c.getColumnIndex("value")); if (name == null) continue; if (name.equals("listen_for_tickles")) { - setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value), 0); + setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value), 0, + ContentResolver.SYNC_EXEMPTION_NONE); } else if (name.startsWith("sync_provider_")) { String provider = name.substring("sync_provider_".length(), name.length()); @@ -2143,11 +2146,11 @@ public class SyncStorageEngine { } private void requestSync(AuthorityInfo authorityInfo, int reason, Bundle extras, - boolean exemptFromAppStandby) { + @SyncExemption int syncExemptionFlag) { if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID && mSyncRequestListener != null) { mSyncRequestListener.onSyncRequest(authorityInfo.target, reason, extras, - exemptFromAppStandby); + syncExemptionFlag); } else { SyncRequest.Builder req = new SyncRequest.Builder() @@ -2159,7 +2162,7 @@ public class SyncStorageEngine { } private void requestSync(Account account, int userId, int reason, String authority, - Bundle extras, boolean exemptFromAppStandby) { + Bundle extras, @SyncExemption int syncExemptionFlag) { // If this is happening in the system process, then call the syncrequest listener // to make a request back to the SyncManager directly. // If this is probably a test instance, then call back through the ContentResolver @@ -2168,7 +2171,7 @@ public class SyncStorageEngine { && mSyncRequestListener != null) { mSyncRequestListener.onSyncRequest( new EndPoint(account, authority, userId), - reason, extras, exemptFromAppStandby); + reason, extras, syncExemptionFlag); } else { ContentResolver.requestSync(account, authority, extras); } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index c4b2b5ed3ccf..c7ae1f4f5e50 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -695,6 +695,27 @@ public final class DisplayManagerService extends SystemService { } } + private void setSaturationLevelInternal(float level) { + if (level < 0 || level > 1) { + throw new IllegalArgumentException("Saturation level must be between 0 and 1"); + } + float[] matrix = (level == 1.0f ? null : computeSaturationMatrix(level)); + DisplayTransformManager dtm = LocalServices.getService(DisplayTransformManager.class); + dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_SATURATION, matrix); + } + + private static float[] computeSaturationMatrix(float saturation) { + float desaturation = 1.0f - saturation; + float[] luminance = {0.231f * desaturation, 0.715f * desaturation, 0.072f * desaturation}; + float[] matrix = { + luminance[0] + saturation, luminance[0], luminance[0], 0, + luminance[1], luminance[1] + saturation, luminance[1], 0, + luminance[2], luminance[2], luminance[2] + saturation, 0, + 0, 0, 0, 1 + }; + return matrix; + } + private int createVirtualDisplayInternal(IVirtualDisplayCallback callback, IMediaProjection projection, int callingUid, String packageName, String name, int width, int height, int densityDpi, Surface surface, int flags, String uniqueId) { @@ -1687,6 +1708,19 @@ public final class DisplayManagerService extends SystemService { } @Override // Binder call + public void setSaturationLevel(float level) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_SATURATION, + "Permission required to set display saturation level"); + final long token = Binder.clearCallingIdentity(); + try { + setSaturationLevelInternal(level); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call public int createVirtualDisplay(IVirtualDisplayCallback callback, IMediaProjection projection, String packageName, String name, int width, int height, int densityDpi, Surface surface, int flags, diff --git a/services/core/java/com/android/server/display/DisplayTransformManager.java b/services/core/java/com/android/server/display/DisplayTransformManager.java index 000fcf3e992b..a94f049f7ef8 100644 --- a/services/core/java/com/android/server/display/DisplayTransformManager.java +++ b/services/core/java/com/android/server/display/DisplayTransformManager.java @@ -17,7 +17,6 @@ package com.android.server.display; import android.app.ActivityManager; -import android.app.IActivityManager; import android.opengl.Matrix; import android.os.IBinder; import android.os.Parcel; @@ -28,7 +27,6 @@ import android.util.Log; import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; - import com.android.internal.app.ColorDisplayController; import java.util.Arrays; @@ -46,6 +44,10 @@ public class DisplayTransformManager { */ public static final int LEVEL_COLOR_MATRIX_NIGHT_DISPLAY = 100; /** + * Color transform level used to adjust the color saturation of the display. + */ + public static final int LEVEL_COLOR_MATRIX_SATURATION = 150; + /** * Color transform level used by A11y services to make the display monochromatic. */ public static final int LEVEL_COLOR_MATRIX_GRAYSCALE = 200; diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index 262a2f8ea817..92d3772e0607 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -1470,6 +1470,8 @@ public class FingerprintService extends SystemService implements IHwBinder.Death proto.end(userToken); } proto.flush(); + mPerformanceMap.clear(); + mCryptoPerformanceMap.clear(); } @Override diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java index 1dbcfd6a5bdf..40d2a3a4a094 100644 --- a/services/core/java/com/android/server/job/controllers/IdleController.java +++ b/services/core/java/com/android/server/job/controllers/IdleController.java @@ -19,7 +19,6 @@ package com.android.server.job.controllers; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import android.app.AlarmManager; -import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -101,18 +100,19 @@ public final class IdleController extends StateController { final class IdlenessTracker extends BroadcastReceiver { private AlarmManager mAlarm; - private PendingIntent mIdleTriggerIntent; - boolean mIdle; - boolean mScreenOn; + + // After construction, mutations of idle/screen-on state will only happen + // on the main looper thread, either in onReceive() or in an alarm callback. + private boolean mIdle; + private boolean mScreenOn; + + private AlarmManager.OnAlarmListener mIdleAlarmListener = () -> { + handleIdleTrigger(); + }; public IdlenessTracker() { mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - Intent intent = new Intent(ActivityManagerService.ACTION_TRIGGER_IDLE) - .setPackage("android") - .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - mIdleTriggerIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); - // At boot we presume that the user has just "interacted" with the // device in some meaningful way. mIdle = false; @@ -150,7 +150,7 @@ public final class IdleController extends StateController { } mScreenOn = true; //cancel the alarm - mAlarm.cancel(mIdleTriggerIntent); + mAlarm.cancel(mIdleAlarmListener); if (mIdle) { // possible transition to not-idle mIdle = false; @@ -169,20 +169,24 @@ public final class IdleController extends StateController { } mScreenOn = false; mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, - when, mIdleWindowSlop, mIdleTriggerIntent); + when, mIdleWindowSlop, "JS idleness", mIdleAlarmListener, null); } else if (action.equals(ActivityManagerService.ACTION_TRIGGER_IDLE)) { - // idle time starts now. Do not set mIdle if screen is on. - if (!mIdle && !mScreenOn) { - if (DEBUG) { - Slog.v(TAG, "Idle trigger fired @ " + sElapsedRealtimeClock.millis()); - } - mIdle = true; - reportNewIdleState(mIdle); - } else { - if (DEBUG) { - Slog.v(TAG, "TRIGGER_IDLE received but not changing state; idle=" - + mIdle + " screen=" + mScreenOn); - } + handleIdleTrigger(); + } + } + + private void handleIdleTrigger() { + // idle time starts now. Do not set mIdle if screen is on. + if (!mIdle && !mScreenOn) { + if (DEBUG) { + Slog.v(TAG, "Idle trigger fired @ " + sElapsedRealtimeClock.millis()); + } + mIdle = true; + reportNewIdleState(mIdle); + } else { + if (DEBUG) { + Slog.v(TAG, "TRIGGER_IDLE received but not changing state; idle=" + + mIdle + " screen=" + mScreenOn); } } } diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index 3374b30448d4..5955c9c3dd0a 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -2513,6 +2513,8 @@ public class GnssLocationProvider implements LocationProviderInterface { * this handler. */ private void handleInitialize() { + native_init_once(); + /* * A cycle of native_init() and native_cleanup() is needed so that callbacks are * registered after bootup even when location is disabled. @@ -2576,9 +2578,9 @@ public class GnssLocationProvider implements LocationProviderInterface { // register for connectivity change events, this is equivalent to the deprecated way of // registering for CONNECTIVITY_ACTION broadcasts NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder(); - networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); - networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); - networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH); + networkRequestBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + networkRequestBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); + networkRequestBuilder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN); NetworkRequest networkRequest = networkRequestBuilder.build(); mConnMgr.registerNetworkCallback(networkRequest, mNetworkConnectivityCallback); @@ -2899,6 +2901,8 @@ public class GnssLocationProvider implements LocationProviderInterface { private static native boolean native_is_gnss_configuration_supported(); + private static native void native_init_once(); + private native boolean native_init(); private native void native_cleanup(); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index f617964481bf..4b58d537e844 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -2079,11 +2079,6 @@ public class LockSettingsService extends ILockSettings.Stub { } @Override - public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException { - return mRecoverableKeyStoreManager.generateAndStoreKey(alias); - } - - @Override public @Nullable String generateKey(@NonNull String alias) throws RemoteException { return mRecoverableKeyStoreManager.generateKey(alias); } 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 050c1f4a6141..b0afac2c62dd 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java @@ -79,6 +79,7 @@ public class KeySyncTask implements Runnable { private final PlatformKeyManager mPlatformKeyManager; private final RecoverySnapshotStorage mRecoverySnapshotStorage; private final RecoverySnapshotListenersStorage mSnapshotListenersStorage; + private final TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper; public static KeySyncTask newInstance( Context context, @@ -98,7 +99,8 @@ public class KeySyncTask implements Runnable { credentialType, credential, credentialUpdated, - PlatformKeyManager.getInstance(context, recoverableKeyStoreDb)); + PlatformKeyManager.getInstance(context, recoverableKeyStoreDb), + new TestOnlyInsecureCertificateHelper()); } /** @@ -110,6 +112,7 @@ public class KeySyncTask implements Runnable { * @param credential The credential, encoded as a {@link String}. * @param credentialUpdated signals weather credentials were updated. * @param platformKeyManager platform key manager + * @param TestOnlyInsecureCertificateHelper utility class used for end-to-end tests */ @VisibleForTesting KeySyncTask( @@ -120,7 +123,8 @@ public class KeySyncTask implements Runnable { int credentialType, String credential, boolean credentialUpdated, - PlatformKeyManager platformKeyManager) { + PlatformKeyManager platformKeyManager, + TestOnlyInsecureCertificateHelper TestOnlyInsecureCertificateHelper) { mSnapshotListenersStorage = recoverySnapshotListenersStorage; mRecoverableKeyStoreDb = recoverableKeyStoreDb; mUserId = userId; @@ -129,6 +133,7 @@ public class KeySyncTask implements Runnable { mCredentialUpdated = credentialUpdated; mPlatformKeyManager = platformKeyManager; mRecoverySnapshotStorage = snapshotStorage; + mTestOnlyInsecureCertificateHelper = TestOnlyInsecureCertificateHelper; } @Override @@ -189,8 +194,9 @@ public class KeySyncTask implements Runnable { PublicKey publicKey; String rootCertAlias = mRecoverableKeyStoreDb.getActiveRootOfTrust(mUserId, recoveryAgentUid); + rootCertAlias = mTestOnlyInsecureCertificateHelper + .getDefaultCertificateAliasIfEmpty(rootCertAlias); - rootCertAlias = replaceEmptyValueWithSecureDefault(rootCertAlias); CertPath certPath = mRecoverableKeyStoreDb.getRecoveryServiceCertPath(mUserId, recoveryAgentUid, rootCertAlias); if (certPath != null) { @@ -212,12 +218,18 @@ public class KeySyncTask implements Runnable { return; } - // The only place in this class which uses credential value - if (!TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS.equals( - rootCertAlias)) { - // TODO: allow only whitelisted LSKF usage - Log.w(TAG, "Untrusted root certificate is used by recovery agent " + if (mTestOnlyInsecureCertificateHelper.isTestOnlyCertificate(rootCertAlias)) { + Log.w(TAG, "Insecure root certificate is used by recovery agent " + recoveryAgentUid); + if (mTestOnlyInsecureCertificateHelper.doesCredentailSupportInsecureMode( + mCredentialType, mCredential)) { + Log.w(TAG, "Whitelisted credential is used to generate snapshot by " + + "recovery agent "+ recoveryAgentUid); + } else { + Log.w(TAG, "Non whitelisted credential is used to generate recovery snapshot by " + + recoveryAgentUid + " - ignore attempt."); + return; // User secret will not be used. + } } byte[] salt = generateSalt(); @@ -239,8 +251,10 @@ public class KeySyncTask implements Runnable { return; } - // TODO: filter raw keys based on the root of trust. - // It is the only place in the class where raw key material is used. + // Only include insecure key material for test + if (mTestOnlyInsecureCertificateHelper.isTestOnlyCertificate(rootCertAlias)) { + rawKeys = mTestOnlyInsecureCertificateHelper.keepOnlyWhitelistedInsecureKeys(rawKeys); + } SecretKey recoveryKey; try { recoveryKey = generateRecoveryKey(); @@ -467,14 +481,4 @@ public class KeySyncTask implements Runnable { } return keyEntries; } - - private @NonNull String replaceEmptyValueWithSecureDefault( - @Nullable String rootCertificateAlias) { - if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) { - Log.e(TAG, "rootCertificateAlias is null or empty"); - // Use the default Google Key Vault Service CA certificate if the alias is not provided - rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS; - } - return rootCertificateAlias; - } } 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 30125f8199de..9f6ac10378ba 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -31,7 +31,6 @@ import android.annotation.Nullable; import android.app.PendingIntent; import android.content.Context; import android.os.Binder; -import android.os.Process; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserHandle; @@ -39,7 +38,6 @@ import android.security.keystore.recovery.KeyChainProtectionParams; import android.security.keystore.recovery.KeyChainSnapshot; import android.security.keystore.recovery.RecoveryCertPath; import android.security.keystore.recovery.RecoveryController; -import android.security.keystore.recovery.TrustedRootCertificates; import android.security.keystore.recovery.WrappedApplicationKey; import android.security.KeyStore; import android.util.ArrayMap; @@ -100,8 +98,8 @@ public class RecoverableKeyStoreManager { private final RecoverableKeyGenerator mRecoverableKeyGenerator; private final RecoverySnapshotStorage mSnapshotStorage; private final PlatformKeyManager mPlatformKeyManager; - private final KeyStore mKeyStore; private final ApplicationKeyStorage mApplicationKeyStorage; + private final TestOnlyInsecureCertificateHelper mTestCertHelper; /** * Returns a new or existing instance. @@ -126,14 +124,14 @@ public class RecoverableKeyStoreManager { mInstance = new RecoverableKeyStoreManager( context.getApplicationContext(), - keystore, db, new RecoverySessionStorage(), Executors.newSingleThreadExecutor(), - new RecoverySnapshotStorage(), + RecoverySnapshotStorage.newInstance(), new RecoverySnapshotListenersStorage(), platformKeyManager, - applicationKeyStorage); + applicationKeyStorage, + new TestOnlyInsecureCertificateHelper()); } return mInstance; } @@ -141,16 +139,15 @@ public class RecoverableKeyStoreManager { @VisibleForTesting RecoverableKeyStoreManager( Context context, - KeyStore keystore, RecoverableKeyStoreDb recoverableKeyStoreDb, RecoverySessionStorage recoverySessionStorage, ExecutorService executorService, RecoverySnapshotStorage snapshotStorage, RecoverySnapshotListenersStorage listenersStorage, PlatformKeyManager platformKeyManager, - ApplicationKeyStorage applicationKeyStorage) { + ApplicationKeyStorage applicationKeyStorage, + TestOnlyInsecureCertificateHelper TestOnlyInsecureCertificateHelper) { mContext = context; - mKeyStore = keystore; mDatabase = recoverableKeyStoreDb; mRecoverySessionStorage = recoverySessionStorage; mExecutorService = executorService; @@ -158,6 +155,7 @@ public class RecoverableKeyStoreManager { mSnapshotStorage = snapshotStorage; mPlatformKeyManager = platformKeyManager; mApplicationKeyStorage = applicationKeyStorage; + mTestCertHelper = TestOnlyInsecureCertificateHelper; try { mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase); @@ -176,7 +174,8 @@ public class RecoverableKeyStoreManager { checkRecoverKeyStorePermission(); int userId = UserHandle.getCallingUserId(); int uid = Binder.getCallingUid(); - rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias); + rootCertificateAlias + = mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias); // Always set active alias to the argument of the last call to initRecoveryService method, // even if cert file is incorrect. @@ -221,14 +220,14 @@ public class RecoverableKeyStoreManager { // Randomly choose and validate an endpoint certificate from the list CertPath certPath; - X509Certificate rootCert = getRootCertificate(rootCertificateAlias); + X509Certificate rootCert = + mTestCertHelper.getRootCertificate(rootCertificateAlias); try { Log.d(TAG, "Getting and validating a random endpoint certificate"); certPath = certXml.getRandomEndpointCert(rootCert); } catch (CertValidationException e) { Log.e(TAG, "Invalid endpoint cert", e); - throw new ServiceSpecificException( - ERROR_INVALID_CERTIFICATE, "Failed to validate certificate."); + throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage()); } boolean wasInitialized = mDatabase.getRecoveryServiceCertPath(userId, uid, @@ -249,8 +248,7 @@ public class RecoverableKeyStoreManager { } } catch (CertificateEncodingException e) { Log.e(TAG, "Failed to encode CertPath", e); - throw new ServiceSpecificException( - ERROR_BAD_CERTIFICATE_FORMAT, "Failed to encode CertPath."); + throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage()); } } @@ -270,7 +268,8 @@ public class RecoverableKeyStoreManager { @NonNull byte[] recoveryServiceSigFile) throws RemoteException { checkRecoverKeyStorePermission(); - rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias); + rootCertificateAlias = + mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias); Preconditions.checkNotNull(recoveryServiceCertFile, "recoveryServiceCertFile is null"); Preconditions.checkNotNull(recoveryServiceSigFile, "recoveryServiceSigFile is null"); @@ -280,19 +279,18 @@ public class RecoverableKeyStoreManager { } catch (CertParsingException e) { Log.d(TAG, "Failed to parse the sig file: " + HexDump.toHexString( recoveryServiceSigFile)); - throw new ServiceSpecificException( - ERROR_BAD_CERTIFICATE_FORMAT, "Failed to parse the sig file."); + throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage()); } - X509Certificate rootCert = getRootCertificate(rootCertificateAlias); + X509Certificate rootCert = + mTestCertHelper.getRootCertificate(rootCertificateAlias); try { sigXml.verifyFileSignature(rootCert, recoveryServiceCertFile); } catch (CertValidationException e) { Log.d(TAG, "The signature over the cert file is invalid." + " Cert: " + HexDump.toHexString(recoveryServiceCertFile) + " Sig: " + HexDump.toHexString(recoveryServiceSigFile)); - throw new ServiceSpecificException( - ERROR_INVALID_CERTIFICATE, "The signature over the cert file is invalid."); + throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage()); } initRecoveryService(rootCertificateAlias, recoveryServiceCertFile); @@ -469,8 +467,7 @@ public class RecoverableKeyStoreManager { try { publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey); } catch (InvalidKeySpecException e) { - throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, - "Not a valid X509 key"); + throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage()); } // The raw public key bytes contained in vaultParams must match the ones given in // verifierPublicKey; otherwise, the user secret may be decrypted by a key that is not owned @@ -524,7 +521,8 @@ public class RecoverableKeyStoreManager { @NonNull List<KeyChainProtectionParams> secrets) throws RemoteException { checkRecoverKeyStorePermission(); - rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias); + rootCertificateAlias = + mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias); Preconditions.checkNotNull(sessionId, "invalid session"); Preconditions.checkNotNull(verifierCertPath, "verifierCertPath is null"); Preconditions.checkNotNull(vaultParams, "vaultParams is null"); @@ -534,12 +532,12 @@ public class RecoverableKeyStoreManager { try { certPath = verifierCertPath.getCertPath(); } catch (CertificateException e) { - throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, - "Failed decode the certificate path"); + throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage()); } try { - CertUtils.validateCertPath(getRootCertificate(rootCertificateAlias), certPath); + CertUtils.validateCertPath( + mTestCertHelper.getRootCertificate(rootCertificateAlias), certPath); } catch (CertValidationException e) { Log.e(TAG, "Failed to validate the given cert path", e); throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage()); @@ -668,40 +666,6 @@ public class RecoverableKeyStoreManager { } /** - * Deprecated - * Generates a key named {@code alias} in the recoverable store for the calling uid. Then - * returns the raw key material. - * - * <p>TODO: Once AndroidKeyStore has added move api, do not return raw bytes. - * - * @deprecated - * @hide - */ - public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException { - checkRecoverKeyStorePermission(); - int uid = Binder.getCallingUid(); - int userId = UserHandle.getCallingUserId(); - - PlatformEncryptionKey encryptionKey; - try { - encryptionKey = mPlatformKeyManager.getEncryptKey(userId); - } catch (NoSuchAlgorithmException e) { - // Impossible: all algorithms must be supported by AOSP - throw new RuntimeException(e); - } catch (KeyStoreException | UnrecoverableKeyException e) { - throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); - } catch (InsecureUserException e) { - throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage()); - } - - try { - return mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, uid, alias); - } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) { - throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); - } - } - - /** * Destroys the session with the given {@code sessionId}. */ public void closeSession(@NonNull String sessionId) throws RemoteException { @@ -965,27 +929,6 @@ public class RecoverableKeyStoreManager { } } - private X509Certificate getRootCertificate(String rootCertificateAlias) throws RemoteException { - rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias); - X509Certificate rootCertificate = - TrustedRootCertificates.getRootCertificate(rootCertificateAlias); - if (rootCertificate == null) { - throw new ServiceSpecificException( - ERROR_INVALID_CERTIFICATE, "The provided root certificate alias is invalid"); - } - return rootCertificate; - } - - private @NonNull String replaceEmptyValueWithSecureDefault( - @Nullable String rootCertificateAlias) { - if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) { - Log.e(TAG, "rootCertificateAlias is null or empty"); - // Use the default Google Key Vault Service CA certificate if the alias is not provided - rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS; - } - return rootCertificateAlias; - } - private void checkRecoverKeyStorePermission() { mContext.enforceCallingOrSelfPermission( Manifest.permission.RECOVER_KEYSTORE, diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java new file mode 100644 index 000000000000..490f733f149f --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java @@ -0,0 +1,103 @@ +/* + * 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.recoverablekeystore; + +import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE; + +import com.android.internal.widget.LockPatternUtils; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.security.keystore.recovery.TrustedRootCertificates; +import android.util.Log; + +import java.util.HashMap; +import java.security.cert.X509Certificate; +import java.util.Map; +import javax.crypto.SecretKey; + +/** + * The class provides helper methods to support end-to-end test with insecure certificate. + */ +public class TestOnlyInsecureCertificateHelper { + private static final String TAG = "TestCertHelper"; + + /** + * Constructor for the helper class. + */ + public TestOnlyInsecureCertificateHelper() { + } + + /** + * Returns a root certificate installed in the system for given alias. + * Returns default secure certificate if alias is empty or null. + * Can return insecure certificate for its alias. + */ + public @NonNull X509Certificate + getRootCertificate(String rootCertificateAlias) throws RemoteException { + rootCertificateAlias = getDefaultCertificateAliasIfEmpty(rootCertificateAlias); + if (isTestOnlyCertificate(rootCertificateAlias)) { + return TrustedRootCertificates.getTestOnlyInsecureCertificate(); + } + + X509Certificate rootCertificate = + TrustedRootCertificates.getRootCertificate(rootCertificateAlias); + if (rootCertificate == null) { + throw new ServiceSpecificException( + ERROR_INVALID_CERTIFICATE, "The provided root certificate alias is invalid"); + } + return rootCertificate; + } + + public @NonNull String getDefaultCertificateAliasIfEmpty( + @Nullable String rootCertificateAlias) { + if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) { + Log.e(TAG, "rootCertificateAlias is null or empty - use secure default value"); + // Use the default Google Key Vault Service CA certificate if the alias is not provided + rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS; + } + return rootCertificateAlias; + } + + public boolean isTestOnlyCertificate(String rootCertificateAlias) { + return TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS + .equals(rootCertificateAlias); + } + + public boolean doesCredentailSupportInsecureMode(int credentialType, String credential) { + return (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) + && (credential != null) + && credential.startsWith(TrustedRootCertificates.INSECURE_PASSWORD_PREFIX); + } + + public Map<String, SecretKey> keepOnlyWhitelistedInsecureKeys(Map<String, SecretKey> rawKeys) { + if (rawKeys == null) { + return null; + } + Map<String, SecretKey> filteredKeys = new HashMap<>(); + for (Map.Entry<String, SecretKey> entry : rawKeys.entrySet()) { + String alias = entry.getKey(); + if (alias != null + && alias.startsWith(TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX)) { + filteredKeys.put(entry.getKey(), entry.getValue()); + Log.d(TAG, "adding key with insecure alias " + alias + " to the recovery snapshot"); + } + } + return filteredKeys; + } +} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotDeserializer.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotDeserializer.java new file mode 100644 index 000000000000..f789155cee52 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotDeserializer.java @@ -0,0 +1,409 @@ +/* + * 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.recoverablekeystore.serialization; + +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.CERTIFICATE_FACTORY_TYPE; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.NAMESPACE; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.OUTPUT_ENCODING; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_ALGORITHM; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_ALIAS; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEY; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEYS; + +import static com.android.server.locksettings.recoverablekeystore.serialization + .KeyChainSnapshotSchema.TAG_BACKEND_PUBLIC_KEY; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_COUNTER_ID; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_RECOVERY_KEY_MATERIAL; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_PROTECTION_PARAMS; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_SNAPSHOT; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_DERIVATION_PARAMS; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_MATERIAL; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_LOCK_SCREEN_UI_TYPE; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_MAX_ATTEMPTS; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_MEMORY_DIFFICULTY; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SALT; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SERVER_PARAMS; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SNAPSHOT_VERSION; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_TRUSTED_HARDWARE_CERT_PATH; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_USER_SECRET_TYPE; + +import android.security.keystore.recovery.KeyChainProtectionParams; +import android.security.keystore.recovery.KeyChainSnapshot; +import android.security.keystore.recovery.KeyDerivationParams; +import android.security.keystore.recovery.WrappedApplicationKey; +import android.util.Base64; +import android.util.Xml; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.cert.CertPath; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +/** + * Deserializes a {@link android.security.keystore.recovery.KeyChainSnapshot} instance from XML. + */ +public class KeyChainSnapshotDeserializer { + + /** + * Deserializes a {@link KeyChainSnapshot} instance from the XML in the {@code inputStream}. + * + * @throws IOException if there is an IO error reading from the stream. + * @throws KeyChainSnapshotParserException if the XML does not conform to the expected XML for + * a snapshot. + */ + public static KeyChainSnapshot deserialize(InputStream inputStream) + throws KeyChainSnapshotParserException, IOException { + try { + return deserializeInternal(inputStream); + } catch (XmlPullParserException e) { + throw new KeyChainSnapshotParserException("Malformed KeyChainSnapshot XML", e); + } + } + + private static KeyChainSnapshot deserializeInternal(InputStream inputStream) throws IOException, + XmlPullParserException, KeyChainSnapshotParserException { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(inputStream, OUTPUT_ENCODING); + + parser.nextTag(); + parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_KEY_CHAIN_SNAPSHOT); + + KeyChainSnapshot.Builder builder = new KeyChainSnapshot.Builder(); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + + String name = parser.getName(); + + switch (name) { + case TAG_SNAPSHOT_VERSION: + builder.setSnapshotVersion(readIntTag(parser, TAG_SNAPSHOT_VERSION)); + break; + + case TAG_RECOVERY_KEY_MATERIAL: + builder.setEncryptedRecoveryKeyBlob( + readBlobTag(parser, TAG_RECOVERY_KEY_MATERIAL)); + break; + + case TAG_COUNTER_ID: + builder.setCounterId(readLongTag(parser, TAG_COUNTER_ID)); + break; + + case TAG_SERVER_PARAMS: + builder.setServerParams(readBlobTag(parser, TAG_SERVER_PARAMS)); + break; + + case TAG_MAX_ATTEMPTS: + builder.setMaxAttempts(readIntTag(parser, TAG_MAX_ATTEMPTS)); + break; + + case TAG_TRUSTED_HARDWARE_CERT_PATH: + try { + builder.setTrustedHardwareCertPath( + readCertPathTag(parser, TAG_TRUSTED_HARDWARE_CERT_PATH)); + } catch (CertificateException e) { + throw new KeyChainSnapshotParserException( + "Could not set trustedHardwareCertPath", e); + } + break; + + case TAG_BACKEND_PUBLIC_KEY: + builder.setTrustedHardwarePublicKey( + readBlobTag(parser, TAG_BACKEND_PUBLIC_KEY)); + break; + + case TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST: + builder.setKeyChainProtectionParams(readKeyChainProtectionParamsList(parser)); + break; + + case TAG_APPLICATION_KEYS: + builder.setWrappedApplicationKeys(readWrappedApplicationKeys(parser)); + break; + + default: + throw new KeyChainSnapshotParserException(String.format( + Locale.US, "Unexpected tag %s in keyChainSnapshot", name)); + } + } + + parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_KEY_CHAIN_SNAPSHOT); + try { + return builder.build(); + } catch (NullPointerException e) { + throw new KeyChainSnapshotParserException("Failed to build KeyChainSnapshot", e); + } + } + + private static List<WrappedApplicationKey> readWrappedApplicationKeys(XmlPullParser parser) + throws IOException, XmlPullParserException, KeyChainSnapshotParserException { + parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_APPLICATION_KEYS); + ArrayList<WrappedApplicationKey> keys = new ArrayList<>(); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + keys.add(readWrappedApplicationKey(parser)); + } + parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_APPLICATION_KEYS); + return keys; + } + + private static WrappedApplicationKey readWrappedApplicationKey(XmlPullParser parser) + throws IOException, XmlPullParserException, KeyChainSnapshotParserException { + parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_APPLICATION_KEY); + WrappedApplicationKey.Builder builder = new WrappedApplicationKey.Builder(); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + + String name = parser.getName(); + + switch (name) { + case TAG_ALIAS: + builder.setAlias(readStringTag(parser, TAG_ALIAS)); + break; + + case TAG_KEY_MATERIAL: + builder.setEncryptedKeyMaterial(readBlobTag(parser, TAG_KEY_MATERIAL)); + break; + + default: + throw new KeyChainSnapshotParserException(String.format( + Locale.US, "Unexpected tag %s in wrappedApplicationKey", name)); + } + } + parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_APPLICATION_KEY); + + try { + return builder.build(); + } catch (NullPointerException e) { + throw new KeyChainSnapshotParserException("Failed to build WrappedApplicationKey", e); + } + } + + private static List<KeyChainProtectionParams> readKeyChainProtectionParamsList( + XmlPullParser parser) throws IOException, XmlPullParserException, + KeyChainSnapshotParserException { + parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST); + + ArrayList<KeyChainProtectionParams> keyChainProtectionParamsList = new ArrayList<>(); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + keyChainProtectionParamsList.add(readKeyChainProtectionParams(parser)); + } + + parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST); + return keyChainProtectionParamsList; + } + + private static KeyChainProtectionParams readKeyChainProtectionParams(XmlPullParser parser) + throws IOException, XmlPullParserException, KeyChainSnapshotParserException { + parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS); + + KeyChainProtectionParams.Builder builder = new KeyChainProtectionParams.Builder(); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + + String name = parser.getName(); + + switch (name) { + case TAG_LOCK_SCREEN_UI_TYPE: + builder.setLockScreenUiFormat(readIntTag(parser, TAG_LOCK_SCREEN_UI_TYPE)); + break; + + case TAG_USER_SECRET_TYPE: + builder.setUserSecretType(readIntTag(parser, TAG_USER_SECRET_TYPE)); + break; + + case TAG_KEY_DERIVATION_PARAMS: + builder.setKeyDerivationParams(readKeyDerivationParams(parser)); + break; + + default: + throw new KeyChainSnapshotParserException(String.format( + Locale.US, + "Unexpected tag %s in keyChainProtectionParams", + name)); + + } + } + + parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS); + + try { + return builder.build(); + } catch (NullPointerException e) { + throw new KeyChainSnapshotParserException( + "Failed to build KeyChainProtectionParams", e); + } + } + + private static KeyDerivationParams readKeyDerivationParams(XmlPullParser parser) + throws XmlPullParserException, IOException, KeyChainSnapshotParserException { + parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_KEY_DERIVATION_PARAMS); + + int memoryDifficulty = -1; + int algorithm = -1; + byte[] salt = null; + + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + + String name = parser.getName(); + + switch (name) { + case TAG_MEMORY_DIFFICULTY: + memoryDifficulty = readIntTag(parser, TAG_MEMORY_DIFFICULTY); + break; + + case TAG_ALGORITHM: + algorithm = readIntTag(parser, TAG_ALGORITHM); + break; + + case TAG_SALT: + salt = readBlobTag(parser, TAG_SALT); + break; + + default: + throw new KeyChainSnapshotParserException( + String.format( + Locale.US, + "Unexpected tag %s in keyDerivationParams", + name)); + } + } + + if (salt == null) { + throw new KeyChainSnapshotParserException("salt was not set in keyDerivationParams"); + } + + KeyDerivationParams keyDerivationParams = null; + + switch (algorithm) { + case KeyDerivationParams.ALGORITHM_SHA256: + keyDerivationParams = KeyDerivationParams.createSha256Params(salt); + break; + + case KeyDerivationParams.ALGORITHM_SCRYPT: + keyDerivationParams = KeyDerivationParams.createScryptParams( + salt, memoryDifficulty); + break; + + default: + throw new KeyChainSnapshotParserException( + "Unknown algorithm in keyDerivationParams"); + } + + parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_KEY_DERIVATION_PARAMS); + return keyDerivationParams; + } + + private static int readIntTag(XmlPullParser parser, String tagName) + throws IOException, XmlPullParserException, KeyChainSnapshotParserException { + parser.require(XmlPullParser.START_TAG, NAMESPACE, tagName); + String text = readText(parser); + parser.require(XmlPullParser.END_TAG, NAMESPACE, tagName); + try { + return Integer.valueOf(text); + } catch (NumberFormatException e) { + throw new KeyChainSnapshotParserException( + String.format( + Locale.US, "%s expected int but got '%s'", tagName, text), e); + } + } + + private static long readLongTag(XmlPullParser parser, String tagName) + throws IOException, XmlPullParserException, KeyChainSnapshotParserException { + parser.require(XmlPullParser.START_TAG, NAMESPACE, tagName); + String text = readText(parser); + parser.require(XmlPullParser.END_TAG, NAMESPACE, tagName); + try { + return Long.valueOf(text); + } catch (NumberFormatException e) { + throw new KeyChainSnapshotParserException( + String.format( + Locale.US, "%s expected long but got '%s'", tagName, text), e); + } + } + + private static String readStringTag(XmlPullParser parser, String tagName) + throws IOException, XmlPullParserException { + parser.require(XmlPullParser.START_TAG, NAMESPACE, tagName); + String text = readText(parser); + parser.require(XmlPullParser.END_TAG, NAMESPACE, tagName); + return text; + } + + private static byte[] readBlobTag(XmlPullParser parser, String tagName) + throws IOException, XmlPullParserException, KeyChainSnapshotParserException { + parser.require(XmlPullParser.START_TAG, NAMESPACE, tagName); + String text = readText(parser); + parser.require(XmlPullParser.END_TAG, NAMESPACE, tagName); + + try { + return Base64.decode(text, /*flags=*/ Base64.DEFAULT); + } catch (IllegalArgumentException e) { + throw new KeyChainSnapshotParserException( + String.format( + Locale.US, + "%s expected base64 encoded bytes but got '%s'", + tagName, text), e); + } + } + + private static CertPath readCertPathTag(XmlPullParser parser, String tagName) + throws IOException, XmlPullParserException, KeyChainSnapshotParserException { + byte[] bytes = readBlobTag(parser, tagName); + try { + return CertificateFactory.getInstance(CERTIFICATE_FACTORY_TYPE) + .generateCertPath(new ByteArrayInputStream(bytes)); + } catch (CertificateException e) { + throw new KeyChainSnapshotParserException("Could not parse CertPath in tag " + tagName, + e); + } + } + + private static String readText(XmlPullParser parser) + throws IOException, XmlPullParserException { + String result = ""; + if (parser.next() == XmlPullParser.TEXT) { + result = parser.getText(); + parser.nextTag(); + } + return result; + } + + // Statics only + private KeyChainSnapshotDeserializer() {} +} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotParserException.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotParserException.java new file mode 100644 index 000000000000..a3208afddf9b --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotParserException.java @@ -0,0 +1,32 @@ +/* + * 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.recoverablekeystore.serialization; + +/** + * Error thrown when parsing invalid XML, while trying to read a + * {@link android.security.keystore.recovery.KeyChainSnapshot}. + */ +public class KeyChainSnapshotParserException extends Exception { + + public KeyChainSnapshotParserException(String message, Throwable cause) { + super(message, cause); + } + + public KeyChainSnapshotParserException(String message) { + super(message); + } +} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSchema.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSchema.java new file mode 100644 index 000000000000..ff30ecd7d3a7 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSchema.java @@ -0,0 +1,58 @@ +/* + * 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.recoverablekeystore.serialization; + +/** + * Describes the XML schema of the {@link android.security.keystore.recovery.KeyChainSnapshot} file. + */ +class KeyChainSnapshotSchema { + static final String NAMESPACE = null; + + static final String OUTPUT_ENCODING = "UTF-8"; + + static final String CERTIFICATE_FACTORY_TYPE = "X.509"; + static final String CERT_PATH_ENCODING = "PkiPath"; + + static final String TAG_KEY_CHAIN_SNAPSHOT = "keyChainSnapshot"; + + static final String TAG_SNAPSHOT_VERSION = "snapshotVersion"; + static final String TAG_COUNTER_ID = "counterId"; + static final String TAG_MAX_ATTEMPTS = "maxAttempts"; + static final String TAG_RECOVERY_KEY_MATERIAL = "recoveryKeyMaterial"; + static final String TAG_SERVER_PARAMS = "serverParams"; + static final String TAG_TRUSTED_HARDWARE_CERT_PATH = "thmCertPath"; + static final String TAG_BACKEND_PUBLIC_KEY = "backendPublicKey"; + + static final String TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST = + "keyChainProtectionParamsList"; + static final String TAG_KEY_CHAIN_PROTECTION_PARAMS = "keyChainProtectionParams"; + static final String TAG_USER_SECRET_TYPE = "userSecretType"; + static final String TAG_LOCK_SCREEN_UI_TYPE = "lockScreenUiType"; + + static final String TAG_KEY_DERIVATION_PARAMS = "keyDerivationParams"; + static final String TAG_ALGORITHM = "algorithm"; + static final String TAG_MEMORY_DIFFICULTY = "memoryDifficulty"; + static final String TAG_SALT = "salt"; + + static final String TAG_APPLICATION_KEYS = "applicationKeysList"; + static final String TAG_APPLICATION_KEY = "applicationKey"; + static final String TAG_ALIAS = "alias"; + static final String TAG_KEY_MATERIAL = "keyMaterial"; + + // Statics only + private KeyChainSnapshotSchema() {} +} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializer.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializer.java new file mode 100644 index 000000000000..17a16bf5906b --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializer.java @@ -0,0 +1,203 @@ +/* + * 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.recoverablekeystore.serialization; + + +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.CERT_PATH_ENCODING; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.NAMESPACE; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.OUTPUT_ENCODING; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_ALGORITHM; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_ALIAS; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEY; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEYS; + +import static com.android.server.locksettings.recoverablekeystore.serialization + .KeyChainSnapshotSchema.TAG_BACKEND_PUBLIC_KEY; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_COUNTER_ID; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_RECOVERY_KEY_MATERIAL; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_PROTECTION_PARAMS; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_SNAPSHOT; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_DERIVATION_PARAMS; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_MATERIAL; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_LOCK_SCREEN_UI_TYPE; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_MAX_ATTEMPTS; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_MEMORY_DIFFICULTY; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SALT; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SERVER_PARAMS; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SNAPSHOT_VERSION; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_TRUSTED_HARDWARE_CERT_PATH; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_USER_SECRET_TYPE; + +import android.security.keystore.recovery.KeyChainProtectionParams; +import android.security.keystore.recovery.KeyChainSnapshot; +import android.security.keystore.recovery.KeyDerivationParams; +import android.security.keystore.recovery.WrappedApplicationKey; +import android.util.Base64; +import android.util.Xml; + +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.io.OutputStream; +import java.security.cert.CertPath; +import java.security.cert.CertificateEncodingException; +import java.util.List; + +/** + * Serializes a {@link KeyChainSnapshot} instance to XML. + */ +public class KeyChainSnapshotSerializer { + + /** + * Serializes {@code keyChainSnapshot} to XML, writing to {@code outputStream}. + * + * @throws IOException if there was an IO error writing to the stream. + * @throws CertificateEncodingException if the {@link CertPath} from + * {@link KeyChainSnapshot#getTrustedHardwareCertPath()} is not encoded correctly. + */ + public static void serialize(KeyChainSnapshot keyChainSnapshot, OutputStream outputStream) + throws IOException, CertificateEncodingException { + XmlSerializer xmlSerializer = Xml.newSerializer(); + xmlSerializer.setOutput(outputStream, OUTPUT_ENCODING); + xmlSerializer.startDocument( + /*encoding=*/ null, + /*standalone=*/ null); + xmlSerializer.startTag(NAMESPACE, TAG_KEY_CHAIN_SNAPSHOT); + writeKeyChainSnapshotProperties(xmlSerializer, keyChainSnapshot); + writeKeyChainProtectionParams(xmlSerializer, + keyChainSnapshot.getKeyChainProtectionParams()); + writeApplicationKeys(xmlSerializer, + keyChainSnapshot.getWrappedApplicationKeys()); + xmlSerializer.endTag(NAMESPACE, TAG_KEY_CHAIN_SNAPSHOT); + xmlSerializer.endDocument(); + } + + private static void writeApplicationKeys( + XmlSerializer xmlSerializer, List<WrappedApplicationKey> wrappedApplicationKeys) + throws IOException { + xmlSerializer.startTag(NAMESPACE, TAG_APPLICATION_KEYS); + for (WrappedApplicationKey key : wrappedApplicationKeys) { + xmlSerializer.startTag(NAMESPACE, TAG_APPLICATION_KEY); + writeApplicationKeyProperties(xmlSerializer, key); + xmlSerializer.endTag(NAMESPACE, TAG_APPLICATION_KEY); + } + xmlSerializer.endTag(NAMESPACE, TAG_APPLICATION_KEYS); + } + + private static void writeApplicationKeyProperties( + XmlSerializer xmlSerializer, WrappedApplicationKey applicationKey) throws IOException { + writePropertyTag(xmlSerializer, TAG_ALIAS, applicationKey.getAlias()); + writePropertyTag(xmlSerializer, TAG_KEY_MATERIAL, applicationKey.getEncryptedKeyMaterial()); + } + + private static void writeKeyChainProtectionParams( + XmlSerializer xmlSerializer, + List<KeyChainProtectionParams> keyChainProtectionParamsList) throws IOException { + xmlSerializer.startTag(NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST); + for (KeyChainProtectionParams keyChainProtectionParams : keyChainProtectionParamsList) { + xmlSerializer.startTag(NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS); + writeKeyChainProtectionParamsProperties(xmlSerializer, keyChainProtectionParams); + xmlSerializer.endTag(NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS); + } + xmlSerializer.endTag(NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST); + } + + private static void writeKeyChainProtectionParamsProperties( + XmlSerializer xmlSerializer, KeyChainProtectionParams keyChainProtectionParams) + throws IOException { + writePropertyTag(xmlSerializer, TAG_USER_SECRET_TYPE, + keyChainProtectionParams.getUserSecretType()); + writePropertyTag(xmlSerializer, TAG_LOCK_SCREEN_UI_TYPE, + keyChainProtectionParams.getLockScreenUiFormat()); + + // NOTE: Do not serialize the 'secret' field. It should never be set anyway for snapshots + // we generate. + + writeKeyDerivationParams(xmlSerializer, keyChainProtectionParams.getKeyDerivationParams()); + } + + private static void writeKeyDerivationParams( + XmlSerializer xmlSerializer, KeyDerivationParams keyDerivationParams) + throws IOException { + xmlSerializer.startTag(NAMESPACE, TAG_KEY_DERIVATION_PARAMS); + writeKeyDerivationParamsProperties( + xmlSerializer, keyDerivationParams); + xmlSerializer.endTag(NAMESPACE, TAG_KEY_DERIVATION_PARAMS); + } + + private static void writeKeyDerivationParamsProperties( + XmlSerializer xmlSerializer, KeyDerivationParams keyDerivationParams) + throws IOException { + writePropertyTag(xmlSerializer, TAG_ALGORITHM, keyDerivationParams.getAlgorithm()); + writePropertyTag(xmlSerializer, TAG_SALT, keyDerivationParams.getSalt()); + writePropertyTag(xmlSerializer, TAG_MEMORY_DIFFICULTY, + keyDerivationParams.getMemoryDifficulty()); + } + + private static void writeKeyChainSnapshotProperties( + XmlSerializer xmlSerializer, KeyChainSnapshot keyChainSnapshot) + throws IOException, CertificateEncodingException { + + writePropertyTag(xmlSerializer, TAG_SNAPSHOT_VERSION, + keyChainSnapshot.getSnapshotVersion()); + writePropertyTag(xmlSerializer, TAG_MAX_ATTEMPTS, keyChainSnapshot.getMaxAttempts()); + writePropertyTag(xmlSerializer, TAG_COUNTER_ID, keyChainSnapshot.getCounterId()); + writePropertyTag(xmlSerializer, TAG_RECOVERY_KEY_MATERIAL, + keyChainSnapshot.getEncryptedRecoveryKeyBlob()); + writePropertyTag(xmlSerializer, TAG_SERVER_PARAMS, keyChainSnapshot.getServerParams()); + writePropertyTag(xmlSerializer, TAG_TRUSTED_HARDWARE_CERT_PATH, + keyChainSnapshot.getTrustedHardwareCertPath()); + if (keyChainSnapshot.getTrustedHardwarePublicKey() != null) { + writePropertyTag(xmlSerializer, TAG_BACKEND_PUBLIC_KEY, + keyChainSnapshot.getTrustedHardwarePublicKey()); + } + } + + private static void writePropertyTag( + XmlSerializer xmlSerializer, String propertyName, long propertyValue) + throws IOException { + xmlSerializer.startTag(NAMESPACE, propertyName); + xmlSerializer.text(Long.toString(propertyValue)); + xmlSerializer.endTag(NAMESPACE, propertyName); + } + + private static void writePropertyTag( + XmlSerializer xmlSerializer, String propertyName, String propertyValue) + throws IOException { + xmlSerializer.startTag(NAMESPACE, propertyName); + xmlSerializer.text(propertyValue); + xmlSerializer.endTag(NAMESPACE, propertyName); + } + + private static void writePropertyTag( + XmlSerializer xmlSerializer, String propertyName, byte[] propertyValue) + throws IOException { + xmlSerializer.startTag(NAMESPACE, propertyName); + xmlSerializer.text(Base64.encodeToString(propertyValue, /*flags=*/ Base64.DEFAULT)); + xmlSerializer.endTag(NAMESPACE, propertyName); + } + + private static void writePropertyTag( + XmlSerializer xmlSerializer, String propertyName, CertPath certPath) + throws IOException, CertificateEncodingException { + writePropertyTag(xmlSerializer, propertyName, certPath.getEncoded(CERT_PATH_ENCODING)); + } + + // Statics only + private KeyChainSnapshotSerializer() {} +} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java index 1eff2d472144..7ee809aa3865 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java @@ -70,121 +70,6 @@ class RecoverableKeyStoreDbContract { } /** - * Table holding encrypted snapshots of the recoverable key store. - */ - static class SnapshotsEntry implements BaseColumns { - static final String TABLE_NAME = "snapshots"; - - /** - * The version number of the snapshot. - */ - static final String COLUMN_NAME_VERSION = "version"; - - /** - * The ID of the user whose keystore was snapshotted. - */ - static final String COLUMN_NAME_USER_ID = "user_id"; - - /** - * The UID of the app that owns the snapshot (i.e., the recovery agent). - */ - static final String COLUMN_NAME_UID = "uid"; - - /** - * The maximum number of attempts allowed to attempt to decrypt the recovery key. - */ - static final String COLUMN_NAME_MAX_ATTEMPTS = "max_attempts"; - - /** - * The ID of the counter in the trusted hardware module. - */ - static final String COLUMN_NAME_COUNTER_ID = "counter_id"; - - /** - * Server parameters used to help identify the device (during recovery). - */ - static final String SERVER_PARAMS = "server_params"; - - /** - * The public key of the trusted hardware module. This key has been used to encrypt the - * snapshot, to ensure that it can only be read by the trusted module. - */ - static final String TRUSTED_HARDWARE_PUBLIC_KEY = "thm_public_key"; - - /** - * {@link java.security.cert.CertPath} signing the trusted hardware module to whose public - * key this snapshot is encrypted. - */ - static final String CERT_PATH = "cert_path"; - - /** - * The recovery key, encrypted with the user's lock screen and the trusted hardware module's - * public key. - */ - static final String ENCRYPTED_RECOVERY_KEY = "encrypted_recovery_key"; - } - - /** - * Table holding encrypted keys belonging to a particular snapshot. - */ - static class SnapshotKeysEntry implements BaseColumns { - static final String TABLE_NAME = "snapshot_keys"; - - /** - * ID of the associated snapshot entry in {@link SnapshotsEntry}. - */ - static final String COLUMN_NAME_SNAPSHOT_ID = "snapshot_id"; - - /** - * Alias of the key. - */ - static final String COLUMN_NAME_ALIAS = "alias"; - - /** - * Key material, encrypted with the recovery key from the snapshot. - */ - static final String COLUMN_NAME_ENCRYPTED_BYTES = "encrypted_key_bytes"; - } - - /** - * A layer of protection associated with a snapshot. - */ - static class SnapshotProtectionParams implements BaseColumns { - static final String TABLE_NAME = "snapshot_protection_params"; - - /** - * ID of the associated snapshot entry in {@link SnapshotsEntry}. - */ - static final String COLUMN_NAME_SNAPSHOT_ID = "snapshot_id"; - - /** - * Type of secret used to generate recovery key. One of - * {@link android.security.keystore.recovery.KeyChainProtectionParams#TYPE_LOCKSCREEN} or - */ - static final String COLUMN_NAME_SECRET_TYPE = "secret_type"; - - /** - * If a lock screen, the type of UI used. One of - * {@link android.security.keystore.recovery.KeyChainProtectionParams#UI_FORMAT_PATTERN}, - * {@link android.security.keystore.recovery.KeyChainProtectionParams#UI_FORMAT_PIN}, or - * {@link android.security.keystore.recovery.KeyChainProtectionParams#UI_FORMAT_PASSWORD}. - */ - static final String COLUMN_NAME_LOCKSCREEN_UI_TYPE = "lock_screen_ui_type"; - - /** - * The algorithm used to derive cryptographic material from the key and salt. One of - * {@link android.security.keystore.recovery.KeyDerivationParams#ALGORITHM_SHA256} or - * {@link android.security.keystore.recovery.KeyDerivationParams#ALGORITHM_SCRYPT}. - */ - static final String COLUMN_NAME_KEY_DERIVATION_ALGORITHM = "key_derivation_algorithm"; - - /** - * The salt used along with the secret to generate cryptographic material. - */ - static final String COLUMN_NAME_KEY_DERIVATION_SALT = "key_derivation_salt"; - } - - /** * Recoverable KeyStore metadata for a specific user profile. */ static class UserMetadataEntry implements BaseColumns { 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 3f93cc6f5f88..c02b103f1d33 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,13 +17,28 @@ package com.android.server.locksettings.recoverablekeystore.storage; import android.annotation.Nullable; +import android.os.Environment; import android.security.keystore.recovery.KeyChainSnapshot; +import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.locksettings.recoverablekeystore.serialization + .KeyChainSnapshotDeserializer; +import com.android.server.locksettings.recoverablekeystore.serialization + .KeyChainSnapshotParserException; +import com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSerializer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.security.cert.CertificateEncodingException; +import java.util.Locale; /** - * In-memory storage for recovery snapshots. + * Storage for recovery snapshots. Stores snapshots in memory, backed by disk storage. * * <p>Recovery snapshots are generated after a successful screen unlock. They are only generated if * the recoverable keystore has been mutated since the previous snapshot. This class stores only the @@ -33,14 +48,46 @@ import com.android.internal.annotations.GuardedBy; * {@link com.android.server.locksettings.recoverablekeystore.KeySyncTask} thread. */ public class RecoverySnapshotStorage { + + private static final String TAG = "RecoverySnapshotStorage"; + + private static final String ROOT_PATH = "system"; + private static final String STORAGE_PATH = "recoverablekeystore/snapshots/"; + @GuardedBy("this") private final SparseArray<KeyChainSnapshot> mSnapshotByUid = new SparseArray<>(); + private final File rootDirectory; + + /** + * A new instance, storing snapshots in /data/system/recoverablekeystore/snapshots. + * + * <p>NOTE: calling this multiple times DOES NOT return the same instance, so will NOT be backed + * by the same in-memory store. + */ + public static RecoverySnapshotStorage newInstance() { + return new RecoverySnapshotStorage( + new File(Environment.getDataDirectory(), ROOT_PATH)); + } + + @VisibleForTesting + public RecoverySnapshotStorage(File rootDirectory) { + this.rootDirectory = rootDirectory; + } + /** * Sets the latest {@code snapshot} for the recovery agent {@code uid}. */ public synchronized void put(int uid, KeyChainSnapshot snapshot) { mSnapshotByUid.put(uid, snapshot); + + try { + writeToDisk(uid, snapshot); + } catch (IOException | CertificateEncodingException e) { + Log.e(TAG, + String.format(Locale.US, "Error persisting snapshot for %d to disk", uid), + e); + } } /** @@ -48,7 +95,17 @@ public class RecoverySnapshotStorage { */ @Nullable public synchronized KeyChainSnapshot get(int uid) { - return mSnapshotByUid.get(uid); + KeyChainSnapshot snapshot = mSnapshotByUid.get(uid); + if (snapshot != null) { + return snapshot; + } + + try { + return readFromDisk(uid); + } catch (IOException | KeyChainSnapshotParserException e) { + Log.e(TAG, String.format(Locale.US, "Error reading snapshot for %d from disk", uid), e); + return null; + } } /** @@ -56,5 +113,66 @@ public class RecoverySnapshotStorage { */ public synchronized void remove(int uid) { mSnapshotByUid.remove(uid); + getSnapshotFile(uid).delete(); + } + + /** + * Writes the snapshot for recovery agent {@code uid} to disk. + * + * @throws IOException if an IO error occurs writing to disk. + */ + private void writeToDisk(int uid, KeyChainSnapshot snapshot) + throws IOException, CertificateEncodingException { + File snapshotFile = getSnapshotFile(uid); + + try ( + FileOutputStream fileOutputStream = new FileOutputStream(snapshotFile) + ) { + KeyChainSnapshotSerializer.serialize(snapshot, fileOutputStream); + } catch (IOException | CertificateEncodingException e) { + // If we fail to write the latest snapshot, we should delete any older snapshot that + // happens to be around. Otherwise snapshot syncs might end up going 'back in time'. + snapshotFile.delete(); + throw e; + } + } + + /** + * Reads the last snapshot for recovery agent {@code uid} from disk. + * + * @return The snapshot, or null if none existed. + * @throws IOException if an IO error occurs reading from disk. + */ + @Nullable + private KeyChainSnapshot readFromDisk(int uid) + throws IOException, KeyChainSnapshotParserException { + File snapshotFile = getSnapshotFile(uid); + + try ( + FileInputStream fileInputStream = new FileInputStream(snapshotFile) + ) { + return KeyChainSnapshotDeserializer.deserialize(fileInputStream); + } catch (IOException | KeyChainSnapshotParserException e) { + // If we fail to read the latest snapshot, we should delete it in case it is in some way + // corrupted. We can regenerate snapshots anyway. + snapshotFile.delete(); + throw e; + } + } + + private File getSnapshotFile(int uid) { + File folder = getStorageFolder(); + String fileName = getSnapshotFileName(uid); + return new File(folder, fileName); + } + + private String getSnapshotFileName(int uid) { + return String.format(Locale.US, "%d.xml", uid); + } + + private File getStorageFolder() { + File folder = new File(rootDirectory, STORAGE_PATH); + folder.mkdirs(); + return folder; } } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 664d2f9789f0..0d1644bb399f 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -71,8 +71,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { */ private static final int OPTIMISTIC_VOLUME_TIMEOUT = 1000; - private static final int UID_NOT_SET = -1; - private final MessageHandler mHandler; private final int mOwnerPid; @@ -117,9 +115,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { private boolean mIsActive = false; private boolean mDestroyed = false; - private int mCallingUid = UID_NOT_SET; - private String mCallingPackage; - public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName, ISessionCallback cb, String tag, MediaSessionService service, Looper handlerLooper) { mOwnerPid = ownerPid; @@ -234,14 +229,14 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE}, * {@link AudioManager#ADJUST_SAME}. * - * @param direction The direction to adjust volume in. - * @param flags Any of the flags from {@link AudioManager}. * @param packageName The package that made the original volume request. + * @param pid The pid that made the original volume request. * @param uid The uid that made the original volume request. + * @param direction The direction to adjust volume in. + * @param flags Any of the flags from {@link AudioManager}. * @param useSuggested True to use adjustSuggestedStreamVolume instead of - * adjustStreamVolume. */ - public void adjustVolume(int direction, int flags, String packageName, int uid, + public void adjustVolume(String packageName, int pid, int uid, int direction, int flags, boolean useSuggested) { int previousFlagPlaySound = flags & AudioManager.FLAG_PLAY_SOUND; if (isPlaybackActive() || hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) { @@ -263,7 +258,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { Log.w(TAG, "Muting remote playback is not supported"); return; } - mSessionCb.adjustVolume(direction); + mSessionCb.adjustVolume(packageName, pid, uid, direction); int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume); mOptimisticVolume = volumeBefore + direction; @@ -282,7 +277,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } } - public void setVolumeTo(int value, int flags, String packageName, int uid) { + public void setVolumeTo(String packageName, int pid, int uid, int value, int flags) { if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) { int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs); mAudioManagerInternal.setStreamVolumeForUid(stream, value, flags, packageName, uid); @@ -292,7 +287,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { return; } value = Math.max(0, Math.min(value, mMaxVolume)); - mSessionCb.setVolumeTo(value); + mSessionCb.setVolumeTo(packageName, pid, uid, value); int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume); mOptimisticVolume = Math.max(0, Math.min(value, mMaxVolume)); @@ -423,10 +418,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { return mSessionCb.mCb; } - public void sendMediaButton(KeyEvent ke, int sequenceId, - ResultReceiver cb, int uid, String packageName) { - updateCallingPackage(uid, packageName); - mSessionCb.sendMediaButton(ke, sequenceId, cb); + public void sendMediaButton(String packageName, int pid, int uid, KeyEvent ke, int sequenceId, + ResultReceiver cb) { + mSessionCb.sendMediaButton(packageName, pid, uid, ke, sequenceId, cb); } public void dump(PrintWriter pw, String prefix) { @@ -703,22 +697,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { return -1; } - private void updateCallingPackage() { - updateCallingPackage(UID_NOT_SET, null); - } - - private void updateCallingPackage(int uid, String packageName) { - if (uid == UID_NOT_SET) { - uid = Binder.getCallingUid(); - } - synchronized (mLock) { - if (mCallingUid == UID_NOT_SET || mCallingUid != uid) { - mCallingUid = uid; - mCallingPackage = packageName != null ? packageName : getPackageName(uid); - } - } - } - private String getPackageName(int uid) { Context context = mService.getContext(); if (context == null) { @@ -920,11 +898,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { mHandler.post(MessageHandler.MSG_UPDATE_VOLUME); } } - - @Override - public String getCallingPackage() { - return mCallingPackage; - } } class SessionCb { @@ -934,11 +907,12 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { mCb = cb; } - public boolean sendMediaButton(KeyEvent keyEvent, int sequenceId, ResultReceiver cb) { + public boolean sendMediaButton(String packageName, int pid, int uid, KeyEvent keyEvent, + int sequenceId, ResultReceiver cb) { Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); try { - mCb.onMediaButton(mediaButtonIntent, sequenceId, cb); + mCb.onMediaButton(packageName, pid, uid, mediaButtonIntent, sequenceId, cb); return true; } catch (RemoteException e) { Slog.e(TAG, "Remote failure in sendMediaRequest.", e); @@ -946,169 +920,176 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { return false; } - public void sendCommand(String command, Bundle args, ResultReceiver cb) { + public void sendCommand(String packageName, int pid, int uid, String command, Bundle args, + ResultReceiver cb) { try { - mCb.onCommand(command, args, cb); + mCb.onCommand(packageName, pid, uid, command, args, cb); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in sendCommand.", e); } } - public void sendCustomAction(String action, Bundle args) { + public void sendCustomAction(String packageName, int pid, int uid, String action, + Bundle args) { try { - mCb.onCustomAction(action, args); + mCb.onCustomAction(packageName, pid, uid, action, args); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in sendCustomAction.", e); } } - public void prepare() { + public void prepare(String packageName, int pid, int uid) { try { - mCb.onPrepare(); + mCb.onPrepare(packageName, pid, uid); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in prepare.", e); } } - public void prepareFromMediaId(String mediaId, Bundle extras) { + public void prepareFromMediaId(String packageName, int pid, int uid, String mediaId, + Bundle extras) { try { - mCb.onPrepareFromMediaId(mediaId, extras); + mCb.onPrepareFromMediaId(packageName, pid, uid, mediaId, extras); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in prepareFromMediaId.", e); } } - public void prepareFromSearch(String query, Bundle extras) { + public void prepareFromSearch(String packageName, int pid, int uid, String query, + Bundle extras) { try { - mCb.onPrepareFromSearch(query, extras); + mCb.onPrepareFromSearch(packageName, pid, uid, query, extras); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in prepareFromSearch.", e); } } - public void prepareFromUri(Uri uri, Bundle extras) { + public void prepareFromUri(String packageName, int pid, int uid, Uri uri, + Bundle extras) { try { - mCb.onPrepareFromUri(uri, extras); + mCb.onPrepareFromUri(packageName, pid, uid, uri, extras); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in prepareFromUri.", e); } } - public void play() { + public void play(String packageName, int pid, int uid) { try { - mCb.onPlay(); + mCb.onPlay(packageName, pid, uid); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in play.", e); } } - public void playFromMediaId(String mediaId, Bundle extras) { + public void playFromMediaId(String packageName, int pid, int uid, String mediaId, + Bundle extras) { try { - mCb.onPlayFromMediaId(mediaId, extras); + mCb.onPlayFromMediaId(packageName, pid, uid, mediaId, extras); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in playFromMediaId.", e); } } - public void playFromSearch(String query, Bundle extras) { + public void playFromSearch(String packageName, int pid, int uid, String query, + Bundle extras) { try { - mCb.onPlayFromSearch(query, extras); + mCb.onPlayFromSearch(packageName, pid, uid, query, extras); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in playFromSearch.", e); } } - public void playFromUri(Uri uri, Bundle extras) { + public void playFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras) { try { - mCb.onPlayFromUri(uri, extras); + mCb.onPlayFromUri(packageName, pid, uid, uri, extras); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in playFromUri.", e); } } - public void skipToTrack(long id) { + public void skipToTrack(String packageName, int pid, int uid, long id) { try { - mCb.onSkipToTrack(id); + mCb.onSkipToTrack(packageName, pid, uid, id); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in skipToTrack", e); } } - public void pause() { + public void pause(String packageName, int pid, int uid) { try { - mCb.onPause(); + mCb.onPause(packageName, pid, uid); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in pause.", e); } } - public void stop() { + public void stop(String packageName, int pid, int uid) { try { - mCb.onStop(); + mCb.onStop(packageName, pid, uid); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in stop.", e); } } - public void next() { + public void next(String packageName, int pid, int uid) { try { - mCb.onNext(); + mCb.onNext(packageName, pid, uid); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in next.", e); } } - public void previous() { + public void previous(String packageName, int pid, int uid) { try { - mCb.onPrevious(); + mCb.onPrevious(packageName, pid, uid); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in previous.", e); } } - public void fastForward() { + public void fastForward(String packageName, int pid, int uid) { try { - mCb.onFastForward(); + mCb.onFastForward(packageName, pid, uid); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in fastForward.", e); } } - public void rewind() { + public void rewind(String packageName, int pid, int uid) { try { - mCb.onRewind(); + mCb.onRewind(packageName, pid, uid); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in rewind.", e); } } - public void seekTo(long pos) { + public void seekTo(String packageName, int pid, int uid, long pos) { try { - mCb.onSeekTo(pos); + mCb.onSeekTo(packageName, pid, uid, pos); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in seekTo.", e); } } - public void rate(Rating rating) { + public void rate(String packageName, int pid, int uid, Rating rating) { try { - mCb.onRate(rating); + mCb.onRate(packageName, pid, uid, rating); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in rate.", e); } } - public void adjustVolume(int direction) { + public void adjustVolume(String packageName, int pid, int uid, int direction) { try { - mCb.onAdjustVolume(direction); + mCb.onAdjustVolume(packageName, pid, uid, direction); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in adjustVolume.", e); } } - public void setVolumeTo(int value) { + public void setVolumeTo(String packageName, int pid, int uid, int value) { try { - mCb.onSetVolumeTo(value); + mCb.onSetVolumeTo(packageName, pid, uid, value); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in setVolumeTo.", e); } @@ -1117,16 +1098,16 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { class ControllerStub extends ISessionController.Stub { @Override - public void sendCommand(String command, Bundle args, ResultReceiver cb) - throws RemoteException { - updateCallingPackage(); - mSessionCb.sendCommand(command, args, cb); + public void sendCommand(String packageName, String command, Bundle args, + ResultReceiver cb) { + mSessionCb.sendCommand(packageName, Binder.getCallingPid(), Binder.getCallingUid(), + command, args, cb); } @Override - public boolean sendMediaButton(KeyEvent mediaButtonIntent) { - updateCallingPackage(); - return mSessionCb.sendMediaButton(mediaButtonIntent, 0, null); + public boolean sendMediaButton(String packageName, KeyEvent mediaButtonIntent) { + return mSessionCb.sendMediaButton(packageName, Binder.getCallingPid(), + Binder.getCallingUid(), mediaButtonIntent, 0, null); } @Override @@ -1207,137 +1188,125 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } @Override - public void adjustVolume(int direction, int flags, String packageName) { - updateCallingPackage(); + public void adjustVolume(String packageName, int direction, int flags) { + int pid = Binder.getCallingPid(); int uid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { - MediaSessionRecord.this.adjustVolume(direction, flags, packageName, uid, false); + MediaSessionRecord.this.adjustVolume(packageName, pid, uid, direction, flags, + false /* useSuggested */); } finally { Binder.restoreCallingIdentity(token); } } @Override - public void setVolumeTo(int value, int flags, String packageName) { - updateCallingPackage(); + public void setVolumeTo(String packageName, int value, int flags) { + int pid = Binder.getCallingPid(); int uid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { - MediaSessionRecord.this.setVolumeTo(value, flags, packageName, uid); + MediaSessionRecord.this.setVolumeTo(packageName, pid, uid, value, flags); } finally { Binder.restoreCallingIdentity(token); } } @Override - public void prepare() throws RemoteException { - updateCallingPackage(); - mSessionCb.prepare(); + public void prepare(String packageName) { + mSessionCb.prepare(packageName, Binder.getCallingPid(), Binder.getCallingUid()); } @Override - public void prepareFromMediaId(String mediaId, Bundle extras) - throws RemoteException { - updateCallingPackage(); - mSessionCb.prepareFromMediaId(mediaId, extras); + public void prepareFromMediaId(String packageName, String mediaId, Bundle extras) { + mSessionCb.prepareFromMediaId(packageName, Binder.getCallingPid(), + Binder.getCallingUid(), mediaId, extras); } @Override - public void prepareFromSearch(String query, Bundle extras) throws RemoteException { - updateCallingPackage(); - mSessionCb.prepareFromSearch(query, extras); + public void prepareFromSearch(String packageName, String query, Bundle extras) { + mSessionCb.prepareFromSearch(packageName, Binder.getCallingPid(), + Binder.getCallingUid(), query, extras); } @Override - public void prepareFromUri(Uri uri, Bundle extras) throws RemoteException { - updateCallingPackage(); - mSessionCb.prepareFromUri(uri, extras); + public void prepareFromUri(String packageName, Uri uri, Bundle extras) { + mSessionCb.prepareFromUri(packageName, Binder.getCallingPid(), Binder.getCallingUid(), + uri, extras); } @Override - public void play() throws RemoteException { - updateCallingPackage(); - mSessionCb.play(); + public void play(String packageName) { + mSessionCb.play(packageName, Binder.getCallingPid(), Binder.getCallingUid()); } @Override - public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException { - updateCallingPackage(); - mSessionCb.playFromMediaId(mediaId, extras); + public void playFromMediaId(String packageName, String mediaId, Bundle extras) { + mSessionCb.playFromMediaId(packageName, Binder.getCallingPid(), Binder.getCallingUid(), + mediaId, extras); } @Override - public void playFromSearch(String query, Bundle extras) throws RemoteException { - updateCallingPackage(); - mSessionCb.playFromSearch(query, extras); + public void playFromSearch(String packageName, String query, Bundle extras) { + mSessionCb.playFromSearch(packageName, Binder.getCallingPid(), Binder.getCallingUid(), + query, extras); } @Override - public void playFromUri(Uri uri, Bundle extras) throws RemoteException { - updateCallingPackage(); - mSessionCb.playFromUri(uri, extras); + public void playFromUri(String packageName, Uri uri, Bundle extras) { + mSessionCb.playFromUri(packageName, Binder.getCallingPid(), Binder.getCallingUid(), + uri, extras); } @Override - public void skipToQueueItem(long id) { - updateCallingPackage(); - mSessionCb.skipToTrack(id); + public void skipToQueueItem(String packageName, long id) { + mSessionCb.skipToTrack(packageName, Binder.getCallingPid(), Binder.getCallingUid(), id); } @Override - public void pause() throws RemoteException { - updateCallingPackage(); - mSessionCb.pause(); + public void pause(String packageName) { + mSessionCb.pause(packageName, Binder.getCallingPid(), Binder.getCallingUid()); } @Override - public void stop() throws RemoteException { - updateCallingPackage(); - mSessionCb.stop(); + public void stop(String packageName) { + mSessionCb.stop(packageName, Binder.getCallingPid(), Binder.getCallingUid()); } @Override - public void next() throws RemoteException { - updateCallingPackage(); - mSessionCb.next(); + public void next(String packageName) { + mSessionCb.next(packageName, Binder.getCallingPid(), Binder.getCallingUid()); } @Override - public void previous() throws RemoteException { - updateCallingPackage(); - mSessionCb.previous(); + public void previous(String packageName) { + mSessionCb.previous(packageName, Binder.getCallingPid(), Binder.getCallingUid()); } @Override - public void fastForward() throws RemoteException { - updateCallingPackage(); - mSessionCb.fastForward(); + public void fastForward(String packageName) { + mSessionCb.fastForward(packageName, Binder.getCallingPid(), Binder.getCallingUid()); } @Override - public void rewind() throws RemoteException { - updateCallingPackage(); - mSessionCb.rewind(); + public void rewind(String packageName) { + mSessionCb.rewind(packageName, Binder.getCallingPid(), Binder.getCallingUid()); } @Override - public void seekTo(long pos) throws RemoteException { - updateCallingPackage(); - mSessionCb.seekTo(pos); + public void seekTo(String packageName, long pos) { + mSessionCb.seekTo(packageName, Binder.getCallingPid(), Binder.getCallingUid(), pos); } @Override - public void rate(Rating rating) throws RemoteException { - updateCallingPackage(); - mSessionCb.rate(rating); + public void rate(String packageName, Rating rating) { + mSessionCb.rate(packageName, Binder.getCallingPid(), Binder.getCallingUid(), rating); } @Override - public void sendCustomAction(String action, Bundle args) - throws RemoteException { - updateCallingPackage(); - mSessionCb.sendCustomAction(action, args); + public void sendCustomAction(String packageName, String action, Bundle args) { + mSessionCb.sendCustomAction(packageName, Binder.getCallingPid(), Binder.getCallingUid(), + action, args); } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 0eb906d5044b..6413ba986eae 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -1804,8 +1804,8 @@ public class MediaSessionService extends SystemService implements Monitor { } }); } else { - session.adjustVolume(direction, flags, getContext().getPackageName(), - Process.SYSTEM_UID, true); + session.adjustVolume(getContext().getPackageName(), Process.myPid(), + Process.SYSTEM_UID, direction, flags, true); } } @@ -1843,10 +1843,12 @@ public class MediaSessionService extends SystemService implements Monitor { mKeyEventReceiver.aquireWakeLockLocked(); } // If we don't need a wakelock use -1 as the id so we won't release it later. - session.sendMediaButton(keyEvent, + session.sendMediaButton(getContext().getPackageName(), + Process.myPid(), + Process.SYSTEM_UID, + keyEvent, needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, - mKeyEventReceiver, Process.SYSTEM_UID, - getContext().getPackageName()); + mKeyEventReceiver); if (mCurrentFullUserRecord.mCallback != null) { try { mCurrentFullUserRecord.mCallback.onMediaKeyEventDispatchedToMediaSession( diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index b7842d5365b5..d5a32aa30936 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -982,7 +982,11 @@ abstract public class ManagedServices { Slog.w(TAG, getCaption() + " binding died: " + name); synchronized (mMutex) { mServicesBinding.remove(servicesBindingTag); - mContext.unbindService(this); + try { + mContext.unbindService(this); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "failed to unbind " + name, e); + } if (!mServicesRebinding.contains(servicesBindingTag)) { mServicesRebinding.add(servicesBindingTag); mHandler.postDelayed(new Runnable() { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 210857e1b097..f31ca0a2fa09 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -182,6 +182,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BackgroundThread; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.util.ArrayUtils; @@ -898,6 +899,8 @@ public class NotificationManagerService extends SystemService { @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) { + // update system notification channels + SystemNotificationChannels.createAll(context); mZenModeHelper.updateDefaultZenRules(); mRankingHelper.onLocaleChanged(context, ActivityManager.getCurrentUser()); } @@ -2149,7 +2152,8 @@ public class NotificationManagerService extends SystemService { final NotificationChannel channel = channels.get(i); Preconditions.checkNotNull(channel, "channel in list is null"); mRankingHelper.createNotificationChannel(pkg, uid, channel, - true /* fromTargetApp */); + true /* fromTargetApp */, mConditionProviders.isPackageOrComponentAllowed( + pkg, UserHandle.getUserId(uid))); mListeners.notifyNotificationChannelChanged(pkg, UserHandle.getUserHandleForUid(uid), mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), false), @@ -4691,11 +4695,12 @@ public class NotificationManagerService extends SystemService { private boolean playSound(final NotificationRecord record, Uri soundUri) { boolean looping = (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0; - // do not play notifications if there is a user of exclusive audio focus - // or the device is in vibrate mode - if (!mAudioManager.isAudioFocusExclusive() && (mAudioManager.getRingerModeInternal() - != AudioManager.RINGER_MODE_VIBRATE || mAudioManager.getStreamVolume( - AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0)) { + // play notifications if there is no user of exclusive audio focus + // and the stream volume is not 0 (non-zero volume implies not silenced by SILENT or + // VIBRATE ringer mode) + if (!mAudioManager.isAudioFocusExclusive() + && (mAudioManager.getStreamVolume( + AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0)) { final long identity = Binder.clearCallingIdentity(); try { final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index b1b0bf26d9ee..af6468333ba0 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -38,7 +38,7 @@ public interface RankingConfig { ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg, int uid, boolean includeDeleted, boolean includeNonGrouped); void createNotificationChannel(String pkg, int uid, NotificationChannel channel, - boolean fromTargetApp); + boolean fromTargetApp, boolean hasDndAccess); void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser); NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted); void deleteNotificationChannel(String pkg, int uid, String channelId); diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index f16311320972..98d5c9a59f6b 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -569,7 +569,7 @@ public class RankingHelper implements RankingConfig { @Override public void createNotificationChannel(String pkg, int uid, NotificationChannel channel, - boolean fromTargetApp) { + boolean fromTargetApp, boolean hasDndAccess) { Preconditions.checkNotNull(pkg); Preconditions.checkNotNull(channel); Preconditions.checkNotNull(channel.getId()); @@ -610,8 +610,9 @@ public class RankingHelper implements RankingConfig { existing.setImportance(channel.getImportance()); } - // system apps can bypass dnd if the user hasn't changed any fields on the channel yet - if (existing.getUserLockedFields() == 0 & isSystemApp) { + // system apps and dnd access apps can bypass dnd if the user hasn't changed any + // fields on the channel yet + if (existing.getUserLockedFields() == 0 && (isSystemApp || hasDndAccess)) { existing.setBypassDnd(channel.canBypassDnd()); } @@ -624,7 +625,7 @@ public class RankingHelper implements RankingConfig { } // Reset fields that apps aren't allowed to set. - if (fromTargetApp && !isSystemApp) { + if (fromTargetApp && !(isSystemApp || hasDndAccess)) { channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX); } if (fromTargetApp) { diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java index c0c66b248ea5..6cf8f86aed83 100644 --- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java +++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java @@ -468,12 +468,14 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { private final LinkedList<String> mPendingLookups; private final Context mContext; + // Amount of time to wait for a result from the contacts db before rechecking affinity. + private static final long LOOKUP_TIME = 1000; private float mContactAffinity = NONE; private NotificationRecord mRecord; private PeopleRankingReconsideration(Context context, String key, LinkedList<String> pendingLookups) { - super(key); + super(key, LOOKUP_TIME); mContext = context; mPendingLookups = pendingLookups; } diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 24abf8657b88..9d3f48b428ce 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -484,11 +484,11 @@ public class Installer extends SystemService { } } - public void installApkVerity(String filePath, FileDescriptor verityInput) + public void installApkVerity(String filePath, FileDescriptor verityInput, int contentSize) throws InstallerException { if (!checkBeforeRemote()) return; try { - mInstalld.installApkVerity(filePath, verityInput); + mInstalld.installApkVerity(filePath, verityInput, contentSize); } 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 fb3839805b1a..8e78703f37c2 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -364,6 +364,17 @@ public class LauncherAppsService extends SystemService { } @Override + public Bundle getSuspendedPackageLauncherExtras(String packageName, + UserHandle user) { + if (!canAccessProfile(user.getIdentifier(), "Cannot get launcher extras")) { + return null; + } + final PackageManagerInternal pmi = + LocalServices.getService(PackageManagerInternal.class); + return pmi.getSuspendedPackageLauncherExtras(packageName, user.getIdentifier()); + } + + @Override public ApplicationInfo getApplicationInfo( String callingPackage, String packageName, int flags, UserHandle user) throws RemoteException { @@ -732,7 +743,7 @@ public class LauncherAppsService extends SystemService { } @Override - public void onPackagesSuspended(String[] packages) { + public void onPackagesSuspended(String[] packages, Bundle launcherExtras) { UserHandle user = new UserHandle(getChangingUserId()); final int n = mListeners.beginBroadcast(); try { @@ -741,7 +752,7 @@ public class LauncherAppsService extends SystemService { BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i); if (!isEnabledProfileOf(cookie.user, user, "onPackagesSuspended")) continue; try { - listener.onPackagesSuspended(user, packages); + listener.onPackagesSuspended(user, packages, launcherExtras); } catch (RemoteException re) { Slog.d(TAG, "Callback failed ", re); } @@ -750,7 +761,7 @@ public class LauncherAppsService extends SystemService { mListeners.finishBroadcast(); } - super.onPackagesSuspended(packages); + super.onPackagesSuspended(packages, launcherExtras); } @Override diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 61c6be7c84e1..892fa12ddf52 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -449,13 +449,13 @@ public class PackageDexOptimizer { pw.increaseIndent(); for (String isa : dexCodeInstructionSets) { - String status = null; try { - status = DexFile.getDexFileStatus(path, isa); + DexFile.OptimizationInfo info = DexFile.getDexFileOptimizationInfo(path, isa); + pw.println(isa + ": [status=" + info.getStatus() + +"] [reason=" + info.getReason() + "]"); } catch (IOException ioe) { - status = "[Exception]: " + ioe.getMessage(); + pw.println(isa + ": [Exception]: " + ioe.getMessage()); } - pw.println(isa + ": " + status); } if (useInfo.isUsedByOtherApps(path)) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 775fdaa8d7ca..52bdea6cbf9a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -828,11 +828,9 @@ public class PackageManagerService extends IPackageManager.Stub // // OverlayManagerService will update each of them with a correct gid from its // target package app id. - synchronized (mInstallLock) { - mInstaller.idmap(targetPath, overlayPackage.baseCodePath, - UserHandle.getSharedAppGid( - UserHandle.getUserGid(UserHandle.USER_SYSTEM))); - } + mInstaller.idmap(targetPath, overlayPackage.baseCodePath, + UserHandle.getSharedAppGid( + UserHandle.getUserGid(UserHandle.USER_SYSTEM))); if (overlayPathList == null) { overlayPathList = new ArrayList<String>(); } @@ -847,13 +845,15 @@ public class PackageManagerService extends IPackageManager.Stub String[] getStaticOverlayPaths(String targetPackageName, String targetPath) { List<PackageParser.Package> overlayPackages; - synchronized (mPackages) { - overlayPackages = getStaticOverlayPackages( - mPackages.values(), targetPackageName); + synchronized (mInstallLock) { + synchronized (mPackages) { + overlayPackages = getStaticOverlayPackages( + mPackages.values(), targetPackageName); + } + // It is safe to keep overlayPackages without holding mPackages because static overlay + // packages can't be uninstalled or disabled. + return getStaticOverlayPaths(overlayPackages, targetPath); } - // It is safe to keep overlayPackages without holding mPackages because static overlay - // packages can't be uninstalled or disabled. - return getStaticOverlayPaths(overlayPackages, targetPath); } @Override public final String[] getOverlayApks(String targetPackageName) { @@ -886,7 +886,9 @@ public class PackageManagerService extends IPackageManager.Stub synchronized String[] getStaticOverlayPaths(String targetPackageName, String targetPath) { // We can trust mOverlayPackages without holding mPackages because package uninstall // can't happen while running parallel parsing. - // Moreover holding mPackages on each parsing thread causes dead-lock. + // And we can call mInstaller inside getStaticOverlayPaths without holding mInstallLock + // because mInstallLock is held before running parallel parsing. + // Moreover holding mPackages or mInstallLock on each parsing thread causes dead-lock. return mOverlayPackages == null ? null : getStaticOverlayPaths( getStaticOverlayPackages(mOverlayPackages, targetPackageName), @@ -13816,11 +13818,15 @@ public class PackageManagerService extends IPackageManager.Stub info.sendPackageRemovedBroadcasts(true /*killApp*/); } - private void sendPackagesSuspendedForUser(String[] pkgList, int userId, boolean suspended) { + private void sendPackagesSuspendedForUser(String[] pkgList, int userId, boolean suspended, + PersistableBundle launcherExtras) { if (pkgList.length > 0) { Bundle extras = new Bundle(1); extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList); - + if (launcherExtras != null) { + extras.putBundle(Intent.EXTRA_LAUNCHER_EXTRAS, + new Bundle(launcherExtras.deepCopy())); + } sendPackageBroadcast( suspended ? Intent.ACTION_PACKAGES_SUSPENDED : Intent.ACTION_PACKAGES_UNSUSPENDED, @@ -14032,7 +14038,7 @@ public class PackageManagerService extends IPackageManager.Stub if (!changedPackagesList.isEmpty()) { final String[] changedPackages = changedPackagesList.toArray( new String[changedPackagesList.size()]); - sendPackagesSuspendedForUser(changedPackages, userId, suspended); + sendPackagesSuspendedForUser(changedPackages, userId, suspended, launcherExtras); sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, appExtras, userId); synchronized (mPackages) { scheduleWritePackageRestrictionsLocked(userId); @@ -16801,12 +16807,6 @@ public class PackageManagerService extends IPackageManager.Stub if (userId != UserHandle.USER_ALL) { ps.setInstalled(true, userId); ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName); - } else { - for (int currentUserId : sUserManager.getUserIds()) { - ps.setInstalled(true, currentUserId); - ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, currentUserId, - installerPackageName); - } } // When replacing an existing package, preserve the original install reason for all @@ -17326,8 +17326,11 @@ public class PackageManagerService extends IPackageManager.Stub if (Build.IS_DEBUGGABLE) Slog.i(TAG, "Enabling apk verity to " + apkPath); FileDescriptor fd = result.getUnownedFileDescriptor(); try { - mInstaller.installApkVerity(apkPath, fd); - } catch (InstallerException e) { + final byte[] signedRootHash = VerityUtils.generateFsverityRootHash(apkPath); + mInstaller.installApkVerity(apkPath, fd, result.getContentSize()); + mInstaller.assertFsverityRootHashMatches(apkPath, signedRootHash); + } catch (InstallerException | IOException | DigestException | + NoSuchAlgorithmException e) { res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Failed to set up verity: " + e); return; @@ -23745,6 +23748,18 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } @Override + public Bundle getSuspendedPackageLauncherExtras(String packageName, int userId) { + synchronized (mPackages) { + final PackageSetting ps = mSettings.mPackages.get(packageName); + PersistableBundle launcherExtras = null; + if (ps != null) { + launcherExtras = ps.readUserState(userId).suspendedLauncherExtras; + } + return (launcherExtras != null) ? new Bundle(launcherExtras.deepCopy()) : null; + } + } + + @Override public int getPackageUid(String packageName, int flags, int userId) { return PackageManagerService.this .getPackageUid(packageName, flags, userId); diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java index cdafe5715ffc..9c2ad4635d58 100644 --- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java +++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java @@ -527,9 +527,10 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { String compilationFilter; try { String isa = VMRuntime.getInstructionSet(abi); - String[] stats = DexFile.getDexFileOptimizationStatus(info.getBaseCodePath(), isa); - compilationFilter = stats[0]; - compilationReason = stats[1]; + DexFile.OptimizationInfo optInfo = + DexFile.getDexFileOptimizationInfo(info.getBaseCodePath(), isa); + compilationFilter = optInfo.getStatus(); + compilationReason = optInfo.getReason(); } catch (FileNotFoundException e) { Slog.e(TAG, "Could not get optimizations status for " + info.getBaseCodePath(), e); compilationFilter = "error"; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 2e6e348c51a4..fd023476c9e4 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1330,6 +1330,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { mHandler.post(mHiddenNavPanic); } + // Abort possibly stuck animations. + mHandler.post(mWindowManagerFuncs::triggerAnimationFailsafe); + // Latch power key state to detect screenshot chord. if (interactive && !mScreenshotChordPowerKeyTriggered && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { @@ -4362,6 +4365,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { * given the situation with the keyguard. */ void launchHomeFromHotKey(final boolean awakenFromDreams, final boolean respectKeyguard) { + // Abort possibly stuck animations. + mHandler.post(mWindowManagerFuncs::triggerAnimationFailsafe); + if (respectKeyguard) { if (isKeyguardShowingAndNotOccluded()) { // don't launch home if keyguard showing @@ -7175,7 +7181,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - public int rotationForOrientationLw(int orientation, int lastRotation) { + public int rotationForOrientationLw(int orientation, int lastRotation, boolean defaultDisplay) { if (false) { Slog.v(TAG, "rotationForOrientationLw(orient=" + orientation + ", last=" + lastRotation @@ -7196,7 +7202,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } final int preferredRotation; - if (mLidState == LID_OPEN && mLidOpenRotation >= 0) { + if (!defaultDisplay) { + // For secondary displays we ignore things like displays sensors, docking mode and + // rotation lock, and always prefer a default rotation. + preferredRotation = Surface.ROTATION_0; + } else if (mLidState == LID_OPEN && mLidOpenRotation >= 0) { // Ignore sensor when lid switch is open and rotation is forced. preferredRotation = mLidOpenRotation; } else if (mDockMode == Intent.EXTRA_DOCK_STATE_CAR diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index ec0521dda40d..0a6ae4e2f2d9 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -407,7 +407,10 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { /** * Returns true if this window has been shown on screen at some time in * the past. Must be called with the window manager lock held. + * + * @deprecated Use {@link #isDrawnLw} or any of the other drawn/visibility methods. */ + @Deprecated public boolean hasDrawnLw(); /** @@ -649,6 +652,12 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { return Integer.toString(lens); } } + + /** + * Hint to window manager that the user has started a navigation action that should + * abort animations that have no timeout, in case they got stuck. + */ + void triggerAnimationFailsafe(); } /** Window has been added to the screen. */ @@ -1426,10 +1435,13 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { * @param orientation An orientation constant, such as * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_LANDSCAPE}. * @param lastRotation The most recently used rotation. + * @param defaultDisplay Flag indicating whether the rotation is computed for the default + * display. Currently for all non-default displays sensors, docking mode, + * rotation lock and other factors are ignored. * @return The surface rotation to use. */ public int rotationForOrientationLw(@ActivityInfo.ScreenOrientation int orientation, - int lastRotation); + int lastRotation, boolean defaultDisplay); /** * Given an orientation constant and a rotation, returns true if the rotation diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java index 941cd4441e23..e56caf849aac 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java @@ -19,6 +19,8 @@ package com.android.server.policy.keyguard; import android.app.ActivityManager; import android.content.Context; import android.os.RemoteException; +import android.os.ServiceManager; +import android.security.IKeystoreService; import android.util.Slog; import com.android.internal.policy.IKeyguardService; @@ -51,11 +53,16 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub { private final LockPatternUtils mLockPatternUtils; private final StateCallback mCallback; + IKeystoreService mKeystoreService; + public KeyguardStateMonitor(Context context, IKeyguardService service, StateCallback callback) { mLockPatternUtils = new LockPatternUtils(context); mCurrentUserId = ActivityManager.getCurrentUser(); mCallback = callback; + mKeystoreService = IKeystoreService.Stub.asInterface(ServiceManager + .getService("android.security.keystore")); + try { service.addStateMonitorCallback(this); } catch (RemoteException e) { @@ -86,6 +93,12 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub { @Override // Binder interface public void onShowingStateChanged(boolean showing) { mIsShowing = showing; + + try { + mKeystoreService.onKeyguardVisibilityChanged(showing, mCurrentUserId); + } catch (RemoteException e) { + Slog.e(TAG, "Error informing keystore of screen lock", e); + } } @Override // Binder interface @@ -130,4 +143,4 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub { pw.println(prefix + "mTrusted=" + mTrusted); pw.println(prefix + "mCurrentUserId=" + mCurrentUserId); } -}
\ No newline at end of file +} diff --git a/services/core/java/com/android/server/security/VerityUtils.java b/services/core/java/com/android/server/security/VerityUtils.java index d2d0e60dc742..180f34355c94 100644 --- a/services/core/java/com/android/server/security/VerityUtils.java +++ b/services/core/java/com/android/server/security/VerityUtils.java @@ -26,6 +26,7 @@ import android.system.Os; import android.util.apk.ApkSignatureVerifier; import android.util.apk.ByteBufferFactory; import android.util.apk.SignatureNotFoundException; +import android.util.Pair; import android.util.Slog; import java.io.FileDescriptor; @@ -59,12 +60,15 @@ abstract public class VerityUtils { return SetupResult.skipped(); } - shm = generateApkVerityIntoSharedMemory(apkPath, signedRootHash); + Pair<SharedMemory, Integer> result = generateApkVerityIntoSharedMemory(apkPath, + signedRootHash); + shm = result.first; + int contentSize = result.second; FileDescriptor rfd = shm.getFileDescriptor(); if (rfd == null || !rfd.valid()) { return SetupResult.failed(); } - return SetupResult.ok(Os.dup(rfd)); + return SetupResult.ok(Os.dup(rfd), contentSize); } catch (IOException | SecurityException | DigestException | NoSuchAlgorithmException | SignatureNotFoundException | ErrnoException e) { Slog.e(TAG, "Failed to set up apk verity: ", e); @@ -85,10 +89,20 @@ abstract public class VerityUtils { } /** - * Returns a {@code SharedMemory} that contains Merkle tree and fsverity headers for the given - * apk, in the form that can immediately be used for fsverity setup. + * {@see ApkSignatureVerifier#getVerityRootHash(String)}. */ - private static SharedMemory generateApkVerityIntoSharedMemory( + public static byte[] getVerityRootHash(@NonNull String apkPath) + throws IOException, SignatureNotFoundException, SecurityException { + return ApkSignatureVerifier.getVerityRootHash(apkPath); + } + + /** + * Returns a pair of {@code SharedMemory} and {@code Integer}. The {@code SharedMemory} contains + * Merkle tree and fsverity headers for the given apk, in the form that can immediately be used + * for fsverity setup. The data is aligned to the beginning of {@code SharedMemory}, and has + * length equals to the returned {@code Integer}. + */ + private static Pair<SharedMemory, Integer> generateApkVerityIntoSharedMemory( String apkPath, byte[] expectedRootHash) throws IOException, SecurityException, DigestException, NoSuchAlgorithmException, SignatureNotFoundException { @@ -101,6 +115,7 @@ abstract public class VerityUtils { throw new SecurityException("Locally generated verity root hash does not match"); } + int contentSize = shmBufferFactory.getBufferLimit(); SharedMemory shm = shmBufferFactory.releaseSharedMemory(); if (shm == null) { throw new IllegalStateException("Failed to generate verity tree into shared memory"); @@ -108,7 +123,7 @@ abstract public class VerityUtils { if (!shm.setProtect(PROT_READ)) { throw new SecurityException("Failed to set up shared memory correctly"); } - return shm; + return Pair.create(shm, contentSize); } public static class SetupResult { @@ -123,22 +138,24 @@ abstract public class VerityUtils { private final int mCode; private final FileDescriptor mFileDescriptor; + private final int mContentSize; - public static SetupResult ok(@NonNull FileDescriptor fileDescriptor) { - return new SetupResult(RESULT_OK, fileDescriptor); + public static SetupResult ok(@NonNull FileDescriptor fileDescriptor, int contentSize) { + return new SetupResult(RESULT_OK, fileDescriptor, contentSize); } public static SetupResult skipped() { - return new SetupResult(RESULT_SKIPPED, null); + return new SetupResult(RESULT_SKIPPED, null, -1); } public static SetupResult failed() { - return new SetupResult(RESULT_FAILED, null); + return new SetupResult(RESULT_FAILED, null, -1); } - private SetupResult(int code, FileDescriptor fileDescriptor) { + private SetupResult(int code, FileDescriptor fileDescriptor, int contentSize) { this.mCode = code; this.mFileDescriptor = fileDescriptor; + this.mContentSize = contentSize; } public boolean isFailed() { @@ -152,6 +169,10 @@ abstract public class VerityUtils { public @NonNull FileDescriptor getUnownedFileDescriptor() { return mFileDescriptor; } + + public int getContentSize() { + return mContentSize; + } } /** A {@code ByteBufferFactory} that creates a shared memory backed {@code ByteBuffer}. */ @@ -188,5 +209,9 @@ abstract public class VerityUtils { mShm = null; return tmp; } + + public int getBufferLimit() { + return mBuffer == null ? -1 : mBuffer.limit(); + } } } diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java index 0b7d9d02f1cb..fd0b6f1eb457 100644 --- a/services/core/java/com/android/server/slice/SliceManagerService.java +++ b/services/core/java/com/android/server/slice/SliceManagerService.java @@ -168,7 +168,8 @@ public class SliceManagerService extends ISliceManager.Stub { } @Override - public void pinSlice(String pkg, Uri uri, SliceSpec[] specs, IBinder token) throws RemoteException { + public void pinSlice(String pkg, Uri uri, SliceSpec[] specs, IBinder token) + throws RemoteException { verifyCaller(pkg); enforceAccess(pkg, uri); int user = Binder.getCallingUserHandle().getIdentifier(); @@ -210,7 +211,8 @@ public class SliceManagerService extends ISliceManager.Stub { } @Override - public int checkSlicePermission(Uri uri, String pkg, int pid, int uid) throws RemoteException { + public int checkSlicePermission(Uri uri, String pkg, int pid, int uid, + String[] autoGrantPermissions) throws RemoteException { if (mContext.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == PERMISSION_GRANTED) { return SliceManager.PERMISSION_GRANTED; @@ -218,6 +220,11 @@ public class SliceManagerService extends ISliceManager.Stub { if (hasFullSliceAccess(pkg, UserHandle.getUserId(uid))) { return SliceManager.PERMISSION_GRANTED; } + for (String perm : autoGrantPermissions) { + if (mContext.checkPermission(perm, pid, uid) == PERMISSION_GRANTED) { + return SliceManager.PERMISSION_USER_GRANTED; + } + } synchronized (mLock) { if (mUserGrants.contains(new SliceGrant(uri, pkg, UserHandle.getUserId(uid)))) { return SliceManager.PERMISSION_USER_GRANTED; diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index b3d28fcbc4f1..fae0b24ff535 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -39,6 +39,7 @@ import android.os.BatteryStatsInternal; import android.os.Binder; import android.os.Bundle; import android.os.Environment; +import android.os.FileUtils; import android.os.IBinder; import android.os.IStatsCompanionService; import android.os.IStatsManager; @@ -68,14 +69,21 @@ import com.android.internal.os.KernelUidCpuFreqTimeReader; import com.android.internal.os.KernelWakelockReader; import com.android.internal.os.KernelWakelockStats; import com.android.internal.os.PowerProfile; +import com.android.internal.util.DumpUtils; import com.android.server.LocalServices; import com.android.server.SystemService; +import java.io.File; +import java.io.FileDescriptor; import java.io.IOException; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** @@ -89,14 +97,17 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { * How long to wait on an individual subsystem to return its stats. */ private static final long EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS = 2000; + private static final long MILLIS_IN_A_DAY = TimeUnit.DAYS.toMillis(1); public static final String RESULT_RECEIVER_CONTROLLER_KEY = "controller_activity"; + public static final String CONFIG_DIR = "/data/misc/stats-service"; static final String TAG = "StatsCompanionService"; static final boolean DEBUG = false; public static final int CODE_DATA_BROADCAST = 1; public static final int CODE_SUBSCRIBER_BROADCAST = 1; + public static final int DEATH_THRESHOLD = 10; private final Context mContext; private final AlarmManager mAlarmManager; @@ -119,6 +130,10 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { new StatFs(Environment.getRootDirectory().getAbsolutePath()); private final StatFs mStatFsTemp = new StatFs(Environment.getDownloadCacheDirectory().getAbsolutePath()); + @GuardedBy("sStatsdLock") + private final HashSet<Long> mDeathTimeMillis = new HashSet<>(); + @GuardedBy("sStatsdLock") + private final HashMap<Long, String> mDeletedFiles = new HashMap<>(); private KernelUidCpuTimeReader mKernelUidCpuTimeReader = new KernelUidCpuTimeReader(); private KernelCpuSpeedReader[] mKernelCpuSpeedReaders; @@ -156,7 +171,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { informAllUidsLocked(context); } catch (RemoteException e) { Slog.e(TAG, "Failed to inform statsd latest update of all apps", e); - forgetEverything(); + forgetEverythingLocked(); } } } @@ -466,34 +481,32 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } @Override // Binder call - public void setPullingAlarms(long timestampMs, long intervalMs) { - enforceCallingPermission(); - if (DEBUG) - Slog.d(TAG, "Setting pulling alarm for " + timestampMs + " every " + intervalMs + "ms"); - final long callingToken = Binder.clearCallingIdentity(); - try { - // using ELAPSED_REALTIME, not ELAPSED_REALTIME_WAKEUP, so if device is asleep, will - // only fire when it awakens. - // This alarm is inexact, leaving its exactness completely up to the OS optimizations. - // TODO: totally inexact means that stats per bucket could be quite off. Is this okay? - mAlarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, timestampMs, intervalMs, - mPullingAlarmIntent); - } finally { - Binder.restoreCallingIdentity(callingToken); - } + public void setPullingAlarm(long nextPullTimeMs) { + enforceCallingPermission(); + if (DEBUG) + Slog.d(TAG, + "Setting pulling alarm in about " + (nextPullTimeMs - SystemClock.elapsedRealtime())); + final long callingToken = Binder.clearCallingIdentity(); + try { + // using ELAPSED_REALTIME, not ELAPSED_REALTIME_WAKEUP, so if device is asleep, will + // only fire when it awakens. + mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME, nextPullTimeMs, mPullingAlarmIntent); + } finally { + Binder.restoreCallingIdentity(callingToken); + } } @Override // Binder call - public void cancelPullingAlarms() { - enforceCallingPermission(); - if (DEBUG) - Slog.d(TAG, "Cancelling pulling alarm"); - final long callingToken = Binder.clearCallingIdentity(); - try { - mAlarmManager.cancel(mPullingAlarmIntent); - } finally { - Binder.restoreCallingIdentity(callingToken); - } + public void cancelPullingAlarm() { + enforceCallingPermission(); + if (DEBUG) + Slog.d(TAG, "Cancelling pulling alarm"); + final long callingToken = Binder.clearCallingIdentity(); + try { + mAlarmManager.cancel(mPullingAlarmIntent); + } finally { + Binder.restoreCallingIdentity(callingToken); + } } private void addNetworkStats( @@ -660,12 +673,14 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private void pullBluetoothBytesTransfer(int tagId, List<StatsLogEventWrapper> pulledData) { BluetoothActivityEnergyInfo info = pullBluetoothData(); long elapsedNanos = SystemClock.elapsedRealtimeNanos(); - for (UidTraffic traffic : info.getUidTraffic()) { - StatsLogEventWrapper e = new StatsLogEventWrapper(elapsedNanos, tagId, 3); - e.writeInt(traffic.getUid()); - e.writeLong(traffic.getRxBytes()); - e.writeLong(traffic.getTxBytes()); - pulledData.add(e); + if (info.getUidTraffic() != null) { + for (UidTraffic traffic : info.getUidTraffic()) { + StatsLogEventWrapper e = new StatsLogEventWrapper(elapsedNanos, tagId, 3); + e.writeInt(traffic.getUid()); + e.writeLong(traffic.getRxBytes()); + e.writeLong(traffic.getTxBytes()); + pulledData.add(e); + } } } @@ -753,7 +768,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { }); } - private void pullWifiActivityEnergyInfo(int tagId, List<StatsLogEventWrapper> pulledData) { + private void pullWifiActivityInfo(int tagId, List<StatsLogEventWrapper> pulledData) { long token = Binder.clearCallingIdentity(); if (mWifiManager == null) { mWifiManager = @@ -921,8 +936,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { pullKernelUidCpuActiveTime(tagId, ret); break; } - case StatsLog.WIFI_ACTIVITY_ENERGY_INFO: { - pullWifiActivityEnergyInfo(tagId, ret); + case StatsLog.WIFI_ACTIVITY_INFO: { + pullWifiActivityInfo(tagId, ret); break; } case StatsLog.MODEM_ACTIVITY_INFO: { @@ -1057,7 +1072,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { sStatsd.asBinder().linkToDeath(new StatsdDeathRecipient(), 0); } catch (RemoteException e) { Slog.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e); - forgetEverything(); + forgetEverythingLocked(); } // Setup broadcast receiver for updates. IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REPLACED); @@ -1089,7 +1104,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { Slog.i(TAG, "Told statsd that StatsCompanionService is alive."); } catch (RemoteException e) { Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e); - forgetEverything(); + forgetEverythingLocked(); } } } @@ -1098,18 +1113,60 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override public void binderDied() { Slog.i(TAG, "Statsd is dead - erase all my knowledge."); - forgetEverything(); + synchronized (sStatsdLock) { + long now = SystemClock.elapsedRealtime(); + for (Long timeMillis : mDeathTimeMillis) { + long ageMillis = now - timeMillis; + if (ageMillis > MILLIS_IN_A_DAY) { + mDeathTimeMillis.remove(timeMillis); + } + } + for (Long timeMillis : mDeletedFiles.keySet()) { + long ageMillis = now - timeMillis; + if (ageMillis > MILLIS_IN_A_DAY * 7) { + mDeletedFiles.remove(timeMillis); + } + } + mDeathTimeMillis.add(now); + if (mDeathTimeMillis.size() >= DEATH_THRESHOLD) { + mDeathTimeMillis.clear(); + File[] configs = FileUtils.listFilesOrEmpty(new File(CONFIG_DIR)); + if (configs.length > 0) { + String fileName = configs[0].getName(); + if (configs[0].delete()) { + mDeletedFiles.put(now, fileName); + } + } + } + forgetEverythingLocked(); + } } } - private void forgetEverything() { + private void forgetEverythingLocked() { + sStatsd = null; + mContext.unregisterReceiver(mAppUpdateReceiver); + mContext.unregisterReceiver(mUserUpdateReceiver); + mContext.unregisterReceiver(mShutdownEventReceiver); + cancelAnomalyAlarm(); + cancelPullingAlarm(); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return; + synchronized (sStatsdLock) { - sStatsd = null; - mContext.unregisterReceiver(mAppUpdateReceiver); - mContext.unregisterReceiver(mUserUpdateReceiver); - mContext.unregisterReceiver(mShutdownEventReceiver); - cancelAnomalyAlarm(); - cancelPullingAlarms(); + writer.println("Number of configuration files deleted: " + mDeletedFiles.size()); + if (mDeletedFiles.size() > 0) { + writer.println(" timestamp, deleted file name"); + } + long lastBootMillis = + SystemClock.currentThreadTimeMillis() - SystemClock.elapsedRealtime(); + for (Long elapsedMillis : mDeletedFiles.keySet()) { + long deletionMillis = lastBootMillis + elapsedMillis; + writer.println(" " + deletionMillis + ", " + mDeletedFiles.get(elapsedMillis)); + } } } diff --git a/services/core/java/com/android/server/wm/AlertWindowNotification.java b/services/core/java/com/android/server/wm/AlertWindowNotification.java index 9b787deb8298..9177d253e464 100644 --- a/services/core/java/com/android/server/wm/AlertWindowNotification.java +++ b/services/core/java/com/android/server/wm/AlertWindowNotification.java @@ -37,6 +37,8 @@ import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Bundle; + import com.android.internal.R; import com.android.server.policy.IconUtilities; @@ -109,6 +111,8 @@ class AlertWindowNotification { final String message = context.getString(R.string.alert_windows_notification_message, appName); + Bundle extras = new Bundle(); + extras.putStringArray(Notification.EXTRA_FOREGROUND_APPS, new String[] {mPackageName}); final Notification.Builder builder = new Notification.Builder(context, mNotificationTag) .setOngoing(true) .setContentTitle( @@ -118,6 +122,7 @@ class AlertWindowNotification { .setColor(context.getColor(R.color.system_notification_accent_color)) .setStyle(new Notification.BigTextStyle().bigText(message)) .setLocalOnly(true) + .addExtras(extras) .setContentIntent(getContentIntent(context, mPackageName)); if (aInfo != null) { diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 56c9e51d5d3a..f19c554ace97 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -1485,7 +1485,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree if (w == null || winHint != null && w != winHint) { return; } - final boolean surfaceReady = w.hasDrawnLw() // Regular case + final boolean surfaceReady = w.isDrawnLw() // Regular case || w.mWinAnimator.mSurfaceDestroyDeferred // The preserved surface is still ready. || w.isDragResizeChanged(); // Waiting for relayoutWindow to call preserveSurface. final boolean needsLetterbox = w.isLetterboxedAppWindow() && fillsParent() && surfaceReady; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 2ffdbfd4c646..c9ff9e3ba35b 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -909,6 +909,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mRotation; } + @VisibleForTesting void setRotation(int newRotation) { mRotation = newRotation; } @@ -974,7 +975,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final int oldRotation = mRotation; final int lastOrientation = mLastOrientation; final boolean oldAltOrientation = mAltOrientation; - int rotation = mService.mPolicy.rotationForOrientationLw(lastOrientation, oldRotation); + final int rotation = mService.mPolicy.rotationForOrientationLw(lastOrientation, oldRotation, + isDefaultDisplay); + if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Computed rotation=" + rotation + " for display id=" + + mDisplayId + " based on lastOrientation=" + lastOrientation + + " and oldRotation=" + oldRotation); boolean mayRotateSeamlessly = mService.mPolicy.shouldRotateSeamlessly(oldRotation, rotation); @@ -1010,7 +1015,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final boolean altOrientation = !mService.mPolicy.rotationHasCompatibleMetricsLw( lastOrientation, rotation); - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Selected orientation " + lastOrientation + if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display id=" + mDisplayId + + " selected orientation " + lastOrientation + ", got rotation " + rotation + " which has " + (altOrientation ? "incompatible" : "compatible") + " metrics"); @@ -1019,7 +1025,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return false; } - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Rotation changed to " + rotation + if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display id=" + mDisplayId + + " rotation changed to " + rotation + (altOrientation ? " (alt)" : "") + " from " + oldRotation + (oldAltOrientation ? " (alt)" : "") + ", lastOrientation=" + lastOrientation); @@ -1647,8 +1654,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo if (mService.mDisplayFrozen) { if (mLastWindowForcedOrientation != SCREEN_ORIENTATION_UNSPECIFIED) { - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, - "Display is frozen, return " + mLastWindowForcedOrientation); + if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display id=" + mDisplayId + + " is frozen, return " + mLastWindowForcedOrientation); // If the display is frozen, some activities may be in the middle of restarting, and // thus have removed their old window. If the window has the flag to hide the lock // screen, then the lock screen can re-appear and inflict its own orientation on us. @@ -1660,8 +1667,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // window. We don't want to check the show when locked window directly though as // things aren't stable while the display is frozen, for example the window could be // momentarily unavailable due to activity relaunch. - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display is frozen while keyguard locked, " - + "return " + mLastOrientation); + if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display id=" + mDisplayId + + " is frozen while keyguard locked, return " + mLastOrientation); return mLastOrientation; } } else { @@ -3503,19 +3510,22 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // In a car, you cannot physically rotate the screen, so it doesn't make sense to // allow anything but the default orientation. if (DEBUG_ORIENTATION) Slog.v(TAG_WM, - "Forcing UNSPECIFIED orientation in car. Ignoring " + orientation); + "Forcing UNSPECIFIED orientation in car for display id=" + mDisplayId + + ". Ignoring " + orientation); return SCREEN_ORIENTATION_UNSPECIFIED; } if (orientation != SCREEN_ORIENTATION_UNSET && orientation != SCREEN_ORIENTATION_BEHIND) { if (DEBUG_ORIENTATION) Slog.v(TAG_WM, - "App is requesting an orientation, return " + orientation); + "App is requesting an orientation, return " + orientation + + " for display id=" + mDisplayId); return orientation; } if (DEBUG_ORIENTATION) Slog.v(TAG_WM, - "No app is requesting an orientation, return " + mLastOrientation); + "No app is requesting an orientation, return " + mLastOrientation + + " for display id=" + mDisplayId); // The next app has not been requested to be visible, so we keep the current orientation // to prevent freezing/unfreezing the display too early. return mLastOrientation; @@ -3708,7 +3718,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return SCREEN_ORIENTATION_UNSET; } } - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, win + " forcing orientation to " + req); + if (DEBUG_ORIENTATION) Slog.v(TAG_WM, win + " forcing orientation to " + req + + " for display id=" + mDisplayId); return (mLastWindowForcedOrientation = req); } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 10188487fd9b..7274aee3c987 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -34,6 +34,7 @@ import android.app.WindowConfiguration; import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; +import android.os.IBinder.DeathRecipient; import android.os.RemoteException; import android.os.SystemClock; import android.util.ArraySet; @@ -61,15 +62,17 @@ import java.util.ArrayList; * window manager when the animation is completed. In addition, window manager may also notify the * app if it requires the animation to be canceled at any time (ie. due to timeout, etc.) */ -public class RecentsAnimationController { +public class RecentsAnimationController implements DeathRecipient { private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentsAnimationController" : TAG_WM; private static final boolean DEBUG = false; + private static final long FAILSAFE_DELAY = 1000; private final WindowManagerService mService; private final IRecentsAnimationRunner mRunner; private final RecentsAnimationCallbacks mCallbacks; private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>(); private final int mDisplayId; + private final Runnable mFailsafeRunnable = this::cancelAnimation; // The recents component app token that is shown behind the visibile tasks private AppWindowToken mHomeAppToken; @@ -223,6 +226,13 @@ public class RecentsAnimationController { return; } + try { + mRunner.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + cancelAnimation(); + return; + } + // Adjust the wallpaper visibility for the showing home activity final AppWindowToken recentsComponentAppToken = dc.getHomeStack().getTopChild().getTopFullscreenAppToken(); @@ -296,6 +306,7 @@ public class RecentsAnimationController { // We've already canceled the animation return; } + mService.mH.removeCallbacks(mFailsafeRunnable); mCanceled = true; try { mRunner.onAnimationCanceled(); @@ -321,10 +332,21 @@ public class RecentsAnimationController { } mPendingAnimations.clear(); + mRunner.asBinder().unlinkToDeath(this, 0); + mService.mInputMonitor.updateInputWindowsLw(true /*force*/); mService.destroyInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION); } + void scheduleFailsafe() { + mService.mH.postDelayed(mFailsafeRunnable, FAILSAFE_DELAY); + } + + @Override + public void binderDied() { + cancelAnimation(); + } + void checkAnimationReady(WallpaperController wallpaperController) { if (mPendingStart) { final boolean wallpaperReady = !isHomeAppOverWallpaper() diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index 379a1a1528b4..3be7b23590e5 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -26,6 +26,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; import android.os.Handler; +import android.os.IBinder.DeathRecipient; import android.os.RemoteException; import android.os.SystemClock; import android.util.Slog; @@ -47,7 +48,7 @@ import java.util.ArrayList; /** * Helper class to run app animations in a remote process. */ -class RemoteAnimationController { +class RemoteAnimationController implements DeathRecipient { private static final String TAG = TAG_WITH_CLASS_NAME ? "RemoteAnimationController" : TAG_WM; private static final long TIMEOUT_MS = 2000; @@ -56,12 +57,10 @@ class RemoteAnimationController { private final ArrayList<RemoteAnimationAdapterWrapper> mPendingAnimations = new ArrayList<>(); private final Rect mTmpRect = new Rect(); private final Handler mHandler; - private FinishedCallback mFinishedCallback; + private final Runnable mTimeoutRunnable = this::cancelAnimation; - private final Runnable mTimeoutRunnable = () -> { - onAnimationFinished(); - invokeAnimationCancelled(); - }; + private FinishedCallback mFinishedCallback; + private boolean mCanceled; RemoteAnimationController(WindowManagerService service, RemoteAnimationAdapter remoteAnimationAdapter, Handler handler) { @@ -90,7 +89,7 @@ class RemoteAnimationController { * Called when the transition is ready to be started, and all leashes have been set up. */ void goodToGo() { - if (mPendingAnimations.isEmpty()) { + if (mPendingAnimations.isEmpty() || mCanceled) { onAnimationFinished(); return; } @@ -107,8 +106,8 @@ class RemoteAnimationController { } mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> { try { - mRemoteAnimationAdapter.getRunner().onAnimationStart(animations, - mFinishedCallback); + mRemoteAnimationAdapter.getRunner().asBinder().linkToDeath(this, 0); + mRemoteAnimationAdapter.getRunner().onAnimationStart(animations, mFinishedCallback); } catch (RemoteException e) { Slog.e(TAG, "Failed to start remote animation", e); onAnimationFinished(); @@ -120,6 +119,17 @@ class RemoteAnimationController { } } + private void cancelAnimation() { + synchronized (mService.getWindowManagerLock()) { + if (mCanceled) { + return; + } + mCanceled = true; + } + onAnimationFinished(); + invokeAnimationCancelled(); + } + private void writeStartDebugStatement() { Slog.i(TAG, "Starting remote animation"); final StringWriter sw = new StringWriter(); @@ -154,6 +164,7 @@ class RemoteAnimationController { private void onAnimationFinished() { mHandler.removeCallbacks(mTimeoutRunnable); + mRemoteAnimationAdapter.getRunner().asBinder().unlinkToDeath(this, 0); synchronized (mService.mWindowMap) { releaseFinishedCallback(); mService.openSurfaceTransaction(); @@ -193,6 +204,11 @@ class RemoteAnimationController { mService.sendSetRunningRemoteAnimation(pid, running); } + @Override + public void binderDied() { + cancelAnimation(); + } + private static final class FinishedCallback extends IRemoteAnimationFinishedCallback.Stub { RemoteAnimationController mOuter; diff --git a/services/core/java/com/android/server/wm/StrictModeFlash.java b/services/core/java/com/android/server/wm/StrictModeFlash.java index f51a6a921984..e97b36683362 100644 --- a/services/core/java/com/android/server/wm/StrictModeFlash.java +++ b/services/core/java/com/android/server/wm/StrictModeFlash.java @@ -79,17 +79,25 @@ class StrictModeFlash { } // Top - c.clipRect(new Rect(0, 0, dw, mThickness), Region.Op.REPLACE); + c.save(); + c.clipRect(new Rect(0, 0, dw, mThickness)); c.drawColor(Color.RED); + c.restore(); // Left - c.clipRect(new Rect(0, 0, mThickness, dh), Region.Op.REPLACE); + c.save(); + c.clipRect(new Rect(0, 0, mThickness, dh)); c.drawColor(Color.RED); + c.restore(); // Right - c.clipRect(new Rect(dw - mThickness, 0, dw, dh), Region.Op.REPLACE); + c.save(); + c.clipRect(new Rect(dw - mThickness, 0, dw, dh)); c.drawColor(Color.RED); + c.restore(); // Bottom - c.clipRect(new Rect(0, dh - mThickness, dw, dh), Region.Op.REPLACE); + c.save(); + c.clipRect(new Rect(0, dh - mThickness, dw, dh)); c.drawColor(Color.RED); + c.restore(); mSurface.unlockCanvasAndPost(c); } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 6bceda5ae3bc..6686b80d1e18 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -419,4 +419,10 @@ public abstract class WindowManagerInternal { * @see android.view.IWindowManager#lockNow */ public abstract void lockNow(); + + /** + * Return the user that owns the given window, {@link android.os.UserHandle#USER_NULL} if + * the window token is not found. + */ + public abstract int getWindowOwnerUserId(IBinder windowToken); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 0b5c0064d40f..f1cd46bc16d0 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -25,12 +25,10 @@ import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; import static android.app.StatusBarManager.DISABLE_MASK; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; -import static android.content.Intent.EXTRA_USER_HANDLE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Process.SYSTEM_UID; import static android.os.Process.myPid; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; -import static android.os.UserHandle.USER_NULL; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.DOCKED_INVALID; @@ -181,7 +179,6 @@ import android.util.Log; import android.util.MergedConfiguration; import android.util.Pair; import android.util.Slog; -import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.TimeUtils; @@ -2423,8 +2420,8 @@ public class WindowManagerService extends IWindowManager.Stub final int oldRotation = defaultDisplayContent.getRotation(); final boolean oldAltOrientation = defaultDisplayContent.getAltOrientation(); - final int rotation = mPolicy.rotationForOrientationLw(lastOrientation, - oldRotation); + final int rotation = mPolicy.rotationForOrientationLw(lastOrientation, oldRotation, + true /* defaultDisplay */); boolean altOrientation = !mPolicy.rotationHasCompatibleMetricsLw( lastOrientation, rotation); if (oldRotation == rotation && oldAltOrientation == altOrientation) { @@ -2793,6 +2790,11 @@ public class WindowManagerService extends IWindowManager.Stub mTaskSnapshotController.screenTurningOff(listener); } + @Override + public void triggerAnimationFailsafe() { + mH.sendEmptyMessage(H.ANIMATION_FAILSAFE); + } + /** * Starts deferring layout passes. Useful when doing multiple changes but to optimize * performance, only one layout pass should be done. This can be called multiple times, and @@ -4566,6 +4568,7 @@ public class WindowManagerService extends IWindowManager.Stub public static final int NOTIFY_KEYGUARD_TRUSTED_CHANGED = 57; public static final int SET_HAS_OVERLAY_UI = 58; public static final int SET_RUNNING_REMOTE_ANIMATION = 59; + public static final int ANIMATION_FAILSAFE = 60; /** * Used to denote that an integer field in a message will not be used. @@ -4984,6 +4987,14 @@ public class WindowManagerService extends IWindowManager.Stub mAmInternal.setRunningRemoteAnimation(msg.arg1, msg.arg2 == 1); } break; + case ANIMATION_FAILSAFE: { + synchronized (mWindowMap) { + if (mRecentsAnimationController != null) { + mRecentsAnimationController.scheduleFailsafe(); + } + } + } + break; } if (DEBUG_WINDOW_TRACE) { Slog.v(TAG_WM, "handleMessage: exit"); @@ -7351,6 +7362,17 @@ public class WindowManagerService extends IWindowManager.Stub public void lockNow() { WindowManagerService.this.lockNow(null); } + + @Override + public int getWindowOwnerUserId(IBinder token) { + synchronized (mWindowMap) { + WindowState window = mWindowMap.get(token); + if (window != null) { + return UserHandle.getUserId(window.mOwnerUid); + } + return UserHandle.USER_NULL; + } + } } void registerAppFreezeListener(AppFreezeListener listener) { diff --git a/services/core/jni/BroadcastRadio/BroadcastRadioService.cpp b/services/core/jni/BroadcastRadio/BroadcastRadioService.cpp index ecf1a33539e9..f7ca363e54a1 100644 --- a/services/core/jni/BroadcastRadio/BroadcastRadioService.cpp +++ b/services/core/jni/BroadcastRadio/BroadcastRadioService.cpp @@ -231,6 +231,13 @@ static jobject nativeOpenTuner(JNIEnv *env, jobject obj, long nativeContext, jin return nullptr; } bandConfigHal = module.bands[0]; + + /* Prefer FM to workaround possible program list fetching limitation + * (if tuner scans only configured band for programs). */ + auto fmIt = std::find_if(module.bands.begin(), module.bands.end(), + [](const BandConfig & band) { return utils::isFm(band.type); }); + if (fmIt != module.bands.end()) bandConfigHal = *fmIt; + if (bandConfigHal.spacings.size() > 1) { bandConfigHal.spacings = hidl_vec<uint32_t>({ *std::min_element( bandConfigHal.spacings.begin(), bandConfigHal.spacings.end()) }); diff --git a/services/core/jni/BroadcastRadio/convert.cpp b/services/core/jni/BroadcastRadio/convert.cpp index 847222ae4ba1..61b48c2e9316 100644 --- a/services/core/jni/BroadcastRadio/convert.cpp +++ b/services/core/jni/BroadcastRadio/convert.cpp @@ -380,6 +380,7 @@ static JavaRef<jobject> ModulePropertiesFromHal(JNIEnv *env, const V1_0::Propert auto jProduct = make_javastr(env, prop10.product); auto jVersion = make_javastr(env, prop10.version); auto jSerial = make_javastr(env, prop10.serial); + constexpr bool isInitializationRequired = true; bool isBgScanSupported = prop11 ? prop11->supportsBackgroundScanning : false; auto jVendorInfo = prop11 ? VendorInfoFromHal(env, prop11->vendorInfo) : nullptr; @@ -394,9 +395,9 @@ static JavaRef<jobject> ModulePropertiesFromHal(JNIEnv *env, const V1_0::Propert return make_javaref(env, env->NewObject(gjni.ModuleProperties.clazz, gjni.ModuleProperties.cstor, moduleId, jServiceName.get(), prop10.classId, jImplementor.get(), jProduct.get(), jVersion.get(), jSerial.get(), prop10.numTuners, - prop10.numAudioSources, prop10.supportsCapture, jBands.get(), isBgScanSupported, - jSupportedProgramTypes.get(), jSupportedIdentifierTypes.get(), nullptr, - jVendorInfo.get())); + prop10.numAudioSources, isInitializationRequired, prop10.supportsCapture, jBands.get(), + isBgScanSupported, jSupportedProgramTypes.get(), jSupportedIdentifierTypes.get(), + nullptr, jVendorInfo.get())); } JavaRef<jobject> ModulePropertiesFromHal(JNIEnv *env, const V1_0::Properties &properties, @@ -712,7 +713,7 @@ void register_android_server_broadcastradio_convert(JNIEnv *env) { gjni.ModuleProperties.clazz = MakeGlobalRefOrDie(env, modulePropertiesClass); gjni.ModuleProperties.cstor = GetMethodIDOrDie(env, modulePropertiesClass, "<init>", "(ILjava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;" - "Ljava/lang/String;IIZ[Landroid/hardware/radio/RadioManager$BandDescriptor;Z" + "Ljava/lang/String;IIZZ[Landroid/hardware/radio/RadioManager$BandDescriptor;Z" "[I[ILjava/util/Map;Ljava/util/Map;)V"); auto programInfoClass = FindClassOrDie(env, "android/hardware/radio/RadioManager$ProgramInfo"); diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index dcee15106f8a..21fea1c910cb 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -1125,6 +1125,16 @@ Return<void> GnssBatchingCallback::gnssLocationBatchCb( } static void android_location_GnssLocationProvider_class_init_native(JNIEnv* env, jclass clazz) { + gnssHal_V1_1 = IGnss_V1_1::getService(); + if (gnssHal_V1_1 == nullptr) { + ALOGD("gnssHal 1.1 was null, trying 1.0"); + gnssHal = IGnss_V1_0::getService(); + } else { + gnssHal = gnssHal_V1_1; + } +} + +static void android_location_GnssLocationProvider_init_once(JNIEnv* env, jclass clazz) { method_reportLocation = env->GetMethodID(clazz, "reportLocation", "(ZLandroid/location/Location;)V"); method_reportStatus = env->GetMethodID(clazz, "reportStatus", "(I)V"); @@ -1175,15 +1185,6 @@ static void android_location_GnssLocationProvider_class_init_native(JNIEnv* env, LOG_ALWAYS_FATAL("Unable to get Java VM. Error: %d", jvmStatus); } - // TODO(b/31632518) - gnssHal_V1_1 = IGnss_V1_1::getService(); - if (gnssHal_V1_1 == nullptr) { - ALOGD("gnssHal 1.1 was null, trying 1.0"); - gnssHal = IGnss_V1_0::getService(); - } else { - gnssHal = gnssHal_V1_1; - } - if (gnssHal != nullptr) { gnssHalDeathRecipient = new GnssDeathRecipient(); hardware::Return<bool> linked = gnssHal->linkToDeath( @@ -2068,6 +2069,8 @@ static const JNINativeMethod sMethods[] = { {"native_is_gnss_configuration_supported", "()Z", reinterpret_cast<void *>( android_location_gpsLocationProvider_is_gnss_configuration_supported)}, + {"native_init_once", "()V", reinterpret_cast<void *>( + android_location_GnssLocationProvider_init_once)}, {"native_init", "()Z", reinterpret_cast<void *>(android_location_GnssLocationProvider_init)}, {"native_cleanup", "()V", reinterpret_cast<void *>( android_location_GnssLocationProvider_cleanup)}, diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index 71c2ea18eb1b..1c9782fa5565 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -78,23 +78,6 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { return false; } - @Override - public boolean setPasswordBlacklist(ComponentName who, String name, List<String> blacklist, - boolean parent) { - return false; - } - - @Override - public String getPasswordBlacklistName(ComponentName who, @UserIdInt int userId, - boolean parent) { - return null; - } - - @Override - public boolean isPasswordBlacklisted(@UserIdInt int userId, String password) { - return false; - } - public boolean isUsingUnifiedPassword(ComponentName who) { return true; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 2fd776405b39..90e8a9cd74e1 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -51,6 +51,7 @@ import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID; import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL; import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME; +import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; @@ -590,7 +591,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { List<String> mLockTaskPackages = new ArrayList<>(); // Bitfield of feature flags to be enabled during LockTask mode. - int mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE; + // We default on the power button menu, in order to be consistent with pre-P behaviour. + int mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS; boolean mStatusBarDisabled = false; @@ -818,7 +820,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static final String TAG_PASSWORD_HISTORY_LENGTH = "password-history-length"; private static final String TAG_MIN_PASSWORD_LENGTH = "min-password-length"; private static final String ATTR_VALUE = "value"; - private static final String TAG_PASSWORD_BLACKLIST = "password-blacklist"; private static final String TAG_PASSWORD_QUALITY = "password-quality"; private static final String TAG_POLICIES = "policies"; private static final String TAG_CROSS_PROFILE_WIDGET_PROVIDERS = @@ -959,9 +960,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // Default title of confirm credentials screen String organizationName = null; - // 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; @@ -1051,11 +1049,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { out.endTag(null, TAG_MIN_PASSWORD_NONLETTER); } } - if (passwordBlacklistFile != null) { - out.startTag(null, TAG_PASSWORD_BLACKLIST); - out.attribute(null, ATTR_VALUE, passwordBlacklistFile); - out.endTag(null, TAG_PASSWORD_BLACKLIST); - } if (maximumTimeToUnlock != DEF_MAXIMUM_TIME_TO_UNLOCK) { out.startTag(null, TAG_MAX_TIME_TO_UNLOCK); out.attribute(null, ATTR_VALUE, Long.toString(maximumTimeToUnlock)); @@ -1311,8 +1304,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } else if (TAG_MIN_PASSWORD_NONLETTER.equals(tag)) { minimumPasswordMetrics.nonLetter = Integer.parseInt( parser.getAttributeValue(null, ATTR_VALUE)); - } else if (TAG_PASSWORD_BLACKLIST.equals(tag)) { - passwordBlacklistFile = parser.getAttributeValue(null, ATTR_VALUE); }else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) { maximumTimeToUnlock = Long.parseLong( parser.getAttributeValue(null, ATTR_VALUE)); @@ -1587,8 +1578,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { pw.println(minimumPasswordMetrics.symbols); pw.print(prefix); pw.print("minimumPasswordNonLetter="); pw.println(minimumPasswordMetrics.nonLetter); - pw.print(prefix); pw.print("passwordBlacklist="); - pw.println(passwordBlacklistFile != null); pw.print(prefix); pw.print("maximumTimeToUnlock="); pw.println(maximumTimeToUnlock); pw.print(prefix); pw.print("strongAuthUnlockTimeout="); @@ -1855,10 +1844,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return new LockPatternUtils(mContext); } - PasswordBlacklist newPasswordBlacklist(File file) { - return new PasswordBlacklist(file); - } - boolean storageManagerIsFileBasedEncryptionEnabled() { return StorageManager.isFileEncryptedNativeOnly(); } @@ -2026,8 +2011,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Settings.Global.putString(mContext.getContentResolver(), name, value); } - void settingsSystemPutString(String name, String value) { - Settings.System.putString(mContext.getContentResolver(), name, value); + void settingsSystemPutStringForUser(String name, String value, int userId) { + Settings.System.putStringForUser( + mContext.getContentResolver(), name, value, userId); } void securityLogSetLoggingEnabledProperty(boolean enabled) { @@ -4410,136 +4396,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - /* @return the password blacklist set by the admin or {@code null} if none. */ - PasswordBlacklist getAdminPasswordBlacklistLocked(@NonNull ActiveAdmin admin) { - final int userId = UserHandle.getUserId(admin.getUid()); - return admin.passwordBlacklistFile == null ? null : new PasswordBlacklist( - new File(getPolicyFileDirectory(userId), admin.passwordBlacklistFile)); - } - - private static final String PASSWORD_BLACKLIST_FILE_PREFIX = "password-blacklist-"; - private static final String PASSWORD_BLACKLIST_FILE_SUFFIX = ""; - - @Override - public boolean setPasswordBlacklist(ComponentName who, String name, List<String> blacklist, - boolean parent) { - if (!mHasFeature) { - return false; - } - Preconditions.checkNotNull(who, "who is null"); - - synchronized (this) { - final ActiveAdmin admin = getActiveAdminForCallerLocked( - who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent); - final int userId = mInjector.userHandleGetCallingUserId(); - PasswordBlacklist adminBlacklist = getAdminPasswordBlacklistLocked(admin); - - if (blacklist == null || blacklist.isEmpty()) { - // Remove the adminBlacklist - admin.passwordBlacklistFile = null; - saveSettingsLocked(userId); - if (adminBlacklist != null) { - adminBlacklist.delete(); - } - return true; - } - - // Validate server side - Preconditions.checkNotNull(name, "name is null"); - DevicePolicyManager.enforcePasswordBlacklistSize(blacklist); - - // Blacklist is case insensitive so normalize to lower case - final int blacklistSize = blacklist.size(); - for (int i = 0; i < blacklistSize; ++i) { - blacklist.set(i, blacklist.get(i).toLowerCase()); - } - - final boolean isNewBlacklist = adminBlacklist == null; - if (isNewBlacklist) { - // Create a new file for the blacklist. There could be multiple admins, each setting - // different blacklists, to restrict a user's credential, for example a managed - // profile can impose restrictions on its parent while the parent is already - // restricted by its own admin. A deterministic naming scheme would be fragile if - // new types of admin are introduced so we generate and save the file name instead. - // This isn't a temporary file but it reuses the name generation logic - final File file; - try { - file = File.createTempFile(PASSWORD_BLACKLIST_FILE_PREFIX, - PASSWORD_BLACKLIST_FILE_SUFFIX, getPolicyFileDirectory(userId)); - } catch (IOException e) { - Slog.e(LOG_TAG, "Failed to make a file for the blacklist", e); - return false; - } - adminBlacklist = mInjector.newPasswordBlacklist(file); - } - - if (adminBlacklist.savePasswordBlacklist(name, blacklist)) { - if (isNewBlacklist) { - // The blacklist was saved so point the admin to the file - admin.passwordBlacklistFile = adminBlacklist.getFile().getName(); - saveSettingsLocked(userId); - } - return true; - } - } - - return false; - } - - @Override - public String getPasswordBlacklistName(ComponentName who, @UserIdInt int userId, - boolean parent) { - if (!mHasFeature) { - return null; - } - Preconditions.checkNotNull(who, "who is null"); - enforceFullCrossUsersPermission(userId); - synchronized (this) { - final ActiveAdmin admin = getActiveAdminForCallerLocked( - who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent); - final PasswordBlacklist blacklist = getAdminPasswordBlacklistLocked(admin); - if (blacklist == null) { - return null; - } - return blacklist.getName(); - } - } - - @Override - public boolean isPasswordBlacklisted(@UserIdInt int userId, String password) { - if (!mHasFeature) { - return false; - } - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.TEST_BLACKLISTED_PASSWORD, null); - return isPasswordBlacklistedInternal(userId, password); - } - - private boolean isPasswordBlacklistedInternal(@UserIdInt int userId, String password) { - Preconditions.checkNotNull(password, "Password is null"); - enforceFullCrossUsersPermission(userId); - - // Normalize to lower case for case insensitive blacklist match - final String lowerCasePassword = password.toLowerCase(); - - synchronized (this) { - final List<ActiveAdmin> admins = - getActiveAdminsForLockscreenPoliciesLocked(userId, /* parent */ false); - final int N = admins.size(); - for (int i = 0; i < N; i++) { - final PasswordBlacklist blacklist - = getAdminPasswordBlacklistLocked(admins.get(i)); - if (blacklist != null) { - if (blacklist.isPasswordBlacklisted(lowerCasePassword)) { - return true; - } - } - } - } - - return false; - } - @Override public boolean isActivePasswordSufficient(int userHandle, boolean parent) { if (!mHasFeature) { @@ -4935,11 +4791,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } } - - if (isPasswordBlacklistedInternal(userHandle, password)) { - Slog.w(LOG_TAG, "resetPassword: the password is blacklisted"); - return false; - } } DevicePolicyData policy = getUserData(userHandle); @@ -5531,10 +5382,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .setAttestationChallenge(null) .build(); - final boolean generationResult = keyChain.generateKeyPair(algorithm, + final int generationResult = keyChain.generateKeyPair(algorithm, new ParcelableKeyGenParameterSpec(noAttestationSpec)); - if (!generationResult) { - Log.e(LOG_TAG, "KeyChain failed to generate a keypair."); + if (generationResult != KeyChain.KEY_GEN_SUCCESS) { + Log.e(LOG_TAG, String.format( + "KeyChain failed to generate a keypair, error %d.", generationResult)); return false; } @@ -5547,12 +5399,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final byte[] attestationChallenge = keySpec.getAttestationChallenge(); if (attestationChallenge != null) { - final boolean attestationResult = keyChain.attestKey( + final int attestationResult = keyChain.attestKey( alias, attestationChallenge, attestationUtilsFlags, attestationChain); - if (!attestationResult) { + if (attestationResult != KeyChain.KEY_ATTESTATION_SUCCESS) { Log.e(LOG_TAG, String.format( - "Attestation for %s failed, deleting key.", alias)); + "Attestation for %s failed (rc=%d), deleting key.", + alias, attestationResult)); keyChain.removeKeyPair(alias); + if (attestationResult == KeyChain.KEY_ATTESTATION_CANNOT_ATTEST_IDS) { + throw new UnsupportedOperationException( + "Device does not support Device ID attestation."); + } return false; } } @@ -8738,6 +8595,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public List getPermittedInputMethodsForCurrentUser() { + enforceManageUsers(); UserInfo currentUser; try { currentUser = mInjector.getIActivityManager().getCurrentUser(); @@ -9909,6 +9767,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { boolean hasOverview = (flags & LOCK_TASK_FEATURE_OVERVIEW) != 0; Preconditions.checkArgument(hasHome || !hasOverview, "Cannot use LOCK_TASK_FEATURE_OVERVIEW without LOCK_TASK_FEATURE_HOME"); + boolean hasNotification = (flags & LOCK_TASK_FEATURE_NOTIFICATIONS) != 0; + Preconditions.checkArgument(hasHome || !hasNotification, + "Cannot use LOCK_TASK_FEATURE_NOTIFICATIONS without LOCK_TASK_FEATURE_HOME"); final int userHandle = mInjector.userHandleGetCallingUserId(); synchronized (this) { @@ -10037,15 +9898,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Preconditions.checkStringNotEmpty(setting, "String setting is null or empty"); synchronized (this) { - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); if (!SYSTEM_SETTINGS_WHITELIST.contains(setting)) { throw new SecurityException(String.format( "Permission denial: device owners cannot update %1$s", setting)); } - mInjector.binderWithCleanCallingIdentity(() -> mInjector.settingsSystemPutString( - setting, value)); + final int callingUserId = mInjector.userHandleGetCallingUserId(); + + mInjector.binderWithCleanCallingIdentity(() -> + mInjector.settingsSystemPutStringForUser(setting, value, callingUserId)); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PasswordBlacklist.java b/services/devicepolicy/java/com/android/server/devicepolicy/PasswordBlacklist.java deleted file mode 100644 index a17a1075f100..000000000000 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PasswordBlacklist.java +++ /dev/null @@ -1,165 +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.devicepolicy; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.util.AtomicFile; -import android.util.Slog; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.List; - -/** - * Manages the blacklisted passwords. - * - * This caller must ensure synchronized access. - */ -public class PasswordBlacklist { - private static final String TAG = "PasswordBlacklist"; - - private final AtomicFile mFile; - - /** - * Create an object to manage the password blacklist. - * - * This is a lightweight operation to prepare variables but not perform any IO. - */ - public PasswordBlacklist(File file) { - mFile = new AtomicFile(file, "device-policy"); - } - - /** - * Atomically replace the blacklist. - * - * Pass {@code null} for an empty list. - */ - public boolean savePasswordBlacklist(@NonNull String name, @NonNull List<String> blacklist) { - FileOutputStream fos = null; - try { - fos = mFile.startWrite(); - final DataOutputStream out = buildStreamForWriting(fos); - final Header header = new Header(Header.VERSION_1, name, blacklist.size()); - header.write(out); - final int blacklistSize = blacklist.size(); - for (int i = 0; i < blacklistSize; ++i) { - out.writeUTF(blacklist.get(i)); - } - out.flush(); - mFile.finishWrite(fos); - return true; - } catch (IOException e) { - mFile.failWrite(fos); - return false; - } - } - - /** @return the name of the blacklist or {@code null} if none set. */ - public String getName() { - try (DataInputStream in = openForReading()) { - return Header.read(in).mName; - } catch (IOException e) { - Slog.wtf(TAG, "Failed to read blacklist file", e); - } - return null; - } - - /** @return the number of blacklisted passwords. */ - public int getSize() { - final int blacklistSize; - try (DataInputStream in = openForReading()) { - return Header.read(in).mSize; - } catch (IOException e) { - Slog.wtf(TAG, "Failed to read blacklist file", e); - } - return 0; - } - - /** @return whether the password matches an blacklisted item. */ - public boolean isPasswordBlacklisted(@NonNull String password) { - final int blacklistSize; - try (DataInputStream in = openForReading()) { - final Header header = Header.read(in); - for (int i = 0; i < header.mSize; ++i) { - if (in.readUTF().equals(password)) { - return true; - } - } - } catch (IOException e) { - Slog.wtf(TAG, "Failed to read blacklist file", e); - // Fail safe and block all passwords. Setting a new blacklist should resolve this - // problem which can be identified by examining the log. - return true; - } - return false; - } - - /** Delete the blacklist completely from disk. */ - public void delete() { - mFile.delete(); - } - - /** Get the file the blacklist is stored in. */ - public File getFile() { - return mFile.getBaseFile(); - } - - private DataOutputStream buildStreamForWriting(FileOutputStream fos) { - return new DataOutputStream(new BufferedOutputStream(fos)); - } - - private DataInputStream openForReading() throws IOException { - return new DataInputStream(new BufferedInputStream(mFile.openRead())); - } - - /** - * Helper to read and write the header of the blacklist file. - */ - private static class Header { - static final int VERSION_1 = 1; - - final int mVersion; // File format version - final String mName; - final int mSize; - - Header(int version, String name, int size) { - mVersion = version; - mName = name; - mSize = size; - } - - void write(DataOutputStream out) throws IOException { - out.writeInt(mVersion); - out.writeUTF(mName); - out.writeInt(mSize); - } - - static Header read(DataInputStream in) throws IOException { - final int version = in.readInt(); - final String name = in.readUTF(); - final int size = in.readInt(); - return new Header(version, name, size); - } - } -} diff --git a/services/net/java/android/net/apf/ApfGenerator.java b/services/net/java/android/net/apf/ApfGenerator.java index d41fbce1f206..ca8f72760457 100644 --- a/services/net/java/android/net/apf/ApfGenerator.java +++ b/services/net/java/android/net/apf/ApfGenerator.java @@ -367,7 +367,7 @@ public class ApfGenerator { */ public boolean setApfVersion(int version) { // This version number syncs up with APF_VERSION in hardware/google/apf/apf_interpreter.h - return version == 2; + return version >= 2; } private void addInstruction(Instruction instruction) { diff --git a/services/net/java/android/net/util/InterfaceSet.java b/services/net/java/android/net/util/InterfaceSet.java new file mode 100644 index 000000000000..9f26fa17ef5c --- /dev/null +++ b/services/net/java/android/net/util/InterfaceSet.java @@ -0,0 +1,52 @@ +/* + * 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.net.util; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.StringJoiner; + + +/** + * @hide + */ +public class InterfaceSet { + public final Set<String> ifnames; + + public InterfaceSet(String... names) { + final Set<String> nameSet = new HashSet<>(); + for (String name : names) { + if (name != null) nameSet.add(name); + } + ifnames = Collections.unmodifiableSet(nameSet); + } + + @Override + public String toString() { + final StringJoiner sj = new StringJoiner(",", "[", "]"); + for (String ifname : ifnames) sj.add(ifname); + return sj.toString(); + } + + @Override + public boolean equals(Object obj) { + return obj != null + && obj instanceof InterfaceSet + && ifnames.equals(((InterfaceSet)obj).ifnames); + } +} diff --git a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java index f603a09baa37..fa41220dc44c 100644 --- a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java +++ b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java @@ -147,6 +147,15 @@ public class PerformBackupTaskTest { Looper backupLooper = startBackupThreadAndGetLooper(); mShadowBackupLooper = shadowOf(backupLooper); + + Handler mainHandler = new Handler(Looper.getMainLooper()); + BackupAgentTimeoutParameters agentTimeoutParameters = + new BackupAgentTimeoutParameters(mainHandler, application.getContentResolver()); + agentTimeoutParameters.start(); + + // We need to mock BMS timeout parameters before initializing the BackupHandler since + // the constructor of BackupHandler relies on the timeout parameters. + when(mBackupManagerService.getAgentTimeoutParameters()).thenReturn(agentTimeoutParameters); mBackupHandler = new BackupHandler(mBackupManagerService, backupLooper); mBackupManager = spy(FakeIBackupManager.class); @@ -157,7 +166,8 @@ public class PerformBackupTaskTest { mTransportManager, packageManager, mBackupHandler, - mWakeLock); + mWakeLock, + agentTimeoutParameters); when(mBackupManagerService.getBaseStateDir()).thenReturn(mBaseStateDir); when(mBackupManagerService.getDataDir()).thenReturn(dataDir); when(mBackupManagerService.getBackupManagerBinder()).thenReturn(mBackupManager); diff --git a/services/robotests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java b/services/robotests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java index 03792b1d40ba..92d6bbd54fab 100644 --- a/services/robotests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java +++ b/services/robotests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java @@ -41,12 +41,14 @@ import android.app.backup.IRestoreSession; import android.app.backup.RestoreSet; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; +import android.os.Handler; import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import com.android.server.EventLogTags; +import com.android.server.backup.BackupAgentTimeoutParameters; import com.android.server.backup.BackupManagerService; import com.android.server.backup.TransportManager; import com.android.server.backup.internal.BackupHandler; @@ -115,6 +117,15 @@ public class ActiveRestoreSessionTest { Looper backupLooper = startBackupThreadAndGetLooper(); mShadowBackupLooper = shadowOf(backupLooper); + + Handler mainHandler = new Handler(Looper.getMainLooper()); + BackupAgentTimeoutParameters agentTimeoutParameters = + new BackupAgentTimeoutParameters(mainHandler, application.getContentResolver()); + agentTimeoutParameters.start(); + + // We need to mock BMS timeout parameters before initializing the BackupHandler since + // the constructor of BackupHandler relies on it. + when(mBackupManagerService.getAgentTimeoutParameters()).thenReturn(agentTimeoutParameters); BackupHandler backupHandler = new BackupHandler(mBackupManagerService, backupLooper); mWakeLock = createBackupWakeLock(application); @@ -125,7 +136,8 @@ public class ActiveRestoreSessionTest { mTransportManager, application.getPackageManager(), backupHandler, - mWakeLock); + mWakeLock, + agentTimeoutParameters); when(mBackupManagerService.getPendingRestores()).thenReturn(new ArrayDeque<>()); } diff --git a/services/robotests/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java b/services/robotests/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java index c210fdea6e89..5a886e33622f 100644 --- a/services/robotests/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java +++ b/services/robotests/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java @@ -28,6 +28,7 @@ import android.os.Looper; import android.os.PowerManager; import android.util.SparseArray; +import com.android.server.backup.BackupAgentTimeoutParameters; import com.android.server.backup.BackupManagerService; import com.android.server.backup.TransportManager; import com.android.server.backup.internal.BackupHandler; @@ -43,7 +44,8 @@ public class BackupManagerServiceTestUtils { TransportManager transportManager, PackageManager packageManager, BackupHandler backupHandler, - PowerManager.WakeLock wakeLock) { + PowerManager.WakeLock wakeLock, + BackupAgentTimeoutParameters agentTimeoutParameters) { when(backupManagerService.getContext()).thenReturn(context); when(backupManagerService.getTransportManager()).thenReturn(transportManager); when(backupManagerService.getPackageManager()).thenReturn(packageManager); @@ -53,6 +55,7 @@ public class BackupManagerServiceTestUtils { when(backupManagerService.getCurrentOperations()).thenReturn(new SparseArray<>()); when(backupManagerService.getActivityManager()).thenReturn(mock(IActivityManager.class)); when(backupManagerService.getWakelock()).thenReturn(wakeLock); + when(backupManagerService.getAgentTimeoutParameters()).thenReturn(agentTimeoutParameters); } public static PowerManager.WakeLock createBackupWakeLock(Application application) { diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml index 082827c23b53..0ec16b523a1b 100644 --- a/services/tests/servicestests/AndroidTest.xml +++ b/services/tests/servicestests/AndroidTest.xml @@ -14,7 +14,9 @@ limitations under the License. --> <configuration description="Runs Frameworks Services Tests."> - <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true" /> <option name="test-file-name" value="FrameworksServicesTests.apk" /> <option name="test-file-name" value="JobTestApp.apk" /> @@ -22,7 +24,6 @@ <option name="test-file-name" value="SuspendTestApp.apk" /> </target_preparer> - <option name="test-suite-tag" value="apct" /> <option name="test-tag" value="FrameworksServicesTests" /> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="com.android.frameworks.servicestests" /> diff --git a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java index 7c3ea4ffe86a..e37ed7976a86 100644 --- a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java +++ b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java @@ -17,6 +17,7 @@ package com.android.server.content; import android.accounts.Account; +import android.content.ContentResolver; import android.os.Bundle; import android.os.PersistableBundle; import android.test.AndroidTestCase; @@ -60,7 +61,7 @@ public class SyncOperationTest extends AndroidTestCase { "authority1", b1, false, - /*isAppStandbyExempted=*/ false); + ContentResolver.SYNC_EXEMPTION_NONE); // Same as op1 but different time infos SyncOperation op2 = new SyncOperation(account1, 0, @@ -69,7 +70,7 @@ public class SyncOperationTest extends AndroidTestCase { "authority1", b1, false, - /*isAppStandbyExempted=*/ false); + ContentResolver.SYNC_EXEMPTION_NONE); // Same as op1 but different authority SyncOperation op3 = new SyncOperation(account1, 0, @@ -78,7 +79,7 @@ public class SyncOperationTest extends AndroidTestCase { "authority2", b1, false, - /*isAppStandbyExempted=*/ false); + ContentResolver.SYNC_EXEMPTION_NONE); // Same as op1 but different account SyncOperation op4 = new SyncOperation(account2, 0, @@ -87,7 +88,7 @@ public class SyncOperationTest extends AndroidTestCase { "authority1", b1, false, - /*isAppStandbyExempted=*/ false); + ContentResolver.SYNC_EXEMPTION_NONE); // Same as op1 but different bundle SyncOperation op5 = new SyncOperation(account1, 0, @@ -96,7 +97,7 @@ public class SyncOperationTest extends AndroidTestCase { "authority1", b2, false, - /*isAppStandbyExempted=*/ false); + ContentResolver.SYNC_EXEMPTION_NONE); assertEquals(op1.key, op2.key); assertNotSame(op1.key, op3.key); @@ -117,7 +118,7 @@ public class SyncOperationTest extends AndroidTestCase { "authority1", b1, false, - /*isAppStandbyExempted=*/ false); + ContentResolver.SYNC_EXEMPTION_NONE); PersistableBundle pb = op1.toJobInfoExtras(); SyncOperation op2 = SyncOperation.maybeCreateFromJobExtras(pb); @@ -145,7 +146,7 @@ public class SyncOperationTest extends AndroidTestCase { Bundle extras = new Bundle(); SyncOperation periodic = new SyncOperation(ep, 0, "package", 0, 0, extras, false, true, SyncOperation.NO_JOB_ID, 60000, 10000, - /*isAppStandbyExempted=*/ false); + ContentResolver.SYNC_EXEMPTION_NONE); SyncOperation oneoff = periodic.createOneTimeSyncOperation(); assertFalse("Conversion to oneoff sync failed.", oneoff.isPeriodic); assertEquals("Period not restored", periodic.periodMillis, oneoff.periodMillis); diff --git a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java deleted file mode 100644 index 7209c7971145..000000000000 --- a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java +++ /dev/null @@ -1,349 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.content; - -import android.accounts.Account; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.ContextWrapper; -import android.content.Intent; -import android.content.res.Resources; -import android.os.Bundle; -import android.test.AndroidTestCase; -import android.test.RenamingDelegatingContext; -import android.test.mock.MockContentResolver; -import android.test.mock.MockContext; -import android.test.suitebuilder.annotation.LargeTest; -import android.test.suitebuilder.annotation.MediumTest; -import android.test.suitebuilder.annotation.SmallTest; -import android.test.suitebuilder.annotation.Suppress; - -import com.android.internal.os.AtomicFile; - -import java.io.File; -import java.io.FileOutputStream; - -/** - * Test for SyncStorageEngine. - * - * bit FrameworksServicesTests:com.android.server.content.SyncStorageEngineTest - * - * TODO Broken. Fix it. b/62485315 - */ -@Suppress -public class SyncStorageEngineTest extends AndroidTestCase { - - protected Account account1; - protected Account account2; - protected ComponentName syncService1; - protected String authority1 = "testprovider"; - protected Bundle defaultBundle; - protected final int DEFAULT_USER = 0; - - /* Some default poll frequencies. */ - final long dayPoll = (60 * 60 * 24); - final long dayFuzz = 60; - final long thousandSecs = 1000; - final long thousandSecsFuzz = 100; - - MockContentResolver mockResolver; - SyncStorageEngine engine; - - private File getSyncDir() { - return new File(new File(getContext().getFilesDir(), "system"), "sync"); - } - - @Override - public void setUp() { - account1 = new Account("a@example.com", "example.type"); - account2 = new Account("b@example.com", "example.type"); - syncService1 = new ComponentName("com.example", "SyncService"); - // Default bundle. - defaultBundle = new Bundle(); - defaultBundle.putInt("int_key", 0); - defaultBundle.putString("string_key", "hello"); - // Set up storage engine. - mockResolver = new MockContentResolver(); - engine = SyncStorageEngine.newTestInstance( - new TestContext(mockResolver, getContext())); - } - - /** - * Test that we handle the case of a history row being old enough to purge before the - * corresponding sync is finished. This can happen if the clock changes while we are syncing. - * - */ - // TODO: this test causes AidlTest to fail. Omit for now - // @SmallTest - public void testPurgeActiveSync() throws Exception { - final Account account = new Account("a@example.com", "example.type"); - final String authority = "testprovider"; - - MockContentResolver mockResolver = new MockContentResolver(); - - SyncStorageEngine engine = SyncStorageEngine.newTestInstance( - new TestContext(mockResolver, getContext())); - long time0 = 1000; - SyncOperation op = new SyncOperation(account, 0, 0, "foo", - SyncOperation.REASON_PERIODIC, - SyncStorageEngine.SOURCE_LOCAL, - authority, - Bundle.EMPTY, true, - /*isAppStandbyExempted=*/ false); - long historyId = engine.insertStartSyncEvent(op, time0); - long time1 = time0 + SyncStorageEngine.MILLIS_IN_4WEEKS * 2; - engine.stopSyncEvent(historyId, time1 - time0, "yay", 0, 0); - } - - @LargeTest - public void testAuthorityPersistence() throws Exception { - final Account account1 = new Account("a@example.com", "example.type"); - final Account account2 = new Account("b@example.com", "example.type.2"); - final String authority1 = "testprovider1"; - final String authority2 = "testprovider2"; - - engine.setMasterSyncAutomatically(false, 0); - - engine.setIsSyncable(account1, 0, authority1, 1); - engine.setSyncAutomatically(account1, 0, authority1, true); - - engine.setIsSyncable(account2, 0, authority1, 1); - engine.setSyncAutomatically(account2, 0, authority1, true); - - engine.setIsSyncable(account1, 0, authority2, 1); - engine.setSyncAutomatically(account1, 0, authority2, false); - - engine.setIsSyncable(account2, 0, authority2, 0); - engine.setSyncAutomatically(account2, 0, authority2, true); - - engine.writeAllState(); - engine.clearAndReadState(); - - assertEquals(true, engine.getSyncAutomatically(account1, 0, authority1)); - assertEquals(true, engine.getSyncAutomatically(account2, 0, authority1)); - assertEquals(false, engine.getSyncAutomatically(account1, 0, authority2)); - assertEquals(true, engine.getSyncAutomatically(account2, 0, authority2)); - - assertEquals(1, engine.getIsSyncable(account1, 0, authority1)); - assertEquals(1, engine.getIsSyncable(account2, 0, authority1)); - assertEquals(1, engine.getIsSyncable(account1, 0, authority2)); - assertEquals(0, engine.getIsSyncable(account2, 0, authority2)); - } - - @MediumTest - public void testListenForTicklesParsing() throws Exception { - byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" - + "<accounts>\n" - + "<listenForTickles user=\"0\" enabled=\"false\" />" - + "<listenForTickles user=\"1\" enabled=\"true\" />" - + "<authority id=\"0\" user=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n" - + "<authority id=\"1\" user=\"1\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n" - + "</accounts>\n").getBytes(); - - MockContentResolver mockResolver = new MockContentResolver(); - final TestContext testContext = new TestContext(mockResolver, getContext()); - - File syncDir = getSyncDir(); - syncDir.mkdirs(); - AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml")); - FileOutputStream fos = accountInfoFile.startWrite(); - fos.write(accountsFileData); - accountInfoFile.finishWrite(fos); - - SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext); - - assertEquals(false, engine.getMasterSyncAutomatically(0)); - assertEquals(true, engine.getMasterSyncAutomatically(1)); - assertEquals(true, engine.getMasterSyncAutomatically(2)); - - } - - @MediumTest - public void testAuthorityRenaming() throws Exception { - final Account account1 = new Account("acc1", "type1"); - final Account account2 = new Account("acc2", "type2"); - final String authorityContacts = "contacts"; - final String authorityCalendar = "calendar"; - final String authorityOther = "other"; - final String authorityContactsNew = "com.android.contacts"; - final String authorityCalendarNew = "com.android.calendar"; - - MockContentResolver mockResolver = new MockContentResolver(); - - final TestContext testContext = new TestContext(mockResolver, getContext()); - - byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" - + "<accounts>\n" - + "<authority id=\"0\" account=\"acc1\" type=\"type1\" authority=\"contacts\" />\n" - + "<authority id=\"1\" account=\"acc1\" type=\"type1\" authority=\"calendar\" />\n" - + "<authority id=\"2\" account=\"acc1\" type=\"type1\" authority=\"other\" />\n" - + "<authority id=\"3\" account=\"acc2\" type=\"type2\" authority=\"contacts\" />\n" - + "<authority id=\"4\" account=\"acc2\" type=\"type2\" authority=\"calendar\" />\n" - + "<authority id=\"5\" account=\"acc2\" type=\"type2\" authority=\"other\" />\n" - + "<authority id=\"6\" account=\"acc2\" type=\"type2\" enabled=\"false\"" - + " authority=\"com.android.calendar\" />\n" - + "<authority id=\"7\" account=\"acc2\" type=\"type2\" enabled=\"false\"" - + " authority=\"com.android.contacts\" />\n" - + "</accounts>\n").getBytes(); - - File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync"); - syncDir.mkdirs(); - AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml")); - FileOutputStream fos = accountInfoFile.startWrite(); - fos.write(accountsFileData); - accountInfoFile.finishWrite(fos); - - SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext); - - assertEquals(false, engine.getSyncAutomatically(account1, 0, authorityContacts)); - assertEquals(false, engine.getSyncAutomatically(account1, 0, authorityCalendar)); - assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityOther)); - assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityContactsNew)); - assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityCalendarNew)); - - assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityContacts)); - assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityCalendar)); - assertEquals(true, engine.getSyncAutomatically(account2, 0, authorityOther)); - assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityContactsNew)); - assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityCalendarNew)); - } - - @SmallTest - public void testSyncableMigration() throws Exception { - final Account account = new Account("acc", "type"); - - MockContentResolver mockResolver = new MockContentResolver(); - - final TestContext testContext = new TestContext(mockResolver, getContext()); - - byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" - + "<accounts>\n" - + "<authority id=\"0\" account=\"acc\" authority=\"other1\" />\n" - + "<authority id=\"1\" account=\"acc\" type=\"type\" authority=\"other2\" />\n" - + "<authority id=\"2\" account=\"acc\" type=\"type\" syncable=\"false\"" - + " authority=\"other3\" />\n" - + "<authority id=\"3\" account=\"acc\" type=\"type\" syncable=\"true\"" - + " authority=\"other4\" />\n" - + "</accounts>\n").getBytes(); - - File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync"); - syncDir.mkdirs(); - AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml")); - FileOutputStream fos = accountInfoFile.startWrite(); - fos.write(accountsFileData); - accountInfoFile.finishWrite(fos); - - SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext); - - assertEquals(-1, engine.getIsSyncable(account, 0, "other1")); - assertEquals(1, engine.getIsSyncable(account, 0, "other2")); - assertEquals(0, engine.getIsSyncable(account, 0, "other3")); - assertEquals(1, engine.getIsSyncable(account, 0, "other4")); - } - - /** - * Verify that the API cannot cause a run-time reboot by passing in the empty string as an - * authority. The problem here is that - * {@link SyncStorageEngine#getOrCreateAuthorityLocked(account, provider)} would register - * an empty authority which causes a RTE in {@link SyncManager#scheduleReadyPeriodicSyncs()}. - * This is not strictly a SSE test, but it does depend on the SSE data structures. - */ - @SmallTest - public void testExpectedIllegalArguments() throws Exception { - try { - ContentResolver.setSyncAutomatically(account1, "", true); - fail("empty provider string should throw IllegalArgumentException"); - } catch (IllegalArgumentException expected) {} - - try { - ContentResolver.addPeriodicSync(account1, "", Bundle.EMPTY, 84000L); - fail("empty provider string should throw IllegalArgumentException"); - } catch (IllegalArgumentException expected) {} - - try { - ContentResolver.removePeriodicSync(account1, "", Bundle.EMPTY); - fail("empty provider string should throw IllegalArgumentException"); - } catch (IllegalArgumentException expected) {} - - try { - ContentResolver.cancelSync(account1, ""); - fail("empty provider string should throw IllegalArgumentException"); - } catch (IllegalArgumentException expected) {} - - try { - ContentResolver.setIsSyncable(account1, "", 0); - fail("empty provider string should throw IllegalArgumentException"); - } catch (IllegalArgumentException expected) {} - - try { - ContentResolver.cancelSync(account1, ""); - fail("empty provider string should throw IllegalArgumentException"); - } catch (IllegalArgumentException expected) {} - - try { - ContentResolver.requestSync(account1, "", Bundle.EMPTY); - fail("empty provider string should throw IllegalArgumentException"); - } catch (IllegalArgumentException expected) {} - - try { - ContentResolver.getSyncStatus(account1, ""); - fail("empty provider string should throw IllegalArgumentException"); - } catch (IllegalArgumentException expected) {} - - // Make sure we aren't blocking null account/provider for those functions that use it - // to specify ALL accounts/providers. - ContentResolver.requestSync(null, null, Bundle.EMPTY); - ContentResolver.cancelSync(null, null); - } -} - -class TestContext extends ContextWrapper { - - ContentResolver mResolver; - - private final Context mRealContext; - - public TestContext(ContentResolver resolver, Context realContext) { - super(new RenamingDelegatingContext(new MockContext(), realContext, "test.")); - mRealContext = realContext; - mResolver = resolver; - } - - @Override - public Resources getResources() { - return mRealContext.getResources(); - } - - @Override - public File getFilesDir() { - return mRealContext.getFilesDir(); - } - - @Override - public void enforceCallingOrSelfPermission(String permission, String message) { - } - - @Override - public void sendBroadcast(Intent intent) { - } - - @Override - public ContentResolver getContentResolver() { - return mResolver; - } -} diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java index ab0bfefbbd4d..cd3928558c4b 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java @@ -98,12 +98,6 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi this.context = injector.context; } - @Override - public boolean isPasswordBlacklisted(int userId, String password) { - return false; - } - - public void notifyChangeToContentObserver(Uri uri, int userHandle) { ContentObserver co = mMockInjector.mContentObservers.get(new Pair<>(uri, userHandle)); if (co != null) { @@ -220,11 +214,6 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi } @Override - PasswordBlacklist newPasswordBlacklist(File file) { - return services.passwordBlacklist; - } - - @Override boolean storageManagerIsFileBasedEncryptionEnabled() { return services.storageManager.isFileBasedEncryptionEnabled(); } @@ -393,8 +382,8 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi } @Override - void settingsSystemPutString(String name, String value) { - services.settings.settingsSystemPutString(name, value); + void settingsSystemPutStringForUser(String name, String value, int userId) { + services.settings.settingsSystemPutStringForUser(name, value, userId); } @Override diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 290de2b7d16a..b76064b7ebdf 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -3454,18 +3454,19 @@ public class DevicePolicyManagerTest extends DpmTestBase { dpm.setSystemSetting(admin1, Settings.System.SCREEN_BRIGHTNESS_FOR_VR, "0")); } - public void testSetSystemSettingFailWithPO() throws Exception { - setupProfileOwner(); - assertExpectException(SecurityException.class, null, () -> - dpm.setSystemSetting(admin1, Settings.System.SCREEN_BRIGHTNESS, "0")); - } - - public void testSetSystemSetting() throws Exception { + public void testSetSystemSettingWithDO() throws Exception { mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setupDeviceOwner(); dpm.setSystemSetting(admin1, Settings.System.SCREEN_BRIGHTNESS, "0"); - verify(getServices().settings).settingsSystemPutString( - Settings.System.SCREEN_BRIGHTNESS, "0"); + verify(getServices().settings).settingsSystemPutStringForUser( + Settings.System.SCREEN_BRIGHTNESS, "0", UserHandle.USER_SYSTEM); + } + + public void testSetSystemSettingWithPO() throws Exception { + setupProfileOwner(); + dpm.setSystemSetting(admin1, Settings.System.SCREEN_BRIGHTNESS, "0"); + verify(getServices().settings).settingsSystemPutStringForUser( + Settings.System.SCREEN_BRIGHTNESS, "0", DpmMockContext.CALLER_USER_HANDLE); } public void testSetTime() throws Exception { @@ -3719,7 +3720,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { } private void verifyLockTaskState(int userId) throws Exception { - verifyLockTaskState(userId, new String[0], DevicePolicyManager.LOCK_TASK_FEATURE_NONE); + verifyLockTaskState(userId, new String[0], + DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS); } private void verifyLockTaskState(int userId, String[] packages, int flags) throws Exception { @@ -4190,36 +4192,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertTrue(dpm.clearResetPasswordToken(admin1)); } - public void testSetPasswordBlacklistCannotBeCalledByNonAdmin() throws Exception { - assertExpectException(SecurityException.class, /* messageRegex= */ null, - () -> dpm.setPasswordBlacklist(admin1, null, null)); - verifyZeroInteractions(getServices().passwordBlacklist); - } - - public void testClearingPasswordBlacklistDoesNotCreateNewBlacklist() throws Exception { - setupProfileOwner(); - dpm.setPasswordBlacklist(admin1, null, null); - verifyZeroInteractions(getServices().passwordBlacklist); - } - - public void testSetPasswordBlacklistCreatesNewBlacklist() throws Exception { - final String name = "myblacklist"; - final List<String> explicit = Arrays.asList("password", "letmein"); - setupProfileOwner(); - dpm.setPasswordBlacklist(admin1, name, explicit); - verify(getServices().passwordBlacklist).savePasswordBlacklist(name, explicit); - } - - public void testSetPasswordBlacklistOnlyConvertsExplicitToLowerCase() throws Exception { - final List<String> mixedCase = Arrays.asList("password", "LETMEIN", "FooTBAll"); - final List<String> lowerCase = Arrays.asList("password", "letmein", "football"); - mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; - setupDeviceOwner(); - final String name = "Name of the Blacklist"; - dpm.setPasswordBlacklist(admin1, name, mixedCase); - verify(getServices().passwordBlacklist).savePasswordBlacklist(name, lowerCase); - } - public void testIsActivePasswordSufficient() throws Exception { mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; mContext.packageName = admin1.getPackageName(); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java index 34c69f5b0c5b..e753df1f30ec 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java @@ -96,7 +96,6 @@ public class MockSystemServices { public final IBackupManager ibackupManager; public final IAudioService iaudioService; public final LockPatternUtils lockPatternUtils; - public final PasswordBlacklist passwordBlacklist; public final StorageManagerForMock storageManager; public final WifiManager wifiManager; public final SettingsForMock settings; @@ -135,7 +134,6 @@ public class MockSystemServices { ibackupManager = mock(IBackupManager.class); iaudioService = mock(IAudioService.class); lockPatternUtils = mock(LockPatternUtils.class); - passwordBlacklist = mock(PasswordBlacklist.class); storageManager = mock(StorageManagerForMock.class); wifiManager = mock(WifiManager.class); settings = mock(SettingsForMock.class); @@ -419,7 +417,7 @@ public class MockSystemServices { public void settingsGlobalPutString(String name, String value) { } - public void settingsSystemPutString(String name, String value) { + public void settingsSystemPutStringForUser(String name, String value, int callingUserId) { } public int settingsGlobalGetInt(String name, int value) { diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/PasswordBlacklistTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/PasswordBlacklistTest.java deleted file mode 100644 index 1b3fc2c1f207..000000000000 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/PasswordBlacklistTest.java +++ /dev/null @@ -1,110 +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.devicepolicy; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.Arrays; -import java.util.ArrayList; -import java.util.List; - -/** - * Unit tests for {@link PasswordBlacklist}. - * - * bit FrameworksServicesTests:com.android.server.devicepolicy.PasswordBlacklistTest - * runtest -x frameworks/base/services/tests/servicestests/src/com/android/server/devicepolicy/PasswordBlacklistTest.java - */ -@RunWith(AndroidJUnit4.class) -public final class PasswordBlacklistTest { - private File mBlacklistFile; - private PasswordBlacklist mBlacklist; - - @Before - public void setUp() throws IOException { - mBlacklistFile = File.createTempFile("pwdbl", null); - mBlacklist = new PasswordBlacklist(mBlacklistFile); - } - - @After - public void tearDown() { - mBlacklist.delete(); - } - - @Test - public void matchIsExact() { - // Note: Case sensitivity is handled by the user of PasswordBlacklist by normalizing the - // values stored in and tested against it. - mBlacklist.savePasswordBlacklist("matchIsExact", Arrays.asList("password", "qWERty")); - assertTrue(mBlacklist.isPasswordBlacklisted("password")); - assertTrue(mBlacklist.isPasswordBlacklisted("qWERty")); - assertFalse(mBlacklist.isPasswordBlacklisted("Password")); - assertFalse(mBlacklist.isPasswordBlacklisted("qwert")); - assertFalse(mBlacklist.isPasswordBlacklisted("letmein")); - } - - @Test - public void matchIsNotRegex() { - mBlacklist.savePasswordBlacklist("matchIsNotRegex", Arrays.asList("a+b*")); - assertTrue(mBlacklist.isPasswordBlacklisted("a+b*")); - assertFalse(mBlacklist.isPasswordBlacklisted("aaaa")); - assertFalse(mBlacklist.isPasswordBlacklisted("abbbb")); - assertFalse(mBlacklist.isPasswordBlacklisted("aaaa")); - } - - @Test - public void matchFailsSafe() throws IOException { - try (FileOutputStream fos = new FileOutputStream(mBlacklistFile)) { - // Write a malformed blacklist file - fos.write(17); - } - assertTrue(mBlacklist.isPasswordBlacklisted("anything")); - assertTrue(mBlacklist.isPasswordBlacklisted("at")); - assertTrue(mBlacklist.isPasswordBlacklisted("ALL")); - } - - @Test - public void blacklistCanBeNamed() { - final String name = "identifier"; - mBlacklist.savePasswordBlacklist(name, Arrays.asList("one", "two", "three")); - assertEquals(mBlacklist.getName(), name); - } - - @Test - public void reportsTheCorrectNumberOfEntries() { - mBlacklist.savePasswordBlacklist("Count Entries", Arrays.asList("1", "2", "3", "4")); - assertEquals(mBlacklist.getSize(), 4); - } - - @Test - public void reportsBlacklistFile() { - assertEquals(mBlacklistFile, mBlacklist.getFile()); - } -} 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 9ae45ea15ca2..b8d2c3e3d5b0 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 @@ -33,23 +33,28 @@ 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.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; 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.FileUtils; import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; -import android.security.keystore.recovery.KeyDerivationParams; import android.security.keystore.recovery.KeyChainSnapshot; +import android.security.keystore.recovery.KeyDerivationParams; import android.security.keystore.recovery.RecoveryController; import android.security.keystore.recovery.WrappedApplicationKey; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.util.Log; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage; @@ -59,6 +64,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.io.File; import java.nio.charset.StandardCharsets; @@ -72,6 +78,9 @@ import javax.crypto.SecretKey; @SmallTest @RunWith(AndroidJUnit4.class) public class KeySyncTaskTest { + + private static final String SNAPSHOT_TOP_LEVEL_DIRECTORY = "recoverablekeystore"; + private static final String KEY_ALGORITHM = "AES"; private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore"; private static final String TEST_ROOT_CERT_ALIAS = "trusted_root"; @@ -91,6 +100,7 @@ public class KeySyncTaskTest { @Mock private PlatformKeyManager mPlatformKeyManager; @Mock private RecoverySnapshotListenersStorage mSnapshotListenersStorage; + @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper; private RecoverySnapshotStorage mRecoverySnapshotStorage; private RecoverableKeyStoreDb mRecoverableKeyStoreDb; @@ -117,7 +127,7 @@ public class KeySyncTaskTest { TEST_ROOT_CERT_ALIAS); mRecoverableKeyStoreDb.setActiveRootOfTrust(TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, TEST_ROOT_CERT_ALIAS); - mRecoverySnapshotStorage = new RecoverySnapshotStorage(); + mRecoverySnapshotStorage = new RecoverySnapshotStorage(context.getFilesDir()); mKeySyncTask = new KeySyncTask( mRecoverableKeyStoreDb, @@ -127,7 +137,8 @@ public class KeySyncTaskTest { TEST_CREDENTIAL_TYPE, TEST_CREDENTIAL, /*credentialUpdated=*/ false, - mPlatformKeyManager); + mPlatformKeyManager, + mTestOnlyInsecureCertificateHelper); mWrappingKey = generateAndroidKeyStoreKey(); mEncryptKey = new PlatformEncryptionKey(TEST_GENERATION_ID, mWrappingKey); @@ -139,6 +150,10 @@ public class KeySyncTaskTest { public void tearDown() { mRecoverableKeyStoreDb.close(); mDatabaseFile.delete(); + + File file = new File(InstrumentationRegistry.getTargetContext().getFilesDir(), + SNAPSHOT_TOP_LEVEL_DIRECTORY); + FileUtils.deleteContentsAndDir(file); } @Test @@ -277,6 +292,100 @@ public class KeySyncTaskTest { } @Test + public void run_InTestModeWithWhitelistedCredentials() throws Exception { + mRecoverableKeyStoreDb.setServerParams( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE); + mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); + addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS); + mRecoverableKeyStoreDb.setRecoveryServiceCertPath( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1); + + // Enter test mode with whitelisted credentials + when(mTestOnlyInsecureCertificateHelper.isTestOnlyCertificate(any())).thenReturn(true); + when(mTestOnlyInsecureCertificateHelper.doesCredentailSupportInsecureMode(anyInt(), any())) + .thenReturn(true); + mKeySyncTask.run(); + + verify(mTestOnlyInsecureCertificateHelper) + .getDefaultCertificateAliasIfEmpty(eq(TEST_ROOT_CERT_ALIAS)); + + // run whitelist checks + verify(mTestOnlyInsecureCertificateHelper) + .doesCredentailSupportInsecureMode(anyInt(), any()); + verify(mTestOnlyInsecureCertificateHelper) + .keepOnlyWhitelistedInsecureKeys(any()); + + KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID); + assertNotNull(keyChainSnapshot); // created snapshot + List<WrappedApplicationKey> applicationKeys = keyChainSnapshot.getWrappedApplicationKeys(); + assertThat(applicationKeys).hasSize(0); // non whitelisted key is not included + } + + @Test + public void run_InTestModeWithNonWhitelistedCredentials() throws Exception { + mRecoverableKeyStoreDb.setServerParams( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE); + mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); + addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS); + mRecoverableKeyStoreDb.setRecoveryServiceCertPath( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1); + + // Enter test mode with non whitelisted credentials + when(mTestOnlyInsecureCertificateHelper.isTestOnlyCertificate(any())).thenReturn(true); + when(mTestOnlyInsecureCertificateHelper.doesCredentailSupportInsecureMode(anyInt(), any())) + .thenReturn(false); + mKeySyncTask.run(); + + assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID)); // not created + verify(mTestOnlyInsecureCertificateHelper) + .getDefaultCertificateAliasIfEmpty(eq(TEST_ROOT_CERT_ALIAS)); + verify(mTestOnlyInsecureCertificateHelper) + .doesCredentailSupportInsecureMode(anyInt(), any()); + } + + @Test + public void run_doesNotFilterCredentialsAndAliasesInProd() throws Exception { + mRecoverableKeyStoreDb.setServerParams( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE); + mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); + addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS); + mRecoverableKeyStoreDb.setRecoveryServiceCertPath( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1); + + mKeySyncTask.run(); + assertNotNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID)); + + verify(mTestOnlyInsecureCertificateHelper) + .getDefaultCertificateAliasIfEmpty(eq(TEST_ROOT_CERT_ALIAS)); + verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) + .isTestOnlyCertificate(eq(TEST_ROOT_CERT_ALIAS)); + + // no whitelists check + verify(mTestOnlyInsecureCertificateHelper, never()) + .doesCredentailSupportInsecureMode(anyInt(), any()); + verify(mTestOnlyInsecureCertificateHelper, never()) + .keepOnlyWhitelistedInsecureKeys(any()); + } + + @Test + public void run_replacesNullActiveRootAliasWithDefaultValue() throws Exception { + mRecoverableKeyStoreDb.setServerParams( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE); + mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); + addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS); + mRecoverableKeyStoreDb.setRecoveryServiceCertPath( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1); + mRecoverableKeyStoreDb.setActiveRootOfTrust(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, + /*alias=*/ null); + + when(mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(null)) + .thenReturn(TEST_ROOT_CERT_ALIAS); // override default. + mKeySyncTask.run(); + + verify(mTestOnlyInsecureCertificateHelper).getDefaultCertificateAliasIfEmpty(null); + } + + @Test public void run_sendsEncryptedKeysIfAvailableToSync_withRawPublicKey() throws Exception { mRecoverableKeyStoreDb.setRecoveryServiceCertPath( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1); @@ -391,7 +500,8 @@ public class KeySyncTaskTest { CREDENTIAL_TYPE_PASSWORD, "password", /*credentialUpdated=*/ false, - mPlatformKeyManager); + mPlatformKeyManager, + mTestOnlyInsecureCertificateHelper); mRecoverableKeyStoreDb.setRecoveryServiceCertPath( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1); @@ -417,7 +527,8 @@ public class KeySyncTaskTest { CREDENTIAL_TYPE_PASSWORD, /*credential=*/ "1234", /*credentialUpdated=*/ false, - mPlatformKeyManager); + mPlatformKeyManager, + mTestOnlyInsecureCertificateHelper); mRecoverableKeyStoreDb.setRecoveryServiceCertPath( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1); @@ -444,7 +555,8 @@ public class KeySyncTaskTest { CREDENTIAL_TYPE_PATTERN, "12345", /*credentialUpdated=*/ false, - mPlatformKeyManager); + mPlatformKeyManager, + mTestOnlyInsecureCertificateHelper); mRecoverableKeyStoreDb.setRecoveryServiceCertPath( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1); @@ -525,7 +637,8 @@ public class KeySyncTaskTest { /*credentialType=*/ 3, "12345", /*credentialUpdated=*/ false, - mPlatformKeyManager); + mPlatformKeyManager, + mTestOnlyInsecureCertificateHelper); addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS); 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 f5f5027da771..5efe5d2d655e 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 @@ -18,6 +18,8 @@ package com.android.server.locksettings.recoverablekeystore; import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_LOCKSCREEN; import static android.security.keystore.recovery.KeyChainProtectionParams.UI_FORMAT_PASSWORD; +import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT; +import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertArrayEquals; @@ -27,6 +29,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -67,6 +70,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.io.File; import java.nio.charset.StandardCharsets; @@ -93,6 +97,8 @@ public class RecoverableKeyStoreManagerTest { private static final String ROOT_CERTIFICATE_ALIAS = ""; private static final String DEFAULT_ROOT_CERT_ALIAS = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS; + private static final String INSECURE_CERTIFICATE_ALIAS = + TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS; private static final String TEST_SESSION_ID = "karlin"; private static final byte[] TEST_PUBLIC_KEY = new byte[] { (byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a, @@ -160,6 +166,7 @@ public class RecoverableKeyStoreManagerTest { @Mock private KeyguardManager mKeyguardManager; @Mock private PlatformKeyManager mPlatformKeyManager; @Mock private ApplicationKeyStorage mApplicationKeyStorage; + @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper; private RecoverableKeyStoreDb mRecoverableKeyStoreDb; private File mDatabaseFile; @@ -189,14 +196,14 @@ public class RecoverableKeyStoreManagerTest { mRecoverableKeyStoreManager = new RecoverableKeyStoreManager( mMockContext, - KeyStore.getInstance(), mRecoverableKeyStoreDb, mRecoverySessionStorage, Executors.newSingleThreadExecutor(), mRecoverySnapshotStorage, mMockListenersStorage, mPlatformKeyManager, - mApplicationKeyStorage); + mApplicationKeyStorage, + mTestOnlyInsecureCertificateHelper); } @After @@ -206,24 +213,6 @@ public class RecoverableKeyStoreManagerTest { } @Test - public void generateAndStoreKey_storesTheKey() throws Exception { - int uid = Binder.getCallingUid(); - int userId = UserHandle.getCallingUserId(); - - mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS); - - assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNotNull(); - - assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue(); - } - - @Test - public void generateAndStoreKey_returnsAKeyOfAppropriateSize() throws Exception { - assertThat(mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS)) - .hasLength(RECOVERABLE_KEY_SIZE_BYTES); - } - - @Test public void importKey_storesTheKey() throws Exception { int uid = Binder.getCallingUid(); int userId = UserHandle.getCallingUserId(); @@ -260,7 +249,7 @@ public class RecoverableKeyStoreManagerTest { @Test public void removeKey_removesAKey() throws Exception { int uid = Binder.getCallingUid(); - mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS); + mRecoverableKeyStoreManager.generateKey(TEST_ALIAS); mRecoverableKeyStoreManager.removeKey(TEST_ALIAS); @@ -271,7 +260,7 @@ public class RecoverableKeyStoreManagerTest { public void removeKey_updatesShouldCreateSnapshot() throws Exception { int uid = Binder.getCallingUid(); int userId = UserHandle.getCallingUserId(); - mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS); + mRecoverableKeyStoreManager.generateKey(TEST_ALIAS); // Pretend that key was synced mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); @@ -301,6 +290,9 @@ public class RecoverableKeyStoreManagerTest { mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, TestData.getCertXmlWithSerial(certSerial)); + verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) + .getDefaultCertificateAliasIfEmpty(ROOT_CERTIFICATE_ALIAS); + assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid, DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(TestData.CERT_PATH_1); @@ -310,6 +302,67 @@ public class RecoverableKeyStoreManagerTest { } @Test + public void initRecoveryService_triesToFilterRootAlias() throws Exception { + int uid = Binder.getCallingUid(); + int userId = UserHandle.getCallingUserId(); + long certSerial = 1000L; + mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); + + mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, + TestData.getCertXmlWithSerial(certSerial)); + + verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) + .getDefaultCertificateAliasIfEmpty(eq(ROOT_CERTIFICATE_ALIAS)); + + verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) + .getRootCertificate(eq(DEFAULT_ROOT_CERT_ALIAS)); + + String activeRootAlias = mRecoverableKeyStoreDb.getActiveRootOfTrust(userId, uid); + assertThat(activeRootAlias).isEqualTo(DEFAULT_ROOT_CERT_ALIAS); + + } + + @Test + public void initRecoveryService_usesProdCertificateForEmptyRootAlias() throws Exception { + int uid = Binder.getCallingUid(); + int userId = UserHandle.getCallingUserId(); + long certSerial = 1000L; + mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); + + mRecoverableKeyStoreManager.initRecoveryService(/*rootCertificateAlias=*/ "", + TestData.getCertXmlWithSerial(certSerial)); + + verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) + .getDefaultCertificateAliasIfEmpty(eq("")); + + verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) + .getRootCertificate(eq(DEFAULT_ROOT_CERT_ALIAS)); + + String activeRootAlias = mRecoverableKeyStoreDb.getActiveRootOfTrust(userId, uid); + assertThat(activeRootAlias).isEqualTo(DEFAULT_ROOT_CERT_ALIAS); + } + + @Test + public void initRecoveryService_usesProdCertificateForNullRootAlias() throws Exception { + int uid = Binder.getCallingUid(); + int userId = UserHandle.getCallingUserId(); + long certSerial = 1000L; + mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); + + mRecoverableKeyStoreManager.initRecoveryService(/*rootCertificateAlias=*/ null, + TestData.getCertXmlWithSerial(certSerial)); + + verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) + .getDefaultCertificateAliasIfEmpty(null); + + verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) + .getRootCertificate(eq(DEFAULT_ROOT_CERT_ALIAS)); + + String activeRootAlias = mRecoverableKeyStoreDb.getActiveRootOfTrust(userId, uid); + assertThat(activeRootAlias).isEqualTo(DEFAULT_ROOT_CERT_ALIAS); + } + + @Test public void initRecoveryService_regeneratesCounterId() throws Exception { int uid = Binder.getCallingUid(); int userId = UserHandle.getCallingUserId(); @@ -335,7 +388,7 @@ public class RecoverableKeyStoreManagerTest { modifiedCertXml); fail("should have thrown"); } catch (ServiceSpecificException e) { - assertThat(e.getMessage()).contains("validate cert"); + assertThat(e.errorCode).isEqualTo(ERROR_INVALID_CERTIFICATE); } } @@ -418,6 +471,24 @@ public class RecoverableKeyStoreManagerTest { } @Test + public void initRecoveryServiceWithSigFile_usesProdCertificateForNullRootAlias() + throws Exception { + int uid = Binder.getCallingUid(); + int userId = UserHandle.getCallingUserId(); + long certSerial = 1000L; + mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); + + mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile( + /*rootCertificateAlias=*/null, TestData.getCertXml(), TestData.getSigXml()); + + verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) + .getDefaultCertificateAliasIfEmpty(null); + + verify(mTestOnlyInsecureCertificateHelper, atLeast(1)) + .getRootCertificate(eq(DEFAULT_ROOT_CERT_ALIAS)); + } + + @Test public void initRecoveryServiceWithSigFile_throwsIfNullCertFile() throws Exception { try { mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile( @@ -449,7 +520,19 @@ public class RecoverableKeyStoreManagerTest { getUtf8Bytes("wrong-sig-file-format")); fail("should have thrown"); } catch (ServiceSpecificException e) { - assertThat(e.getMessage()).contains("parse the sig file"); + assertThat(e.errorCode).isEqualTo(ERROR_BAD_CERTIFICATE_FORMAT); + } + } + + @Test + public void initRecoveryServiceWithSigFile_throwsIfTestAliasUsedWithProdCert() + throws Exception { + try { + mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile( + INSECURE_CERTIFICATE_ALIAS, TestData.getCertXml(), TestData.getSigXml()); + fail("should have thrown"); + } catch (ServiceSpecificException e) { + assertThat(e.errorCode).isEqualTo(ERROR_INVALID_CERTIFICATE); } } @@ -713,7 +796,8 @@ public class RecoverableKeyStoreManagerTest { } @Test - public void recoverKeyChainSnapshot_throwsIfFailedToDecryptAllApplicationKeys() throws Exception { + public void recoverKeyChainSnapshot_throwsIfFailedToDecryptAllApplicationKeys() + throws Exception { mRecoverableKeyStoreManager.startRecoverySession( TEST_SESSION_ID, TEST_PUBLIC_KEY, @@ -793,7 +877,8 @@ public class RecoverableKeyStoreManagerTest { } @Test - public void recoverKeyChainSnapshot_worksOnOtherApplicationKeysIfOneDecryptionFails() throws Exception { + public void recoverKeyChainSnapshot_worksOnOtherApplicationKeysIfOneDecryptionFails() + throws Exception { mRecoverableKeyStoreManager.startRecoverySession( TEST_SESSION_ID, TEST_PUBLIC_KEY, @@ -955,7 +1040,7 @@ public class RecoverableKeyStoreManagerTest { int userId = UserHandle.getCallingUserId(); mRecoverableKeyStoreManager.setRecoverySecretTypes(new int[] { 1 }); - mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS); + mRecoverableKeyStoreManager.generateKey(TEST_ALIAS); // Pretend that key was synced mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); mRecoverableKeyStoreManager.setRecoverySecretTypes(new int[] { 2 }); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java index 4b059c665b54..9b2c853d6b6f 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java @@ -14,8 +14,12 @@ import java.security.cert.CertificateFactory; import java.security.cert.CertPath; import java.security.spec.ECPrivateKeySpec; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + public final class TestData { + private static final String KEY_ALGORITHM = "AES"; private static final long DEFAULT_SERIAL = 1000; private static final String CERT_PATH_ENCODING = "PkiPath"; @@ -308,4 +312,10 @@ public final class TestData { KeyFactory keyFactory = KeyFactory.getInstance("EC"); return keyFactory.generatePrivate(new ECPrivateKeySpec(priv, SecureBox.EC_PARAM_SPEC)); } + + public static SecretKey generateKey() throws Exception { + KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM); + keyGenerator.init(/*keySize=*/ 256); + return keyGenerator.generateKey(); + } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelperTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelperTest.java new file mode 100644 index 000000000000..bc50c9e487e8 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelperTest.java @@ -0,0 +1,128 @@ +package com.android.server.locksettings.recoverablekeystore; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.security.keystore.recovery.TrustedRootCertificates; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.Log; + +import com.android.internal.widget.LockPatternUtils; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.HashMap; +import java.security.cert.X509Certificate; +import java.util.Map; +import javax.crypto.SecretKey; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TestOnlyInsecureCertificateHelperTest { + private final TestOnlyInsecureCertificateHelper mHelper + = new TestOnlyInsecureCertificateHelper(); + + @Test + public void testDoesCredentailSupportInsecureMode_forNonWhitelistedPassword() throws Exception { + assertThat(mHelper.doesCredentailSupportInsecureMode( + LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, "secret12345")).isFalse(); + assertThat(mHelper.doesCredentailSupportInsecureMode( + LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, "1234")).isFalse(); + } + + @Test + public void testDoesCredentailSupportInsecureMode_forWhitelistedPassword() throws Exception { + assertThat(mHelper.doesCredentailSupportInsecureMode( + LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, + TrustedRootCertificates.INSECURE_PASSWORD_PREFIX)).isTrue(); + + assertThat(mHelper.doesCredentailSupportInsecureMode( + LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, + TrustedRootCertificates.INSECURE_PASSWORD_PREFIX + "12")).isTrue(); + } + + @Test + public void testDoesCredentailSupportInsecureMode_Pattern() throws Exception { + assertThat(mHelper.doesCredentailSupportInsecureMode( + LockPatternUtils.CREDENTIAL_TYPE_PATTERN, + TrustedRootCertificates.INSECURE_PASSWORD_PREFIX)).isFalse(); + assertThat(mHelper.doesCredentailSupportInsecureMode( + LockPatternUtils.CREDENTIAL_TYPE_NONE, + TrustedRootCertificates.INSECURE_PASSWORD_PREFIX)).isFalse(); + } + + @Test + public void testIsTestOnlyCertificate() throws Exception { + assertThat(mHelper.isTestOnlyCertificate( + TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS)).isFalse(); + assertThat(mHelper.isTestOnlyCertificate( + TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS)).isTrue(); + assertThat(mHelper.isTestOnlyCertificate( + "UNKNOWN_ALIAS")).isFalse(); + } + + @Test + public void testKeepOnlyWhitelistedInsecureKeys_emptyKeysList() throws Exception { + Map<String, SecretKey> rawKeys = new HashMap<>(); + Map<String, SecretKey> expectedResult = new HashMap<>(); + + Map<String, SecretKey> filteredKeys = + mHelper.keepOnlyWhitelistedInsecureKeys(rawKeys); + assertThat(filteredKeys.entrySet()).containsExactlyElementsIn(expectedResult.entrySet()); + assertThat(filteredKeys.entrySet()).containsAllIn(rawKeys.entrySet()); + } + + @Test + public void testKeepOnlyWhitelistedInsecureKeys_singleNonWhitelistedKey() throws Exception { + Map<String, SecretKey> rawKeys = new HashMap<>(); + Map<String, SecretKey> expectedResult = new HashMap<>(); + + String alias = "secureAlias"; + rawKeys.put(alias, TestData.generateKey()); + + Map<String, SecretKey> filteredKeys = + mHelper.keepOnlyWhitelistedInsecureKeys(rawKeys); + assertThat(filteredKeys.entrySet()).containsExactlyElementsIn(expectedResult.entrySet()); + assertThat(rawKeys.entrySet()).containsAllIn(filteredKeys.entrySet()); + } + + @Test + public void testKeepOnlyWhitelistedInsecureKeys_singleWhitelistedKey() throws Exception { + Map<String, SecretKey> rawKeys = new HashMap<>(); + Map<String, SecretKey> expectedResult = new HashMap<>(); + + String alias = TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX; + rawKeys.put(alias, TestData.generateKey()); + expectedResult.put(alias, rawKeys.get(alias)); + + Map<String, SecretKey> filteredKeys = + mHelper.keepOnlyWhitelistedInsecureKeys(rawKeys); + assertThat(filteredKeys.entrySet()).containsExactlyElementsIn(expectedResult.entrySet()); + assertThat(rawKeys.entrySet()).containsAllIn(filteredKeys.entrySet()); + } + + @Test + public void testKeepOnlyWhitelistedInsecureKeys() throws Exception { + Map<String, SecretKey> rawKeys = new HashMap<>(); + Map<String, SecretKey> expectedResult = new HashMap<>(); + + String alias = "SECURE_ALIAS" + TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX; + rawKeys.put(alias, TestData.generateKey()); + + alias = TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX + "1"; + rawKeys.put(alias, TestData.generateKey()); + expectedResult.put(alias, rawKeys.get(alias)); + + alias = TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX + "2"; + rawKeys.put(alias, TestData.generateKey()); + expectedResult.put(alias, rawKeys.get(alias)); + + Map<String, SecretKey> filteredKeys = + mHelper.keepOnlyWhitelistedInsecureKeys(rawKeys); + assertThat(filteredKeys.entrySet()).containsExactlyElementsIn(expectedResult.entrySet()); + assertThat(rawKeys.entrySet()).containsAllIn(filteredKeys.entrySet()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializerTest.java new file mode 100644 index 000000000000..2f4da86bf38f --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializerTest.java @@ -0,0 +1,250 @@ +/* + * 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.recoverablekeystore.serialization; + +import static com.google.common.truth.Truth.assertThat; + +import android.security.keystore.recovery.KeyChainProtectionParams; +import android.security.keystore.recovery.KeyChainSnapshot; +import android.security.keystore.recovery.KeyDerivationParams; +import android.security.keystore.recovery.WrappedApplicationKey; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.server.locksettings.recoverablekeystore.TestData; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.security.cert.CertPath; +import java.util.ArrayList; +import java.util.List; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class KeyChainSnapshotSerializerTest { + private static final int COUNTER_ID = 2134; + private static final int SNAPSHOT_VERSION = 125; + private static final int MAX_ATTEMPTS = 21; + private static final byte[] SERVER_PARAMS = new byte[] { 8, 2, 4 }; + private static final byte[] KEY_BLOB = new byte[] { 124, 53, 53, 53 }; + private static final byte[] PUBLIC_KEY_BLOB = new byte[] { 6, 6, 6, 6, 6, 6, 7 }; + private static final CertPath CERT_PATH = TestData.CERT_PATH_1; + private static final int SECRET_TYPE = KeyChainProtectionParams.TYPE_LOCKSCREEN; + private static final int LOCK_SCREEN_UI = KeyChainProtectionParams.UI_FORMAT_PASSWORD; + private static final byte[] SALT = new byte[] { 5, 4, 3, 2, 1 }; + private static final int MEMORY_DIFFICULTY = 45; + private static final int ALGORITHM = KeyDerivationParams.ALGORITHM_SCRYPT; + private static final byte[] SECRET = new byte[] { 1, 2, 3, 4 }; + + private static final String TEST_KEY_1_ALIAS = "key1"; + private static final byte[] TEST_KEY_1_BYTES = new byte[] { 66, 77, 88 }; + + private static final String TEST_KEY_2_ALIAS = "key2"; + private static final byte[] TEST_KEY_2_BYTES = new byte[] { 99, 33, 11 }; + + private static final String TEST_KEY_3_ALIAS = "key3"; + private static final byte[] TEST_KEY_3_BYTES = new byte[] { 2, 8, 100 }; + + @Test + public void roundTrip_persistsCounterId() throws Exception { + assertThat(roundTrip().getCounterId()).isEqualTo(COUNTER_ID); + } + + @Test + public void roundTrip_persistsSnapshotVersion() throws Exception { + assertThat(roundTrip().getSnapshotVersion()).isEqualTo(SNAPSHOT_VERSION); + } + + @Test + public void roundTrip_persistsMaxAttempts() throws Exception { + assertThat(roundTrip().getMaxAttempts()).isEqualTo(MAX_ATTEMPTS); + } + + @Test + public void roundTrip_persistsRecoveryKey() throws Exception { + assertThat(roundTrip().getEncryptedRecoveryKeyBlob()).isEqualTo(KEY_BLOB); + } + + @Test + public void roundTrip_persistsServerParams() throws Exception { + assertThat(roundTrip().getServerParams()).isEqualTo(SERVER_PARAMS); + } + + @Test + public void roundTrip_persistsCertPath() throws Exception { + assertThat(roundTrip().getTrustedHardwareCertPath()).isEqualTo(CERT_PATH); + } + + @Test + public void roundTrip_persistsBackendPublicKey() throws Exception { + assertThat(roundTrip().getTrustedHardwarePublicKey()).isEqualTo(PUBLIC_KEY_BLOB); + } + + @Test + public void roundTrip_persistsParamsList() throws Exception { + assertThat(roundTrip().getKeyChainProtectionParams()).hasSize(1); + } + + @Test + public void roundTripParams_persistsUserSecretType() throws Exception { + assertThat(roundTripParams().getUserSecretType()).isEqualTo(SECRET_TYPE); + } + + @Test + public void roundTripParams_persistsLockScreenUi() throws Exception { + assertThat(roundTripParams().getLockScreenUiFormat()).isEqualTo(LOCK_SCREEN_UI); + } + + @Test + public void roundTripParams_persistsSalt() throws Exception { + assertThat(roundTripParams().getKeyDerivationParams().getSalt()).isEqualTo(SALT); + } + + @Test + public void roundTripParams_persistsAlgorithm() throws Exception { + assertThat(roundTripParams().getKeyDerivationParams().getAlgorithm()).isEqualTo(ALGORITHM); + } + + @Test + public void roundTripParams_persistsMemoryDifficulty() throws Exception { + assertThat(roundTripParams().getKeyDerivationParams().getMemoryDifficulty()) + .isEqualTo(MEMORY_DIFFICULTY); + } + + @Test + public void roundTripParams_doesNotPersistSecret() throws Exception { + assertThat(roundTripParams().getSecret()).isEmpty(); + } + + @Test + public void roundTripKeys_hasCorrectLength() throws Exception { + assertThat(roundTripKeys()).hasSize(3); + } + + @Test + public void roundTripKeys_0_persistsAlias() throws Exception { + assertThat(roundTripKeys().get(0).getAlias()).isEqualTo(TEST_KEY_1_ALIAS); + } + + @Test + public void roundTripKeys_0_persistsKeyBytes() throws Exception { + assertThat(roundTripKeys().get(0).getEncryptedKeyMaterial()).isEqualTo(TEST_KEY_1_BYTES); + } + + @Test + public void roundTripKeys_1_persistsAlias() throws Exception { + assertThat(roundTripKeys().get(1).getAlias()).isEqualTo(TEST_KEY_2_ALIAS); + } + + @Test + public void roundTripKeys_1_persistsKeyBytes() throws Exception { + assertThat(roundTripKeys().get(1).getEncryptedKeyMaterial()).isEqualTo(TEST_KEY_2_BYTES); + } + + @Test + public void roundTripKeys_2_persistsAlias() throws Exception { + assertThat(roundTripKeys().get(2).getAlias()).isEqualTo(TEST_KEY_3_ALIAS); + } + + @Test + public void roundTripKeys_2_persistsKeyBytes() throws Exception { + assertThat(roundTripKeys().get(2).getEncryptedKeyMaterial()).isEqualTo(TEST_KEY_3_BYTES); + } + + @Test + public void serialize_doesNotThrowForNullPublicKey() throws Exception { + KeyChainSnapshotSerializer.serialize( + createTestKeyChainSnapshotNoPublicKey(), new ByteArrayOutputStream()); + } + + private static List<WrappedApplicationKey> roundTripKeys() throws Exception { + return roundTrip().getWrappedApplicationKeys(); + } + + private static KeyChainProtectionParams roundTripParams() throws Exception { + return roundTrip().getKeyChainProtectionParams().get(0); + } + + public static KeyChainSnapshot roundTrip() throws Exception { + KeyChainSnapshot snapshot = createTestKeyChainSnapshot(); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + KeyChainSnapshotSerializer.serialize(snapshot, byteArrayOutputStream); + return KeyChainSnapshotDeserializer.deserialize( + new ByteArrayInputStream(byteArrayOutputStream.toByteArray())); + } + + private static KeyChainSnapshot createTestKeyChainSnapshot() throws Exception { + return new KeyChainSnapshot.Builder() + .setCounterId(COUNTER_ID) + .setSnapshotVersion(SNAPSHOT_VERSION) + .setServerParams(SERVER_PARAMS) + .setMaxAttempts(MAX_ATTEMPTS) + .setEncryptedRecoveryKeyBlob(KEY_BLOB) + .setKeyChainProtectionParams(createKeyChainProtectionParamsList()) + .setWrappedApplicationKeys(createKeys()) + .setTrustedHardwareCertPath(CERT_PATH) + .setTrustedHardwarePublicKey(PUBLIC_KEY_BLOB) + .build(); + } + + private static KeyChainSnapshot createTestKeyChainSnapshotNoPublicKey() throws Exception { + return new KeyChainSnapshot.Builder() + .setCounterId(COUNTER_ID) + .setSnapshotVersion(SNAPSHOT_VERSION) + .setServerParams(SERVER_PARAMS) + .setMaxAttempts(MAX_ATTEMPTS) + .setEncryptedRecoveryKeyBlob(KEY_BLOB) + .setKeyChainProtectionParams(createKeyChainProtectionParamsList()) + .setWrappedApplicationKeys(createKeys()) + .setTrustedHardwareCertPath(CERT_PATH) + .build(); + } + + private static List<WrappedApplicationKey> createKeys() { + ArrayList<WrappedApplicationKey> keyList = new ArrayList<>(); + keyList.add(createKey(TEST_KEY_1_ALIAS, TEST_KEY_1_BYTES)); + keyList.add(createKey(TEST_KEY_2_ALIAS, TEST_KEY_2_BYTES)); + keyList.add(createKey(TEST_KEY_3_ALIAS, TEST_KEY_3_BYTES)); + return keyList; + } + + private static List<KeyChainProtectionParams> createKeyChainProtectionParamsList() { + KeyDerivationParams keyDerivationParams = + KeyDerivationParams.createScryptParams(SALT, MEMORY_DIFFICULTY); + KeyChainProtectionParams keyChainProtectionParams = new KeyChainProtectionParams.Builder() + .setKeyDerivationParams(keyDerivationParams) + .setUserSecretType(SECRET_TYPE) + .setLockScreenUiFormat(LOCK_SCREEN_UI) + .setSecret(SECRET) + .build(); + ArrayList<KeyChainProtectionParams> keyChainProtectionParamsList = + new ArrayList<>(1); + keyChainProtectionParamsList.add(keyChainProtectionParams); + return keyChainProtectionParamsList; + } + + private static WrappedApplicationKey createKey(String alias, byte[] bytes) { + return new WrappedApplicationKey.Builder() + .setAlias(alias) + .setEncryptedKeyMaterial(bytes) + .build(); + } +} 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 c7729566b353..ad14c3a8adef 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 @@ -1,27 +1,82 @@ package com.android.server.locksettings.recoverablekeystore.storage; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import android.content.Context; +import android.os.FileUtils; +import android.security.keystore.recovery.KeyChainProtectionParams; import android.security.keystore.recovery.KeyChainSnapshot; +import android.security.keystore.recovery.KeyDerivationParams; +import android.security.keystore.recovery.WrappedApplicationKey; +import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import com.android.server.locksettings.recoverablekeystore.TestData; +import com.google.common.io.Files; + +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.security.cert.CertPath; import java.security.cert.CertificateException; import java.util.ArrayList; +import java.util.List; @SmallTest @RunWith(AndroidJUnit4.class) public class RecoverySnapshotStorageTest { + private static final int COUNTER_ID = 432546; + private static final int MAX_ATTEMPTS = 10; + private static final byte[] SERVER_PARAMS = new byte[] { 12, 8, 2, 4, 15, 64 }; + private static final byte[] KEY_BLOB = new byte[] { 124, 56, 53, 99, 0, 0, 1 }; + private static final CertPath CERT_PATH = TestData.CERT_PATH_2; + private static final int SECRET_TYPE = KeyChainProtectionParams.TYPE_LOCKSCREEN; + private static final int LOCK_SCREEN_UI = KeyChainProtectionParams.UI_FORMAT_PATTERN; + private static final byte[] SALT = new byte[] { 1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1 }; + private static final int MEMORY_DIFFICULTY = 12; + private static final byte[] SECRET = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 }; + + private static final String TEST_KEY_1_ALIAS = "alias1"; + private static final byte[] TEST_KEY_1_BYTES = new byte[] { 100, 32, 43, 66, 77, 88 }; + + private static final String TEST_KEY_2_ALIAS = "alias11"; + private static final byte[] TEST_KEY_2_BYTES = new byte[] { 100, 0, 0, 99, 33, 11 }; + + private static final String TEST_KEY_3_ALIAS = "alias111"; + private static final byte[] TEST_KEY_3_BYTES = new byte[] { 1, 1, 1, 0, 2, 8, 100 }; + + private static final int TEST_UID = 1000; + private static final String SNAPSHOT_DIRECTORY = "recoverablekeystore/snapshots"; + private static final String SNAPSHOT_FILE_PATH = "1000.xml"; + private static final String SNAPSHOT_TOP_LEVEL_DIRECTORY = "recoverablekeystore"; + private static final KeyChainSnapshot MINIMAL_KEYCHAIN_SNAPSHOT = - createMinimalKeyChainSnapshot(); + createTestKeyChainSnapshot(1); - private final RecoverySnapshotStorage mRecoverySnapshotStorage = new RecoverySnapshotStorage(); + private Context mContext; + private RecoverySnapshotStorage mRecoverySnapshotStorage; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getTargetContext(); + mRecoverySnapshotStorage = new RecoverySnapshotStorage(mContext.getFilesDir()); + } + + @After + public void tearDown() { + File file = new File(mContext.getFilesDir(), SNAPSHOT_TOP_LEVEL_DIRECTORY); + FileUtils.deleteContentsAndDir(file); + } @Test public void get_isNullForNonExistentSnapshot() { @@ -30,37 +85,153 @@ public class RecoverySnapshotStorageTest { @Test public void get_returnsSetSnapshot() { - int userId = 1000; + mRecoverySnapshotStorage.put(TEST_UID, MINIMAL_KEYCHAIN_SNAPSHOT); - mRecoverySnapshotStorage.put(userId, MINIMAL_KEYCHAIN_SNAPSHOT); + assertEquals(MINIMAL_KEYCHAIN_SNAPSHOT, mRecoverySnapshotStorage.get(TEST_UID)); + } + + @Test + public void get_readsFromDiskIfNoneInMemory() { + mRecoverySnapshotStorage.put(TEST_UID, MINIMAL_KEYCHAIN_SNAPSHOT); + RecoverySnapshotStorage storage = new RecoverySnapshotStorage(mContext.getFilesDir()); - assertEquals(MINIMAL_KEYCHAIN_SNAPSHOT, mRecoverySnapshotStorage.get(userId)); + assertKeyChainSnapshotsAreEqual(MINIMAL_KEYCHAIN_SNAPSHOT, storage.get(TEST_UID)); } @Test - public void remove_removesSnapshots() { - int userId = 1000; + public void get_deletesFileIfItIsInvalidSnapshot() throws Exception { + File folder = new File(mContext.getFilesDir(), SNAPSHOT_DIRECTORY); + folder.mkdirs(); + File file = new File(folder, SNAPSHOT_FILE_PATH); + byte[] fileContents = "<keyChainSnapshot></keyChainSnapshot>".getBytes( + StandardCharsets.UTF_8); + Files.write(fileContents, file); + assertTrue(file.exists()); - mRecoverySnapshotStorage.put(userId, MINIMAL_KEYCHAIN_SNAPSHOT); - mRecoverySnapshotStorage.remove(userId); + assertNull(mRecoverySnapshotStorage.get(TEST_UID)); - assertNull(mRecoverySnapshotStorage.get(1000)); + assertFalse(file.exists()); + } + + @Test + public void put_overwritesOldFiles() { + int snapshotVersion = 2; + mRecoverySnapshotStorage.put(TEST_UID, MINIMAL_KEYCHAIN_SNAPSHOT); + + mRecoverySnapshotStorage.put(TEST_UID, createTestKeyChainSnapshot(snapshotVersion)); + + KeyChainSnapshot snapshot = new RecoverySnapshotStorage(mContext.getFilesDir()) + .get(TEST_UID); + assertEquals(snapshotVersion, snapshot.getSnapshotVersion()); } - private static KeyChainSnapshot createMinimalKeyChainSnapshot() { + @Test + public void put_doesNotThrowIfCannotCreateFiles() throws Exception { + File evilFile = new File(mContext.getFilesDir(), "recoverablekeystore"); + Files.write(new byte[] { 1 }, evilFile); + + mRecoverySnapshotStorage.put(TEST_UID, MINIMAL_KEYCHAIN_SNAPSHOT); + + assertNull(new RecoverySnapshotStorage(mContext.getFilesDir()).get(TEST_UID)); + } + + @Test + public void remove_removesSnapshotsFromMemory() { + mRecoverySnapshotStorage.put(TEST_UID, MINIMAL_KEYCHAIN_SNAPSHOT); + mRecoverySnapshotStorage.remove(TEST_UID); + + assertNull(mRecoverySnapshotStorage.get(TEST_UID)); + } + + @Test + public void remove_removesSnapshotsFromDisk() { + mRecoverySnapshotStorage.put(TEST_UID, MINIMAL_KEYCHAIN_SNAPSHOT); + + new RecoverySnapshotStorage(mContext.getFilesDir()).remove(TEST_UID); + + assertNull(new RecoverySnapshotStorage(mContext.getFilesDir()).get(TEST_UID)); + } + + private void assertKeyChainSnapshotsAreEqual(KeyChainSnapshot a, KeyChainSnapshot b) { + assertEquals(b.getCounterId(), a.getCounterId()); + assertEquals(b.getSnapshotVersion(), a.getSnapshotVersion()); + assertArrayEquals(b.getServerParams(), a.getServerParams()); + assertEquals(b.getMaxAttempts(), a.getMaxAttempts()); + assertArrayEquals(b.getEncryptedRecoveryKeyBlob(), a.getEncryptedRecoveryKeyBlob()); + assertEquals(b.getTrustedHardwareCertPath(), a.getTrustedHardwareCertPath()); + + List<WrappedApplicationKey> aKeys = a.getWrappedApplicationKeys(); + List<WrappedApplicationKey> bKeys = b.getWrappedApplicationKeys(); + assertEquals(bKeys.size(), aKeys.size()); + for (int i = 0; i < aKeys.size(); i++) { + assertWrappedApplicationKeysAreEqual(aKeys.get(i), bKeys.get(i)); + } + + List<KeyChainProtectionParams> aParams = a.getKeyChainProtectionParams(); + List<KeyChainProtectionParams> bParams = b.getKeyChainProtectionParams(); + assertEquals(bParams.size(), aParams.size()); + for (int i = 0; i < aParams.size(); i++) { + assertKeyChainProtectionParamsAreEqual(aParams.get(i), bParams.get(i)); + } + } + + private void assertWrappedApplicationKeysAreEqual( + WrappedApplicationKey a, WrappedApplicationKey b) { + assertEquals(b.getAlias(), a.getAlias()); + assertArrayEquals(b.getEncryptedKeyMaterial(), a.getEncryptedKeyMaterial()); + } + + private void assertKeyChainProtectionParamsAreEqual( + KeyChainProtectionParams a, KeyChainProtectionParams b) { + assertEquals(b.getUserSecretType(), a.getUserSecretType()); + assertEquals(b.getLockScreenUiFormat(), a.getLockScreenUiFormat()); + assertKeyDerivationParamsAreEqual(a.getKeyDerivationParams(), b.getKeyDerivationParams()); + } + + private void assertKeyDerivationParamsAreEqual(KeyDerivationParams a, KeyDerivationParams b) { + assertEquals(b.getAlgorithm(), a.getAlgorithm()); + assertEquals(b.getMemoryDifficulty(), a.getMemoryDifficulty()); + assertArrayEquals(b.getSalt(), a.getSalt()); + } + + private static KeyChainSnapshot createTestKeyChainSnapshot(int snapshotVersion) { + KeyDerivationParams keyDerivationParams = + KeyDerivationParams.createScryptParams(SALT, MEMORY_DIFFICULTY); + KeyChainProtectionParams keyChainProtectionParams = new KeyChainProtectionParams.Builder() + .setKeyDerivationParams(keyDerivationParams) + .setUserSecretType(SECRET_TYPE) + .setLockScreenUiFormat(LOCK_SCREEN_UI) + .setSecret(SECRET) + .build(); + ArrayList<KeyChainProtectionParams> keyChainProtectionParamsList = + new ArrayList<>(1); + keyChainProtectionParamsList.add(keyChainProtectionParams); + + ArrayList<WrappedApplicationKey> keyList = new ArrayList<>(); + keyList.add(createKey(TEST_KEY_1_ALIAS, TEST_KEY_1_BYTES)); + keyList.add(createKey(TEST_KEY_2_ALIAS, TEST_KEY_2_BYTES)); + keyList.add(createKey(TEST_KEY_3_ALIAS, TEST_KEY_3_BYTES)); + try { return new KeyChainSnapshot.Builder() - .setCounterId(1) - .setSnapshotVersion(1) - .setServerParams(new byte[0]) - .setMaxAttempts(10) - .setEncryptedRecoveryKeyBlob(new byte[0]) - .setKeyChainProtectionParams(new ArrayList<>()) - .setWrappedApplicationKeys(new ArrayList<>()) - .setTrustedHardwareCertPath(TestData.CERT_PATH_1) + .setCounterId(COUNTER_ID) + .setSnapshotVersion(snapshotVersion) + .setServerParams(SERVER_PARAMS) + .setMaxAttempts(MAX_ATTEMPTS) + .setEncryptedRecoveryKeyBlob(KEY_BLOB) + .setKeyChainProtectionParams(keyChainProtectionParamsList) + .setWrappedApplicationKeys(keyList) + .setTrustedHardwareCertPath(CERT_PATH) .build(); } catch (CertificateException e) { throw new RuntimeException(e); } } + + private static WrappedApplicationKey createKey(String alias, byte[] bytes) { + return new WrappedApplicationKey.Builder() + .setAlias(alias) + .setEncryptedKeyMaterial(bytes) + .build(); + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java index ee0ad39e9575..43a439bd72c6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java @@ -29,6 +29,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.IPackageManager; +import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.os.BaseBundle; import android.os.Bundle; @@ -36,9 +37,11 @@ import android.os.Handler; import android.os.Looper; import android.os.PersistableBundle; import android.os.RemoteException; +import android.os.UserHandle; import android.support.test.InstrumentationRegistry; import android.support.test.filters.LargeTest; import android.support.test.runner.AndroidJUnit4; +import android.util.Log; import com.android.servicestests.apps.suspendtestapp.SuspendTestReceiver; @@ -47,6 +50,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; @@ -55,6 +59,7 @@ import java.util.concurrent.atomic.AtomicReference; @RunWith(AndroidJUnit4.class) @LargeTest public class SuspendPackagesTest { + private static final String TAG = SuspendPackagesTest.class.getSimpleName(); private static final String TEST_APP_PACKAGE_NAME = SuspendTestReceiver.PACKAGE_NAME; private static final String[] PACKAGES_TO_SUSPEND = new String[]{TEST_APP_PACKAGE_NAME}; @@ -66,9 +71,11 @@ public class SuspendPackagesTest { private Context mContext; private PackageManager mPackageManager; + private LauncherApps mLauncherApps; private Handler mReceiverHandler; private ComponentName mTestReceiverComponent; private AppCommunicationReceiver mAppCommsReceiver; + private StubbedCallback mTestCallback; private static final class AppCommunicationReceiver extends BroadcastReceiver { private Context context; @@ -95,6 +102,7 @@ public class SuspendPackagesTest { @Override public void onReceive(Context context, Intent intent) { + Log.d(TAG, "AppCommunicationReceiver#onReceive: " + intent.getAction()); try { intentQueue.offer(intent, 5, TimeUnit.SECONDS); } catch (InterruptedException ie) { @@ -121,6 +129,7 @@ public class SuspendPackagesTest { public void setUp() { mContext = InstrumentationRegistry.getTargetContext(); mPackageManager = mContext.getPackageManager(); + mLauncherApps = (LauncherApps) mContext.getSystemService(Context.LAUNCHER_APPS_SERVICE); mReceiverHandler = new Handler(Looper.getMainLooper()); mTestReceiverComponent = new ComponentName(TEST_APP_PACKAGE_NAME, SuspendTestReceiver.class.getCanonicalName()); @@ -179,15 +188,19 @@ public class SuspendPackagesTest { } - private static void assertSameExtras(String message, BaseBundle expected, BaseBundle received) { + private static boolean areSameExtras(BaseBundle expected, BaseBundle received) { if (expected != null) { expected.get(""); // hack to unparcel the bundles. } if (received != null) { received.get(""); } + return BaseBundle.kindofEquals(expected, received); + } + + private static void assertSameExtras(String message, BaseBundle expected, BaseBundle received) { assertTrue(message + ": [expected: " + expected + "; received: " + received + "]", - BaseBundle.kindofEquals(expected, received)); + areSameExtras(expected, received)); } @Test @@ -260,14 +273,143 @@ public class SuspendPackagesTest { assertEquals(mContext.getOpPackageName(), unchangedPkgs[0]); } + @Test + public void testGetLauncherExtrasNonNull() { + final Bundle extrasWhenUnsuspended = mLauncherApps.getSuspendedPackageLauncherExtras( + TEST_APP_PACKAGE_NAME, mContext.getUser()); + assertNull("Non null extras when package unsuspended:", extrasWhenUnsuspended); + final PersistableBundle launcherExtras = getExtras("testGetLauncherExtras", 1, "1", 0.1); + suspendTestPackage(null, launcherExtras); + final Bundle receivedExtras = mLauncherApps.getSuspendedPackageLauncherExtras( + TEST_APP_PACKAGE_NAME, mContext.getUser()); + assertSameExtras("Received launcher extras different to the ones supplied", launcherExtras, + receivedExtras); + } + + @Test + public void testGetLauncherExtrasNull() { + suspendTestPackage(null, null); + final Bundle extrasWhenNoneGiven = mLauncherApps.getSuspendedPackageLauncherExtras( + TEST_APP_PACKAGE_NAME, mContext.getUser()); + assertNull("Non null extras when null extras provided:", extrasWhenNoneGiven); + } + + @Test + public void testGetLauncherExtrasInvalidPackage() { + final Bundle extrasForInvalidPackage = mLauncherApps.getSuspendedPackageLauncherExtras( + "test.nonexistent.packagename", mContext.getUser()); + assertNull("Non null extras for an invalid package:", extrasForInvalidPackage); + } + + @Test + public void testOnPackagesSuspendedNewAndOld() throws InterruptedException { + final PersistableBundle suppliedExtras = getExtras( + "testOnPackagesSuspendedNewAndOld", 2, "2", 0.2); + final AtomicReference<String> overridingBothCallbackResult = new AtomicReference<>(""); + final CountDownLatch twoCallbackLatch = new CountDownLatch(2); + mTestCallback = new StubbedCallback() { + @Override + public void onPackagesSuspended(String[] packageNames, UserHandle user) { + overridingBothCallbackResult.set(overridingBothCallbackResult.get() + + "Old callback called even when the new one is overriden. "); + twoCallbackLatch.countDown(); + } + + @Override + public void onPackagesSuspended(String[] packageNames, Bundle launcherExtras, + UserHandle user) { + final StringBuilder errorString = new StringBuilder(); + if (!Arrays.equals(packageNames, PACKAGES_TO_SUSPEND)) { + errorString.append("Received unexpected packageNames in onPackagesSuspended:"); + for (String packageName : packageNames) { + errorString.append(" " + packageName); + } + errorString.append(". "); + } + if (user.getIdentifier() != mContext.getUserId()) { + errorString.append("Received wrong user " + user.getIdentifier() + ". "); + } + if (!areSameExtras(launcherExtras, suppliedExtras)) { + errorString.append("Unexpected launcherExtras, supplied: " + suppliedExtras + + ", received: " + launcherExtras + ". "); + } + overridingBothCallbackResult.set(overridingBothCallbackResult.get() + + errorString.toString()); + twoCallbackLatch.countDown(); + } + }; + mLauncherApps.registerCallback(mTestCallback, mReceiverHandler); + suspendTestPackage(null, suppliedExtras); + assertFalse("Both callbacks were invoked", twoCallbackLatch.await(5, TimeUnit.SECONDS)); + twoCallbackLatch.countDown(); + assertTrue("No callback was invoked", twoCallbackLatch.await(2, TimeUnit.SECONDS)); + final String result = overridingBothCallbackResult.get(); + assertTrue("Callbacks did not complete as expected: " + result, result.isEmpty()); + } + + @Test + public void testOnPackagesSuspendedOld() throws InterruptedException { + final PersistableBundle suppliedExtras = getExtras( + "testOnPackagesSuspendedOld", 2, "2", 0.2); + final AtomicReference<String> overridingOneCallbackResult = new AtomicReference<>(""); + final CountDownLatch oneCallbackLatch = new CountDownLatch(1); + mTestCallback = new StubbedCallback() { + @Override + public void onPackagesSuspended(String[] packageNames, UserHandle user) { + final StringBuilder errorString = new StringBuilder(); + if (!Arrays.equals(packageNames, PACKAGES_TO_SUSPEND)) { + errorString.append("Received unexpected packageNames in onPackagesSuspended:"); + for (String packageName : packageNames) { + errorString.append(" " + packageName); + } + errorString.append(". "); + } + if (user.getIdentifier() != mContext.getUserId()) { + errorString.append("Received wrong user " + user.getIdentifier() + ". "); + } + overridingOneCallbackResult.set(overridingOneCallbackResult.get() + + errorString.toString()); + oneCallbackLatch.countDown(); + } + }; + mLauncherApps.registerCallback(mTestCallback, mReceiverHandler); + suspendTestPackage(null, suppliedExtras); + assertTrue("Callback not invoked", oneCallbackLatch.await(5, TimeUnit.SECONDS)); + final String result = overridingOneCallbackResult.get(); + assertTrue("Callback did not complete as expected: " + result, result.isEmpty()); + } + @After public void tearDown() throws Exception { mAppCommsReceiver.unregister(); + if (mTestCallback != null) { + mLauncherApps.unregisterCallback(mTestCallback); + } Thread.sleep(250); // To prevent any race with the next registerReceiver } - @FunctionalInterface - interface Condition { - boolean isTrue(); + private static abstract class StubbedCallback extends LauncherApps.Callback { + + @Override + public void onPackageRemoved(String packageName, UserHandle user) { + } + + @Override + public void onPackageAdded(String packageName, UserHandle user) { + } + + @Override + public void onPackageChanged(String packageName, UserHandle user) { + } + + @Override + public void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing) { + + } + + @Override + public void onPackagesUnavailable(String[] packageNames, UserHandle user, + boolean replacing) { + } } } diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 601999dde84b..c2a0ccfbb810 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -281,6 +281,7 @@ public class AppStandbyControllerTests { MyContextWrapper myContext = new MyContextWrapper(InstrumentationRegistry.getContext()); mInjector = new MyInjector(myContext, Looper.getMainLooper()); mController = setupController(); + setChargingState(mController, false); } @Test @@ -381,8 +382,6 @@ public class AppStandbyControllerTests { @Test public void testForcedIdle() throws Exception { - setChargingState(mController, false); - mController.forceIdleState(PACKAGE_1, USER_ID, true); assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController)); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); @@ -395,8 +394,6 @@ public class AppStandbyControllerTests { @Test public void testNotificationEvent() throws Exception { - setChargingState(mController, false); - reportEvent(mController, USER_INTERACTION, 0); assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController)); mInjector.mElapsedRealtime = 1; @@ -410,8 +407,6 @@ public class AppStandbyControllerTests { @Test public void testSlicePinnedEvent() throws Exception { - setChargingState(mController, false); - reportEvent(mController, USER_INTERACTION, 0); assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController)); mInjector.mElapsedRealtime = 1; @@ -425,8 +420,6 @@ public class AppStandbyControllerTests { @Test public void testSlicePinnedPrivEvent() throws Exception { - setChargingState(mController, false); - mController.forceIdleState(PACKAGE_1, USER_ID, true); reportEvent(mController, SLICE_PINNED_PRIV, mInjector.mElapsedRealtime); assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController)); @@ -434,14 +427,13 @@ public class AppStandbyControllerTests { @Test public void testPredictionTimedout() throws Exception { - setChargingState(mController, false); // Set it to timeout or usage, so that prediction can override it mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, - REASON_MAIN_TIMEOUT, 1 * HOUR_MS); + REASON_MAIN_TIMEOUT, HOUR_MS); assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController)); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_MAIN_PREDICTED, 1 * HOUR_MS); + REASON_MAIN_PREDICTED, HOUR_MS); assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController)); // Fast forward 12 hours @@ -464,7 +456,6 @@ public class AppStandbyControllerTests { @Test public void testOverrides() throws Exception { - setChargingState(mController, false); // Can force to NEVER mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, REASON_MAIN_FORCED, 1 * HOUR_MS); @@ -494,8 +485,6 @@ public class AppStandbyControllerTests { @Test public void testTimeout() throws Exception { - setChargingState(mController, false); - reportEvent(mController, USER_INTERACTION, 0); assertBucket(STANDBY_BUCKET_ACTIVE); @@ -505,19 +494,19 @@ public class AppStandbyControllerTests { assertBucket(STANDBY_BUCKET_ACTIVE); // bucketing works after timeout - mInjector.mElapsedRealtime = FREQUENT_THRESHOLD - 100; + mInjector.mElapsedRealtime = mController.mPredictionTimeoutMillis - 100; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_WORKING_SET); - - mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); + // Use recent prediction assertBucket(STANDBY_BUCKET_FREQUENT); + + // Way past prediction timeout, use system thresholds + mInjector.mElapsedRealtime = RARE_THRESHOLD * 4; + mController.checkIdleStates(USER_ID); + assertBucket(STANDBY_BUCKET_RARE); } @Test public void testCascadingTimeouts() throws Exception { - setChargingState(mController, false); - reportEvent(mController, USER_INTERACTION, 0); assertBucket(STANDBY_BUCKET_ACTIVE); @@ -539,8 +528,6 @@ public class AppStandbyControllerTests { @Test public void testOverlappingTimeouts() throws Exception { - setChargingState(mController, false); - reportEvent(mController, USER_INTERACTION, 0); assertBucket(STANDBY_BUCKET_ACTIVE); @@ -596,8 +583,6 @@ public class AppStandbyControllerTests { @Test public void testPredictionNotOverridden() throws Exception { - setChargingState(mController, false); - reportEvent(mController, USER_INTERACTION, 0); assertBucket(STANDBY_BUCKET_ACTIVE); @@ -623,6 +608,31 @@ public class AppStandbyControllerTests { } @Test + public void testPredictionStrikesBack() throws Exception { + reportEvent(mController, USER_INTERACTION, 0); + assertBucket(STANDBY_BUCKET_ACTIVE); + + // Predict to FREQUENT + mInjector.mElapsedRealtime = RARE_THRESHOLD; + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, + REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); + assertBucket(STANDBY_BUCKET_FREQUENT); + + // Add a short timeout event + mInjector.mElapsedRealtime += 1000; + reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime); + assertBucket(STANDBY_BUCKET_ACTIVE); + mInjector.mElapsedRealtime += 1000; + mController.checkIdleStates(USER_ID); + assertBucket(STANDBY_BUCKET_ACTIVE); + + // Verify it reverted to predicted + mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD / 2; + mController.checkIdleStates(USER_ID); + assertBucket(STANDBY_BUCKET_FREQUENT); + } + + @Test public void testAddActiveDeviceAdmin() { assertActiveAdmins(USER_ID, (String[]) null); assertActiveAdmins(USER_ID2, (String[]) null); diff --git a/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 553d65824c54..95361f03fe4b 100644 --- a/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -17,14 +17,20 @@ 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.atLeast; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; import android.graphics.Point; import android.graphics.Rect; +import android.os.Binder; +import android.os.IInterface; +import android.platform.test.annotations.Presubmit; import android.support.test.filters.FlakyTest; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -50,7 +56,7 @@ import org.mockito.MockitoAnnotations; * atest FrameworksServicesTests:com.android.server.wm.RemoteAnimationControllerTest */ @SmallTest -@FlakyTest(detail = "Promote to presubmit if non-flakyness is established") +@Presubmit @RunWith(AndroidJUnit4.class) public class RemoteAnimationControllerTest extends WindowTestsBase { @@ -67,6 +73,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { public void setUp() throws Exception { super.setUp(); MockitoAnnotations.initMocks(this); + when(mMockRunner.asBinder()).thenReturn(new Binder()); mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50); mAdapter.setCallingPid(123); sWm.mH.runWithScissors(() -> { @@ -166,7 +173,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { @Test public void testZeroAnimations() throws Exception { mController.goodToGo(); - verifyZeroInteractions(mMockRunner); + verifyNoMoreInteractionsExceptAsBinder(mMockRunner); } @Test @@ -175,7 +182,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { mController.createAnimationAdapter(win.mAppToken, new Point(50, 100), new Rect(50, 100, 150, 150)); mController.goodToGo(); - verifyZeroInteractions(mMockRunner); + verifyNoMoreInteractionsExceptAsBinder(mMockRunner); } @Test @@ -206,7 +213,12 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); win.mAppToken.removeImmediately(); mController.goodToGo(); - verifyZeroInteractions(mMockRunner); + verifyNoMoreInteractionsExceptAsBinder(mMockRunner); verify(mFinishedCallback).onAnimationFinished(eq(adapter)); } + + private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) { + verify(binder, atLeast(0)).asBinder(); + verifyNoMoreInteractions(binder); + } } diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java index 6d9167fda585..1af344bb0a89 100644 --- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -445,8 +445,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } @Override - public int rotationForOrientationLw(int orientation, - int lastRotation) { + public int rotationForOrientationLw(int orientation, int lastRotation, boolean defaultDisplay) { return rotationToReport; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java index 09d88fd8c87c..0815876e5915 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java @@ -367,8 +367,8 @@ public class RankingHelperTest extends UiServiceTestCase { mHelper.createNotificationChannelGroup(PKG, UID, ncg, true); mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true); - mHelper.createNotificationChannel(PKG, UID, channel1, true); - mHelper.createNotificationChannel(PKG, UID, channel2, false); + mHelper.createNotificationChannel(PKG, UID, channel1, true, false); + mHelper.createNotificationChannel(PKG, UID, channel2, false, false); mHelper.setShowBadge(PKG, UID, true); @@ -427,10 +427,10 @@ public class RankingHelperTest extends UiServiceTestCase { mHelper.createNotificationChannelGroup(PKG, UID, ncg, true); mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true); - mHelper.createNotificationChannel(PKG, UID, channel1, true); - mHelper.createNotificationChannel(PKG, UID, channel2, false); - mHelper.createNotificationChannel(PKG, UID, channel3, false); - mHelper.createNotificationChannel(UPDATED_PKG, UID2, getChannel(), true); + mHelper.createNotificationChannel(PKG, UID, channel1, true, false); + mHelper.createNotificationChannel(PKG, UID, channel2, false, false); + mHelper.createNotificationChannel(PKG, UID, channel3, false, false); + mHelper.createNotificationChannel(UPDATED_PKG, UID2, getChannel(), true, false); mHelper.setShowBadge(PKG, UID, true); @@ -481,7 +481,7 @@ public class RankingHelperTest extends UiServiceTestCase { NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_LOW); channel.setSound(SOUND_URI, mAudioAttributes); - mHelper.createNotificationChannel(PKG, UID, channel, true); + mHelper.createNotificationChannel(PKG, UID, channel, true, false); ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId()); @@ -507,7 +507,7 @@ public class RankingHelperTest extends UiServiceTestCase { NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_LOW); channel.setSound(SOUND_URI, mAudioAttributes); - mHelper.createNotificationChannel(PKG, UID, channel, true); + mHelper.createNotificationChannel(PKG, UID, channel, true, false); ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId()); loadStreamXml(baos, true); @@ -528,7 +528,7 @@ public class RankingHelperTest extends UiServiceTestCase { NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_LOW); channel.setSound(SOUND_URI, mAudioAttributes); - mHelper.createNotificationChannel(PKG, UID, channel, true); + mHelper.createNotificationChannel(PKG, UID, channel, true, false); ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId()); loadStreamXml(baos, true); @@ -569,7 +569,7 @@ public class RankingHelperTest extends UiServiceTestCase { NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_LOW); channel.setSound(null, mAudioAttributes); - mHelper.createNotificationChannel(PKG, UID, channel, true); + mHelper.createNotificationChannel(PKG, UID, channel, true, false); ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId()); loadStreamXml(baos, true); @@ -593,9 +593,9 @@ public class RankingHelperTest extends UiServiceTestCase { mHelper.createNotificationChannelGroup(PKG, UID, ncg, true); mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true); - mHelper.createNotificationChannel(PKG, UID, channel1, true); - mHelper.createNotificationChannel(PKG, UID, channel2, false); - mHelper.createNotificationChannel(PKG, UID, channel3, true); + mHelper.createNotificationChannel(PKG, UID, channel1, true, false); + mHelper.createNotificationChannel(PKG, UID, channel2, false, false); + mHelper.createNotificationChannel(PKG, UID, channel3, true, false); mHelper.deleteNotificationChannel(PKG, UID, channel1.getId()); mHelper.deleteNotificationChannelGroup(PKG, UID, ncg.getId()); @@ -701,7 +701,7 @@ public class RankingHelperTest extends UiServiceTestCase { @Test public void testDeletesDefaultChannelAfterChannelIsCreated() throws Exception { mHelper.createNotificationChannel(PKG, UID, - new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true); + new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false); ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, NotificationChannel.DEFAULT_CHANNEL_ID, "bananas"); @@ -721,7 +721,7 @@ public class RankingHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, NotificationChannel.DEFAULT_CHANNEL_ID, "bananas"); mHelper.createNotificationChannel(PKG, UID, - new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true); + new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false); loadStreamXml(baos, false); @@ -734,36 +734,39 @@ public class RankingHelperTest extends UiServiceTestCase { mHelper.setImportance(PKG, UID, IMPORTANCE_NONE); mHelper.createNotificationChannel(PKG, UID, - new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true); + new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false); } @Test public void testCreateChannel_badImportance() throws Exception { try { mHelper.createNotificationChannel(PKG, UID, - new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE - 1), true); + new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE - 1), + true, false); fail("Was allowed to create a channel with invalid importance"); } catch (IllegalArgumentException e) { // yay } try { mHelper.createNotificationChannel(PKG, UID, - new NotificationChannel("bananas", "bananas", IMPORTANCE_UNSPECIFIED), true); + new NotificationChannel("bananas", "bananas", IMPORTANCE_UNSPECIFIED), + true, false); fail("Was allowed to create a channel with invalid importance"); } catch (IllegalArgumentException e) { // yay } try { mHelper.createNotificationChannel(PKG, UID, - new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX + 1), true); + new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX + 1), + true, false); fail("Was allowed to create a channel with invalid importance"); } catch (IllegalArgumentException e) { // yay } mHelper.createNotificationChannel(PKG, UID, - new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE), true); + new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE), true, false); mHelper.createNotificationChannel(PKG, UID, - new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX), true); + new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX), true, false); } @@ -777,7 +780,7 @@ public class RankingHelperTest extends UiServiceTestCase { channel.setBypassDnd(true); channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET); - mHelper.createNotificationChannel(PKG, UID, channel, false); + mHelper.createNotificationChannel(PKG, UID, channel, false, false); // same id, try to update all fields final NotificationChannel channel2 = @@ -824,7 +827,7 @@ public class RankingHelperTest extends UiServiceTestCase { public void testUpdate_postUpgrade_noUpdateAppFields() throws Exception { final NotificationChannel channel = new NotificationChannel("id2", "name2", IMPORTANCE_LOW); - mHelper.createNotificationChannel(PKG, UID, channel, false); + mHelper.createNotificationChannel(PKG, UID, channel, false, false); assertTrue(mHelper.canShowBadge(PKG, UID)); assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG, UID)); assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE, @@ -865,7 +868,7 @@ public class RankingHelperTest extends UiServiceTestCase { } channel.lockFields(lockMask); - mHelper.createNotificationChannel(PKG, UID, channel, true); + mHelper.createNotificationChannel(PKG, UID, channel, true, false); NotificationChannel savedChannel = mHelper.getNotificationChannel(PKG, UID, channel.getId(), false); @@ -894,7 +897,7 @@ public class RankingHelperTest extends UiServiceTestCase { } channel.lockFields(lockMask); - mHelper.createNotificationChannel(PKG, UID, channel, true); + mHelper.createNotificationChannel(PKG, UID, channel, true, false); NotificationChannel savedChannel = mHelper.getNotificationChannel(PKG, UID, channel.getId(), false); @@ -920,7 +923,7 @@ public class RankingHelperTest extends UiServiceTestCase { @Test public void testLockFields_soundAndVibration() throws Exception { - mHelper.createNotificationChannel(PKG, UID, getChannel(), true); + mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false); final NotificationChannel update1 = getChannel(); update1.setSound(new Uri.Builder().scheme("test").build(), @@ -944,7 +947,7 @@ public class RankingHelperTest extends UiServiceTestCase { @Test public void testLockFields_vibrationAndLights() throws Exception { - mHelper.createNotificationChannel(PKG, UID, getChannel(), true); + mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false); final NotificationChannel update1 = getChannel(); update1.setVibrationPattern(new long[]{7945, 46 ,246}); @@ -964,7 +967,7 @@ public class RankingHelperTest extends UiServiceTestCase { @Test public void testLockFields_lightsAndImportance() throws Exception { - mHelper.createNotificationChannel(PKG, UID, getChannel(), true); + mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false); final NotificationChannel update1 = getChannel(); update1.setLightColor(Color.GREEN); @@ -984,7 +987,7 @@ public class RankingHelperTest extends UiServiceTestCase { @Test public void testLockFields_visibilityAndDndAndBadge() throws Exception { - mHelper.createNotificationChannel(PKG, UID, getChannel(), true); + mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false); assertEquals(0, mHelper.getNotificationChannel(PKG, UID, getChannel().getId(), false) .getUserLockedFields()); @@ -1029,7 +1032,7 @@ public class RankingHelperTest extends UiServiceTestCase { channel.enableVibration(true); channel.setVibrationPattern(new long[]{100, 67, 145, 156}); - mHelper.createNotificationChannel(PKG, UID, channel, true); + mHelper.createNotificationChannel(PKG, UID, channel, true, false); mHelper.deleteNotificationChannel(PKG, UID, channel.getId()); // Does not return deleted channel @@ -1058,8 +1061,8 @@ public class RankingHelperTest extends UiServiceTestCase { NotificationChannel channel2 = new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH); channelMap.put(channel2.getId(), channel2); - mHelper.createNotificationChannel(PKG, UID, channel, true); - mHelper.createNotificationChannel(PKG, UID, channel2, true); + mHelper.createNotificationChannel(PKG, UID, channel, true, false); + mHelper.createNotificationChannel(PKG, UID, channel2, true, false); mHelper.deleteNotificationChannel(PKG, UID, channel.getId()); @@ -1091,9 +1094,9 @@ public class RankingHelperTest extends UiServiceTestCase { new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH); NotificationChannel channel3 = new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH); - mHelper.createNotificationChannel(PKG, UID, channel, true); - mHelper.createNotificationChannel(PKG, UID, channel2, true); - mHelper.createNotificationChannel(PKG, UID, channel3, true); + mHelper.createNotificationChannel(PKG, UID, channel, true, false); + mHelper.createNotificationChannel(PKG, UID, channel2, true, false); + mHelper.createNotificationChannel(PKG, UID, channel3, true, false); mHelper.deleteNotificationChannel(PKG, UID, channel.getId()); mHelper.deleteNotificationChannel(PKG, UID, channel3.getId()); @@ -1109,14 +1112,14 @@ public class RankingHelperTest extends UiServiceTestCase { new NotificationChannel("id2", "name2", IMPORTANCE_LOW); channel.setVibrationPattern(vibration); - mHelper.createNotificationChannel(PKG, UID, channel, true); + mHelper.createNotificationChannel(PKG, UID, channel, true, false); mHelper.deleteNotificationChannel(PKG, UID, channel.getId()); NotificationChannel newChannel = new NotificationChannel( channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH); newChannel.setVibrationPattern(new long[]{100}); - mHelper.createNotificationChannel(PKG, UID, newChannel, true); + mHelper.createNotificationChannel(PKG, UID, newChannel, true, false); // No long deleted, using old settings compareChannels(channel, @@ -1128,7 +1131,7 @@ public class RankingHelperTest extends UiServiceTestCase { assertTrue(mHelper.onlyHasDefaultChannel(PKG, UID)); assertFalse(mHelper.onlyHasDefaultChannel(UPDATED_PKG, UID2)); - mHelper.createNotificationChannel(PKG, UID, getChannel(), true); + mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false); assertFalse(mHelper.onlyHasDefaultChannel(PKG, UID)); } @@ -1136,7 +1139,7 @@ public class RankingHelperTest extends UiServiceTestCase { public void testCreateChannel_defaultChannelId() throws Exception { try { mHelper.createNotificationChannel(PKG, UID, new NotificationChannel( - NotificationChannel.DEFAULT_CHANNEL_ID, "ha", IMPORTANCE_HIGH), true); + NotificationChannel.DEFAULT_CHANNEL_ID, "ha", IMPORTANCE_HIGH), true, false); fail("Allowed to create default channel"); } catch (IllegalArgumentException e) { // pass @@ -1150,13 +1153,13 @@ public class RankingHelperTest extends UiServiceTestCase { new NotificationChannel("id2", "name2", IMPORTANCE_LOW); channel.setVibrationPattern(vibration); - mHelper.createNotificationChannel(PKG, UID, channel, true); + mHelper.createNotificationChannel(PKG, UID, channel, true, false); NotificationChannel newChannel = new NotificationChannel( channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH); newChannel.setVibrationPattern(new long[]{100}); - mHelper.createNotificationChannel(PKG, UID, newChannel, true); + mHelper.createNotificationChannel(PKG, UID, newChannel, true, false); // Old settings not overridden compareChannels(channel, @@ -1169,7 +1172,7 @@ public class RankingHelperTest extends UiServiceTestCase { final NotificationChannel channel = new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_DEFAULT); channel.setSound(sound, mAudioAttributes); - mHelper.createNotificationChannel(PKG, UID, channel, true); + mHelper.createNotificationChannel(PKG, UID, channel, true, false); assertEquals(sound, mHelper.getNotificationChannel( PKG, UID, channel.getId(), false).getSound()); } @@ -1181,8 +1184,8 @@ public class RankingHelperTest extends UiServiceTestCase { NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW); - mHelper.createNotificationChannel(PKG, UID, channel1, true); - mHelper.createNotificationChannel(PKG, UID, channel2, false); + mHelper.createNotificationChannel(PKG, UID, channel1, true, false); + mHelper.createNotificationChannel(PKG, UID, channel2, false, false); mHelper.permanentlyDeleteNotificationChannels(PKG, UID); @@ -1205,9 +1208,9 @@ public class RankingHelperTest extends UiServiceTestCase { mHelper.createNotificationChannelGroup(PKG, UID, notDeleted, true); mHelper.createNotificationChannelGroup(PKG, UID, deleted, true); - mHelper.createNotificationChannel(PKG, UID, nonGroupedNonDeletedChannel, true); - mHelper.createNotificationChannel(PKG, UID, groupedAndDeleted, true); - mHelper.createNotificationChannel(PKG, UID, groupedButNotDeleted, true); + mHelper.createNotificationChannel(PKG, UID, nonGroupedNonDeletedChannel, true, false); + mHelper.createNotificationChannel(PKG, UID, groupedAndDeleted, true, false); + mHelper.createNotificationChannel(PKG, UID, groupedButNotDeleted, true, false); mHelper.deleteNotificationChannelGroup(PKG, UID, deleted.getId()); @@ -1264,14 +1267,14 @@ public class RankingHelperTest extends UiServiceTestCase { // Deleted NotificationChannel channel1 = new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH); - mHelper.createNotificationChannel(PKG, UID, channel1, true); + mHelper.createNotificationChannel(PKG, UID, channel1, true, false); mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID}); assertEquals(0, mHelper.getNotificationChannels(PKG, UID, true).getList().size()); // Not deleted - mHelper.createNotificationChannel(PKG, UID, channel1, true); + mHelper.createNotificationChannel(PKG, UID, channel1, true, false); mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID}); assertEquals(2, mHelper.getNotificationChannels(PKG, UID, false).getList().size()); @@ -1302,7 +1305,7 @@ public class RankingHelperTest extends UiServiceTestCase { @Test public void testOnPackageChange_downgradeTargetSdk() throws Exception { // create channel as api 26 - mHelper.createNotificationChannel(UPDATED_PKG, UID2, getChannel(), true); + mHelper.createNotificationChannel(UPDATED_PKG, UID2, getChannel(), true, false); // install new app version targeting 25 final ApplicationInfo legacy = new ApplicationInfo(); @@ -1338,7 +1341,7 @@ public class RankingHelperTest extends UiServiceTestCase { new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH); channel1.setGroup("garbage"); try { - mHelper.createNotificationChannel(PKG, UID, channel1, true); + mHelper.createNotificationChannel(PKG, UID, channel1, true, false); fail("Created a channel with a bad group"); } catch (IllegalArgumentException e) { } @@ -1351,7 +1354,7 @@ public class RankingHelperTest extends UiServiceTestCase { NotificationChannel channel1 = new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH); channel1.setGroup(ncg.getId()); - mHelper.createNotificationChannel(PKG, UID, channel1, true); + mHelper.createNotificationChannel(PKG, UID, channel1, true, false); assertEquals(ncg.getId(), mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false).getGroup()); @@ -1369,20 +1372,20 @@ public class RankingHelperTest extends UiServiceTestCase { NotificationChannel channel1 = new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH); channel1.setGroup(ncg.getId()); - mHelper.createNotificationChannel(PKG, UID, channel1, true); + mHelper.createNotificationChannel(PKG, UID, channel1, true, false); NotificationChannel channel1a = new NotificationChannel("id1a", "name1", NotificationManager.IMPORTANCE_HIGH); channel1a.setGroup(ncg.getId()); - mHelper.createNotificationChannel(PKG, UID, channel1a, true); + mHelper.createNotificationChannel(PKG, UID, channel1a, true, false); NotificationChannel channel2 = new NotificationChannel("id2", "name1", NotificationManager.IMPORTANCE_HIGH); channel2.setGroup(ncg2.getId()); - mHelper.createNotificationChannel(PKG, UID, channel2, true); + mHelper.createNotificationChannel(PKG, UID, channel2, true, false); NotificationChannel channel3 = new NotificationChannel("id3", "name1", NotificationManager.IMPORTANCE_HIGH); - mHelper.createNotificationChannel(PKG, UID, channel3, true); + mHelper.createNotificationChannel(PKG, UID, channel3, true, false); List<NotificationChannelGroup> actual = mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList(); @@ -1416,7 +1419,7 @@ public class RankingHelperTest extends UiServiceTestCase { NotificationChannel channel1 = new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH); channel1.setGroup(ncg.getId()); - mHelper.createNotificationChannel(PKG, UID, channel1, true); + mHelper.createNotificationChannel(PKG, UID, channel1, true, false); mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList(); channel1.setImportance(IMPORTANCE_LOW); @@ -1436,12 +1439,12 @@ public class RankingHelperTest extends UiServiceTestCase { @Test public void testCreateChannel_updateName() throws Exception { NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT); - mHelper.createNotificationChannel(PKG, UID, nc, true); + mHelper.createNotificationChannel(PKG, UID, nc, true, false); NotificationChannel actual = mHelper.getNotificationChannel(PKG, UID, "id", false); assertEquals("hello", actual.getName()); nc = new NotificationChannel("id", "goodbye", IMPORTANCE_HIGH); - mHelper.createNotificationChannel(PKG, UID, nc, true); + mHelper.createNotificationChannel(PKG, UID, nc, true, false); actual = mHelper.getNotificationChannel(PKG, UID, "id", false); assertEquals("goodbye", actual.getName()); @@ -1455,13 +1458,13 @@ public class RankingHelperTest extends UiServiceTestCase { NotificationChannelGroup group = new NotificationChannelGroup("group", ""); mHelper.createNotificationChannelGroup(PKG, UID, group, true); NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT); - mHelper.createNotificationChannel(PKG, UID, nc, true); + mHelper.createNotificationChannel(PKG, UID, nc, true, false); NotificationChannel actual = mHelper.getNotificationChannel(PKG, UID, "id", false); assertNull(actual.getGroup()); nc = new NotificationChannel("id", "hello", IMPORTANCE_HIGH); nc.setGroup(group.getId()); - mHelper.createNotificationChannel(PKG, UID, nc, true); + mHelper.createNotificationChannel(PKG, UID, nc, true, false); actual = mHelper.getNotificationChannel(PKG, UID, "id", false); assertNotNull(actual.getGroup()); @@ -1486,7 +1489,7 @@ public class RankingHelperTest extends UiServiceTestCase { int numChannels = ThreadLocalRandom.current().nextInt(1, 10); for (int j = 0; j < numChannels; j++) { mHelper.createNotificationChannel(pkgName, UID, - new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true); + new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true, false); } expectedChannels.put(pkgName, numChannels); } @@ -1621,10 +1624,10 @@ public class RankingHelperTest extends UiServiceTestCase { c.setGroup(group.getId()); NotificationChannel d = new NotificationChannel("d", "d", IMPORTANCE_DEFAULT); - mHelper.createNotificationChannel(PKG, UID, a, true); - mHelper.createNotificationChannel(PKG, UID, b, true); - mHelper.createNotificationChannel(PKG, UID, c, true); - mHelper.createNotificationChannel(PKG, UID, d, true); + mHelper.createNotificationChannel(PKG, UID, a, true, false); + mHelper.createNotificationChannel(PKG, UID, b, true, false); + mHelper.createNotificationChannel(PKG, UID, c, true, false); + mHelper.createNotificationChannel(PKG, UID, d, true, false); mHelper.deleteNotificationChannel(PKG, UID, c.getId()); NotificationChannelGroup retrieved = mHelper.getNotificationChannelGroupWithChannels( @@ -1641,22 +1644,31 @@ public class RankingHelperTest extends UiServiceTestCase { @Test public void testAndroidPkgCanBypassDnd_creation() { - NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW); test.setBypassDnd(true); - mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, test, true); + mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, test, true, false); assertTrue(mHelper.getNotificationChannel(SYSTEM_PKG, SYSTEM_UID, "A", false) .canBypassDnd()); } @Test + public void testDndPkgCanBypassDnd_creation() { + NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW); + test.setBypassDnd(true); + + mHelper.createNotificationChannel(PKG, UID, test, true, true); + + assertTrue(mHelper.getNotificationChannel(PKG, UID, "A", false).canBypassDnd()); + } + + @Test public void testNormalPkgCannotBypassDnd_creation() { NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW); test.setBypassDnd(true); - mHelper.createNotificationChannel(PKG, 1000, test, true); + mHelper.createNotificationChannel(PKG, 1000, test, true, false); assertFalse(mHelper.getNotificationChannel(PKG, 1000, "A", false).canBypassDnd()); } @@ -1664,11 +1676,11 @@ public class RankingHelperTest extends UiServiceTestCase { @Test public void testAndroidPkgCanBypassDnd_update() throws Exception { NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW); - mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, test, true); + mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, test, true, false); NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW); update.setBypassDnd(true); - mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, update, true); + mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, update, true, false); assertTrue(mHelper.getNotificationChannel(SYSTEM_PKG, SYSTEM_UID, "A", false) .canBypassDnd()); @@ -1678,12 +1690,24 @@ public class RankingHelperTest extends UiServiceTestCase { } @Test + public void testDndPkgCanBypassDnd_update() throws Exception { + NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW); + mHelper.createNotificationChannel(PKG, UID, test, true, true); + + NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW); + update.setBypassDnd(true); + mHelper.createNotificationChannel(PKG, UID, update, true, true); + + assertTrue(mHelper.getNotificationChannel(PKG, UID, "A", false).canBypassDnd()); + } + + @Test public void testNormalPkgCannotBypassDnd_update() { NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW); - mHelper.createNotificationChannel(PKG, 1000, test, true); + mHelper.createNotificationChannel(PKG, 1000, test, true, false); NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW); update.setBypassDnd(true); - mHelper.createNotificationChannel(PKG, 1000, update, true); + mHelper.createNotificationChannel(PKG, 1000, update, true, false); assertFalse(mHelper.getNotificationChannel(PKG, 1000, "A", false).canBypassDnd()); } } 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 36136a8932c9..ce7445788489 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java @@ -206,6 +206,31 @@ public class ScheduleCalendarTest extends UiServiceTestCase { } @Test + public void testShouldExitForAlarm_oldAlarm() { + // Cal: today 2:15pm + Calendar cal = new GregorianCalendar(); + cal.set(Calendar.HOUR_OF_DAY, 14); + cal.set(Calendar.MINUTE, 15); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + + // ScheduleInfo: today 12:16pm - today 3:15pm + mScheduleInfo.days = new int[] {getTodayDay()}; + mScheduleInfo.startHour = 12; + mScheduleInfo.endHour = 3; + mScheduleInfo.startMinute = 16; + mScheduleInfo.endMinute = 15; + mScheduleInfo.exitAtAlarm = true; + mScheduleInfo.nextAlarm = 1000; // very old alarm + + mScheduleCalendar.setSchedule(mScheduleInfo); + assertTrue(mScheduleCalendar.isInSchedule(cal.getTimeInMillis())); + + // don't exit for an alarm if it's an old alarm + assertFalse(mScheduleCalendar.shouldExitForAlarm(1000)); + } + + @Test public void testMaybeSetNextAlarm_settingOff() { mScheduleInfo.exitAtAlarm = false; mScheduleInfo.nextAlarm = 0; diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java index 1073a800527a..5e2a36486972 100644 --- a/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java @@ -15,8 +15,10 @@ package com.android.server.slice; import static android.content.ContentProvider.maybeAddUserId; +import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -35,9 +37,11 @@ import android.content.pm.PackageManagerInternal; import android.net.Uri; import android.os.Binder; import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; +import android.testing.TestableContext; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; @@ -63,6 +67,7 @@ public class SliceManagerServiceTest extends UiServiceTestCase { private SliceManagerService mService; private PinnedSliceState mCreatedSliceState; private IBinder mToken = new Binder(); + private TestableContext mContextSpy; @Before public void setup() { @@ -72,7 +77,8 @@ public class SliceManagerServiceTest extends UiServiceTestCase { mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class)); mContext.getTestablePermissions().setPermission(TEST_URI, PERMISSION_GRANTED); - mService = spy(new SliceManagerService(mContext, TestableLooper.get(this).getLooper())); + mContextSpy = spy(mContext); + mService = spy(new SliceManagerService(mContextSpy, TestableLooper.get(this).getLooper())); mCreatedSliceState = mock(PinnedSliceState.class); doReturn(mCreatedSliceState).when(mService).createPinnedSlice(eq(TEST_URI), anyString()); } @@ -103,4 +109,23 @@ public class SliceManagerServiceTest extends UiServiceTestCase { verify(mCreatedSliceState, never()).destroy(); } + @Test + public void testCheckAutoGrantPermissions() throws RemoteException { + String[] testPerms = new String[] { + "perm1", + "perm2", + }; + when(mContextSpy.checkUriPermission(any(), anyInt(), anyInt(), anyInt())) + .thenReturn(PERMISSION_DENIED); + when(mContextSpy.checkPermission("perm1", Process.myPid(), Process.myUid())) + .thenReturn(PERMISSION_DENIED); + when(mContextSpy.checkPermission("perm2", Process.myPid(), Process.myUid())) + .thenReturn(PERMISSION_GRANTED); + mService.checkSlicePermission(TEST_URI, mContext.getPackageName(), Process.myPid(), + Process.myUid(), testPerms); + + verify(mContextSpy).checkPermission(eq("perm1"), eq(Process.myPid()), eq(Process.myUid())); + verify(mContextSpy).checkPermission(eq("perm2"), eq(Process.myPid()), eq(Process.myUid())); + } + }
\ No newline at end of file diff --git a/services/usage/java/com/android/server/usage/AppIdleHistory.java b/services/usage/java/com/android/server/usage/AppIdleHistory.java index fd28b65137f1..271f8130e106 100644 --- a/services/usage/java/com/android/server/usage/AppIdleHistory.java +++ b/services/usage/java/com/android/server/usage/AppIdleHistory.java @@ -70,6 +70,8 @@ public class AppIdleHistory { private SparseArray<ArrayMap<String,AppUsageHistory>> mIdleHistory = new SparseArray<>(); private static final long ONE_MINUTE = 60 * 1000; + private static final int STANDBY_BUCKET_UNKNOWN = -1; + @VisibleForTesting static final String APP_IDLE_FILENAME = "app_idle_stats.xml"; private static final String TAG_PACKAGES = "packages"; @@ -111,6 +113,9 @@ public class AppIdleHistory { long lastUsedScreenTime; // Last predicted time using elapsed timebase long lastPredictedTime; + // Last predicted bucket + @UsageStatsManager.StandbyBuckets + int lastPredictedBucket = STANDBY_BUCKET_UNKNOWN; // Standby bucket @UsageStatsManager.StandbyBuckets int currentBucket; @@ -342,6 +347,7 @@ public class AppIdleHistory { appUsageHistory.bucketingReason = reason; if ((reason & REASON_MAIN_MASK) == REASON_MAIN_PREDICTED) { appUsageHistory.lastPredictedTime = getElapsedTime(elapsedRealtime); + appUsageHistory.lastPredictedBucket = bucket; } if (DEBUG) { Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket @@ -350,6 +356,17 @@ public class AppIdleHistory { } /** + * Update the prediction for the app but don't change the actual bucket + * @param app The app for which the prediction was made + * @param elapsedTimeAdjusted The elapsed time in the elapsed duration timebase + * @param bucket The predicted bucket + */ + public void updateLastPrediction(AppUsageHistory app, long elapsedTimeAdjusted, int bucket) { + app.lastPredictedTime = elapsedTimeAdjusted; + app.lastPredictedBucket = bucket; + } + + /** * Marks the last time a job was run, with the given elapsedRealtime. The time stored is * based on the elapsed timebase. * @param packageName diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java index 9139a4cd7c1a..5f01518b7449 100644 --- a/services/usage/java/com/android/server/usage/AppStandbyController.java +++ b/services/usage/java/com/android/server/usage/AppStandbyController.java @@ -22,7 +22,9 @@ import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK; import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED; import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT; import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; +import static android.app.usage.UsageStatsManager.REASON_SUB_PREDICTED_RESTORED; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_ACTIVE_TIMEOUT; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_EXEMPTED_SYNC_START; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_BACKGROUND; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_FOREGROUND; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_NOTIFICATION_SEEN; @@ -30,13 +32,14 @@ import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYNC_ADAPTER; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYSTEM_INTERACTION; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYSTEM_UPDATE; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SLICE_PINNED; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SLICE_PINNED_PRIV; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_EXEMPTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; - import static com.android.server.SystemService.PHASE_BOOT_COMPLETED; import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; @@ -184,6 +187,7 @@ public class AppStandbyController { static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10; /** Check the state of one app: arg1 = userId, arg2 = uid, obj = (String) packageName */ static final int MSG_CHECK_PACKAGE_IDLE_STATE = 11; + static final int MSG_REPORT_EXEMPTED_SYNC_START = 12; long mCheckIdleIntervalMillis; long mAppIdleParoleIntervalMillis; @@ -200,6 +204,8 @@ public class AppStandbyController { long mPredictionTimeoutMillis; /** Maximum time a sync adapter associated with a CP should keep the buckets elevated. */ long mSyncAdapterTimeoutMillis; + /** Maximum time an exempted sync should keep the buckets elevated. */ + long mExemptedSyncAdapterTimeoutMillis; /** Maximum time a system interaction should keep the buckets elevated. */ long mSystemInteractionTimeoutMillis; @@ -373,6 +379,21 @@ public class AppStandbyController { } } + void reportExemptedSyncStart(String packageName, int userId) { + if (!mAppIdleEnabled) return; + + final long elapsedRealtime = mInjector.elapsedRealtime(); + + synchronized (mAppIdleLock) { + AppUsageHistory appUsage = mAppIdleHistory.reportUsage(packageName, userId, + STANDBY_BUCKET_ACTIVE, REASON_SUB_USAGE_EXEMPTED_SYNC_START, + 0, + elapsedRealtime + mExemptedSyncAdapterTimeoutMillis); + maybeInformListeners(packageName, userId, elapsedRealtime, + appUsage.currentBucket, appUsage.bucketingReason, false); + } + } + void setChargingState(boolean charging) { synchronized (mAppIdleLock) { if (mCharging != charging) { @@ -538,19 +559,30 @@ public class AppStandbyController { } final int oldBucket = app.currentBucket; int newBucket = Math.max(oldBucket, STANDBY_BUCKET_ACTIVE); // Undo EXEMPTED - boolean predictionLate = false; + boolean predictionLate = predictionTimedOut(app, elapsedRealtime); // Compute age-based bucket if (oldMainReason == REASON_MAIN_DEFAULT || oldMainReason == REASON_MAIN_USAGE || oldMainReason == REASON_MAIN_TIMEOUT - || (predictionLate = predictionTimedOut(app, elapsedRealtime))) { - newBucket = getBucketForLocked(packageName, userId, - elapsedRealtime); - if (DEBUG) { - Slog.d(TAG, "Evaluated AOSP newBucket = " + newBucket); + || predictionLate) { + + if (!predictionLate && app.lastPredictedBucket >= STANDBY_BUCKET_ACTIVE + && app.lastPredictedBucket <= STANDBY_BUCKET_RARE) { + newBucket = app.lastPredictedBucket; + reason = REASON_MAIN_PREDICTED | REASON_SUB_PREDICTED_RESTORED; + if (DEBUG) { + Slog.d(TAG, "Restored predicted newBucket = " + newBucket); + } + } else { + newBucket = getBucketForLocked(packageName, userId, + elapsedRealtime); + if (DEBUG) { + Slog.d(TAG, "Evaluated AOSP newBucket = " + newBucket); + } + reason = REASON_MAIN_TIMEOUT; } - reason = REASON_MAIN_TIMEOUT; } + // Check if the app is within one of the timeouts for forced bucket elevation final long elapsedTimeAdjusted = mAppIdleHistory.getElapsedTime(elapsedRealtime); if (newBucket >= STANDBY_BUCKET_ACTIVE @@ -587,8 +619,7 @@ public class AppStandbyController { /** Returns true if there hasn't been a prediction for the app in a while. */ private boolean predictionTimedOut(AppIdleHistory.AppUsageHistory app, long elapsedRealtime) { - return (app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_PREDICTED - && app.lastPredictedTime > 0 + return app.lastPredictedTime > 0 && mAppIdleHistory.getElapsedTime(elapsedRealtime) - app.lastPredictedTime > mPredictionTimeoutMillis; } @@ -747,6 +778,8 @@ public class AppStandbyController { case UsageEvents.Event.SYSTEM_INTERACTION: return REASON_SUB_USAGE_SYSTEM_INTERACTION; case UsageEvents.Event.USER_INTERACTION: return REASON_SUB_USAGE_USER_INTERACTION; case UsageEvents.Event.NOTIFICATION_SEEN: return REASON_SUB_USAGE_NOTIFICATION_SEEN; + case UsageEvents.Event.SLICE_PINNED: return REASON_SUB_USAGE_SLICE_PINNED; + case UsageEvents.Event.SLICE_PINNED_PRIV: return REASON_SUB_USAGE_SLICE_PINNED_PRIV; default: return 0; } } @@ -1032,6 +1065,10 @@ public class AppStandbyController { if (predicted) { // Check if the app is within one of the timeouts for forced bucket elevation final long elapsedTimeAdjusted = mAppIdleHistory.getElapsedTime(elapsedRealtime); + // In case of not using the prediction, just keep track of it for applying after + // ACTIVE or WORKING_SET timeout. + mAppIdleHistory.updateLastPrediction(app, elapsedTimeAdjusted, newBucket); + if (newBucket > STANDBY_BUCKET_ACTIVE && app.bucketActiveTimeoutTime > elapsedTimeAdjusted) { newBucket = STANDBY_BUCKET_ACTIVE; @@ -1256,6 +1293,11 @@ public class AppStandbyController { .sendToTarget(); } + void postReportExemptedSyncStart(String packageName, int userId) { + mHandler.obtainMessage(MSG_REPORT_EXEMPTED_SYNC_START, userId, 0, packageName) + .sendToTarget(); + } + void dumpUser(IndentingPrintWriter idpw, int userId, String pkg) { synchronized (mAppIdleLock) { mAppIdleHistory.dump(idpw, userId, pkg); @@ -1470,6 +1512,11 @@ public class AppStandbyController { checkAndUpdateStandbyState((String) msg.obj, msg.arg1, msg.arg2, mInjector.elapsedRealtime()); break; + + case MSG_REPORT_EXEMPTED_SYNC_START: + reportExemptedSyncStart((String) msg.obj, msg.arg1); + break; + default: super.handleMessage(msg); break; @@ -1532,6 +1579,7 @@ public class AppStandbyController { "system_update_usage_duration"; private static final String KEY_PREDICTION_TIMEOUT = "prediction_timeout"; private static final String KEY_SYNC_ADAPTER_HOLD_DURATION = "sync_adapter_duration"; + private static final String KEY_EXEMPTED_SYNC_HOLD_DURATION = "exempted_sync_duration"; private static final String KEY_SYSTEM_INTERACTION_HOLD_DURATION = "system_interaction_duration"; public static final long DEFAULT_STRONG_USAGE_TIMEOUT = 1 * ONE_HOUR; @@ -1539,6 +1587,7 @@ public class AppStandbyController { public static final long DEFAULT_SYSTEM_UPDATE_TIMEOUT = 2 * ONE_HOUR; public static final long DEFAULT_SYSTEM_INTERACTION_TIMEOUT = 10 * ONE_MINUTE; public static final long DEFAULT_SYNC_ADAPTER_TIMEOUT = 10 * ONE_MINUTE; + public static final long DEFAULT_EXEMPTED_SYNC_TIMEOUT = 10 * ONE_MINUTE; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -1614,6 +1663,9 @@ public class AppStandbyController { mSyncAdapterTimeoutMillis = mParser.getDurationMillis (KEY_SYNC_ADAPTER_HOLD_DURATION, COMPRESS_TIME ? ONE_MINUTE : DEFAULT_SYNC_ADAPTER_TIMEOUT); + mExemptedSyncAdapterTimeoutMillis = mParser.getDurationMillis + (KEY_EXEMPTED_SYNC_HOLD_DURATION, + COMPRESS_TIME ? ONE_MINUTE : DEFAULT_EXEMPTED_SYNC_TIMEOUT); mSystemInteractionTimeoutMillis = mParser.getDurationMillis (KEY_SYSTEM_INTERACTION_HOLD_DURATION, COMPRESS_TIME ? ONE_MINUTE : DEFAULT_SYSTEM_INTERACTION_TIMEOUT); diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 2258b243cc3a..1fbc27b58854 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -1279,5 +1279,10 @@ public class UsageStatsService extends SystemService implements public void onAdminDataAvailable() { mAppStandby.onAdminDataAvailable(); } + + @Override + public void reportExemptedSyncStart(String packageName, int userId) { + mAppStandby.postReportExemptedSyncStart(packageName, userId); + } } } diff --git a/telephony/java/android/telephony/MbmsDownloadSession.java b/telephony/java/android/telephony/MbmsDownloadSession.java index 9dc07c1659db..da04a0d1075c 100644 --- a/telephony/java/android/telephony/MbmsDownloadSession.java +++ b/telephony/java/android/telephony/MbmsDownloadSession.java @@ -920,11 +920,11 @@ public class MbmsDownloadSession implements AutoCloseable { try { if (!token.createNewFile()) { throw new RuntimeException("Failed to create download token for request " - + request); + + request + ". Token location is " + token.getPath()); } } catch (IOException e) { throw new RuntimeException("Failed to create download token for request " + request - + " due to IOException " + e); + + " due to IOException " + e + ". Attempted to write to " + token.getPath()); } } diff --git a/telephony/java/android/telephony/NetworkServiceCallback.java b/telephony/java/android/telephony/NetworkServiceCallback.java index 92ebf367025c..dbad02fd5640 100644 --- a/telephony/java/android/telephony/NetworkServiceCallback.java +++ b/telephony/java/android/telephony/NetworkServiceCallback.java @@ -83,6 +83,8 @@ public class NetworkServiceCallback { } catch (RemoteException e) { Rlog.e(mTag, "Failed to onGetNetworkRegistrationStateComplete on the remote"); } + } else { + Rlog.e(mTag, "Weak reference of callback is null."); } } }
\ No newline at end of file diff --git a/telephony/java/android/telephony/ims/ImsCallForwardInfo.java b/telephony/java/android/telephony/ims/ImsCallForwardInfo.java index 6d7218179067..283112793d87 100644 --- a/telephony/java/android/telephony/ims/ImsCallForwardInfo.java +++ b/telephony/java/android/telephony/ims/ImsCallForwardInfo.java @@ -29,27 +29,47 @@ import android.os.Parcelable; public final class ImsCallForwardInfo implements Parcelable { // Refer to ImsUtInterface#CDIV_CF_XXX /** @hide */ + // TODO: Make private, do not modify this field directly, use getter. public int mCondition; // 0: disabled, 1: enabled /** @hide */ + // TODO: Make private, do not modify this field directly, use getter. public int mStatus; // 0x91: International, 0x81: Unknown /** @hide */ + // TODO: Make private, do not modify this field directly, use getter. public int mToA; // Service class /** @hide */ + // TODO: Make private, do not modify this field directly, use getter. public int mServiceClass; // Number (it will not include the "sip" or "tel" URI scheme) /** @hide */ + // TODO: Make private, do not modify this field directly, use getter. public String mNumber; // No reply timer for CF /** @hide */ + // TODO: Make private, do not modify this field directly, use getter. public int mTimeSeconds; /** @hide */ + // TODO: Will be removed in the future, use public constructor instead. public ImsCallForwardInfo() { } + /** + * IMS Call Forward Information. + */ + public ImsCallForwardInfo(int condition, int status, int toA, int serviceClass, String number, + int replyTimerSec) { + mCondition = condition; + mStatus = status; + mToA = toA; + mServiceClass = serviceClass; + mNumber = number; + mTimeSeconds = replyTimerSec; + } + /** @hide */ public ImsCallForwardInfo(Parcel in) { readFromParcel(in); diff --git a/telephony/java/android/telephony/ims/ImsSsData.java b/telephony/java/android/telephony/ims/ImsSsData.java index 1ddf1994f26b..49ead770b42a 100644 --- a/telephony/java/android/telephony/ims/ImsSsData.java +++ b/telephony/java/android/telephony/ims/ImsSsData.java @@ -15,19 +15,24 @@ */ package android.telephony.ims; +import android.annotation.IntDef; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** - * Provided STK Call Control Suplementary Service information + * Provides STK Call Control Supplementary Service information. * * {@hide} */ @SystemApi public final class ImsSsData implements Parcelable { - //ServiceType + // Supplementary Service Type + // Call Forwarding public static final int SS_CFU = 0; public static final int SS_CF_BUSY = 1; public static final int SS_CF_NO_REPLY = 2; @@ -35,12 +40,16 @@ public final class ImsSsData implements Parcelable { public static final int SS_CF_ALL = 4; public static final int SS_CF_ALL_CONDITIONAL = 5; public static final int SS_CFUT = 6; + // Called Line Presentation public static final int SS_CLIP = 7; public static final int SS_CLIR = 8; public static final int SS_COLP = 9; public static final int SS_COLR = 10; + // Calling Name Presentation public static final int SS_CNAP = 11; + // Call Waiting public static final int SS_WAIT = 12; + // Call Barring public static final int SS_BAOC = 13; public static final int SS_BAOIC = 14; public static final int SS_BAOIC_EXC_HOME = 15; @@ -52,14 +61,14 @@ public final class ImsSsData implements Parcelable { public static final int SS_INCOMING_BARRING_DN = 21; public static final int SS_INCOMING_BARRING_ANONYMOUS = 22; - //SSRequestType + //Supplementary Service Request Types public static final int SS_ACTIVATION = 0; public static final int SS_DEACTIVATION = 1; public static final int SS_INTERROGATION = 2; public static final int SS_REGISTRATION = 3; public static final int SS_ERASURE = 4; - //TeleserviceType + // Supplementary Service Teleservice Type public static final int SS_ALL_TELE_AND_BEARER_SERVICES = 0; public static final int SS_ALL_TELESEVICES = 1; public static final int SS_TELEPHONY = 2; @@ -67,40 +76,226 @@ public final class ImsSsData implements Parcelable { public static final int SS_SMS_SERVICES = 4; public static final int SS_ALL_TELESERVICES_EXCEPT_SMS = 5; - // Refer to ServiceType + // Service Class of Supplementary Service + // See 27.007 +CCFC or +CLCK /** @hide */ - public int serviceType; - // Refere to SSRequestType + public static final int SERVICE_CLASS_NONE = 0; // no user input /** @hide */ - public int requestType; - // Refer to TeleserviceType + public static final int SERVICE_CLASS_VOICE = 1; /** @hide */ - public int teleserviceType; - // Service Class + public static final int SERVICE_CLASS_DATA = (1 << 1); /** @hide */ - public int serviceClass; - // Error information + public static final int SERVICE_CLASS_FAX = (1 << 2); /** @hide */ - public int result; - + public static final int SERVICE_CLASS_SMS = (1 << 3); + /** @hide */ + public static final int SERVICE_CLASS_DATA_SYNC = (1 << 4); + /** @hide */ + public static final int SERVICE_CLASS_DATA_ASYNC = (1 << 5); /** @hide */ - public int[] ssInfo; /* Valid for all supplementary services. - This field will be empty for RequestType SS_INTERROGATION - and ServiceType SS_CF_*, SS_INCOMING_BARRING_DN, - SS_INCOMING_BARRING_ANONYMOUS.*/ + public static final int SERVICE_CLASS_PACKET = (1 << 6); + /** @hide */ + public static final int SERVICE_CLASS_PAD = (1 << 7); + + /** + * Result code used if the operation was successful. See {@link #result}. + * @hide + */ + public static final int RESULT_SUCCESS = 0; /** @hide */ - public ImsCallForwardInfo[] cfInfo; /* Valid only for supplementary services - ServiceType SS_CF_* and RequestType SS_INTERROGATION */ + @IntDef(flag = true, prefix = { "SS_" }, value = { + SS_CFU, + SS_CF_BUSY, + SS_CF_NO_REPLY, + SS_CF_NOT_REACHABLE, + SS_CF_ALL, + SS_CF_ALL_CONDITIONAL, + SS_CFUT, + SS_CLIP, + SS_CLIR, + SS_COLP, + SS_COLR, + SS_CNAP, + SS_WAIT, + SS_BAOC, + SS_BAOIC, + SS_BAOIC_EXC_HOME, + SS_BAIC, + SS_BAIC_ROAMING, + SS_ALL_BARRING, + SS_OUTGOING_BARRING, + SS_INCOMING_BARRING, + SS_INCOMING_BARRING_DN, + SS_INCOMING_BARRING_ANONYMOUS + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ServiceType{} /** @hide */ - public ImsSsInfo[] imsSsInfo; /* Valid only for ServiceType SS_INCOMING_BARRING_DN and - ServiceType SS_INCOMING_BARRING_ANONYMOUS */ + @IntDef(flag = true, prefix = { "SERVICE_CLASS" }, value = { + SERVICE_CLASS_NONE, + SERVICE_CLASS_VOICE, + SERVICE_CLASS_DATA, + SERVICE_CLASS_FAX, + SERVICE_CLASS_SMS, + SERVICE_CLASS_DATA_SYNC, + SERVICE_CLASS_DATA_ASYNC, + SERVICE_CLASS_PACKET, + SERVICE_CLASS_PAD + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ServiceClass{} + + /** + * The Service type of this Supplementary service. Valid values include: + * SS_CFU, + * SS_CF_BUSY, + * SS_CF_NO_REPLY, + * SS_CF_NOT_REACHABLE, + * SS_CF_ALL, + * SS_CF_ALL_CONDITIONAL, + * SS_CFUT, + * SS_CLIP, + * SS_CLIR, + * SS_COLP, + * SS_COLR, + * SS_CNAP, + * SS_WAIT, + * SS_BAOC, + * SS_BAOIC, + * SS_BAOIC_EXC_HOME, + * SS_BAIC, + * SS_BAIC_ROAMING, + * SS_ALL_BARRING, + * SS_OUTGOING_BARRING, + * SS_INCOMING_BARRING, + * SS_INCOMING_BARRING_DN, + * SS_INCOMING_BARRING_ANONYMOUS + * + * @hide + */ + // TODO: Make final, do not modify this field directly! + public int serviceType; + + /** + * Supplementary Service request Type. Valid values are: + * SS_ACTIVATION, + * SS_DEACTIVATION, + * SS_INTERROGATION, + * SS_REGISTRATION, + * SS_ERASURE + * + * @hide + */ + // TODO: Make final, do not modify this field directly! + public int requestType; + + /** + * Supplementary Service teleservice type: + * SS_TELESERVICE_ALL_TELE_AND_BEARER, + * SS_TELESERVICE_ALL_TELESEVICES, + * SS_TELESERVICE_TELEPHONY, + * SS_TELESERVICE_ALL_DATA, + * SS_TELESERVICE_SMS, + * SS_TELESERVICE_ALL_TELESERVICES_EXCEPT_SMS + * + * @hide + */ + // TODO: Make this param final! Do not try to modify this param directly. + public int teleserviceType; + + /** + * Supplementary Service service class. Valid values are: + * SERVICE_CLASS_NONE, + * SERVICE_CLASS_VOICE, + * SERVICE_CLASS_DATA, + * SERVICE_CLASS_FAX, + * SERVICE_CLASS_SMS, + * SERVICE_CLASS_DATA_SYNC, + * SERVICE_CLASS_DATA_ASYNC, + * SERVICE_CLASS_PACKET, + * SERVICE_CLASS_PAD + * + * @hide + */ + // TODO: Make this param final! Do not try to modify this param directly. + public int serviceClass; + + /** + * Result of Supplementary Service operation. Valid values are: + * RESULT_SUCCESS if the result is success, or + * ImsReasonInfo code if the result is a failure. + * + * @hide + */ + // TODO: Make this param final! Do not try to modify this param directly. + public final int result; + + private int[] mSsInfo; + private ImsCallForwardInfo[] mCfInfo; + private ImsSsInfo[] mImsSsInfo; - public ImsSsData() {} + /** + * Generate IMS Supplementary Service information. + * @param serviceType The Supplementary Service type. Valid entries: + * SS_CFU, + * SS_CF_BUSY, + * SS_CF_NO_REPLY, + * SS_CF_NOT_REACHABLE, + * SS_CF_ALL, + * SS_CF_ALL_CONDITIONAL, + * SS_CFUT, + * SS_CLIP, + * SS_CLIR, + * SS_COLP, + * SS_COLR, + * SS_CNAP, + * SS_WAIT, + * SS_BAOC, + * SS_BAOIC, + * SS_BAOIC_EXC_HOME, + * SS_BAIC, + * SS_BAIC_ROAMING, + * SS_ALL_BARRING, + * SS_OUTGOING_BARRING, + * SS_INCOMING_BARRING, + * SS_INCOMING_BARRING_DN, + * SS_INCOMING_BARRING_ANONYMOUS + * @param requestType Supplementary Service request Type. Valid values are: + * SS_ACTIVATION, + * SS_DEACTIVATION, + * SS_INTERROGATION, + * SS_REGISTRATION, + * SS_ERASURE + * @param teleserviceType Supplementary Service teleservice type: + * SS_TELESERVICE_ALL_TELE_AND_BEARER, + * SS_TELESERVICE_ALL_TELESEVICES, + * SS_TELESERVICE_TELEPHONY, + * SS_TELESERVICE_ALL_DATA, + * SS_TELESERVICE_SMS, + * SS_TELESERVICE_ALL_TELESERVICES_EXCEPT_SMS + * @param serviceClass Supplementary Service service class. See See 27.007 +CCFC or +CLCK. + * @param result Result of Supplementary Service operation. Valid values are 0 if the result is + * success, or ImsReasonInfo code if the result is a failure. + */ + public ImsSsData(@ServiceType int serviceType, int requestType, int teleserviceType, + @ServiceClass int serviceClass, int result) { + this.serviceType = serviceType; + this.requestType = requestType; + this.teleserviceType = teleserviceType; + this.serviceClass = serviceClass; + this.result = result; + } private ImsSsData(Parcel in) { - readFromParcel(in); + serviceType = in.readInt(); + requestType = in.readInt(); + teleserviceType = in.readInt(); + serviceClass = in.readInt(); + result = in.readInt(); + mSsInfo = in.createIntArray(); + mCfInfo = (ImsCallForwardInfo[])in.readParcelableArray(this.getClass().getClassLoader()); } public static final Creator<ImsSsData> CREATOR = new Creator<ImsSsData>() { @@ -122,18 +317,8 @@ public final class ImsSsData implements Parcelable { out.writeInt(teleserviceType); out.writeInt(serviceClass); out.writeInt(result); - out.writeIntArray(ssInfo); - out.writeParcelableArray(cfInfo, 0); - } - - private void readFromParcel(Parcel in) { - serviceType = in.readInt(); - requestType = in.readInt(); - teleserviceType = in.readInt(); - serviceClass = in.readInt(); - result = in.readInt(); - ssInfo = in.createIntArray(); - cfInfo = (ImsCallForwardInfo[])in.readParcelableArray(this.getClass().getClassLoader()); + out.writeIntArray(mSsInfo); + out.writeParcelableArray(mCfInfo, 0); } @Override @@ -200,7 +385,55 @@ public final class ImsSsData implements Parcelable { } public boolean isTypeInterrogation() { - return (requestType == SS_INTERROGATION); + return (serviceType == SS_INTERROGATION); + } + + /** @hide */ + public void setSuppServiceInfo(int[] ssInfo) { + mSsInfo = ssInfo; + } + + /** @hide */ + public void setImsSpecificSuppServiceInfo(ImsSsInfo[] imsSsInfo) { + mImsSsInfo = imsSsInfo; + } + + /** @hide */ + public void setCallForwardingInfo(ImsCallForwardInfo[] cfInfo) { + mCfInfo = cfInfo; + } + + /** + * This field will be null for RequestType SS_INTERROGATION + * and ServiceType SS_CF_*, SS_INCOMING_BARRING_DN, + * SS_INCOMING_BARRING_ANONYMOUS. + * + * @hide + */ + public int[] getSuppServiceInfo() { + return mSsInfo; + } + + /** + * Valid only for ServiceTypes + * - SS_INCOMING_BARRING_DN and + * - ServiceType SS_INCOMING_BARRING_ANONYMOUS. + * Will be null otherwise. + * @hide + */ + public ImsSsInfo[] getImsSpecificSuppServiceInfo() { + return mImsSsInfo; + } + + /** + * Valid only for supplementary services + * - ServiceType SS_CF_* and + * - RequestType SS_INTERROGATION. + * Will be null otherwise. + * @hide + **/ + public ImsCallForwardInfo[] getCallForwardInfo() { + return mCfInfo; } public String toString() { diff --git a/telephony/java/android/telephony/ims/ImsSsInfo.java b/telephony/java/android/telephony/ims/ImsSsInfo.java index 1d1292fb9f72..c6f8622f3fd9 100644 --- a/telephony/java/android/telephony/ims/ImsSsInfo.java +++ b/telephony/java/android/telephony/ims/ImsSsInfo.java @@ -36,13 +36,31 @@ public final class ImsSsInfo implements Parcelable { // 0: disabled, 1: enabled /** @hide */ + // TODO: Make private, do not modify this field directly, use getter! public int mStatus; /** @hide */ + // TODO: Make private, do not modify this field directly, use getter! public String mIcbNum; + /**@hide*/ + // TODO: Remove! Do not use this constructor, instead use public version. public ImsSsInfo() { } + /** + * + * @param status The status of the service registration of activation/deactiviation. Valid + * entries include: + * {@link #NOT_REGISTERED}, + * {@link #DISABLED}, + * {@link #ENABLED} + * @param icbNum The Incoming barring number. + */ + public ImsSsInfo(int status, String icbNum) { + mStatus = status; + mIcbNum = icbNum; + } + private ImsSsInfo(Parcel in) { readFromParcel(in); } @@ -81,6 +99,12 @@ public final class ImsSsInfo implements Parcelable { } }; + /** + * @return Supplementary Service Configuration status. Valid Values are: + * {@link #NOT_REGISTERED}, + * {@link #DISABLED}, + * {@link #ENABLED} + */ public int getStatus() { return mStatus; } diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java index b0c00c6284a6..fe7533f57b12 100644 --- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java +++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java @@ -297,7 +297,9 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { for (Uri tempFileUri : tempFiles) { if (verifyTempFilePath(context, request.getFileServiceId(), tempFileUri)) { File tempFile = new File(tempFileUri.getSchemeSpecificPart()); - tempFile.delete(); + if (!tempFile.delete()) { + Log.w(LOG_TAG, "Failed to delete temp file at " + tempFile.getPath()); + } } } } @@ -474,6 +476,8 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { if (!MbmsUtils.isContainedIn( MbmsUtils.getEmbmsTempFileDirForService(context, serviceId), tempFile)) { + Log.w(LOG_TAG, "File at " + path + " is not contained in the temp file root," + + " which is " + MbmsUtils.getEmbmsTempFileDirForService(context, serviceId)); return false; } diff --git a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java index 3734412981d8..e247951f16ef 100644 --- a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java +++ b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java @@ -173,7 +173,7 @@ public final class BackgroundDexOptServiceIntegrationTests { private static String getCompilerFilter(String pkg) throws IOException { String cmd = String.format("dumpsys package %s", pkg); String[] lines = runShellCommandSplitLines(cmd); - final String substr = "compilation_filter="; + final String substr = "[status="; for (String line : lines) { int startIndex = line.indexOf(substr); if (startIndex < 0) { diff --git a/tests/net/java/android/app/usage/NetworkStatsManagerTest.java b/tests/net/java/android/app/usage/NetworkStatsManagerTest.java new file mode 100644 index 000000000000..25e147416d2c --- /dev/null +++ b/tests/net/java/android/app/usage/NetworkStatsManagerTest.java @@ -0,0 +1,213 @@ +/* + * 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.app.usage; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.net.ConnectivityManager; +import android.net.INetworkStatsService; +import android.net.INetworkStatsSession; +import android.net.NetworkStats.Entry; +import android.net.NetworkStatsHistory; +import android.net.NetworkTemplate; +import android.os.RemoteException; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetworkStatsManagerTest { + + private @Mock INetworkStatsService mService; + private @Mock INetworkStatsSession mStatsSession; + + private NetworkStatsManager mManager; + + // TODO: change to NetworkTemplate.MATCH_MOBILE once internal constant rename is merged to aosp. + private static final int MATCH_MOBILE_ALL = 1; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mManager = new NetworkStatsManager(InstrumentationRegistry.getContext(), mService); + } + + @Test + public void testQueryDetails() throws RemoteException { + final String subscriberId = "subid"; + final long startTime = 1; + final long endTime = 100; + final int uid1 = 10001; + final int uid2 = 10002; + final int uid3 = 10003; + + Entry uid1Entry1 = new Entry("if1", uid1, + android.net.NetworkStats.SET_DEFAULT, android.net.NetworkStats.TAG_NONE, + 100, 10, 200, 20, 0); + + Entry uid1Entry2 = new Entry( + "if2", uid1, + android.net.NetworkStats.SET_DEFAULT, android.net.NetworkStats.TAG_NONE, + 100, 10, 200, 20, 0); + + Entry uid2Entry1 = new Entry("if1", uid2, + android.net.NetworkStats.SET_DEFAULT, android.net.NetworkStats.TAG_NONE, + 150, 10, 250, 20, 0); + + Entry uid2Entry2 = new Entry( + "if2", uid2, + android.net.NetworkStats.SET_DEFAULT, android.net.NetworkStats.TAG_NONE, + 150, 10, 250, 20, 0); + + NetworkStatsHistory history1 = new NetworkStatsHistory(10, 2); + history1.recordData(10, 20, uid1Entry1); + history1.recordData(20, 30, uid1Entry2); + + NetworkStatsHistory history2 = new NetworkStatsHistory(10, 2); + history1.recordData(30, 40, uid2Entry1); + history1.recordData(35, 45, uid2Entry2); + + + when(mService.openSessionForUsageStats(anyInt(), anyString())).thenReturn(mStatsSession); + when(mStatsSession.getRelevantUids()).thenReturn(new int[] { uid1, uid2, uid3 }); + + when(mStatsSession.getHistoryIntervalForUid(any(NetworkTemplate.class), + eq(uid1), eq(android.net.NetworkStats.SET_ALL), + eq(android.net.NetworkStats.TAG_NONE), + eq(NetworkStatsHistory.FIELD_ALL), eq(startTime), eq(endTime))) + .then((InvocationOnMock inv) -> { + NetworkTemplate template = inv.getArgument(0); + assertEquals(MATCH_MOBILE_ALL, template.getMatchRule()); + assertEquals(subscriberId, template.getSubscriberId()); + return history1; + }); + + when(mStatsSession.getHistoryIntervalForUid(any(NetworkTemplate.class), + eq(uid2), eq(android.net.NetworkStats.SET_ALL), + eq(android.net.NetworkStats.TAG_NONE), + eq(NetworkStatsHistory.FIELD_ALL), eq(startTime), eq(endTime))) + .then((InvocationOnMock inv) -> { + NetworkTemplate template = inv.getArgument(0); + assertEquals(MATCH_MOBILE_ALL, template.getMatchRule()); + assertEquals(subscriberId, template.getSubscriberId()); + return history2; + }); + + + NetworkStats stats = mManager.queryDetails( + ConnectivityManager.TYPE_MOBILE, subscriberId, startTime, endTime); + + NetworkStats.Bucket bucket = new NetworkStats.Bucket(); + + // First 2 buckets exactly match entry timings + assertTrue(stats.getNextBucket(bucket)); + assertEquals(10, bucket.getStartTimeStamp()); + assertEquals(20, bucket.getEndTimeStamp()); + assertBucketMatches(uid1Entry1, bucket); + + assertTrue(stats.getNextBucket(bucket)); + assertEquals(20, bucket.getStartTimeStamp()); + assertEquals(30, bucket.getEndTimeStamp()); + assertBucketMatches(uid1Entry2, bucket); + + // 30 -> 40: contains uid2Entry1 and half of uid2Entry2 + assertTrue(stats.getNextBucket(bucket)); + assertEquals(30, bucket.getStartTimeStamp()); + assertEquals(40, bucket.getEndTimeStamp()); + assertEquals(225, bucket.getRxBytes()); + assertEquals(15, bucket.getRxPackets()); + assertEquals(375, bucket.getTxBytes()); + assertEquals(30, bucket.getTxPackets()); + + // 40 -> 50: contains half of uid2Entry2 + assertTrue(stats.getNextBucket(bucket)); + assertEquals(40, bucket.getStartTimeStamp()); + assertEquals(50, bucket.getEndTimeStamp()); + assertEquals(75, bucket.getRxBytes()); + assertEquals(5, bucket.getRxPackets()); + assertEquals(125, bucket.getTxBytes()); + assertEquals(10, bucket.getTxPackets()); + + assertFalse(stats.hasNextBucket()); + } + + @Test + public void testQueryDetails_NoSubscriberId() throws RemoteException { + final long startTime = 1; + final long endTime = 100; + final int uid1 = 10001; + final int uid2 = 10002; + + when(mService.openSessionForUsageStats(anyInt(), anyString())).thenReturn(mStatsSession); + when(mStatsSession.getRelevantUids()).thenReturn(new int[] { uid1, uid2 }); + + NetworkStats stats = mManager.queryDetails( + ConnectivityManager.TYPE_MOBILE, null, startTime, endTime); + + when(mStatsSession.getHistoryIntervalForUid(any(NetworkTemplate.class), + anyInt(), anyInt(), anyInt(), anyInt(), anyLong(), anyLong())) + .thenReturn(new NetworkStatsHistory(10, 0)); + + verify(mStatsSession, times(1)).getHistoryIntervalForUid( + argThat((NetworkTemplate t) -> + // No subscriberId: MATCH_MOBILE_WILDCARD template + t.getMatchRule() == NetworkTemplate.MATCH_MOBILE_WILDCARD), + eq(uid1), eq(android.net.NetworkStats.SET_ALL), + eq(android.net.NetworkStats.TAG_NONE), + eq(NetworkStatsHistory.FIELD_ALL), eq(startTime), eq(endTime)); + + verify(mStatsSession, times(1)).getHistoryIntervalForUid( + argThat((NetworkTemplate t) -> + // No subscriberId: MATCH_MOBILE_WILDCARD template + t.getMatchRule() == NetworkTemplate.MATCH_MOBILE_WILDCARD), + eq(uid2), eq(android.net.NetworkStats.SET_ALL), + eq(android.net.NetworkStats.TAG_NONE), + eq(NetworkStatsHistory.FIELD_ALL), eq(startTime), eq(endTime)); + + assertFalse(stats.hasNextBucket()); + } + + private void assertBucketMatches(Entry expected, + NetworkStats.Bucket actual) { + assertEquals(expected.uid, actual.getUid()); + assertEquals(expected.rxBytes, actual.getRxBytes()); + assertEquals(expected.rxPackets, actual.getRxPackets()); + assertEquals(expected.txBytes, actual.getTxBytes()); + assertEquals(expected.txPackets, actual.getTxPackets()); + } +} diff --git a/tests/net/java/android/net/IpSecAlgorithmTest.java b/tests/net/java/android/net/IpSecAlgorithmTest.java index 6bdfdc6db2d1..85e836179b5e 100644 --- a/tests/net/java/android/net/IpSecAlgorithmTest.java +++ b/tests/net/java/android/net/IpSecAlgorithmTest.java @@ -22,8 +22,12 @@ import static org.junit.Assert.fail; import android.os.Parcel; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; + +import java.util.AbstractMap.SimpleEntry; import java.util.Arrays; +import java.util.Map.Entry; import java.util.Random; + import org.junit.Test; import org.junit.runner.RunWith; @@ -40,19 +44,29 @@ public class IpSecAlgorithmTest { }; @Test - public void testDefaultTruncLen() throws Exception { - IpSecAlgorithm explicit = - new IpSecAlgorithm( - IpSecAlgorithm.AUTH_HMAC_SHA256, Arrays.copyOf(KEY_MATERIAL, 256 / 8), 256); - IpSecAlgorithm implicit = + public void testNoTruncLen() throws Exception { + Entry<String, Integer>[] authAndAeadList = + new Entry[] { + new SimpleEntry<>(IpSecAlgorithm.AUTH_HMAC_MD5, 128), + new SimpleEntry<>(IpSecAlgorithm.AUTH_HMAC_SHA1, 160), + new SimpleEntry<>(IpSecAlgorithm.AUTH_HMAC_SHA256, 256), + new SimpleEntry<>(IpSecAlgorithm.AUTH_HMAC_SHA384, 384), + new SimpleEntry<>(IpSecAlgorithm.AUTH_HMAC_SHA512, 512), + new SimpleEntry<>(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, 224) + }; + + // Expect auth and aead algorithms to throw errors if trunclen is omitted. + for (Entry<String, Integer> algData : authAndAeadList) { + try { new IpSecAlgorithm( - IpSecAlgorithm.AUTH_HMAC_SHA256, Arrays.copyOf(KEY_MATERIAL, 256 / 8)); - assertTrue( - "Default Truncation Length Incorrect, Explicit: " - + explicit - + "implicit: " - + implicit, - IpSecAlgorithm.equals(explicit, implicit)); + algData.getKey(), Arrays.copyOf(KEY_MATERIAL, algData.getValue() / 8)); + fail("Expected exception on unprovided auth trunclen"); + } catch (IllegalArgumentException expected) { + } + } + + // Ensure crypt works with no truncation length supplied. + new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, Arrays.copyOf(KEY_MATERIAL, 256 / 8)); } @Test diff --git a/tests/net/java/android/net/IpSecConfigTest.java b/tests/net/java/android/net/IpSecConfigTest.java index f186ee55d2c7..771faaf4955d 100644 --- a/tests/net/java/android/net/IpSecConfigTest.java +++ b/tests/net/java/android/net/IpSecConfigTest.java @@ -62,7 +62,8 @@ public class IpSecConfigTest { c.setAuthentication( new IpSecAlgorithm( IpSecAlgorithm.AUTH_HMAC_MD5, - new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0})); + new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0}, + 128)); c.setAuthenticatedEncryption( new IpSecAlgorithm( IpSecAlgorithm.AUTH_CRYPT_AES_GCM, diff --git a/tests/net/java/android/net/IpSecManagerTest.java b/tests/net/java/android/net/IpSecManagerTest.java index 0ca20dee427f..970596d6b0f3 100644 --- a/tests/net/java/android/net/IpSecManagerTest.java +++ b/tests/net/java/android/net/IpSecManagerTest.java @@ -179,7 +179,7 @@ public class IpSecManagerTest { IpSecManager.UdpEncapsulationSocket encapSocket = mIpSecManager.openUdpEncapsulationSocket(TEST_UDP_ENCAP_PORT); - assertNotNull(encapSocket.getSocket()); + assertNotNull(encapSocket.getFileDescriptor()); assertEquals(TEST_UDP_ENCAP_PORT, encapSocket.getPort()); encapSocket.close(); @@ -202,7 +202,7 @@ public class IpSecManagerTest { IpSecManager.UdpEncapsulationSocket encapSocket = mIpSecManager.openUdpEncapsulationSocket(); - assertNotNull(encapSocket.getSocket()); + assertNotNull(encapSocket.getFileDescriptor()); assertEquals(TEST_UDP_ENCAP_PORT, encapSocket.getPort()); encapSocket.close(); diff --git a/tests/net/java/android/net/util/InterfaceSetTest.java b/tests/net/java/android/net/util/InterfaceSetTest.java new file mode 100644 index 000000000000..8012838d16fc --- /dev/null +++ b/tests/net/java/android/net/util/InterfaceSetTest.java @@ -0,0 +1,61 @@ +/* + * 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.net.util; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class InterfaceSetTest { + @Test + public void testNullNamesIgnored() { + final InterfaceSet set = new InterfaceSet(null, "if1", null, "if2", null); + assertEquals(2, set.ifnames.size()); + assertTrue(set.ifnames.contains("if1")); + assertTrue(set.ifnames.contains("if2")); + } + + @Test + public void testToString() { + final InterfaceSet set = new InterfaceSet("if1", "if2"); + final String setString = set.toString(); + assertTrue(setString.equals("[if1,if2]") || setString.equals("[if2,if1]")); + } + + @Test + public void testToString_Empty() { + final InterfaceSet set = new InterfaceSet(null, null); + assertEquals("[]", set.toString()); + } + + @Test + public void testEquals() { + assertEquals(new InterfaceSet(null, "if1", "if2"), new InterfaceSet("if2", "if1")); + assertEquals(new InterfaceSet(null, null), new InterfaceSet()); + assertFalse(new InterfaceSet("if1", "if3").equals(new InterfaceSet("if1", "if2"))); + assertFalse(new InterfaceSet("if1", "if2").equals(new InterfaceSet("if1"))); + assertFalse(new InterfaceSet().equals(null)); + } +} diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java index e692652c7ea4..27f90f261ce0 100644 --- a/tests/net/java/com/android/server/connectivity/TetheringTest.java +++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java @@ -27,8 +27,11 @@ import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE; import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; @@ -36,6 +39,7 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -45,18 +49,29 @@ import static org.mockito.Mockito.mock; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; -import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.res.Resources; import android.hardware.usb.UsbManager; import android.net.ConnectivityManager; -import android.net.ConnectivityManager.NetworkCallback; +import android.net.INetd; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; import android.net.InterfaceConfiguration; -import android.net.NetworkRequest; +import android.net.IpPrefix; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.MacAddress; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.NetworkState; +import android.net.NetworkUtils; +import android.net.RouteInfo; +import android.net.ip.RouterAdvertisementDaemon; +import android.net.util.InterfaceParams; +import android.net.util.NetworkConstants; import android.net.util.SharedLog; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; @@ -74,10 +89,16 @@ import android.support.test.runner.AndroidJUnit4; import android.telephony.CarrierConfigManager; import android.test.mock.MockContentResolver; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.StateMachine; import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.internal.util.test.FakeSettingsProvider; +import com.android.server.connectivity.tethering.IControlsTethering; +import com.android.server.connectivity.tethering.IPv6TetheringCoordinator; import com.android.server.connectivity.tethering.OffloadHardwareInterface; +import com.android.server.connectivity.tethering.TetherInterfaceStateMachine; import com.android.server.connectivity.tethering.TetheringDependencies; +import com.android.server.connectivity.tethering.UpstreamNetworkMonitor; import org.junit.After; import org.junit.Before; @@ -86,13 +107,21 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.net.Inet4Address; +import java.net.Inet6Address; import java.util.ArrayList; import java.util.Vector; @RunWith(AndroidJUnit4.class) @SmallTest public class TetheringTest { + private static final int IFINDEX_OFFSET = 100; + private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; + private static final String TEST_MOBILE_IFNAME = "test_rmnet_data0"; + private static final String TEST_XLAT_MOBILE_IFNAME = "v4-test_rmnet_data0"; + private static final String TEST_USB_IFNAME = "test_rndis0"; + private static final String TEST_WLAN_IFNAME = "test_wlan0"; @Mock private ApplicationInfo mApplicationInfo; @Mock private Context mContext; @@ -103,16 +132,21 @@ public class TetheringTest { @Mock private MockableSystemProperties mSystemProperties; @Mock private OffloadHardwareInterface mOffloadHardwareInterface; @Mock private Resources mResources; - @Mock private TetheringDependencies mTetheringDependencies; @Mock private UsbManager mUsbManager; @Mock private WifiManager mWifiManager; @Mock private CarrierConfigManager mCarrierConfigManager; + @Mock private UpstreamNetworkMonitor mUpstreamNetworkMonitor; + @Mock private IPv6TetheringCoordinator mIPv6TetheringCoordinator; + @Mock private RouterAdvertisementDaemon mRouterAdvertisementDaemon; + @Mock private INetd mNetd; + + private final MockTetheringDependencies mTetheringDependencies = + new MockTetheringDependencies(); // Like so many Android system APIs, these cannot be mocked because it is marked final. // We have to use the real versions. private final PersistableBundle mCarrierConfig = new PersistableBundle(); private final TestLooper mLooper = new TestLooper(); - private final String mTestIfname = "test_wlan0"; private Vector<Intent> mIntents; private BroadcastInterceptingContext mServiceContext; @@ -146,23 +180,122 @@ public class TetheringTest { } } + public class MockTetheringDependencies extends TetheringDependencies { + private StateMachine upstreamNetworkMonitorMasterSM; + private ArrayList<TetherInterfaceStateMachine> ipv6CoordinatorNotifyList; + + @Override + public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) { + return mOffloadHardwareInterface; + } + + @Override + public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, + StateMachine target, SharedLog log, int what) { + upstreamNetworkMonitorMasterSM = target; + return mUpstreamNetworkMonitor; + } + + @Override + public IPv6TetheringCoordinator getIPv6TetheringCoordinator( + ArrayList<TetherInterfaceStateMachine> notifyList, SharedLog log) { + ipv6CoordinatorNotifyList = notifyList; + return mIPv6TetheringCoordinator; + } + + @Override + public RouterAdvertisementDaemon getRouterAdvertisementDaemon(InterfaceParams ifParams) { + return mRouterAdvertisementDaemon; + } + + @Override + public INetd getNetdService() { + return mNetd; + } + + @Override + public InterfaceParams getInterfaceParams(String ifName) { + final String[] ifaces = new String[] { TEST_USB_IFNAME, TEST_WLAN_IFNAME, + TEST_MOBILE_IFNAME }; + final int index = ArrayUtils.indexOf(ifaces, ifName); + assertTrue("Non-mocked interface: " + ifName, index >= 0); + return new InterfaceParams(ifName, index + IFINDEX_OFFSET, + MacAddress.ALL_ZEROS_ADDRESS); + } + } + + private static NetworkState buildMobileUpstreamState(boolean withIPv4, boolean withIPv6, + boolean with464xlat) { + final NetworkInfo info = new NetworkInfo(ConnectivityManager.TYPE_MOBILE, 0, null, null); + info.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null); + final LinkProperties prop = new LinkProperties(); + prop.setInterfaceName(TEST_MOBILE_IFNAME); + + if (withIPv4) { + prop.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), + NetworkUtils.numericToInetAddress("10.0.0.1"), TEST_MOBILE_IFNAME)); + } + + if (withIPv6) { + prop.addDnsServer(NetworkUtils.numericToInetAddress("2001:db8::2")); + prop.addLinkAddress( + new LinkAddress(NetworkUtils.numericToInetAddress("2001:db8::"), + NetworkConstants.RFC7421_PREFIX_LENGTH)); + prop.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), + NetworkUtils.numericToInetAddress("2001:db8::1"), TEST_MOBILE_IFNAME)); + } + + if (with464xlat) { + final LinkProperties stackedLink = new LinkProperties(); + stackedLink.setInterfaceName(TEST_XLAT_MOBILE_IFNAME); + stackedLink.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), + NetworkUtils.numericToInetAddress("192.0.0.1"), TEST_XLAT_MOBILE_IFNAME)); + + prop.addStackedLink(stackedLink); + } + + + final NetworkCapabilities capabilities = new NetworkCapabilities() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);; + return new NetworkState(info, prop, capabilities, new Network(100), null, "netid"); + } + + private static NetworkState buildMobileIPv4UpstreamState() { + return buildMobileUpstreamState(true, false, false); + } + + private static NetworkState buildMobileIPv6UpstreamState() { + return buildMobileUpstreamState(false, true, false); + } + + private static NetworkState buildMobileDualStackUpstreamState() { + return buildMobileUpstreamState(true, true, false); + } + + private static NetworkState buildMobile464xlatUpstreamState() { + return buildMobileUpstreamState(false, true, true); + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); when(mResources.getStringArray(com.android.internal.R.array.config_tether_dhcp_range)) .thenReturn(new String[0]); when(mResources.getStringArray(com.android.internal.R.array.config_tether_usb_regexs)) - .thenReturn(new String[0]); + .thenReturn(new String[] { "test_rndis\\d" }); when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_regexs)) - .thenReturn(new String[]{ "test_wlan\\d", "test_rndis\\d" }); + .thenReturn(new String[]{ "test_wlan\\d" }); when(mResources.getStringArray(com.android.internal.R.array.config_tether_bluetooth_regexs)) .thenReturn(new String[0]); when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types)) .thenReturn(new int[0]); when(mNMService.listInterfaces()) - .thenReturn(new String[]{ "test_rmnet_data0", mTestIfname }); + .thenReturn(new String[] { + TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME}); when(mNMService.getInterfaceConfig(anyString())) .thenReturn(new InterfaceConfiguration()); + when(mRouterAdvertisementDaemon.start()) + .thenReturn(true); mServiceContext = new MockContext(mContext); mContentResolver = new MockContentResolver(mServiceContext); @@ -176,8 +309,6 @@ public class TetheringTest { }; mServiceContext.registerReceiver(mBroadcastReceiver, new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED)); - when(mTetheringDependencies.getOffloadHardwareInterface( - any(Handler.class), any(SharedLog.class))).thenReturn(mOffloadHardwareInterface); mTethering = new Tethering(mServiceContext, mNMService, mStatsService, mPolicyManager, mLooper.getLooper(), mSystemProperties, mTetheringDependencies); @@ -264,10 +395,10 @@ public class TetheringTest { } private void verifyInterfaceServingModeStarted() throws Exception { - verify(mNMService, times(1)).getInterfaceConfig(mTestIfname); + verify(mNMService, times(1)).getInterfaceConfig(TEST_WLAN_IFNAME); verify(mNMService, times(1)) - .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class)); - verify(mNMService, times(1)).tetherInterface(mTestIfname); + .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class)); + verify(mNMService, times(1)).tetherInterface(TEST_WLAN_IFNAME); } private void verifyTetheringBroadcast(String ifname, String whichExtra) { @@ -287,7 +418,7 @@ public class TetheringTest { // per-interface state machine to start up, and telling us that // hotspot mode is to be started. if (emulateInterfaceStatusChanged) { - mTethering.interfaceStatusChanged(mTestIfname, true); + mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); } sendWifiApStateChanged(WIFI_AP_STATE_ENABLED); mLooper.dispatchAll(); @@ -297,27 +428,31 @@ public class TetheringTest { // broadcast indicating that the interface is "available". if (emulateInterfaceStatusChanged) { verify(mConnectivityManager, atLeastOnce()).isTetheringSupported(); - verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER); + verifyTetheringBroadcast(TEST_WLAN_IFNAME, ConnectivityManager.EXTRA_AVAILABLE_TETHER); } verifyNoMoreInteractions(mConnectivityManager); verifyNoMoreInteractions(mNMService); verifyNoMoreInteractions(mWifiManager); } - @Test - public void testUsbConfiguredBroadcastStartsTethering() throws Exception { + private void prepareUsbTethering(NetworkState upstreamState) { when(mConnectivityManager.isTetheringSupported()).thenReturn(true); + when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any())) + .thenReturn(upstreamState); // Emulate pressing the USB tethering button in Settings UI. mTethering.startTethering(TETHERING_USB, null, false); mLooper.dispatchAll(); verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS); - // Pretend we receive a USB connected broadcast. Here we also pretend - // that the RNDIS function is somehow enabled, so that we see if we - // might trip ourselves up. - sendUsbBroadcast(true, false, true); - mLooper.dispatchAll(); + mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true); + } + + @Test + public void testUsbConfiguredBroadcastStartsTethering() throws Exception { + NetworkState upstreamState = buildMobileIPv4UpstreamState(); + prepareUsbTethering(upstreamState); + // This should produce no activity of any kind. verifyNoMoreInteractions(mConnectivityManager); verifyNoMoreInteractions(mNMService); @@ -328,6 +463,10 @@ public class TetheringTest { // Now we should see the start of tethering mechanics (in this case: // tetherMatchingInterfaces() which starts by fetching all interfaces). verify(mNMService, times(1)).listInterfaces(); + + // UpstreamNetworkMonitor should receive selected upstream + verify(mUpstreamNetworkMonitor, times(1)).selectPreferredUpstreamType(any()); + verify(mUpstreamNetworkMonitor, times(1)).setCurrentUpstream(upstreamState.network); } @Test @@ -348,26 +487,21 @@ public class TetheringTest { // per-interface state machine to start up, and telling us that // hotspot mode is to be started. if (emulateInterfaceStatusChanged) { - mTethering.interfaceStatusChanged(mTestIfname, true); + mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); } - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, mTestIfname, IFACE_IP_MODE_LOCAL_ONLY); + sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_LOCAL_ONLY); mLooper.dispatchAll(); verifyInterfaceServingModeStarted(); - verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER); + verifyTetheringBroadcast(TEST_WLAN_IFNAME, ConnectivityManager.EXTRA_AVAILABLE_TETHER); verify(mNMService, times(1)).setIpForwardingEnabled(true); verify(mNMService, times(1)).startTethering(any(String[].class)); verifyNoMoreInteractions(mNMService); verify(mWifiManager).updateInterfaceIpState( - mTestIfname, WifiManager.IFACE_IP_MODE_LOCAL_ONLY); + TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_LOCAL_ONLY); verifyNoMoreInteractions(mWifiManager); - verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY); - // UpstreamNetworkMonitor will be started, and will register two callbacks: - // a "listen all" and a "track default". - verify(mConnectivityManager, times(1)).registerNetworkCallback( - any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class)); - verify(mConnectivityManager, times(1)).registerDefaultNetworkCallback( - any(NetworkCallback.class), any(Handler.class)); + verifyTetheringBroadcast(TEST_WLAN_IFNAME, ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY); + verify(mUpstreamNetworkMonitor, times(1)).start(); // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast(). verify(mConnectivityManager, atLeastOnce()).isTetheringSupported(); verifyNoMoreInteractions(mConnectivityManager); @@ -375,14 +509,14 @@ public class TetheringTest { // Emulate externally-visible WifiManager effects, when hotspot mode // is being torn down. sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED); - mTethering.interfaceRemoved(mTestIfname); + mTethering.interfaceRemoved(TEST_WLAN_IFNAME); mLooper.dispatchAll(); - verify(mNMService, times(1)).untetherInterface(mTestIfname); + verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME); // TODO: Why is {g,s}etInterfaceConfig() called more than once? - verify(mNMService, atLeastOnce()).getInterfaceConfig(mTestIfname); + verify(mNMService, atLeastOnce()).getInterfaceConfig(TEST_WLAN_IFNAME); verify(mNMService, atLeastOnce()) - .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class)); + .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class)); verify(mNMService, times(1)).stopTethering(); verify(mNMService, times(1)).setIpForwardingEnabled(false); verifyNoMoreInteractions(mNMService); @@ -390,7 +524,115 @@ public class TetheringTest { // Asking for the last error after the per-interface state machine // has been reaped yields an unknown interface error. assertEquals(ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE, - mTethering.getLastTetherError(mTestIfname)); + mTethering.getLastTetherError(TEST_WLAN_IFNAME)); + } + + /** + * Send CMD_IPV6_TETHER_UPDATE to TISMs as would be done by IPv6TetheringCoordinator. + */ + private void sendIPv6TetherUpdates(NetworkState upstreamState) { + // IPv6TetheringCoordinator must have been notified of downstream + verify(mIPv6TetheringCoordinator, times(1)).addActiveDownstream( + argThat(sm -> sm.linkProperties().getInterfaceName().equals(TEST_USB_IFNAME)), + eq(IControlsTethering.STATE_TETHERED)); + + for (TetherInterfaceStateMachine tism : + mTetheringDependencies.ipv6CoordinatorNotifyList) { + NetworkState ipv6OnlyState = buildMobileUpstreamState(false, true, false); + tism.sendMessage(TetherInterfaceStateMachine.CMD_IPV6_TETHER_UPDATE, 0, 0, + upstreamState.linkProperties.isIPv6Provisioned() + ? ipv6OnlyState.linkProperties + : null); + } + mLooper.dispatchAll(); + } + + private void runUsbTethering(NetworkState upstreamState) { + prepareUsbTethering(upstreamState); + sendUsbBroadcast(true, true, true); + mLooper.dispatchAll(); + } + + @Test + public void workingMobileUsbTethering_IPv4() throws Exception { + NetworkState upstreamState = buildMobileIPv4UpstreamState(); + runUsbTethering(upstreamState); + + verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + + sendIPv6TetherUpdates(upstreamState); + verify(mRouterAdvertisementDaemon, never()).buildNewRa(any(), notNull()); + } + + @Test + public void workingMobileUsbTethering_IPv6() throws Exception { + NetworkState upstreamState = buildMobileIPv6UpstreamState(); + runUsbTethering(upstreamState); + + verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + + sendIPv6TetherUpdates(upstreamState); + verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull()); + verify(mNetd, times(1)).tetherApplyDnsInterfaces(); + } + + @Test + public void workingMobileUsbTethering_DualStack() throws Exception { + NetworkState upstreamState = buildMobileDualStackUpstreamState(); + runUsbTethering(upstreamState); + + verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mRouterAdvertisementDaemon, times(1)).start(); + + sendIPv6TetherUpdates(upstreamState); + verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull()); + verify(mNetd, times(1)).tetherApplyDnsInterfaces(); + } + + @Test + public void workingMobileUsbTethering_MultipleUpstreams() throws Exception { + NetworkState upstreamState = buildMobile464xlatUpstreamState(); + runUsbTethering(upstreamState); + + verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME); + verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, + TEST_XLAT_MOBILE_IFNAME); + + sendIPv6TetherUpdates(upstreamState); + verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull()); + verify(mNetd, times(1)).tetherApplyDnsInterfaces(); + } + + @Test + public void workingMobileUsbTethering_v6Then464xlat() throws Exception { + // Setup IPv6 + NetworkState upstreamState = buildMobileIPv6UpstreamState(); + runUsbTethering(upstreamState); + + // Then 464xlat comes up + upstreamState = buildMobile464xlatUpstreamState(); + when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any())) + .thenReturn(upstreamState); + + // Upstream LinkProperties changed: UpstreamNetworkMonitor sends EVENT_ON_LINKPROPERTIES. + mTetheringDependencies.upstreamNetworkMonitorMasterSM.sendMessage( + Tethering.TetherMasterSM.EVENT_UPSTREAM_CALLBACK, + UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES, + 0, + upstreamState); + mLooper.dispatchAll(); + + // Forwarding is added for 464xlat, and was still added only once for v6 + verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME); + verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, + TEST_XLAT_MOBILE_IFNAME); } @Test @@ -420,12 +662,12 @@ public class TetheringTest { // Emulate externally-visible WifiManager effects, causing the // per-interface state machine to start up, and telling us that // tethering mode is to be started. - mTethering.interfaceStatusChanged(mTestIfname, true); + mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); sendWifiApStateChanged(WIFI_AP_STATE_ENABLED); mLooper.dispatchAll(); verify(mConnectivityManager, atLeastOnce()).isTetheringSupported(); - verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER); + verifyTetheringBroadcast(TEST_WLAN_IFNAME, ConnectivityManager.EXTRA_AVAILABLE_TETHER); verifyNoMoreInteractions(mConnectivityManager); verifyNoMoreInteractions(mNMService); verifyNoMoreInteractions(mWifiManager); @@ -448,30 +690,23 @@ public class TetheringTest { // Emulate externally-visible WifiManager effects, causing the // per-interface state machine to start up, and telling us that // tethering mode is to be started. - mTethering.interfaceStatusChanged(mTestIfname, true); - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, mTestIfname, IFACE_IP_MODE_TETHERED); + mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); + sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); mLooper.dispatchAll(); verifyInterfaceServingModeStarted(); - verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER); + verifyTetheringBroadcast(TEST_WLAN_IFNAME, ConnectivityManager.EXTRA_AVAILABLE_TETHER); verify(mNMService, times(1)).setIpForwardingEnabled(true); verify(mNMService, times(1)).startTethering(any(String[].class)); verifyNoMoreInteractions(mNMService); verify(mWifiManager).updateInterfaceIpState( - mTestIfname, WifiManager.IFACE_IP_MODE_TETHERED); + TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED); verifyNoMoreInteractions(mWifiManager); - verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_ACTIVE_TETHER); - // UpstreamNetworkMonitor will be started, and will register two callbacks: - // a "listen all" and a "track default". - verify(mConnectivityManager, times(1)).registerNetworkCallback( - any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class)); - verify(mConnectivityManager, times(1)).registerDefaultNetworkCallback( - any(NetworkCallback.class), any(Handler.class)); + verifyTetheringBroadcast(TEST_WLAN_IFNAME, ConnectivityManager.EXTRA_ACTIVE_TETHER); + verify(mUpstreamNetworkMonitor, times(1)).start(); // In tethering mode, in the default configuration, an explicit request // for a mobile network is also made. - verify(mConnectivityManager, times(1)).requestNetwork( - any(NetworkRequest.class), any(NetworkCallback.class), eq(0), anyInt(), - any(Handler.class)); + verify(mUpstreamNetworkMonitor, times(1)).registerMobileNetworkRequest(); // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast(). verify(mConnectivityManager, atLeastOnce()).isTetheringSupported(); verifyNoMoreInteractions(mConnectivityManager); @@ -494,14 +729,14 @@ public class TetheringTest { // Emulate externally-visible WifiManager effects, when tethering mode // is being torn down. sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED); - mTethering.interfaceRemoved(mTestIfname); + mTethering.interfaceRemoved(TEST_WLAN_IFNAME); mLooper.dispatchAll(); - verify(mNMService, times(1)).untetherInterface(mTestIfname); + verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME); // TODO: Why is {g,s}etInterfaceConfig() called more than once? - verify(mNMService, atLeastOnce()).getInterfaceConfig(mTestIfname); + verify(mNMService, atLeastOnce()).getInterfaceConfig(TEST_WLAN_IFNAME); verify(mNMService, atLeastOnce()) - .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class)); + .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class)); verify(mNMService, times(1)).stopTethering(); verify(mNMService, times(1)).setIpForwardingEnabled(false); verifyNoMoreInteractions(mNMService); @@ -509,7 +744,7 @@ public class TetheringTest { // Asking for the last error after the per-interface state machine // has been reaped yields an unknown interface error. assertEquals(ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE, - mTethering.getLastTetherError(mTestIfname)); + mTethering.getLastTetherError(TEST_WLAN_IFNAME)); } // TODO: Test with and without interfaceStatusChanged(). @@ -530,21 +765,21 @@ public class TetheringTest { // Emulate externally-visible WifiManager effects, causing the // per-interface state machine to start up, and telling us that // tethering mode is to be started. - mTethering.interfaceStatusChanged(mTestIfname, true); - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, mTestIfname, IFACE_IP_MODE_TETHERED); + mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); + sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); mLooper.dispatchAll(); // We verify get/set called twice here: once for setup and once during // teardown because all events happen over the course of the single // dispatchAll() above. - verify(mNMService, times(2)).getInterfaceConfig(mTestIfname); + verify(mNMService, times(2)).getInterfaceConfig(TEST_WLAN_IFNAME); verify(mNMService, times(2)) - .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class)); - verify(mNMService, times(1)).tetherInterface(mTestIfname); + .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class)); + verify(mNMService, times(1)).tetherInterface(TEST_WLAN_IFNAME); verify(mWifiManager).updateInterfaceIpState( - mTestIfname, WifiManager.IFACE_IP_MODE_TETHERED); + TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED); verify(mConnectivityManager, atLeastOnce()).isTetheringSupported(); - verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER); + verifyTetheringBroadcast(TEST_WLAN_IFNAME, ConnectivityManager.EXTRA_AVAILABLE_TETHER); // This is called, but will throw. verify(mNMService, times(1)).setIpForwardingEnabled(true); // This never gets called because of the exception thrown above. @@ -552,9 +787,9 @@ public class TetheringTest { // When the master state machine transitions to an error state it tells // downstream interfaces, which causes us to tell Wi-Fi about the error // so it can take down AP mode. - verify(mNMService, times(1)).untetherInterface(mTestIfname); + verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME); verify(mWifiManager).updateInterfaceIpState( - mTestIfname, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR); + TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR); verifyNoMoreInteractions(mWifiManager); verifyNoMoreInteractions(mConnectivityManager); @@ -596,7 +831,7 @@ public class TetheringTest { @Test public void testDisallowTetheringWhenAtLeastOneTetheringInterfaceIsActive() throws Exception { - final String[] nonEmptyActiveIfacesList = new String[]{mTestIfname}; + final String[] nonEmptyActiveIfacesList = new String[]{TEST_WLAN_IFNAME}; final boolean currDisallow = false; final boolean nextDisallow = true; final int expectedInteractionsWithShowNotification = 1; @@ -618,7 +853,7 @@ public class TetheringTest { @Test public void testAllowTetheringWhenAtLeastOneTetheringInterfaceIsActive() throws Exception { - final String[] nonEmptyActiveIfacesList = new String[]{mTestIfname}; + final String[] nonEmptyActiveIfacesList = new String[]{TEST_WLAN_IFNAME}; final boolean currDisallow = true; final boolean nextDisallow = false; final int expectedInteractionsWithShowNotification = 0; @@ -629,7 +864,7 @@ public class TetheringTest { @Test public void testDisallowTetheringUnchanged() throws Exception { - final String[] nonEmptyActiveIfacesList = new String[]{mTestIfname}; + final String[] nonEmptyActiveIfacesList = new String[]{TEST_WLAN_IFNAME}; final int expectedInteractionsWithShowNotification = 0; boolean currDisallow = true; boolean nextDisallow = true; diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index f59850d45ae3..e377a472530e 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -70,6 +70,7 @@ import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.INetworkManagementService; import android.os.Looper; +import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.support.test.filters.SmallTest; @@ -88,6 +89,8 @@ import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.net.Inet4Address; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -639,4 +642,32 @@ public class VpnTest { lp.addRoute(new RouteInfo(new IpPrefix("::/1"))); assertTrue(Vpn.providesRoutesToMostDestinations(lp)); } + + @Test + public void testDoesNotLockUpWithTooManyRoutes() { + final LinkProperties lp = new LinkProperties(); + final byte[] ad = new byte[4]; + // Actually evaluating this many routes under 1500ms is impossible on + // current hardware and for some time, as the algorithm is O(n²). + // Make sure the system has a safeguard against this and does not + // lock up. + final int MAX_ROUTES = 4000; + final long MAX_ALLOWED_TIME_MS = 1500; + for (int i = 0; i < MAX_ROUTES; ++i) { + ad[0] = (byte)((i >> 24) & 0xFF); + ad[1] = (byte)((i >> 16) & 0xFF); + ad[2] = (byte)((i >> 8) & 0xFF); + ad[3] = (byte)(i & 0xFF); + try { + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.getByAddress(ad), 32))); + } catch (UnknownHostException e) { + // UnknownHostException is only thrown for an address of illegal length, + // which can't happen in the case above. + } + } + final long start = SystemClock.currentThreadTimeMillis(); + assertTrue(Vpn.providesRoutesToMostDestinations(lp)); + final long end = SystemClock.currentThreadTimeMillis(); + assertTrue(end - start < MAX_ALLOWED_TIME_MS); + } } diff --git a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java index db5373ac34b2..7c77cf59524d 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java @@ -31,7 +31,6 @@ import static org.mockito.Mockito.when; import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR; import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR; -import static android.net.ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR; import static android.net.ConnectivityManager.TETHERING_BLUETOOTH; import static android.net.ConnectivityManager.TETHERING_USB; import static android.net.ConnectivityManager.TETHERING_WIFI; @@ -39,12 +38,12 @@ import static com.android.server.connectivity.tethering.IControlsTethering.STATE import static com.android.server.connectivity.tethering.IControlsTethering.STATE_TETHERED; import static com.android.server.connectivity.tethering.IControlsTethering.STATE_UNAVAILABLE; -import android.net.ConnectivityManager; import android.net.INetworkStatsService; import android.net.InterfaceConfiguration; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.RouteInfo; +import android.net.util.InterfaceSet; import android.net.util.SharedLog; import android.os.INetworkManagementService; import android.os.RemoteException; @@ -75,6 +74,7 @@ public class TetherInterfaceStateMachineTest { @Mock private IControlsTethering mTetherHelper; @Mock private InterfaceConfiguration mInterfaceConfiguration; @Mock private SharedLog mSharedLog; + @Mock private TetheringDependencies mTetheringDependencies; private final TestLooper mLooper = new TestLooper(); private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor = @@ -84,7 +84,7 @@ public class TetherInterfaceStateMachineTest { private void initStateMachine(int interfaceType) throws Exception { mTestedSm = new TetherInterfaceStateMachine( IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, - mNMService, mStatsService, mTetherHelper); + mNMService, mStatsService, mTetherHelper, mTetheringDependencies); mTestedSm.start(); // Starting the state machine always puts us in a consistent state and notifies // the rest of the world that we've changed from an unknown to available state. @@ -111,7 +111,8 @@ public class TetherInterfaceStateMachineTest { @Test public void startsOutAvailable() { mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), - TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mTetherHelper); + TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mTetherHelper, + mTetheringDependencies); mTestedSm.start(); mLooper.dispatchAll(); verify(mTetherHelper).updateInterfaceState( @@ -346,7 +347,7 @@ public class TetherInterfaceStateMachineTest { * Send a command to the state machine under test, and run the event loop to idle. * * @param command One of the TetherInterfaceStateMachine.CMD_* constants. - * @param obj An additional argument to pass. + * @param arg1 An additional argument to pass. */ private void dispatchCommand(int command, int arg1) { mTestedSm.sendMessage(command, arg1); @@ -371,7 +372,7 @@ public class TetherInterfaceStateMachineTest { */ private void dispatchTetherConnectionChanged(String upstreamIface) { mTestedSm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED, - upstreamIface); + new InterfaceSet(upstreamIface)); mLooper.dispatchAll(); } diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java index c3b9defdec4e..9661dc24ca2e 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java @@ -147,6 +147,16 @@ public class UpstreamNetworkMonitorTest { } @Test + public void testCallbacksRegistered() { + mUNM.start(); + verify(mCM, times(1)).registerNetworkCallback(any(), any(), any()); + verify(mCM, times(1)).registerDefaultNetworkCallback(any(), any()); + + mUNM.stop(); + verify(mCM, times(2)).unregisterNetworkCallback(any(NetworkCallback.class)); + } + + @Test public void testRequestsMobileNetwork() throws Exception { assertFalse(mUNM.mobileNetworkRequested()); assertEquals(0, mCM.requested.size()); diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp index 300c701bfc80..499f2549b474 100644 --- a/tools/stats_log_api_gen/main.cpp +++ b/tools/stats_log_api_gen/main.cpp @@ -211,7 +211,7 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, signature != atoms.signatures.end(); signature++) { int argIndex; - fprintf(out, "void\n"); + fprintf(out, "int\n"); fprintf(out, "stats_write(int32_t code"); argIndex = 1; for (vector<java_type_t>::const_iterator arg = signature->begin(); @@ -251,7 +251,7 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, " diff length: %s vs %s\");\n", attributionDecl.fields.front().name.c_str(), chainField.name.c_str()); - fprintf(out, " return;\n"); + fprintf(out, " return -EINVAL;\n"); fprintf(out, " }\n"); } } @@ -284,7 +284,7 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, argIndex++; } - fprintf(out, " event.write(LOG_ID_STATS);\n"); + fprintf(out, " return event.write(LOG_ID_STATS);\n"); fprintf(out, "}\n"); fprintf(out, "\n"); } @@ -293,7 +293,7 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, signature != atoms.non_chained_signatures.end(); signature++) { int argIndex; - fprintf(out, "void\n"); + fprintf(out, "int\n"); fprintf(out, "stats_write_non_chained(int32_t code"); argIndex = 1; for (vector<java_type_t>::const_iterator arg = signature->begin(); @@ -327,7 +327,7 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, argIndex++; } - fprintf(out, " event.write(LOG_ID_STATS);\n"); + fprintf(out, " return event.write(LOG_ID_STATS);\n"); fprintf(out, "}\n"); fprintf(out, "\n"); } @@ -377,7 +377,7 @@ static void write_cpp_method_header( 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()); + fprintf(out, "int %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++) { @@ -522,7 +522,7 @@ static void write_java_method( 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()); + fprintf(out, " public static native int %s(int code", method_name.c_str()); int argIndex = 1; for (vector<java_type_t>::const_iterator arg = signature->begin(); arg != signature->end(); arg++) { @@ -719,7 +719,7 @@ jni_function_signature(const vector<java_type_t>& signature, const AtomDecl &att result += java_type_signature(*arg); } } - result += ")V"; + result += ")I"; return result; } @@ -732,7 +732,7 @@ write_stats_log_jni(FILE* out, const string& java_method_name, const string& cpp signature != signatures.end(); signature++) { int argIndex; - fprintf(out, "static void\n"); + fprintf(out, "static int\n"); fprintf(out, "%s(JNIEnv* env, jobject clazz UNUSED, jint code", jni_function_name(java_method_name, *signature).c_str()); argIndex = 1; @@ -779,7 +779,7 @@ write_stats_log_jni(FILE* out, const string& java_method_name, const string& cpp "\"java/lang/IllegalArgumentException\", " "\"invalid attribution field(%s) length.\");\n", chainField.name.c_str()); - fprintf(out, " return;\n"); + fprintf(out, " return -EINVAL;\n"); fprintf(out, " }\n"); } if (chainField.javaType == JAVA_TYPE_INT) { @@ -822,7 +822,7 @@ write_stats_log_jni(FILE* out, const string& java_method_name, const string& cpp // stats_write call argIndex = 1; - fprintf(out, " android::util::%s(code", cpp_method_name.c_str()); + fprintf(out, " int ret = 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) { @@ -868,6 +868,7 @@ write_stats_log_jni(FILE* out, const string& java_method_name, const string& cpp } argIndex++; } + fprintf(out, " return ret;\n"); fprintf(out, "}\n"); fprintf(out, "\n"); diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index 21ae3a923946..b77b1ad5f255 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -25,11 +25,13 @@ import android.net.MacAddress; import android.net.ProxyInfo; import android.net.StaticIpConfiguration; import android.net.Uri; +import android.net.wifi.WifiInfo; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; import android.text.TextUtils; import android.util.BackupUtils; +import android.util.Log; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; @@ -76,6 +78,8 @@ public class WifiConfiguration implements Parcelable { /** {@hide} */ private String mPasspointManagementObjectTree; + /** {@hide} */ + private static final int MAXIMUM_RANDOM_MAC_GENERATION_RETRY = 3; /** * Recognized key management schemes. @@ -798,27 +802,37 @@ public class WifiConfiguration implements Parcelable { * @hide * Randomized MAC address to use with this particular network */ + @NonNull private MacAddress mRandomizedMacAddress; /** * @hide * Checks if the given MAC address can be used for Connected Mac Randomization - * by verifying that it is non-null, unicast, and locally assigned. + * by verifying that it is non-null, unicast, locally assigned, and not default mac. * @param mac MacAddress to check * @return true if mac is good to use */ - private boolean isValidMacAddressForRandomization(MacAddress mac) { - return mac != null && !mac.isMulticastAddress() && mac.isLocallyAssigned(); + public static boolean isValidMacAddressForRandomization(MacAddress mac) { + return mac != null && !mac.isMulticastAddress() && mac.isLocallyAssigned() + && !MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS).equals(mac); } /** * @hide * Returns Randomized MAC address to use with the network. - * If it is not set/valid, create a new randomized address. + * If it is not set/valid, creates a new randomized address. + * If it can't generate a valid mac, returns the default MAC. */ - public MacAddress getOrCreateRandomizedMacAddress() { - if (!isValidMacAddressForRandomization(mRandomizedMacAddress)) { + public @NonNull MacAddress getOrCreateRandomizedMacAddress() { + int randomMacGenerationCount = 0; + while (!isValidMacAddressForRandomization(mRandomizedMacAddress) + && randomMacGenerationCount < MAXIMUM_RANDOM_MAC_GENERATION_RETRY) { mRandomizedMacAddress = MacAddress.createRandomUnicastAddress(); + randomMacGenerationCount++; + } + + if (!isValidMacAddressForRandomization(mRandomizedMacAddress)) { + mRandomizedMacAddress = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS); } return mRandomizedMacAddress; } @@ -828,10 +842,7 @@ public class WifiConfiguration implements Parcelable { * Returns MAC address set to be the local randomized MAC address. * Does not guarantee that the returned address is valid for use. */ - public MacAddress getRandomizedMacAddress() { - if (mRandomizedMacAddress == null) { - mRandomizedMacAddress = MacAddress.ALL_ZEROS_ADDRESS; - } + public @NonNull MacAddress getRandomizedMacAddress() { return mRandomizedMacAddress; } @@ -839,7 +850,11 @@ public class WifiConfiguration implements Parcelable { * @hide * @param mac MacAddress to change into */ - public void setRandomizedMacAddress(MacAddress mac) { + public void setRandomizedMacAddress(@NonNull MacAddress mac) { + if (mac == null) { + Log.e(TAG, "setRandomizedMacAddress received null MacAddress."); + return; + } mRandomizedMacAddress = mac; } @@ -1532,7 +1547,7 @@ public class WifiConfiguration implements Parcelable { creatorUid = -1; shared = true; dtimInterval = 0; - mRandomizedMacAddress = MacAddress.ALL_ZEROS_ADDRESS; + mRandomizedMacAddress = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS); } /** diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index c8df08773716..433285bfc702 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -1317,6 +1317,7 @@ public class WifiManager { if (pin) { NetworkRequest request = new NetworkRequest.Builder() .clearCapabilities() + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) .build(); NetworkPinner.pin(mContext, request); diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java index 8a3a7f5a2d79..35179847541c 100644 --- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java @@ -18,12 +18,14 @@ package android.net.wifi; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertFalse; import android.os.Parcel; import android.net.MacAddress; import android.net.wifi.WifiConfiguration.NetworkSelectionStatus; +import android.net.wifi.WifiInfo; import org.junit.Before; import org.junit.Test; @@ -176,22 +178,25 @@ public class WifiConfigurationTest { @Test public void testGetOrCreateRandomizedMacAddress_SavesAndReturnsSameAddress() { WifiConfiguration config = new WifiConfiguration(); - assertEquals(MacAddress.ALL_ZEROS_ADDRESS, config.getRandomizedMacAddress()); + MacAddress defaultMac = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS); + assertEquals(defaultMac, config.getRandomizedMacAddress()); MacAddress firstMacAddress = config.getOrCreateRandomizedMacAddress(); MacAddress secondMacAddress = config.getOrCreateRandomizedMacAddress(); + assertNotEquals(defaultMac, firstMacAddress); assertEquals(firstMacAddress, secondMacAddress); } @Test public void testSetRandomizedMacAddress_ChangesSavedAddress() { WifiConfiguration config = new WifiConfiguration(); - assertEquals(MacAddress.ALL_ZEROS_ADDRESS, config.getRandomizedMacAddress()); + MacAddress defaultMac = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS); + assertEquals(defaultMac, config.getRandomizedMacAddress()); MacAddress macToChangeInto = MacAddress.createRandomUnicastAddress(); config.setRandomizedMacAddress(macToChangeInto); - MacAddress macAfterChange = config.getOrCreateRandomizedMacAddress(); + MacAddress macAfterChange = config.getRandomizedMacAddress(); assertEquals(macToChangeInto, macAfterChange); } @@ -200,24 +205,37 @@ public class WifiConfigurationTest { public void testGetOrCreateRandomizedMacAddress_ReRandomizesInvalidAddress() { WifiConfiguration config = new WifiConfiguration(); + MacAddress defaultMac = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS); MacAddress macAddressZeroes = MacAddress.ALL_ZEROS_ADDRESS; MacAddress macAddressMulticast = MacAddress.fromString("03:ff:ff:ff:ff:ff"); MacAddress macAddressGlobal = MacAddress.fromString("fc:ff:ff:ff:ff:ff"); config.setRandomizedMacAddress(null); MacAddress macAfterChange = config.getOrCreateRandomizedMacAddress(); - assertFalse(macAfterChange.equals(null)); + assertNotEquals(macAfterChange, null); + + config.setRandomizedMacAddress(defaultMac); + macAfterChange = config.getOrCreateRandomizedMacAddress(); + assertNotEquals(macAfterChange, defaultMac); config.setRandomizedMacAddress(macAddressZeroes); macAfterChange = config.getOrCreateRandomizedMacAddress(); - assertFalse(macAfterChange.equals(macAddressZeroes)); + assertNotEquals(macAfterChange, macAddressZeroes); config.setRandomizedMacAddress(macAddressMulticast); macAfterChange = config.getOrCreateRandomizedMacAddress(); - assertFalse(macAfterChange.equals(macAddressMulticast)); + assertNotEquals(macAfterChange, macAddressMulticast); config.setRandomizedMacAddress(macAddressGlobal); macAfterChange = config.getOrCreateRandomizedMacAddress(); - assertFalse(macAfterChange.equals(macAddressGlobal)); + assertNotEquals(macAfterChange, macAddressGlobal); + } + + @Test + public void testSetRandomizedMacAddress_DoesNothingWhenNull() { + WifiConfiguration config = new WifiConfiguration(); + MacAddress defaultMac = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS); + config.setRandomizedMacAddress(null); + assertEquals(defaultMac, config.getRandomizedMacAddress()); } } |