diff options
377 files changed, 8941 insertions, 4911 deletions
diff --git a/Android.bp b/Android.bp index 25e738ccb3cf..cc9cfe95ff0a 100644 --- a/Android.bp +++ b/Android.bp @@ -158,9 +158,9 @@ java_defaults { "core/java/android/hardware/IConsumerIrService.aidl", "core/java/android/hardware/ISerialManager.aidl", "core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl", - "core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl", "core/java/android/hardware/biometrics/IBiometricService.aidl", "core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl", + "core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl", "core/java/android/hardware/biometrics/IBiometricServiceLockoutResetCallback.aidl", "core/java/android/hardware/display/IDisplayManager.aidl", "core/java/android/hardware/display/IDisplayManagerCallback.aidl", @@ -290,7 +290,6 @@ java_defaults { "core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl", "core/java/android/service/gatekeeper/IGateKeeperService.aidl", "core/java/android/service/intelligence/IIntelligenceService.aidl", - "core/java/android/service/notification/INotificationListener.aidl", "core/java/android/service/notification/IStatusBarNotificationHolder.aidl", "core/java/android/service/notification/IConditionListener.aidl", @@ -573,6 +572,7 @@ java_defaults { "telephony/java/com/android/internal/telephony/IApnSourceService.aidl", "telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl", "telephony/java/com/android/internal/telephony/IMms.aidl", + "telephony/java/com/android/internal/telephony/INumberVerificationCallback.aidl", "telephony/java/com/android/internal/telephony/IOnSubscriptionsChangedListener.aidl", "telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl", "telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl", @@ -782,9 +782,11 @@ java_library { java_library_host { name: "inspector-annotation", srcs: [ - "core/java/android/view/inspector/InspectableChildren.java", "core/java/android/view/inspector/InspectableNodeName.java", "core/java/android/view/inspector/InspectableProperty.java", + // Needed for the ResourceId.ID_NULL constant + "core/java/android/content/res/ResourceId.java", + "core/java/android/annotation/AnyRes.java", ], } diff --git a/Android.mk b/Android.mk index 770ec20f151e..b7dda9a45ed8 100644 --- a/Android.mk +++ b/Android.mk @@ -73,55 +73,37 @@ $(OUT_DOCS)/offline-sdk-timestamp: $(OUT_DOCS)/offline-sdk-docs-docs.zip ( unzip -qo $< -d $(OUT_DOCS)/offline-sdk && touch -f $@ ) || exit 1 # ==== hiddenapi lists ======================================= -.KATI_RESTAT: \ - $(INTERNAL_PLATFORM_HIDDENAPI_WHITELIST) \ - $(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST) \ - $(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST) \ - $(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST) -$(INTERNAL_PLATFORM_HIDDENAPI_WHITELIST): \ - .KATI_IMPLICIT_OUTPUTS := \ - $(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST) \ - $(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST) \ - $(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST) -$(INTERNAL_PLATFORM_HIDDENAPI_WHITELIST): \ +.KATI_RESTAT: $(INTERNAL_PLATFORM_HIDDENAPI_FLAGS) +$(INTERNAL_PLATFORM_HIDDENAPI_FLAGS): \ frameworks/base/tools/hiddenapi/generate_hiddenapi_lists.py \ frameworks/base/config/hiddenapi-light-greylist.txt \ frameworks/base/config/hiddenapi-vendor-list.txt \ + frameworks/base/config/hiddenapi-greylist-max-o.txt \ frameworks/base/config/hiddenapi-max-sdk-p-blacklist.txt \ frameworks/base/config/hiddenapi-force-blacklist.txt \ $(INTERNAL_PLATFORM_HIDDENAPI_PUBLIC_LIST) \ $(INTERNAL_PLATFORM_HIDDENAPI_PRIVATE_LIST) \ $(INTERNAL_PLATFORM_REMOVED_DEX_API_FILE) frameworks/base/tools/hiddenapi/generate_hiddenapi_lists.py \ - --input-public $(INTERNAL_PLATFORM_HIDDENAPI_PUBLIC_LIST) \ - --input-private $(INTERNAL_PLATFORM_HIDDENAPI_PRIVATE_LIST) \ - --input-whitelists $(PRIVATE_WHITELIST_INPUTS) \ - --input-greylists \ + --public $(INTERNAL_PLATFORM_HIDDENAPI_PUBLIC_LIST) \ + --private $(INTERNAL_PLATFORM_HIDDENAPI_PRIVATE_LIST) \ + --csv $(PRIVATE_FLAGS_INPUTS) \ + --greylist \ frameworks/base/config/hiddenapi-light-greylist.txt \ frameworks/base/config/hiddenapi-vendor-list.txt \ - frameworks/base/config/hiddenapi-max-sdk-p-blacklist.txt \ - <(comm -12 <(sort $(INTERNAL_PLATFORM_REMOVED_DEX_API_FILE)) \ - $(INTERNAL_PLATFORM_HIDDENAPI_PRIVATE_LIST)) \ - $(PRIVATE_GREYLIST_INPUTS) \ - --input-blacklists frameworks/base/config/hiddenapi-force-blacklist.txt \ - --output-whitelist $(INTERNAL_PLATFORM_HIDDENAPI_WHITELIST).tmp \ - --output-light-greylist $(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST).tmp \ - --output-dark-greylist $(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST).tmp \ - --output-blacklist $(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST).tmp - $(call commit-change-for-toc,$(INTERNAL_PLATFORM_HIDDENAPI_WHITELIST)) - $(call commit-change-for-toc,$(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST)) - $(call commit-change-for-toc,$(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST)) - $(call commit-change-for-toc,$(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST)) + --greylist-ignore-conflicts $(INTERNAL_PLATFORM_REMOVED_DEX_API_FILE) \ + --greylist-max-o-ignore-conflicts \ + frameworks/base/config/hiddenapi-greylist-max-o.txt \ + --blacklist frameworks/base/config/hiddenapi-force-blacklist.txt \ + --output $@.tmp + $(call commit-change-for-toc,$@) $(INTERNAL_PLATFORM_HIDDENAPI_GREYLIST_METADATA): \ frameworks/base/tools/hiddenapi/merge_csv.py \ $(PRIVATE_METADATA_INPUTS) frameworks/base/tools/hiddenapi/merge_csv.py $(PRIVATE_METADATA_INPUTS) > $@ -$(call dist-for-goals,droidcore,$(INTERNAL_PLATFORM_HIDDENAPI_WHITELIST)) -$(call dist-for-goals,droidcore,$(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST)) -$(call dist-for-goals,droidcore,$(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST)) -$(call dist-for-goals,droidcore,$(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST)) +$(call dist-for-goals,droidcore,$(INTERNAL_PLATFORM_HIDDENAPI_FLAGS)) $(call dist-for-goals,droidcore,$(INTERNAL_PLATFORM_HIDDENAPI_GREYLIST_METADATA)) # Include subdirectory makefiles diff --git a/api/current.txt b/api/current.txt index 0dce8d490f7c..ebf23394bf46 100644 --- a/api/current.txt +++ b/api/current.txt @@ -150,6 +150,7 @@ package android { field public static final java.lang.String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS"; field public static final java.lang.String USE_BIOMETRIC = "android.permission.USE_BIOMETRIC"; field public static final deprecated java.lang.String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT"; + field public static final java.lang.String USE_FULL_SCREEN_INTENT = "android.permission.USE_FULL_SCREEN_INTENT"; field public static final java.lang.String USE_SIP = "android.permission.USE_SIP"; field public static final java.lang.String VIBRATE = "android.permission.VIBRATE"; field public static final java.lang.String WAKE_LOCK = "android.permission.WAKE_LOCK"; @@ -11339,7 +11340,7 @@ package android.content.pm { method public abstract int checkSignatures(java.lang.String, java.lang.String); method public abstract int checkSignatures(int, int); method public abstract void clearInstantAppCookie(); - method public abstract void clearPackagePreferredActivities(java.lang.String); + method public abstract deprecated void clearPackagePreferredActivities(java.lang.String); method public abstract java.lang.String[] currentToCanonicalPackageNames(java.lang.String[]); method public abstract void extendVerificationTimeout(int, int, long); method public abstract android.graphics.drawable.Drawable getActivityBanner(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; @@ -11383,8 +11384,8 @@ package android.content.pm { method public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int); method public abstract android.content.pm.PermissionGroupInfo getPermissionGroupInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.pm.PermissionInfo getPermissionInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract int getPreferredActivities(java.util.List<android.content.IntentFilter>, java.util.List<android.content.ComponentName>, java.lang.String); - method public abstract java.util.List<android.content.pm.PackageInfo> getPreferredPackages(int); + method public abstract deprecated int getPreferredActivities(java.util.List<android.content.IntentFilter>, java.util.List<android.content.ComponentName>, java.lang.String); + method public abstract deprecated java.util.List<android.content.pm.PackageInfo> getPreferredPackages(int); method public abstract android.content.pm.ProviderInfo getProviderInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.pm.ActivityInfo getReceiverInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.res.Resources getResourcesForActivity(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; @@ -29054,20 +29055,21 @@ package android.net.wifi { public class WifiManager { method public deprecated int addNetwork(android.net.wifi.WifiConfiguration); - method public boolean addNetworkSuggestions(java.util.List<android.net.wifi.WifiNetworkSuggestion>); + method public int addNetworkSuggestions(java.util.List<android.net.wifi.WifiNetworkSuggestion>); method public void addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration); method public static int calculateSignalLevel(int, int); method public deprecated void cancelWps(android.net.wifi.WifiManager.WpsCallback); method public static int compareSignalLevel(int, int); method public android.net.wifi.WifiManager.MulticastLock createMulticastLock(java.lang.String); method public android.net.wifi.WifiManager.WifiLock createWifiLock(int, java.lang.String); - method public android.net.wifi.WifiManager.WifiLock createWifiLock(java.lang.String); + method public deprecated android.net.wifi.WifiManager.WifiLock createWifiLock(java.lang.String); method public deprecated boolean disableNetwork(int); method public deprecated boolean disconnect(); method public deprecated boolean enableNetwork(int, boolean); method public deprecated java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks(); method public android.net.wifi.WifiInfo getConnectionInfo(); method public android.net.DhcpInfo getDhcpInfo(); + method public int getMaxNumberOfNetworkSuggestionsPerApp(); method public java.util.List<android.net.wifi.hotspot2.PasspointConfiguration> getPasspointConfigurations(); method public java.util.List<android.net.wifi.ScanResult> getScanResults(); method public int getWifiState(); @@ -29086,7 +29088,7 @@ package android.net.wifi { method public deprecated boolean reassociate(); method public deprecated boolean reconnect(); method public deprecated boolean removeNetwork(int); - method public boolean removeNetworkSuggestions(java.util.List<android.net.wifi.WifiNetworkSuggestion>); + method public int removeNetworkSuggestions(java.util.List<android.net.wifi.WifiNetworkSuggestion>); method public void removePasspointConfiguration(java.lang.String); method public deprecated boolean saveConfiguration(); method public void setTdlsEnabled(java.net.InetAddress, boolean); @@ -29115,11 +29117,17 @@ package android.net.wifi { field public static final java.lang.String NETWORK_STATE_CHANGED_ACTION = "android.net.wifi.STATE_CHANGE"; field public static final java.lang.String RSSI_CHANGED_ACTION = "android.net.wifi.RSSI_CHANGED"; field public static final java.lang.String SCAN_RESULTS_AVAILABLE_ACTION = "android.net.wifi.SCAN_RESULTS"; + field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE = 2; // 0x2 + field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP = 3; // 0x3 + field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL = 1; // 0x1 + field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID = 4; // 0x4 + field public static final int STATUS_NETWORK_SUGGESTIONS_SUCCESS = 0; // 0x0 field public static final deprecated java.lang.String SUPPLICANT_CONNECTION_CHANGE_ACTION = "android.net.wifi.supplicant.CONNECTION_CHANGE"; field public static final deprecated java.lang.String SUPPLICANT_STATE_CHANGED_ACTION = "android.net.wifi.supplicant.STATE_CHANGE"; - field public static final int WIFI_MODE_FULL = 1; // 0x1 + field public static final deprecated int WIFI_MODE_FULL = 1; // 0x1 field public static final int WIFI_MODE_FULL_HIGH_PERF = 3; // 0x3 - field public static final int WIFI_MODE_SCAN_ONLY = 2; // 0x2 + field public static final int WIFI_MODE_FULL_LOW_LATENCY = 4; // 0x4 + field public static final deprecated int WIFI_MODE_SCAN_ONLY = 2; // 0x2 field public static final java.lang.String WIFI_STATE_CHANGED_ACTION = "android.net.wifi.WIFI_STATE_CHANGED"; field public static final int WIFI_STATE_DISABLED = 1; // 0x1 field public static final int WIFI_STATE_DISABLING = 0; // 0x0 @@ -29156,6 +29164,9 @@ package android.net.wifi { method public void setReferenceCounted(boolean); } + public static abstract class WifiManager.NetworkSuggestionsStatusCode implements java.lang.annotation.Annotation { + } + public class WifiManager.WifiLock { method public void acquire(); method public boolean isHeld(); @@ -33527,6 +33538,7 @@ package android.os { method public static boolean isExternalStorageRemovable(); method public static boolean isExternalStorageRemovable(java.io.File); field public static java.lang.String DIRECTORY_ALARMS; + field public static java.lang.String DIRECTORY_AUDIOBOOKS; field public static java.lang.String DIRECTORY_DCIM; field public static java.lang.String DIRECTORY_DOCUMENTS; field public static java.lang.String DIRECTORY_DOWNLOADS; @@ -37286,6 +37298,7 @@ package android.provider { method public static java.lang.String getVolumeName(android.net.Uri); method public static android.provider.MediaStore.PendingSession openPending(android.content.Context, android.net.Uri); method public static android.net.Uri setIncludePending(android.net.Uri); + method public static android.net.Uri setRequireOriginal(android.net.Uri); field public static final java.lang.String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE"; field public static final java.lang.String ACTION_IMAGE_CAPTURE_SECURE = "android.media.action.IMAGE_CAPTURE_SECURE"; field public static final java.lang.String ACTION_REVIEW = "android.provider.action.REVIEW"; @@ -37383,6 +37396,7 @@ package android.provider { field public static final java.lang.String COMPOSER = "composer"; field public static final java.lang.String DURATION = "duration"; field public static final java.lang.String IS_ALARM = "is_alarm"; + field public static final java.lang.String IS_AUDIOBOOK = "is_audiobook"; field public static final java.lang.String IS_MUSIC = "is_music"; field public static final java.lang.String IS_NOTIFICATION = "is_notification"; field public static final java.lang.String IS_PODCAST = "is_podcast"; @@ -42888,6 +42902,19 @@ package android.telephony { field public static final int BAND_9 = 9; // 0x9 } + public final class AvailableNetworkInfo implements android.os.Parcelable { + ctor public AvailableNetworkInfo(int, int, java.util.ArrayList<java.lang.String>); + method public int describeContents(); + method public java.util.List<java.lang.String> getMccMncs(); + method public int getPriority(); + method public int getSubId(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.AvailableNetworkInfo> CREATOR; + field public static final int PRIORITY_HIGH = 1; // 0x1 + field public static final int PRIORITY_LOW = 3; // 0x3 + field public static final int PRIORITY_MED = 2; // 0x2 + } + public class CarrierConfigManager { method public android.os.PersistableBundle getConfig(); method public android.os.PersistableBundle getConfigForSubId(int); @@ -43866,6 +43893,7 @@ package android.telephony { method public boolean setVoiceMailNumber(java.lang.String, java.lang.String); method public deprecated void setVoicemailRingtoneUri(android.telecom.PhoneAccountHandle, android.net.Uri); method public deprecated void setVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle, boolean); + method public boolean updateAvailableNetworks(java.util.List<android.telephony.AvailableNetworkInfo>); field public static final java.lang.String ACTION_CONFIGURE_VOICEMAIL = "android.telephony.action.CONFIGURE_VOICEMAIL"; field public static final java.lang.String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE"; field public static final java.lang.String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE"; @@ -45837,6 +45865,7 @@ package android.text.style { method public deprecated java.lang.String getLocale(); method public java.util.Locale getLocaleObject(); method public int getSpanTypeId(); + method public int getUnderlineColor(); method public java.lang.String[] getSuggestions(); method public void setFlags(int); method public void updateDrawState(android.text.TextPaint); @@ -49070,10 +49099,12 @@ package android.view { method protected int getTopPaddingOffset(); method public android.view.TouchDelegate getTouchDelegate(); method public java.util.ArrayList<android.view.View> getTouchables(); + method public float getTransitionAlpha(); method public java.lang.String getTransitionName(); method public float getTranslationX(); method public float getTranslationY(); method public float getTranslationZ(); + method public long getUniqueDrawingId(); method public int getVerticalFadingEdgeLength(); method public int getVerticalScrollbarPosition(); method public int getVerticalScrollbarWidth(); @@ -49278,6 +49309,7 @@ package android.view { method public void setActivated(boolean); method public void setAlpha(float); method public void setAnimation(android.view.animation.Animation); + method public void setAnimationMatrix(android.graphics.Matrix); method public void setAutofillHints(java.lang.String...); method public void setAutofillId(android.view.autofill.AutofillId); method public void setBackground(android.graphics.drawable.Drawable); @@ -49329,7 +49361,7 @@ package android.view { method public void setLayoutDirection(int); method public void setLayoutParams(android.view.ViewGroup.LayoutParams); method public final void setLeft(int); - method public void setLeftTopRightBottom(int, int, int, int); + method public final void setLeftTopRightBottom(int, int, int, int); method public void setLongClickable(boolean); method protected final void setMeasuredDimension(int, int); method public void setMinimumHeight(int); @@ -49396,6 +49428,7 @@ package android.view { method public void setTooltipText(java.lang.CharSequence); method public final void setTop(int); method public void setTouchDelegate(android.view.TouchDelegate); + method public void setTransitionAlpha(float); method public final void setTransitionName(java.lang.String); method public void setTranslationX(float); method public void setTranslationY(float); @@ -49869,6 +49902,7 @@ package android.view { method public deprecated boolean isAnimationCacheEnabled(); method protected boolean isChildrenDrawingOrderEnabled(); method protected deprecated boolean isChildrenDrawnWithCacheEnabled(); + method public boolean isLayoutSuppressed(); method public boolean isMotionEventSplittingEnabled(); method public boolean isTransitionGroup(); method public final void layout(int, int, int, int); @@ -49934,6 +49968,7 @@ package android.view { method public android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback, int); method public void startLayoutAnimation(); method public void startViewTransition(android.view.View); + method public void suppressLayout(boolean); method public void updateViewLayout(android.view.View, android.view.ViewGroup.LayoutParams); field protected static final int CLIP_TO_PADDING_MASK = 34; // 0x22 field public static final int FOCUS_AFTER_DESCENDANTS = 262144; // 0x40000 @@ -51947,10 +51982,90 @@ package android.view.inputmethod { } +package android.view.inspector { + + public abstract interface InspectionCompanion<T> { + method public default java.lang.String getNodeName(); + method public abstract void mapProperties(android.view.inspector.PropertyMapper); + method public abstract void readProperties(T, android.view.inspector.PropertyReader); + } + + public static class InspectionCompanion.UninitializedPropertyMapException extends java.lang.RuntimeException { + ctor public InspectionCompanion.UninitializedPropertyMapException(); + } + + public final class IntEnumMapping { + method public java.lang.String nameOf(int); + } + + public static final class IntEnumMapping.Builder { + ctor public IntEnumMapping.Builder(); + method public android.view.inspector.IntEnumMapping.Builder addValue(java.lang.String, int); + method public android.view.inspector.IntEnumMapping build(); + method public void clear(); + } + + public final class IntFlagMapping { + method public java.lang.String[] namesOf(int); + } + + public static final class IntFlagMapping.Builder { + ctor public IntFlagMapping.Builder(); + method public android.view.inspector.IntFlagMapping.Builder addFlag(java.lang.String, int); + method public android.view.inspector.IntFlagMapping.Builder addFlag(java.lang.String, int, int); + method public android.view.inspector.IntFlagMapping build(); + method public void clear(); + } + + public abstract interface PropertyMapper { + method public abstract int mapBoolean(java.lang.String, int); + method public abstract int mapByte(java.lang.String, int); + method public abstract int mapChar(java.lang.String, int); + method public abstract int mapColor(java.lang.String, int); + method public abstract int mapDouble(java.lang.String, int); + method public abstract int mapFloat(java.lang.String, int); + method public abstract int mapGravity(java.lang.String, int); + method public abstract int mapInt(java.lang.String, int); + method public abstract int mapIntEnum(java.lang.String, int, android.view.inspector.IntEnumMapping); + method public abstract int mapIntFlag(java.lang.String, int, android.view.inspector.IntFlagMapping); + method public abstract int mapLong(java.lang.String, int); + method public abstract int mapObject(java.lang.String, int); + method public abstract int mapShort(java.lang.String, int); + } + + public static class PropertyMapper.PropertyConflictException extends java.lang.RuntimeException { + ctor public PropertyMapper.PropertyConflictException(java.lang.String, java.lang.String, java.lang.String); + } + + public abstract interface PropertyReader { + method public abstract void readBoolean(int, boolean); + method public abstract void readByte(int, byte); + method public abstract void readChar(int, char); + method public abstract void readColor(int, int); + method public abstract void readColor(int, long); + method public abstract void readColor(int, android.graphics.Color); + method public abstract void readDouble(int, double); + method public abstract void readFloat(int, float); + method public abstract void readGravity(int, int); + method public abstract void readInt(int, int); + method public abstract void readIntEnum(int, int); + method public abstract void readIntFlag(int, int); + method public abstract void readLong(int, long); + method public abstract void readObject(int, java.lang.Object); + method public abstract void readShort(int, short); + } + + public static class PropertyReader.PropertyTypeMismatchException extends java.lang.RuntimeException { + ctor public PropertyReader.PropertyTypeMismatchException(int, java.lang.String, java.lang.String, java.lang.String); + ctor public PropertyReader.PropertyTypeMismatchException(int, java.lang.String, java.lang.String); + } + +} + package android.view.intelligence { - public final class IntelligenceManager { - method public android.content.ComponentName getIntelligenceServiceComponentName(); + public final class ContentCaptureManager { + method public android.content.ComponentName getServiceComponentName(); method public boolean isContentCaptureEnabled(); method public android.view.ViewStructure newVirtualViewStructure(android.view.autofill.AutofillId, int); method public void notifyViewAppeared(android.view.ViewStructure); diff --git a/api/system-current.txt b/api/system-current.txt index 2a581a806a8a..6ee7afa51931 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -25,7 +25,6 @@ package android { field public static final java.lang.String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH"; field public static final java.lang.String BIND_EUICC_SERVICE = "android.permission.BIND_EUICC_SERVICE"; field public static final java.lang.String BIND_IMS_SERVICE = "android.permission.BIND_IMS_SERVICE"; - field public static final java.lang.String BIND_INTELLIGENCE_SERVICE = "android.permission.BIND_INTELLIGENCE_SERVICE"; field public static final java.lang.String BIND_KEYGUARD_APPWIDGET = "android.permission.BIND_KEYGUARD_APPWIDGET"; field public static final java.lang.String BIND_NETWORK_RECOMMENDATION_SERVICE = "android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE"; field public static final java.lang.String BIND_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE"; @@ -34,6 +33,7 @@ package android { field public static final java.lang.String BIND_RESOLVER_RANKER_SERVICE = "android.permission.BIND_RESOLVER_RANKER_SERVICE"; field public static final java.lang.String BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE = "android.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE"; field public static final java.lang.String BIND_SETTINGS_SUGGESTIONS_SERVICE = "android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE"; + field public static final java.lang.String BIND_SMART_SUGGESTIONS_SERVICE = "android.permission.BIND_SMART_SUGGESTIONS_SERVICE"; field public static final java.lang.String BIND_SOUND_TRIGGER_DETECTION_SERVICE = "android.permission.BIND_SOUND_TRIGGER_DETECTION_SERVICE"; field public static final java.lang.String BIND_TELEPHONY_DATA_SERVICE = "android.permission.BIND_TELEPHONY_DATA_SERVICE"; field public static final java.lang.String BIND_TELEPHONY_NETWORK_SERVICE = "android.permission.BIND_TELEPHONY_NETWORK_SERVICE"; @@ -217,6 +217,10 @@ package android { field public static final java.lang.String WRITE_SECURE_SETTINGS = "android.permission.WRITE_SECURE_SETTINGS"; } + public static final class Manifest.permission_group { + field public static final java.lang.String UNDEFINED = "android.permission-group.UNDEFINED"; + } + public static final class R.array { field public static final int config_keySystemUuidMapping = 17235973; // 0x1070005 } @@ -1240,7 +1244,7 @@ package android.content.pm { method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(android.content.Intent, int, android.os.UserHandle); method public abstract void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback); method public abstract void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener); - method public void replacePreferredActivity(android.content.IntentFilter, int, java.util.List<android.content.ComponentName>, android.content.ComponentName); + method public deprecated void replacePreferredActivity(android.content.IntentFilter, int, java.util.List<android.content.ComponentName>, android.content.ComponentName); method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public void sendDeviceCustomizationReadyBroadcast(); method public abstract boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int); @@ -3689,7 +3693,6 @@ package android.net.wifi { method public void disableEphemeralNetwork(java.lang.String); method public void forget(int, android.net.wifi.WifiManager.ActionListener); method public java.util.List<android.net.wifi.WifiConfiguration> getAllMatchingWifiConfigs(java.util.List<android.net.wifi.ScanResult>); - method public java.util.List<android.net.wifi.hotspot2.OsuProvider> getMatchingOsuProviders(java.util.List<android.net.wifi.ScanResult>); method public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks(); method public android.net.wifi.WifiConfiguration getWifiApConfiguration(); method public int getWifiApState(); @@ -3848,6 +3851,7 @@ package android.net.wifi { ctor public WifiScanner.ScanSettings(); field public int band; field public android.net.wifi.WifiScanner.ChannelSpec[] channels; + field public boolean ignoreLocationSettings; field public int maxPeriodInMs; field public int maxScansToCache; field public int numBssidsPerScan; @@ -4305,6 +4309,7 @@ package android.os { public class UserManager { method public void clearSeedAccountData(); + method public android.os.UserHandle getProfileParent(android.os.UserHandle); method public java.lang.String getSeedAccountName(); method public android.os.PersistableBundle getSeedAccountOptions(); method public java.lang.String getSeedAccountType(); @@ -4636,6 +4641,8 @@ package android.provider { field public static final java.lang.String HUSH_GESTURE_USED = "hush_gesture_used"; field public static final java.lang.String INSTANT_APPS_ENABLED = "instant_apps_enabled"; field public static final java.lang.String LAST_SETUP_SHOWN = "last_setup_shown"; + field public static final java.lang.String LOCATION_ACCESS_CHECK_DELAY_MILLIS = "location_access_check_delay_millis"; + field public static final java.lang.String LOCATION_ACCESS_CHECK_INTERVAL_MILLIS = "location_access_check_interval_millis"; field public static final java.lang.String LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS = "lock_screen_allow_private_notifications"; field public static final java.lang.String LOCK_SCREEN_SHOW_NOTIFICATIONS = "lock_screen_show_notifications"; field public static final java.lang.String MANUAL_RINGER_TOGGLE_COUNT = "manual_ringer_toggle_count"; @@ -4973,6 +4980,13 @@ package android.service.euicc { package android.service.intelligence { + public final class ContentCaptureEventsRequest implements android.os.Parcelable { + method public int describeContents(); + method public java.util.List<android.view.intelligence.ContentCaptureEvent> getEvents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.service.intelligence.ContentCaptureEventsRequest> CREATOR; + } + public final class FillCallback { method public void onSuccess(android.service.intelligence.FillResponse); } @@ -5007,16 +5021,6 @@ package android.service.intelligence { field public static final long FLAG_METADATA_ADDRESS = 1L; // 0x1L } - public abstract class IntelligenceService extends android.app.Service { - ctor public IntelligenceService(); - method public void onActivitySnapshot(android.service.intelligence.InteractionSessionId, android.service.intelligence.SnapshotData); - method public abstract void onContentCaptureEvent(android.service.intelligence.InteractionSessionId, java.util.List<android.view.intelligence.ContentCaptureEvent>); - method public void onCreateInteractionSession(android.service.intelligence.InteractionContext, android.service.intelligence.InteractionSessionId); - method public void onDestroyInteractionSession(android.service.intelligence.InteractionSessionId); - method public void onFillRequest(android.service.intelligence.InteractionSessionId, android.service.intelligence.FillRequest, android.os.CancellationSignal, android.service.intelligence.FillController, android.service.intelligence.FillCallback); - field public static final java.lang.String SERVICE_INTERFACE = "android.service.intelligence.IntelligenceService"; - } - public final class InteractionContext implements android.os.Parcelable { method public int describeContents(); method public android.content.ComponentName getActivityComponent(); @@ -5052,6 +5056,21 @@ package android.service.intelligence { method public android.service.intelligence.PresentationParams.Area getSubArea(android.graphics.Rect); } + public abstract class SmartSuggestionsService extends android.app.Service { + ctor public SmartSuggestionsService(); + method public final java.util.Set<android.content.ComponentName> getContentCaptureDisabledActivities(); + method public final java.util.Set<java.lang.String> getContentCaptureDisabledPackages(); + method public void onActivitySnapshot(android.service.intelligence.InteractionSessionId, android.service.intelligence.SnapshotData); + method public abstract void onContentCaptureEventsRequest(android.service.intelligence.InteractionSessionId, android.service.intelligence.ContentCaptureEventsRequest); + method public void onCreateInteractionSession(android.service.intelligence.InteractionContext, android.service.intelligence.InteractionSessionId); + method public void onDestroyInteractionSession(android.service.intelligence.InteractionSessionId); + method public void onFillRequest(android.service.intelligence.InteractionSessionId, android.service.intelligence.FillRequest, android.os.CancellationSignal, android.service.intelligence.FillController, android.service.intelligence.FillCallback); + method public final void setActivityContentCaptureEnabled(android.content.ComponentName, boolean); + method public final void setContentCaptureWhitelist(java.util.List<java.lang.String>, java.util.List<android.content.ComponentName>); + method public final void setPackageContentCaptureEnabled(java.lang.String, boolean); + field public static final java.lang.String SERVICE_INTERFACE = "android.service.intelligence.SmartSuggestionsService"; + } + public final class SnapshotData implements android.os.Parcelable { method public int describeContents(); method public android.app.assist.AssistContent getAssistContent(); @@ -5174,6 +5193,7 @@ package android.service.notification { package android.service.oemlock { public class OemLockManager { + method public java.lang.String getLockName(); method public boolean isOemUnlockAllowedByCarrier(); method public boolean isOemUnlockAllowedByUser(); method public void setOemUnlockAllowedByCarrier(boolean, byte[]); @@ -5691,6 +5711,26 @@ package android.telephony { field public static final int RESULT_SUCCESS = 0; // 0x0 } + public abstract interface NumberVerificationCallback { + method public default void onCallReceived(java.lang.String); + method public default void onVerificationFailed(int); + field public static final int REASON_CONCURRENT_REQUESTS = 4; // 0x4 + field public static final int REASON_IN_ECBM = 5; // 0x5 + field public static final int REASON_IN_EMERGENCY_CALL = 6; // 0x6 + field public static final int REASON_NETWORK_NOT_AVAILABLE = 2; // 0x2 + field public static final int REASON_TIMED_OUT = 1; // 0x1 + field public static final int REASON_TOO_MANY_CALLS = 3; // 0x3 + field public static final int REASON_UNSPECIFIED = 0; // 0x0 + } + + public final class PhoneNumberRange implements android.os.Parcelable { + ctor public PhoneNumberRange(java.lang.String, java.lang.String, java.lang.String, java.lang.String); + method public int describeContents(); + method public boolean matches(java.lang.String); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.PhoneNumberRange> CREATOR; + } + public class PhoneStateListener { method public void onRadioPowerStateChanged(int); method public void onSrvccStateChanged(int); @@ -5855,6 +5895,7 @@ package android.telephony { method public boolean needsOtaServiceProvisioning(); method public boolean rebootRadio(); method public void requestCellInfoUpdate(android.os.WorkSource, java.util.concurrent.Executor, android.telephony.TelephonyManager.CellInfoCallback); + method public void requestNumberVerification(android.telephony.PhoneNumberRange, long, java.util.concurrent.Executor, android.telephony.NumberVerificationCallback); method public boolean resetRadioConfig(); method public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>); method public void setCarrierDataEnabled(boolean); @@ -5884,6 +5925,7 @@ package android.telephony { field public static final java.lang.String EXTRA_SIM_STATE = "android.telephony.extra.SIM_STATE"; field public static final java.lang.String EXTRA_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL = "android.telephony.extra.VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL"; field public static final java.lang.String EXTRA_VOICEMAIL_SCRAMBLED_PIN_STRING = "android.telephony.extra.VOICEMAIL_SCRAMBLED_PIN_STRING"; + field public static final long MAX_NUMBER_VERIFICATION_TIMEOUT_MILLIS = 60000L; // 0xea60L field public static final int NETWORK_MODE_CDMA_EVDO = 4; // 0x4 field public static final int NETWORK_MODE_CDMA_NO_EVDO = 5; // 0x5 field public static final int NETWORK_MODE_EVDO_NO_CDMA = 6; // 0x6 @@ -7235,14 +7277,6 @@ package android.view.intelligence { field public static final int TYPE_VIEW_TEXT_CHANGED = 7; // 0x7 } - public final class IntelligenceManager { - method public java.util.Set<android.content.ComponentName> getContentCaptureDisabledActivities(); - method public java.util.Set<java.lang.String> getContentCaptureDisabledPackages(); - method public void setActivityContentCaptureEnabled(android.content.ComponentName, boolean); - method public void setContentCaptureWhitelist(java.util.List<java.lang.String>, java.util.List<android.content.ComponentName>); - method public void setPackageContentCaptureEnabled(java.lang.String, boolean); - } - public final class ViewNode extends android.app.assist.AssistStructure.ViewNode { method public android.view.autofill.AutofillId getParentAutofillId(); } diff --git a/api/test-current.txt b/api/test-current.txt index b871c78571ad..46cbb52f6efa 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -943,6 +943,10 @@ package android.os.health { package android.os.storage { + public class StorageManager { + method public static boolean hasIsolatedStorage(); + } + public final class StorageVolume implements android.os.Parcelable { method public java.lang.String getPath(); } @@ -998,6 +1002,7 @@ package android.provider { } public static final class Settings.Secure extends android.provider.Settings.NameValueTable { + method public static void resetToDefaults(android.content.ContentResolver, java.lang.String); field public static final java.lang.String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED = "accessibility_display_magnification_enabled"; field public static final java.lang.String ACCESSIBILITY_SHORTCUT_TARGET_SERVICE = "accessibility_shortcut_target_service"; field public static final java.lang.String AUTOFILL_FEATURE_FIELD_CLASSIFICATION = "autofill_field_classification"; @@ -1009,6 +1014,8 @@ package android.provider { field public static final java.lang.String AUTOFILL_USER_DATA_MIN_VALUE_LENGTH = "autofill_user_data_min_value_length"; field public static final java.lang.String DISABLED_PRINT_SERVICES = "disabled_print_services"; field public static final deprecated java.lang.String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages"; + field public static final java.lang.String LOCATION_ACCESS_CHECK_DELAY_MILLIS = "location_access_check_delay_millis"; + field public static final java.lang.String LOCATION_ACCESS_CHECK_INTERVAL_MILLIS = "location_access_check_interval_millis"; field public static final java.lang.String SYNC_PARENT_SOUNDS = "sync_parent_sounds"; field public static final java.lang.String USER_SETUP_COMPLETE = "user_setup_complete"; field public static final java.lang.String VOICE_INTERACTION_SERVICE = "voice_interaction_service"; diff --git a/cmds/content/src/com/android/commands/content/Content.java b/cmds/content/src/com/android/commands/content/Content.java index 1597c8c9c2b2..52a2ab407f91 100644 --- a/cmds/content/src/com/android/commands/content/Content.java +++ b/cmds/content/src/com/android/commands/content/Content.java @@ -77,7 +77,7 @@ public class Content { + " <BINDING> binds a typed value to a column and is formatted:\n" + " <COLUMN_NAME>:<TYPE>:<COLUMN_VALUE> where:\n" + " <TYPE> specifies data type such as:\n" - + " b - boolean, s - string, i - integer, l - long, f - float, d - double\n" + + " b - boolean, s - string, i - integer, l - long, f - float, d - double, n - null\n" + " Note: Omit the value for passing an empty string, e.g column:s:\n" + " Example:\n" + " # Add \"new_setting\" secure setting with value \"new_value\".\n" @@ -153,6 +153,7 @@ public class Content { private static final String TYPE_LONG = "l"; private static final String TYPE_FLOAT = "f"; private static final String TYPE_DOUBLE = "d"; + private static final String TYPE_NULL = "n"; private static final String COLON = ":"; private static final String ARGUMENT_PREFIX = "--"; @@ -410,6 +411,8 @@ public class Content { values.put(column, Long.parseLong(value)); } else if (TYPE_FLOAT.equalsIgnoreCase(type) || TYPE_DOUBLE.equalsIgnoreCase(type)) { values.put(column, Double.parseDouble(value)); + } else if (TYPE_NULL.equalsIgnoreCase(type)) { + values.putNull(column); } else { throw new IllegalArgumentException("Unsupported type: " + type); } diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp index 1c3ebd877695..b23c1eda87b3 100644 --- a/cmds/incidentd/src/IncidentService.cpp +++ b/cmds/incidentd/src/IncidentService.cpp @@ -24,6 +24,7 @@ #include "incidentd_util.h" #include "section_list.h" +#include <android/os/IncidentReportArgs.h> #include <binder/IPCThreadState.h> #include <binder/IResultReceiver.h> #include <binder/IServiceManager.h> @@ -41,6 +42,15 @@ enum { WHAT_RUN_REPORT = 1, WHAT_SEND_BACKLOG_TO_DROPBOX = 2 }; #define DEFAULT_BYTES_SIZE_LIMIT (20 * 1024 * 1024) // 20MB #define DEFAULT_REFACTORY_PERIOD_MS (24 * 60 * 60 * 1000) // 1 Day +// Skip logs (1100 - 1108) because they are already in the bug report +// Skip 1200, 1201, 1202, 3018 because they take too long +// TODO(120079956): Skip 3008, 3015 because of error +#define SKIPPED_SECTIONS { 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, /* Logs */ \ + 1200, 1201, 1202, /* Native, hal, java traces */ \ + 3008, /* "package --proto" */ \ + 3015, /* "activity --proto processes" */ \ + 3018 /* "meminfo -a --proto" */ } + namespace android { namespace os { namespace incidentd { @@ -391,6 +401,38 @@ status_t IncidentService::cmd_privacy(FILE* in, FILE* out, FILE* err, Vector<Str return NO_ERROR; } +status_t IncidentService::dump(int fd, const Vector<String16>& args) { + if (std::find(args.begin(), args.end(), String16("--proto")) == args.end()) { + ALOGD("Skip dumping incident. Only proto format is supported."); + dprintf(fd, "Incident dump only supports proto version.\n"); + return NO_ERROR; + } + + ALOGD("Dump incident proto"); + IncidentReportArgs incidentArgs; + incidentArgs.setDest(DEST_EXPLICIT); + int skipped[] = SKIPPED_SECTIONS; + for (const Section** section = SECTION_LIST; *section; section++) { + const int id = (*section)->id; + if (std::find(std::begin(skipped), std::end(skipped), id) == std::end(skipped)) { + incidentArgs.addSection(id); + } + } + + if (!checkIncidentPermissions(incidentArgs).isOk()) { + return PERMISSION_DENIED; + } + + int fd1 = dup(fd); + if (fd1 < 0) { + return -errno; + } + + mHandler->scheduleRunReport(new ReportRequest(incidentArgs, NULL, fd1)); + + return NO_ERROR; +} + } // namespace incidentd } // namespace os } // namespace android diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h index e176bfd95c5f..6252ad295224 100644 --- a/cmds/incidentd/src/IncidentService.h +++ b/cmds/incidentd/src/IncidentService.h @@ -112,6 +112,7 @@ public: virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) override; virtual status_t command(FILE* in, FILE* out, FILE* err, Vector<String8>& args); + virtual status_t dump(int fd, const Vector<String16>& args); private: sp<ReportRequestQueue> mQueue; diff --git a/cmds/incidentd/src/main.cpp b/cmds/incidentd/src/main.cpp index 494882336611..098d74ecd786 100644 --- a/cmds/incidentd/src/main.cpp +++ b/cmds/incidentd/src/main.cpp @@ -45,7 +45,8 @@ int main(int /*argc*/, char** /*argv*/) { // Create the service sp<IncidentService> service = new IncidentService(looper); - if (defaultServiceManager()->addService(String16("incident"), service) != 0) { + if (defaultServiceManager()->addService(String16("incident"), service, false, + IServiceManager::DUMP_FLAG_PRIORITY_NORMAL | IServiceManager::DUMP_FLAG_PROTO) != 0) { ALOGE("Failed to add service"); return -1; } diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp index 661768914c69..3e5e82f7f4df 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -101,6 +101,7 @@ const int FIELD_ID_UID_MAP_DELETED_APPS = 4; const std::map<int, std::pair<size_t, size_t>> StatsdStats::kAtomDimensionKeySizeLimitMap = { {android::util::BINDER_CALLS, {6000, 10000}}, + {android::util::LOOPER_STATS, {1500, 2500}}, {android::util::CPU_TIME_PER_UID_FREQ, {6000, 10000}}, }; diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt index 25bd0330ad5c..e10f72913d22 100644 --- a/config/hiddenapi-light-greylist.txt +++ b/config/hiddenapi-light-greylist.txt @@ -2695,7 +2695,6 @@ Lcom/android/internal/telephony/dataconnection/DcTracker;->onRecordsLoadedOrSubI Lcom/android/internal/telephony/dataconnection/DcTracker;->onSetUserDataEnabled(Z)V Lcom/android/internal/telephony/dataconnection/DcTracker;->onTrySetupData(Lcom/android/internal/telephony/dataconnection/ApnContext;)Z Lcom/android/internal/telephony/dataconnection/DcTracker;->onTrySetupData(Ljava/lang/String;)Z -Lcom/android/internal/telephony/dataconnection/DcTracker;->registerForAllDataDisconnected(Landroid/os/Handler;ILjava/lang/Object;)V Lcom/android/internal/telephony/dataconnection/DcTracker;->registerSettingsObserver()V Lcom/android/internal/telephony/dataconnection/DcTracker;->resetPollStats()V Lcom/android/internal/telephony/dataconnection/DcTracker;->restartDataStallAlarm()V @@ -2709,7 +2708,6 @@ Lcom/android/internal/telephony/dataconnection/DcTracker;->startNetStatPoll()V Lcom/android/internal/telephony/dataconnection/DcTracker;->stopDataStallAlarm()V Lcom/android/internal/telephony/dataconnection/DcTracker;->stopNetStatPoll()V Lcom/android/internal/telephony/dataconnection/DcTracker;->unregisterForAllDataDisconnected(Landroid/os/Handler;)V -Lcom/android/internal/telephony/dataconnection/DcTracker;->updateRecords()V Lcom/android/internal/telephony/DctConstants$Activity;->DATAIN:Lcom/android/internal/telephony/DctConstants$Activity; Lcom/android/internal/telephony/DctConstants$Activity;->DATAINANDOUT:Lcom/android/internal/telephony/DctConstants$Activity; Lcom/android/internal/telephony/DctConstants$Activity;->DATAOUT:Lcom/android/internal/telephony/DctConstants$Activity; @@ -3130,7 +3128,6 @@ Lcom/android/internal/telephony/ITelephony;->getCallState()I Lcom/android/internal/telephony/ITelephony;->getDataActivity()I Lcom/android/internal/telephony/ITelephony;->getDataState()I Lcom/android/internal/telephony/ITelephony;->getNetworkType()I -Lcom/android/internal/telephony/ITelephony;->getVoiceMessageCount()I Lcom/android/internal/telephony/ITelephony;->handlePinMmi(Ljava/lang/String;)Z Lcom/android/internal/telephony/ITelephony;->handlePinMmiForSubscriber(ILjava/lang/String;)Z Lcom/android/internal/telephony/ITelephony;->hasIccCard()Z @@ -3201,7 +3198,6 @@ Lcom/android/internal/telephony/Phone;->isVolteEnabled()Z Lcom/android/internal/telephony/Phone;->isWifiCallingEnabled()Z Lcom/android/internal/telephony/Phone;->mCi:Lcom/android/internal/telephony/CommandsInterface; Lcom/android/internal/telephony/Phone;->mContext:Landroid/content/Context; -Lcom/android/internal/telephony/Phone;->mDcTracker:Lcom/android/internal/telephony/dataconnection/DcTracker; Lcom/android/internal/telephony/Phone;->mIccRecords:Ljava/util/concurrent/atomic/AtomicReference; Lcom/android/internal/telephony/Phone;->mImsPhone:Lcom/android/internal/telephony/Phone; Lcom/android/internal/telephony/Phone;->mMmiRegistrants:Landroid/os/RegistrantList; @@ -3294,7 +3290,6 @@ Lcom/android/internal/telephony/ProxyController;->mOldRadioAccessFamily:[I Lcom/android/internal/telephony/ProxyController;->mRadioCapabilitySessionId:I Lcom/android/internal/telephony/ProxyController;->mSetRadioAccessFamilyStatus:[I Lcom/android/internal/telephony/ProxyController;->mUniqueIdGenerator:Ljava/util/concurrent/atomic/AtomicInteger; -Lcom/android/internal/telephony/ProxyController;->registerForAllDataDisconnected(ILandroid/os/Handler;ILjava/lang/Object;)V Lcom/android/internal/telephony/ProxyController;->sendRadioCapabilityRequest(IIIILjava/lang/String;II)V Lcom/android/internal/telephony/ProxyController;->sProxyController:Lcom/android/internal/telephony/ProxyController; Lcom/android/internal/telephony/RadioCapability;->getRadioAccessFamily()I @@ -3394,7 +3389,6 @@ Lcom/android/internal/telephony/ServiceStateTracker;->mVoiceRoamingOnRegistrants Lcom/android/internal/telephony/ServiceStateTracker;->notifyDataRegStateRilRadioTechnologyChanged()V Lcom/android/internal/telephony/ServiceStateTracker;->notifySignalStrength()Z Lcom/android/internal/telephony/ServiceStateTracker;->pollState()V -Lcom/android/internal/telephony/ServiceStateTracker;->powerOffRadioSafely(Lcom/android/internal/telephony/dataconnection/DcTracker;)V Lcom/android/internal/telephony/ServiceStateTracker;->reRegisterNetwork(Landroid/os/Message;)V Lcom/android/internal/telephony/ServiceStateTracker;->resetServiceStateInIwlanMode()V Lcom/android/internal/telephony/ServiceStateTracker;->setOperatorIdd(Ljava/lang/String;)V diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 05bb9a1c2139..f3f065a27e6f 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -122,7 +122,7 @@ import android.view.autofill.AutofillManager.AutofillClient; import android.view.autofill.AutofillPopupWindow; import android.view.autofill.IAutofillWindowPresenter; import android.view.intelligence.ContentCaptureEvent; -import android.view.intelligence.IntelligenceManager; +import android.view.intelligence.ContentCaptureManager; import android.widget.AdapterView; import android.widget.Toast; import android.widget.Toolbar; @@ -824,8 +824,8 @@ public class Activity extends ContextThemeWrapper /** The autofill manager. Always access via {@link #getAutofillManager()}. */ @Nullable private AutofillManager mAutofillManager; - /** The screen observation manager. Always access via {@link #getIntelligenceManager()}. */ - @Nullable private IntelligenceManager mIntelligenceManager; + /** The content capture manager. Always access via {@link #getContentCaptureManager()}. */ + @Nullable private ContentCaptureManager mContentCaptureManager; private final ArrayList<Application.ActivityLifecycleCallbacks> mActivityLifecycleCallbacks = new ArrayList<Application.ActivityLifecycleCallbacks>(); @@ -1016,39 +1016,39 @@ public class Activity extends ContextThemeWrapper } /** - * (Creates, sets, and ) returns the intelligence manager + * (Creates, sets, and ) returns the content capture manager * - * @return The intelligence manager + * @return The content capture manager */ - @NonNull private IntelligenceManager getIntelligenceManager() { - if (mIntelligenceManager == null) { - mIntelligenceManager = getSystemService(IntelligenceManager.class); + @NonNull private ContentCaptureManager getContentCaptureManager() { + if (mContentCaptureManager == null) { + mContentCaptureManager = getSystemService(ContentCaptureManager.class); } - return mIntelligenceManager; + return mContentCaptureManager; } - private void notifyIntelligenceManagerIfNeeded(@ContentCaptureEvent.EventType int event) { - final IntelligenceManager im = getIntelligenceManager(); - if (im == null || !im.isContentCaptureEnabled()) { + private void notifyContentCaptureManagerIfNeeded(@ContentCaptureEvent.EventType int event) { + final ContentCaptureManager cm = getContentCaptureManager(); + if (cm == null || !cm.isContentCaptureEnabled()) { return; } switch (event) { case ContentCaptureEvent.TYPE_ACTIVITY_CREATED: //TODO(b/111276913): decide whether the InteractionSessionId should be // saved / restored in the activity bundle. - im.onActivityCreated(mToken, getComponentName()); + cm.onActivityCreated(mToken, getComponentName()); break; case ContentCaptureEvent.TYPE_ACTIVITY_DESTROYED: - im.onActivityDestroyed(); + cm.onActivityDestroyed(); break; case ContentCaptureEvent.TYPE_ACTIVITY_STARTED: case ContentCaptureEvent.TYPE_ACTIVITY_RESUMED: case ContentCaptureEvent.TYPE_ACTIVITY_PAUSED: case ContentCaptureEvent.TYPE_ACTIVITY_STOPPED: - im.onActivityLifecycleEvent(event); + cm.onActivityLifecycleEvent(event); break; default: - Log.w(TAG, "notifyIntelligenceManagerIfNeeded(): invalid type " + event); + Log.w(TAG, "notifyContentCaptureManagerIfNeeded(): invalid type " + event); } } @@ -1057,6 +1057,7 @@ public class Activity extends ContextThemeWrapper super.attachBaseContext(newBase); if (newBase != null) { newBase.setAutofillClient(this); + newBase.setContentCaptureSupported(true); } } @@ -1066,6 +1067,12 @@ public class Activity extends ContextThemeWrapper return this; } + /** @hide */ + @Override + public boolean isContentCaptureSupported() { + return true; + } + /** * Register an {@link Application.ActivityLifecycleCallbacks} instance that receives * lifecycle callbacks for only this Activity. @@ -1410,7 +1417,7 @@ public class Activity extends ContextThemeWrapper mRestoredFromBundle = savedInstanceState != null; mCalled = true; - notifyIntelligenceManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_CREATED); + notifyContentCaptureManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_CREATED); } /** @@ -1644,7 +1651,7 @@ public class Activity extends ContextThemeWrapper if (mAutoFillResetNeeded) { getAutofillManager().onVisibleForAutofill(); } - notifyIntelligenceManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_STARTED); + notifyContentCaptureManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_STARTED); } /** @@ -1735,7 +1742,7 @@ public class Activity extends ContextThemeWrapper } } } - notifyIntelligenceManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_RESUMED); + notifyContentCaptureManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_RESUMED); mCalled = true; } @@ -2129,7 +2136,7 @@ public class Activity extends ContextThemeWrapper mAutoFillIgnoreFirstResumePause = false; } } - notifyIntelligenceManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_PAUSED); + notifyContentCaptureManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_PAUSED); mCalled = true; } @@ -2318,7 +2325,7 @@ public class Activity extends ContextThemeWrapper getAutofillManager().onPendingSaveUi(AutofillManager.PENDING_UI_OPERATION_CANCEL, mIntent.getIBinderExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)); } - notifyIntelligenceManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_STOPPED); + notifyContentCaptureManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_STOPPED); } } @@ -2390,7 +2397,7 @@ public class Activity extends ContextThemeWrapper dispatchActivityDestroyed(); - notifyIntelligenceManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_DESTROYED); + notifyContentCaptureManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_DESTROYED); } @@ -6806,7 +6813,7 @@ public class Activity extends ContextThemeWrapper } void dumpIntelligenceManager(String prefix, PrintWriter writer) { - final IntelligenceManager im = getIntelligenceManager(); + final ContentCaptureManager im = getContentCaptureManager(); if (im != null) { im.dump(prefix, writer); } else { diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index c879db8967d3..2c435a27cbce 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -31,10 +31,11 @@ import android.util.DisplayMetrics; import android.util.Log; import android.view.IWindowManager; import android.view.InputDevice; -import android.view.InputEvent; import android.view.MotionEvent; import android.view.Surface; +import android.view.SurfaceControl; import android.view.SurfaceHolder; +import android.view.SurfaceSession; import android.view.SurfaceView; import android.view.ViewGroup; import android.view.WindowManager; @@ -59,12 +60,16 @@ public class ActivityView extends ViewGroup { private VirtualDisplay mVirtualDisplay; private final SurfaceView mSurfaceView; - private Surface mSurface; + + /** + * This is the root surface for the VirtualDisplay. The VirtualDisplay child surfaces will be + * re-parented to this surface. This will also be a child of the SurfaceView's SurfaceControl. + */ + private SurfaceControl mRootSurfaceControl; private final SurfaceCallback mSurfaceCallback; private StateCallback mActivityViewCallback; - private IActivityManager mActivityManager; private IActivityTaskManager mActivityTaskManager; private IInputForwarder mInputForwarder; // Temp container to store view coordinates on screen. @@ -75,6 +80,9 @@ public class ActivityView extends ViewGroup { private final CloseGuard mGuard = CloseGuard.get(); private boolean mOpened; // Protected by mGuard. + private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction(); + private Surface mTmpSurface = new Surface(); + @UnsupportedAppUsage public ActivityView(Context context) { this(context, null /* attrs */); @@ -87,7 +95,6 @@ public class ActivityView extends ViewGroup { public ActivityView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mActivityManager = ActivityManager.getService(); mActivityTaskManager = ActivityTaskManager.getService(); mSurfaceView = new SurfaceView(context); mSurfaceCallback = new SurfaceCallback(); @@ -283,9 +290,14 @@ public class ActivityView extends ViewGroup { return super.onGenericMotionEvent(event); } - private boolean injectInputEvent(InputEvent event) { + private boolean injectInputEvent(MotionEvent event) { if (mInputForwarder != null) { try { + // The touch event that the ActivityView gets is in View space, but the event needs + // to get forwarded in screen space. This offsets the touch event by the location + // the ActivityView is on screen and sends it to the input forwarder. + getLocationOnScreen(mLocationOnScreen); + event.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]); return mInputForwarder.forwardEvent(event); } catch (RemoteException e) { e.rethrowAsRuntimeException(); @@ -297,14 +309,19 @@ public class ActivityView extends ViewGroup { private class SurfaceCallback implements SurfaceHolder.Callback { @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { - mSurface = mSurfaceView.getHolder().getSurface(); + mTmpSurface = new Surface(); if (mVirtualDisplay == null) { - initVirtualDisplay(); + initVirtualDisplay(new SurfaceSession(surfaceHolder.getSurface())); if (mVirtualDisplay != null && mActivityViewCallback != null) { mActivityViewCallback.onActivityViewReady(ActivityView.this); } } else { - mVirtualDisplay.setSurface(surfaceHolder.getSurface()); + // TODO (b/119209373): DisplayManager determines if a VirtualDisplay is on by + // whether it has a surface. Setting a fake surface here so DisplayManager will + // consider this display on. + mVirtualDisplay.setSurface(mTmpSurface); + mTmpTransaction.reparent(mRootSurfaceControl, + mSurfaceView.getSurfaceControl().getHandle()).apply(); } updateLocation(); } @@ -319,8 +336,8 @@ public class ActivityView extends ViewGroup { @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { - mSurface.release(); - mSurface = null; + mTmpSurface.release(); + mTmpSurface = null; if (mVirtualDisplay != null) { mVirtualDisplay.setSurface(null); } @@ -328,7 +345,7 @@ public class ActivityView extends ViewGroup { } } - private void initVirtualDisplay() { + private void initVirtualDisplay(SurfaceSession surfaceSession) { if (mVirtualDisplay != null) { throw new IllegalStateException("Trying to initialize for the second time."); } @@ -336,9 +353,13 @@ public class ActivityView extends ViewGroup { final int width = mSurfaceView.getWidth(); final int height = mSurfaceView.getHeight(); final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); + + // TODO (b/119209373): DisplayManager determines if a VirtualDisplay is on by + // whether it has a surface. Setting a fake surface here so DisplayManager will consider + // this display on. mVirtualDisplay = displayManager.createVirtualDisplay( DISPLAY_NAME + "@" + System.identityHashCode(this), - width, height, getBaseDisplayDensity(), mSurface, + width, height, getBaseDisplayDensity(), mTmpSurface, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); if (mVirtualDisplay == null) { @@ -348,11 +369,20 @@ public class ActivityView extends ViewGroup { final int displayId = mVirtualDisplay.getDisplay().getDisplayId(); final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); + + mRootSurfaceControl = new SurfaceControl.Builder(surfaceSession) + .setContainerLayer(true) + .setName(DISPLAY_NAME) + .build(); + try { + wm.reparentDisplayContent(displayId, mRootSurfaceControl.getHandle()); wm.dontOverrideDisplayInfo(displayId); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } + + mTmpTransaction.show(mRootSurfaceControl).apply(); mInputForwarder = InputManager.getInstance().createInputForwarder(displayId); mTaskStackListener = new TaskStackListenerImpl(); try { @@ -392,9 +422,9 @@ public class ActivityView extends ViewGroup { displayReleased = false; } - if (mSurface != null) { - mSurface.release(); - mSurface = null; + if (mTmpSurface != null) { + mTmpSurface.release(); + mTmpSurface = null; } if (displayReleased && mActivityViewCallback != null) { diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 28ecb27b53d2..6f0b6c8687db 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -217,6 +217,8 @@ class ContextImpl extends Context { private AutofillClient mAutofillClient = null; private boolean mIsAutofillCompatEnabled; + private boolean mIsContentCaptureSupported = false; + private final Object mSync = new Object(); @GuardedBy("mSync") @@ -2376,6 +2378,18 @@ class ContextImpl extends Context { mIsAutofillCompatEnabled = autofillCompatEnabled; } + /** @hide */ + @Override + public boolean isContentCaptureSupported() { + return mIsContentCaptureSupported; + } + + /** @hide */ + @Override + public void setContentCaptureSupported(boolean supported) { + mIsContentCaptureSupported = supported; + } + @UnsupportedAppUsage static ContextImpl createSystemContext(ActivityThread mainThread) { LoadedApk packageInfo = new LoadedApk(mainThread); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index e83bcd0e6647..88fb025bf406 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -96,6 +96,7 @@ interface IActivityManager { String callingPackage); void unregisterUidObserver(in IUidObserver observer); boolean isUidActive(int uid, String callingPackage); + int getUidProcessState(int uid, in String callingPackage); // =============== End of transactions used on native side as well ============================ // Special low-level communication with activity manager. @@ -379,8 +380,6 @@ interface IActivityManager { void noteAlarmFinish(in IIntentSender sender, in WorkSource workSource, int sourceUid, in String tag); int getPackageProcessState(in String packageName, in String callingPackage); void updateDeviceOwner(in String packageName); - int getUidProcessState(int uid, in String callingPackage); - // Start of N transactions // Start Binder transaction tracking for all applications. diff --git a/core/java/android/app/IUidObserver.aidl b/core/java/android/app/IUidObserver.aidl index ce8880940c78..e116d989ca75 100644 --- a/core/java/android/app/IUidObserver.aidl +++ b/core/java/android/app/IUidObserver.aidl @@ -43,8 +43,6 @@ oneway interface IUidObserver { */ void onUidIdle(int uid, boolean disabled); - // =============== End of transactions used on native side as well ============================ - /** * General report of a state change of an uid. * @@ -55,6 +53,8 @@ oneway interface IUidObserver { */ void onUidStateChanged(int uid, int procState, long procStateSeq); + // =============== End of transactions used on native side as well ============================ + /** * Report when the cached state of a uid has changed. * If true, a uid has become cached -- that is, it has some active processes that are diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 16d35802a9f6..0281e6a0631e 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -3867,6 +3867,9 @@ public class Notification implements Parcelable * The system UI may choose to display a heads-up notification, instead of * launching this intent, while the user is using the device. * </p> + * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request + * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to + * use full screen intents.</p> * * @param intent The pending intent to launch. * @param highPriority Passing true will cause this notification to be sent @@ -4468,6 +4471,14 @@ public class Notification implements Parcelable } } + private void bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p) { + contentView.setDrawableTint( + R.id.alerted_icon, + false /* targetBackground */, + getNeutralColor(p), + PorterDuff.Mode.SRC_ATOP); + } + /** * @hide */ @@ -4870,6 +4881,7 @@ public class Notification implements Parcelable bindHeaderTextSecondary(contentView, p); bindHeaderChronometerAndTime(contentView, p); bindProfileBadge(contentView, p); + bindAlertedIcon(contentView, p); } bindActivePermissions(contentView, p); bindExpandButton(contentView, p); diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 89ec19bbc6dd..25fa897f2a56 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1424,7 +1424,62 @@ public class NotificationManager { return other.priorityCategories == priorityCategories && other.priorityCallSenders == priorityCallSenders && other.priorityMessageSenders == priorityMessageSenders - && other.suppressedVisualEffects == suppressedVisualEffects; + && suppressedVisualEffectsEqual(suppressedVisualEffects, + other.suppressedVisualEffects); + } + + + private boolean suppressedVisualEffectsEqual(int suppressedEffects, + int otherSuppressedVisualEffects) { + if (suppressedEffects == otherSuppressedVisualEffects) { + return true; + } + + if ((suppressedEffects & SUPPRESSED_EFFECT_SCREEN_ON) != 0) { + suppressedEffects |= SUPPRESSED_EFFECT_PEEK; + } + if ((suppressedEffects & SUPPRESSED_EFFECT_SCREEN_OFF) != 0) { + suppressedEffects |= SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; + suppressedEffects |= SUPPRESSED_EFFECT_LIGHTS; + suppressedEffects |= SUPPRESSED_EFFECT_AMBIENT; + } + + if ((otherSuppressedVisualEffects & SUPPRESSED_EFFECT_SCREEN_ON) != 0) { + otherSuppressedVisualEffects |= SUPPRESSED_EFFECT_PEEK; + } + if ((otherSuppressedVisualEffects & SUPPRESSED_EFFECT_SCREEN_OFF) != 0) { + otherSuppressedVisualEffects |= SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; + otherSuppressedVisualEffects |= SUPPRESSED_EFFECT_LIGHTS; + otherSuppressedVisualEffects |= SUPPRESSED_EFFECT_AMBIENT; + } + + if ((suppressedEffects & SUPPRESSED_EFFECT_SCREEN_ON) + != (otherSuppressedVisualEffects & SUPPRESSED_EFFECT_SCREEN_ON)) { + int currSuppressedEffects = (suppressedEffects & SUPPRESSED_EFFECT_SCREEN_ON) != 0 + ? otherSuppressedVisualEffects : suppressedEffects; + if ((currSuppressedEffects & SUPPRESSED_EFFECT_PEEK) == 0) { + return false; + } + } + + if ((suppressedEffects & SUPPRESSED_EFFECT_SCREEN_OFF) + != (otherSuppressedVisualEffects & SUPPRESSED_EFFECT_SCREEN_OFF)) { + int currSuppressedEffects = (suppressedEffects & SUPPRESSED_EFFECT_SCREEN_OFF) != 0 + ? otherSuppressedVisualEffects : suppressedEffects; + if ((currSuppressedEffects & SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) == 0 + || (currSuppressedEffects & SUPPRESSED_EFFECT_LIGHTS) == 0 + || (currSuppressedEffects & SUPPRESSED_EFFECT_AMBIENT) == 0) { + return false; + } + } + + int thisWithoutOldEffects = suppressedEffects + & ~SUPPRESSED_EFFECT_SCREEN_ON + & ~SUPPRESSED_EFFECT_SCREEN_OFF; + int otherWithoutOldEffects = otherSuppressedVisualEffects + & ~SUPPRESSED_EFFECT_SCREEN_ON + & ~SUPPRESSED_EFFECT_SCREEN_OFF; + return thisWithoutOldEffects == otherWithoutOldEffects; } @Override diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 0123551230b0..dfe371c3248e 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -165,8 +165,8 @@ import android.view.accessibility.CaptioningManager; import android.view.autofill.AutofillManager; import android.view.autofill.IAutoFillManager; import android.view.inputmethod.InputMethodManager; +import android.view.intelligence.ContentCaptureManager; import android.view.intelligence.IIntelligenceManager; -import android.view.intelligence.IntelligenceManager; import android.view.textclassifier.TextClassificationManager; import android.view.textservice.TextServicesManager; @@ -199,6 +199,7 @@ final class SystemServiceRegistry { private SystemServiceRegistry() { } static { + //CHECKSTYLE:OFF IndentationCheck registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class, new CachedServiceFetcher<AccessibilityManager>() { @Override @@ -1050,15 +1051,20 @@ final class SystemServiceRegistry { return new AutofillManager(ctx.getOuterContext(), service); }}); - registerService(Context.INTELLIGENCE_MANAGER_SERVICE, IntelligenceManager.class, - new CachedServiceFetcher<IntelligenceManager>() { + registerService(Context.CONTENT_CAPTURE_MANAGER_SERVICE, ContentCaptureManager.class, + new CachedServiceFetcher<ContentCaptureManager>() { @Override - public IntelligenceManager createService(ContextImpl ctx) + public ContentCaptureManager createService(ContextImpl ctx) throws ServiceNotFoundException { // Get the services without throwing as this is an optional feature - IBinder b = ServiceManager.getService(Context.INTELLIGENCE_MANAGER_SERVICE); - IIntelligenceManager service = IIntelligenceManager.Stub.asInterface(b); - return new IntelligenceManager(ctx.getOuterContext(), service); + Context outerContext = ctx.getOuterContext(); + if (outerContext.isContentCaptureSupported()) { + IBinder b = ServiceManager + .getService(Context.CONTENT_CAPTURE_MANAGER_SERVICE); + IIntelligenceManager service = IIntelligenceManager.Stub.asInterface(b); + return new ContentCaptureManager(outerContext, service); + } + return null; }}); registerService(Context.VR_SERVICE, VrManager.class, new CachedServiceFetcher<VrManager>() { @@ -1138,6 +1144,7 @@ final class SystemServiceRegistry { throws ServiceNotFoundException { return new RoleManager(ctx.getOuterContext()); }}); + //CHECKSTYLE:ON IndentationCheck } /** diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 27471cac10e9..27ae0b0314d0 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -1898,23 +1898,33 @@ public class WallpaperManager { * @hide */ public static ComponentName getDefaultWallpaperComponent(Context context) { + ComponentName cn = null; + String flat = SystemProperties.get(PROP_WALLPAPER_COMPONENT); if (!TextUtils.isEmpty(flat)) { - final ComponentName cn = ComponentName.unflattenFromString(flat); - if (cn != null) { - return cn; + cn = ComponentName.unflattenFromString(flat); + } + + if (cn == null) { + flat = context.getString(com.android.internal.R.string.default_wallpaper_component); + if (!TextUtils.isEmpty(flat)) { + cn = ComponentName.unflattenFromString(flat); } } - flat = context.getString(com.android.internal.R.string.default_wallpaper_component); - if (!TextUtils.isEmpty(flat)) { - final ComponentName cn = ComponentName.unflattenFromString(flat); - if (cn != null) { - return cn; + // Check if the package exists + if (cn != null) { + try { + final PackageManager packageManager = context.getPackageManager(); + packageManager.getPackageInfo(cn.getPackageName(), + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); + } catch (PackageManager.NameNotFoundException e) { + cn = null; } } - return null; + return cn; } /** diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java index 237082e4cb66..2f0b44f76ffb 100644 --- a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java +++ b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java @@ -18,6 +18,7 @@ package android.bluetooth; import android.os.Parcel; import android.os.Parcelable; +import android.util.EventLog; /** @@ -30,6 +31,8 @@ import android.os.Parcelable; */ public final class BluetoothHidDeviceAppSdpSettings implements Parcelable { + private static final int MAX_DESCRIPTOR_SIZE = 2048; + private final String mName; private final String mDescription; private final String mProvider; @@ -55,6 +58,12 @@ public final class BluetoothHidDeviceAppSdpSettings implements Parcelable { mDescription = description; mProvider = provider; mSubclass = subclass; + + if (descriptors == null || descriptors.length > MAX_DESCRIPTOR_SIZE) { + EventLog.writeEvent(0x534e4554, "119819889", -1, ""); + throw new IllegalArgumentException("descriptors must be not null and shorter than " + + MAX_DESCRIPTOR_SIZE); + } mDescriptors = descriptors.clone(); } diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java index 11f8ab7551c2..e3672a7e064f 100644 --- a/core/java/android/bluetooth/BluetoothManager.java +++ b/core/java/android/bluetooth/BluetoothManager.java @@ -52,8 +52,7 @@ import java.util.List; @RequiresFeature(PackageManager.FEATURE_BLUETOOTH) public final class BluetoothManager { private static final String TAG = "BluetoothManager"; - private static final boolean DBG = true; - private static final boolean VDBG = true; + private static final boolean DBG = false; private final BluetoothAdapter mAdapter; diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 437039dcbccf..7d5202d0dbce 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -97,8 +97,7 @@ public abstract class ContentResolver { * * @hide */ - public static final boolean DEPRECATE_DATA_COLUMNS = SystemProperties - .getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false); + public static final boolean DEPRECATE_DATA_COLUMNS = StorageManager.hasIsolatedStorage(); /** * Special filesystem path prefix which indicates that a path should be diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index ff57b0311d60..68aac642ecb0 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3933,12 +3933,13 @@ public abstract class Context { public static final String AUTOFILL_MANAGER_SERVICE = "autofill"; /** - * Official published name of the intelligence service. + * Official published name of the smart suggestions service. * * @hide * @see #getSystemService(String) */ - public static final String INTELLIGENCE_MANAGER_SERVICE = "intelligence"; + // TODO(b/111276913): rename string (will require SELinux change first) + public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "intelligence"; /** * Use with {@link #getSystemService(String)} to access the @@ -5223,6 +5224,25 @@ public abstract class Context { } /** + * Checks whether this context supports content capture. + * + * @hide + */ + // NOTE: for now we just need to check if it's supported so we can optimize calls that can be + // skipped when it isn't. Eventually, we might need a full + // ContentCaptureManager.ContentCaptureClient interface (as it's done with AutofillClient). + // + public boolean isContentCaptureSupported() { + return false; + } + + /** + * @hide + */ + public void setContentCaptureSupported(@SuppressWarnings("unused") boolean supported) { + } + + /** * Throws an exception if the Context is using system resources, * which are non-runtime-overlay-themable and may show inconsistent UI. * @hide diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 2db44b44fd6c..26ed3b736f80 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -1049,4 +1049,20 @@ public class ContextWrapper extends Context { mBase.setAutofillCompatibilityEnabled(autofillCompatEnabled); } } + + /** + * @hide + */ + @Override + public boolean isContentCaptureSupported() { + return mBase.isContentCaptureSupported(); + } + + /** + * @hide + */ + @Override + public void setContentCaptureSupported(boolean supported) { + mBase.setContentCaptureSupported(supported); + } } diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 7c3b5e48cb5b..98a135f96d98 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -1770,7 +1770,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * is on the package whitelist. * * @param policy configured policy for this app, or {@link #HIDDEN_API_ENFORCEMENT_DEFAULT} - * if nothing configured. + * if nothing configured. * @hide */ public void maybeUpdateHiddenApiEnforcementPolicy(@HiddenApiEnforcementPolicy int policy) { diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 6421dc5a3f68..b7df2bf5a5f7 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -5372,6 +5372,10 @@ public abstract class PackageManager { public abstract void removePackageFromPreferred(String packageName); /** + * @deprecated This function no longer does anything; it was an old + * approach to managing preferred activities, which has been superseded + * by (and conflicts with) the modern activity-based preferences. + * * Retrieve the list of all currently configured preferred packages. The * first package on the list is the most preferred, the last is the least * preferred. @@ -5380,6 +5384,7 @@ public abstract class PackageManager { * @return A List of PackageInfo objects, one for each preferred * application, in order of preference. */ + @Deprecated public abstract List<PackageInfo> getPreferredPackages(@PackageInfoFlags int flags); /** @@ -5406,11 +5411,16 @@ public abstract class PackageManager { ComponentName[] set, ComponentName activity); /** + * @deprecated This is a protected API that should not have been available + * to third party applications. It is the platform's responsibility for + * assigning preferred activities and this cannot be directly modified. + * * Same as {@link #addPreferredActivity(IntentFilter, int, ComponentName[], ComponentName)}, but with a specific userId to apply the preference to. * @hide */ + @Deprecated @UnsupportedAppUsage public void addPreferredActivityAsUser(IntentFilter filter, int match, ComponentName[] set, ComponentName activity, @UserIdInt int userId) { @@ -5444,6 +5454,10 @@ public abstract class PackageManager { ComponentName[] set, ComponentName activity); /** + * @deprecated This is a protected API that should not have been available + * to third party applications. It is the platform's responsibility for + * assigning preferred activities and this cannot be directly modified. + * * Replaces an existing preferred activity mapping to the system, and if that were not present * adds a new preferred activity. This will be used to automatically select the given activity * component when {@link Context#startActivity(Intent) Context.startActivity()} finds multiple @@ -5459,6 +5473,7 @@ public abstract class PackageManager { * * @hide */ + @Deprecated @SystemApi public void replacePreferredActivity(@NonNull IntentFilter filter, int match, @NonNull List<ComponentName> set, @NonNull ComponentName activity) { @@ -5476,6 +5491,10 @@ public abstract class PackageManager { } /** + * @deprecated This function no longer does anything; it was an old + * approach to managing preferred activities, which has been superseded + * by (and conflicts with) the modern activity-based preferences. + * * Remove all preferred activity mappings, previously added with * {@link #addPreferredActivity}, from the * system whose activities are implemented in the given package name. @@ -5484,9 +5503,14 @@ public abstract class PackageManager { * @param packageName The name of the package whose preferred activity * mappings are to be removed. */ + @Deprecated public abstract void clearPackagePreferredActivities(String packageName); /** + * @deprecated This function no longer does anything; it was an old + * approach to managing preferred activities, which has been superseded + * by (and conflicts with) the modern activity-based preferences. + * * Retrieve all preferred activities, previously added with * {@link #addPreferredActivity}, that are * currently registered with the system. @@ -5503,6 +5527,7 @@ public abstract class PackageManager { * (the number of distinct IntentFilter records, not the number of unique * activity components) that were found. */ + @Deprecated public abstract int getPreferredActivities(@NonNull List<IntentFilter> outFilters, @NonNull List<ComponentName> outActivities, String packageName); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 49189e53f385..ac18dca74950 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -2513,7 +2513,7 @@ public class PackageParser { // If the storage model feature flag is disabled, we need to fiddle // around with permission definitions to return us to pre-Q behavior. // STOPSHIP(b/112545973): remove once feature enabled by default - if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + if (!StorageManager.hasIsolatedStorage()) { if ("android".equals(pkg.packageName)) { final ArraySet<String> newGroups = new ArraySet<>(); newGroups.add(android.Manifest.permission_group.MEDIA_AURAL); diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java index 79e15a7a9a2d..0ec812fe0350 100644 --- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java +++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java @@ -30,19 +30,29 @@ import java.util.concurrent.Executor; public interface BiometricAuthenticator { /** + * No biometric methods or nothing has been enrolled. + * Move/expose these in BiometricPrompt if we ever want to allow applications to "blacklist" + * modalities when calling authenticate(). * @hide */ - int TYPE_FINGERPRINT = 1; + int TYPE_NONE = 0; + /** + * Constant representing fingerprint. + * @hide + */ + int TYPE_FINGERPRINT = 1 << 0; /** + * Constant representing iris. * @hide */ - int TYPE_IRIS = 2; + int TYPE_IRIS = 1 << 1; /** + * Constant representing face. * @hide */ - int TYPE_FACE = 3; + int TYPE_FACE = 1 << 2; /** * Container for biometric data diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index bd149fd05f59..b238d778f55a 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -251,27 +251,11 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan private Executor mExecutor; private AuthenticationCallback mAuthenticationCallback; - IBiometricPromptReceiver mDialogReceiver = new IBiometricPromptReceiver.Stub() { - @Override - public void onDialogDismissed(int reason) { - // Check the reason and invoke OnClickListener(s) if necessary - if (reason == DISMISSED_REASON_POSITIVE) { - mPositiveButtonInfo.executor.execute(() -> { - mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE); - }); - } else if (reason == DISMISSED_REASON_NEGATIVE) { - mNegativeButtonInfo.executor.execute(() -> { - mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE); - }); - } - } - }; - - IBiometricServiceReceiver mBiometricServiceReceiver = + private final IBiometricServiceReceiver mBiometricServiceReceiver = new IBiometricServiceReceiver.Stub() { @Override - public void onAuthenticationSucceeded(long deviceId) throws RemoteException { + public void onAuthenticationSucceeded() throws RemoteException { mExecutor.execute(() -> { final AuthenticationResult result = new AuthenticationResult(mCryptoObject); mAuthenticationCallback.onAuthenticationSucceeded(result); @@ -279,26 +263,39 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } @Override - public void onAuthenticationFailed(long deviceId) throws RemoteException { + public void onAuthenticationFailed() throws RemoteException { mExecutor.execute(() -> { mAuthenticationCallback.onAuthenticationFailed(); }); } @Override - public void onError(long deviceId, int error, String message) - throws RemoteException { + public void onError(int error, String message) throws RemoteException { mExecutor.execute(() -> { mAuthenticationCallback.onAuthenticationError(error, message); }); } @Override - public void onAcquired(long deviceId, int acquireInfo, String message) { + public void onAcquired(int acquireInfo, String message) throws RemoteException { mExecutor.execute(() -> { mAuthenticationCallback.onAuthenticationHelp(acquireInfo, message); }); } + + @Override + public void onDialogDismissed(int reason) throws RemoteException { + // Check the reason and invoke OnClickListener(s) if necessary + if (reason == DISMISSED_REASON_POSITIVE) { + mPositiveButtonInfo.executor.execute(() -> { + mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE); + }); + } else if (reason == DISMISSED_REASON_NEGATIVE) { + mNegativeButtonInfo.executor.execute(() -> { + mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE); + }); + } + } }; private BiometricPrompt(Context context, Bundle bundle, @@ -557,9 +554,8 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan mExecutor = executor; mAuthenticationCallback = callback; final long sessionId = crypto != null ? crypto.getOpId() : 0; - mService.authenticate(mToken, sessionId, userId, - mBiometricServiceReceiver, 0 /* flags */, mContext.getOpPackageName(), - mBundle, mDialogReceiver); + mService.authenticate(mToken, sessionId, userId, mBiometricServiceReceiver, + mContext.getOpPackageName(), mBundle); } catch (RemoteException e) { Log.e(TAG, "Remote exception while authenticating", e); mExecutor.execute(() -> { diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index e17feff0160a..53a076135aaa 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -18,7 +18,6 @@ package android.hardware.biometrics; import android.os.Bundle; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; -import android.hardware.biometrics.IBiometricPromptReceiver; import android.hardware.biometrics.IBiometricServiceReceiver; /** @@ -32,8 +31,7 @@ interface IBiometricService { // Requests authentication. The service choose the appropriate biometric to use, and show // the corresponding BiometricDialog. void authenticate(IBinder token, long sessionId, int userId, - IBiometricServiceReceiver receiver, int flags, String opPackageName, - in Bundle bundle, IBiometricPromptReceiver dialogReceiver); + IBiometricServiceReceiver receiver, String opPackageName, in Bundle bundle); // Cancel authentication for the given sessionId void cancelAuthentication(IBinder token, String opPackageName); @@ -46,4 +44,8 @@ interface IBiometricService { // Explicitly set the active user. void setActiveUser(int userId); + + // Notify BiometricService when <Biometric>Service is ready to start the prepared client. + // Client lifecycle is still managed in <Biometric>Service. + void onReadyForAuthentication(int cookie, boolean requireConfirmation, int userId); } diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl index a6e3696eeb10..22ef33e86e17 100644 --- a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl +++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl @@ -16,12 +16,18 @@ package android.hardware.biometrics; /** - * Communication channel from the BiometricService back to BiometricPrompt. + * Communication channel from BiometricService back to BiometricPrompt * @hide */ oneway interface IBiometricServiceReceiver { - void onAuthenticationSucceeded(long deviceId); - void onAuthenticationFailed(long deviceId); - void onError(long deviceId, int error, String message); - void onAcquired(long deviceId, int acquiredInfo, String message); + // Notify BiometricPrompt that authentication was successful + void onAuthenticationSucceeded(); + // Noties that authentication failed. + void onAuthenticationFailed(); + // Notify BiometricPrompt that an error has occurred. + void onError(int error, String message); + // Notifies that a biometric has been acquired. + void onAcquired(int acquiredInfo, String message); + // Notifies that the SystemUI dialog has been dismissed. + void onDialogDismissed(int reason); } diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl new file mode 100644 index 000000000000..180daaf97ada --- /dev/null +++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.biometrics; + +/** + * Communication channel from + * 1) BiometricDialogImpl (SysUI) back to BiometricService + * 2) <Biometric>Service back to BiometricService + * Receives messages from the above and does some handling before forwarding to BiometricPrompt + * via IBiometricServiceReceiver. + * @hide + */ +oneway interface IBiometricServiceReceiverInternal { + // Notify BiometricService that authentication was successful. If user confirmation is required, + // the auth token must be submitted into KeyStore. + void onAuthenticationSucceeded(boolean requireConfirmation, in byte[] token); + // Notify BiometricService that an error has occurred. + void onAuthenticationFailed(int cookie, boolean requireConfirmation); + // Notify BiometricService than an error has occured. Forward to the correct receiver depending + // on the cookie. + void onError(int cookie, int error, String message); + // Notifies that a biometric has been acquired. + void onAcquired(int acquiredInfo, String message); + // Notifies that the SystemUI dialog has been dismissed. + void onDialogDismissed(int reason); + // Notifies that the user has pressed the "try again" button on SystemUI + void onTryAgainPressed(); +} diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index 47df8e8f9729..a15dcec3b276 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -15,9 +15,7 @@ */ package android.hardware.face; -import android.os.Bundle; -import android.hardware.biometrics.IBiometricPromptReceiver; -import android.hardware.biometrics.IBiometricServiceReceiver; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.face.IFaceServiceReceiver; import android.hardware.face.Face; @@ -32,19 +30,24 @@ interface IFaceService { void authenticate(IBinder token, long sessionId, IFaceServiceReceiver receiver, int flags, String opPackageName); - // This method invokes the BiometricDialog. The arguments are almost the same as above, - // but should only be called from (BiometricPromptService). - void authenticateFromService(boolean requireConfirmation, IBinder token, long sessionId, - int userId, IBiometricServiceReceiver receiver, int flags, String opPackageName, - in Bundle bundle, IBiometricPromptReceiver dialogReceiver, - int callingUid, int callingPid, int callingUserId); + // This method prepares the service to start authenticating, but doesn't start authentication. + // This is protected by the MANAGE_BIOMETRIC signatuer permission. This method should only be + // called from BiometricService. The additional uid, pid, userId arguments should be determined + // by BiometricService. To start authentication after the clients are ready, use + // startPreparedClient(). + void prepareForAuthentication(boolean requireConfirmation, IBinder token, long sessionId, + int userId, IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName, + int cookie, int callingUid, int callingPid, int callingUserId); + + // Starts authentication with the previously prepared client. + void startPreparedClient(int cookie); // Cancel authentication for the given sessionId void cancelAuthentication(IBinder token, String opPackageName); // Same as above, with extra arguments. void cancelAuthenticationFromService(IBinder token, String opPackageName, - int callingUid, int callingPid, int callingUserId); + int callingUid, int callingPid, int callingUserId, boolean fromClient); // Start face enrollment void enroll(IBinder token, in byte [] cryptoToken, int userId, IFaceServiceReceiver receiver, diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 2662a11c2dd4..dd6b29d87d67 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -15,9 +15,7 @@ */ package android.hardware.fingerprint; -import android.os.Bundle; -import android.hardware.biometrics.IBiometricPromptReceiver; -import android.hardware.biometrics.IBiometricServiceReceiver; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.fingerprint.IFingerprintClientActiveCallback; import android.hardware.fingerprint.IFingerprintServiceReceiver; @@ -35,22 +33,25 @@ interface IFingerprintService { void authenticate(IBinder token, long sessionId, int userId, IFingerprintServiceReceiver receiver, int flags, String opPackageName); - // This method invokes the BiometricDialog. The arguments are almost the same as above, except - // this is protected by the MANAGE_BIOMETRIC signature permission. This method should only be - // called from BiometricPromptService. The additional uid, pid, userId arguments should be - // determined by BiometricPromptService. - void authenticateFromService(IBinder token, long sessionId, int userId, - IBiometricServiceReceiver receiver, int flags, String opPackageName, - in Bundle bundle, IBiometricPromptReceiver dialogReceiver, + // This method prepares the service to start authenticating, but doesn't start authentication. + // This is protected by the MANAGE_BIOMETRIC signatuer permission. This method should only be + // called from BiometricService. The additional uid, pid, userId arguments should be determined + // by BiometricService. To start authentication after the clients are ready, use + // startPreparedClient(). + void prepareForAuthentication(IBinder token, long sessionId, int userId, + IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName, int cookie, int callingUid, int callingPid, int callingUserId); + // Starts authentication with the previously prepared client. + void startPreparedClient(int cookie); + // Cancel authentication for the given sessionId void cancelAuthentication(IBinder token, String opPackageName); // Same as above, except this is protected by the MANAGE_BIOMETRIC signature permission. Takes // an additional uid, pid, userid. void cancelAuthenticationFromService(IBinder token, String opPackageName, - int callingUid, int callingPid, int callingUserId); + int callingUid, int callingPid, int callingUserId, boolean fromClient); // Start fingerprint enrollment void enroll(IBinder token, in byte [] cryptoToken, int groupId, IFingerprintServiceReceiver receiver, diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 1468fe5bfca8..64314a7d8060 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -928,7 +928,9 @@ public class Binder implements IBinder { final long origWorkSource = ThreadLocalWorkSource.setUid(Binder.getCallingUid()); try { if (tracingEnabled) { - Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, getClass().getName() + ":" + code); + final String transactionName = getTransactionName(code); + Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, getClass().getName() + ":" + + (transactionName != null ? transactionName : code)); } res = onTransact(code, data, reply, flags); } catch (RemoteException|RuntimeException e) { diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 0c1aae8bd16c..8904ee61e2e3 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -657,6 +657,12 @@ public class Environment { public static String DIRECTORY_SCREENSHOTS = "Screenshots"; /** + * Standard directory in which to place any audio files which are + * audiobooks. + */ + public static String DIRECTORY_AUDIOBOOKS = "Audiobooks"; + + /** * List of standard storage directories. * <p> * Each of its values have its own constant: @@ -671,6 +677,7 @@ public class Environment { * <li>{@link #DIRECTORY_DOWNLOADS} * <li>{@link #DIRECTORY_DCIM} * <li>{@link #DIRECTORY_DOCUMENTS} + * <li>{@link #DIRECTORY_AUDIOBOOKS} * </ul> * @hide */ @@ -684,7 +691,8 @@ public class Environment { DIRECTORY_MOVIES, DIRECTORY_DOWNLOADS, DIRECTORY_DCIM, - DIRECTORY_DOCUMENTS + DIRECTORY_DOCUMENTS, + DIRECTORY_AUDIOBOOKS, }; /** @@ -709,6 +717,7 @@ public class Environment { /** {@hide} */ public static final int HAS_DOWNLOADS = 1 << 7; /** {@hide} */ public static final int HAS_DCIM = 1 << 8; /** {@hide} */ public static final int HAS_DOCUMENTS = 1 << 9; + /** {@hide} */ public static final int HAS_AUDIOBOOKS = 1 << 10; /** {@hide} */ public static final int HAS_ANDROID = 1 << 16; /** {@hide} */ public static final int HAS_OTHER = 1 << 17; @@ -738,6 +747,7 @@ public class Environment { else if (DIRECTORY_DOWNLOADS.equals(name)) res |= HAS_DOWNLOADS; else if (DIRECTORY_DCIM.equals(name)) res |= HAS_DCIM; else if (DIRECTORY_DOCUMENTS.equals(name)) res |= HAS_DOCUMENTS; + else if (DIRECTORY_AUDIOBOOKS.equals(name)) res |= HAS_AUDIOBOOKS; else if (DIRECTORY_ANDROID.equals(name)) res |= HAS_ANDROID; else res |= HAS_OTHER; } diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index 44b9e311dc0b..6de1ff4bc097 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -17,12 +17,6 @@ package android.os; import static android.system.OsConstants.AF_UNIX; -import static android.system.OsConstants.O_APPEND; -import static android.system.OsConstants.O_CREAT; -import static android.system.OsConstants.O_RDONLY; -import static android.system.OsConstants.O_RDWR; -import static android.system.OsConstants.O_TRUNC; -import static android.system.OsConstants.O_WRONLY; import static android.system.OsConstants.SEEK_SET; import static android.system.OsConstants.SOCK_SEQPACKET; import static android.system.OsConstants.SOCK_STREAM; @@ -254,8 +248,16 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { } /** {@hide} */ - public static ParcelFileDescriptor fromFd( - FileDescriptor fd, Handler handler, final OnCloseListener listener) throws IOException { + public static ParcelFileDescriptor fromPfd(ParcelFileDescriptor pfd, Handler handler, + final OnCloseListener listener) throws IOException { + final FileDescriptor original = new FileDescriptor(); + original.setInt$(pfd.detachFd()); + return fromFd(original, handler, listener); + } + + /** {@hide} */ + public static ParcelFileDescriptor fromFd(FileDescriptor fd, Handler handler, + final OnCloseListener listener) throws IOException { if (handler == null) { throw new IllegalArgumentException("Handler must not be null"); } diff --git a/core/java/android/os/RedactingFileDescriptor.java b/core/java/android/os/RedactingFileDescriptor.java index 60eb5c3c4a89..4e5eaac3442f 100644 --- a/core/java/android/os/RedactingFileDescriptor.java +++ b/core/java/android/os/RedactingFileDescriptor.java @@ -20,15 +20,18 @@ import android.content.Context; import android.os.storage.StorageManager; import android.system.ErrnoException; import android.system.Os; -import android.system.OsConstants; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; + import libcore.io.IoUtils; +import libcore.util.EmptyArray; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.InterruptedIOException; +import java.util.Arrays; /** * Variant of {@link FileDescriptor} that allows its creator to specify regions @@ -40,20 +43,21 @@ public class RedactingFileDescriptor { private static final String TAG = "RedactingFileDescriptor"; private static final boolean DEBUG = true; - private final long[] mRedactRanges; + private volatile long[] mRedactRanges; private FileDescriptor mInner = null; private ParcelFileDescriptor mOuter = null; - private RedactingFileDescriptor(Context context, File file, long[] redactRanges) + private RedactingFileDescriptor(Context context, File file, int mode, long[] redactRanges) throws IOException { mRedactRanges = checkRangesArgument(redactRanges); try { try { - mInner = Os.open(file.getAbsolutePath(), OsConstants.O_RDONLY, 0); + mInner = Os.open(file.getAbsolutePath(), + FileUtils.translateModePfdToPosix(mode), 0); mOuter = context.getSystemService(StorageManager.class) - .openProxyFileDescriptor(ParcelFileDescriptor.MODE_READ_ONLY, mCallback); + .openProxyFileDescriptor(mode, mCallback); } catch (ErrnoException e) { throw e.rethrowAsIOException(); } @@ -78,16 +82,61 @@ public class RedactingFileDescriptor { /** * Open the given {@link File} and returns a {@link ParcelFileDescriptor} - * that offers a redacted, read-only view of the underlying data. + * that offers a redacted view of the underlying data. If a redacted region + * is written to, the newly written data can be read back correctly instead + * of continuing to be redacted. * * @param file The underlying file to open. + * @param mode The {@link ParcelFileDescriptor} mode to open with. * @param redactRanges List of file offsets that should be redacted, stored * as {@code [start1, end1, start2, end2, ...]}. Start values are * inclusive and end values are exclusive. */ - public static ParcelFileDescriptor open(Context context, File file, long[] redactRanges) - throws IOException { - return new RedactingFileDescriptor(context, file, redactRanges).mOuter; + public static ParcelFileDescriptor open(Context context, File file, int mode, + long[] redactRanges) throws IOException { + return new RedactingFileDescriptor(context, file, mode, redactRanges).mOuter; + } + + /** + * Update the given ranges argument to remove any references to the given + * offset and length. This is typically used when a caller has written over + * a previously redacted region. + */ + @VisibleForTesting + public static long[] removeRange(long[] ranges, long start, long end) { + if (start == end) { + return ranges; + } else if (start > end) { + throw new IllegalArgumentException(); + } + + long[] res = EmptyArray.LONG; + for (int i = 0; i < ranges.length; i += 2) { + if (start <= ranges[i] && end >= ranges[i + 1]) { + // Range entirely covered; remove it + } else if (start >= ranges[i] && end <= ranges[i + 1]) { + // Range partially covered; punch a hole + res = Arrays.copyOf(res, res.length + 4); + res[res.length - 4] = ranges[i]; + res[res.length - 3] = start; + res[res.length - 2] = end; + res[res.length - 1] = ranges[i + 1]; + } else { + // Range might covered; adjust edges if needed + res = Arrays.copyOf(res, res.length + 2); + if (end >= ranges[i] && end <= ranges[i + 1]) { + res[res.length - 2] = Math.max(ranges[i], end); + } else { + res[res.length - 2] = ranges[i]; + } + if (start >= ranges[i] && start <= ranges[i + 1]) { + res[res.length - 1] = Math.min(ranges[i + 1], start); + } else { + res[res.length - 1] = ranges[i + 1]; + } + } + } + return res; } private final ProxyFileDescriptorCallback mCallback = new ProxyFileDescriptorCallback() { @@ -126,7 +175,24 @@ public class RedactingFileDescriptor { @Override public int onWrite(long offset, int size, byte[] data) throws ErrnoException { - throw new ErrnoException(TAG, OsConstants.EBADF); + int n = 0; + while (n < size) { + try { + final int res = Os.pwrite(mInner, data, n, size - n, offset + n); + if (res == 0) { + break; + } else { + n += res; + } + } catch (InterruptedIOException e) { + n += e.bytesTransferred; + } + } + + // Clear any relevant redaction ranges before returning, since the + // writer should have access to see the data they just overwrote + mRedactRanges = removeRange(mRedactRanges, offset, offset + n); + return n; } @Override diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 92b316914d82..abfcfaacde21 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2463,6 +2463,27 @@ public class UserManager { } /** + * Get the parent of a user profile. + * + * @param user the handle of the user profile + * + * @return the parent of the user or {@code null} if the user is not profile + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + public @Nullable UserHandle getProfileParent(@NonNull UserHandle user) { + UserInfo info = getProfileParent(user.getIdentifier()); + + if (info == null) { + return null; + } + + return UserHandle.of(info.id); + } + + /** * Enables or disables quiet mode for a managed profile. If quiet mode is enabled, apps in a * managed profile don't run, generate notifications, or consume data or battery. * <p> diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index b42f1c4df4e3..8e11d858128a 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -25,6 +25,7 @@ import android.annotation.SdkConstant; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.annotation.WorkerThread; import android.app.Activity; @@ -1533,6 +1534,12 @@ public class StorageManager { return SystemProperties.getBoolean(PROP_HAS_ADOPTABLE, false); } + /** {@hide} */ + @TestApi + public static boolean hasIsolatedStorage() { + return SystemProperties.getBoolean(PROP_ISOLATED_STORAGE, false); + } + /** * @deprecated disabled now that FUSE has been replaced by sdcardfs * @hide diff --git a/core/java/android/provider/FontsContract.java b/core/java/android/provider/FontsContract.java index 76607e9d42d2..8e37559001db 100644 --- a/core/java/android/provider/FontsContract.java +++ b/core/java/android/provider/FontsContract.java @@ -660,7 +660,22 @@ public class FontsContract { if (familyBuilder == null) { return null; } - return new Typeface.CustomFallbackBuilder(familyBuilder.build()).build(); + + final FontFamily family = familyBuilder.build(); + + final FontStyle normal = new FontStyle(FontStyle.FONT_WEIGHT_NORMAL, + FontStyle.FONT_SLANT_UPRIGHT); + Font bestFont = family.getFont(0); + int bestScore = normal.getMatchScore(bestFont.getStyle()); + for (int i = 1; i < family.getSize(); ++i) { + final Font candidate = family.getFont(i); + final int score = normal.getMatchScore(candidate.getStyle()); + if (score < bestScore) { + bestFont = candidate; + bestScore = score; + } + } + return new Typeface.CustomFallbackBuilder(family).setStyle(bestFont.getStyle()).build(); } /** diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index e0e4fe29f48a..9e26a368ad80 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -121,6 +121,8 @@ public final class MediaStore { public static final String PARAM_INCLUDE_PENDING = "includePending"; /** {@hide} */ public static final String PARAM_PROGRESS = "progress"; + /** {@hide} */ + public static final String PARAM_REQUIRE_ORIGINAL = "requireOriginal"; /** * Activity Action: Launch a music player. @@ -478,6 +480,24 @@ public final class MediaStore { } /** + * Update the given {@link Uri} to indicate that the caller requires the + * original file contents when calling + * {@link ContentResolver#openFileDescriptor(Uri, String)}. + * <p> + * This can be useful when the caller wants to ensure they're backing up the + * exact bytes of the underlying media, without any Exif redaction being + * performed. + * <p> + * If the original file contents cannot be provided, a + * {@link UnsupportedOperationException} will be thrown when the returned + * {@link Uri} is used, such as when the caller doesn't hold + * {@link android.Manifest.permission#ACCESS_MEDIA_LOCATION}. + */ + public static @NonNull Uri setRequireOriginal(@NonNull Uri uri) { + return uri.buildUpon().appendQueryParameter(PARAM_REQUIRE_ORIGINAL, "1").build(); + } + + /** * Create a new pending media item using the given parameters. Pending items * are expected to have a short lifetime, and owners should either * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a @@ -1673,6 +1693,12 @@ public final class MediaStore { public static final String IS_NOTIFICATION = "is_notification"; /** + * Non-zero if the audio file is an audiobook + * <P>Type: INTEGER (boolean)</P> + */ + public static final String IS_AUDIOBOOK = "is_audiobook"; + + /** * The genre of the audio file, if any * <P>Type: TEXT</P> * Does not exist in the database - only used by the media scanner for inserts. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index d7729037bfad..c297ef4f2a85 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5075,6 +5075,7 @@ public final class Settings { * @hide */ @SystemApi + @TestApi @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) public static void resetToDefaults(@NonNull ContentResolver resolver, @Nullable String tag) { @@ -8232,6 +8233,24 @@ public final class Settings { public static final String NOTIFICATION_NEW_INTERRUPTION_MODEL = "new_interruption_model"; /** + * How often to check for location access. + * @hide + */ + @SystemApi + @TestApi + public static final String LOCATION_ACCESS_CHECK_INTERVAL_MILLIS = + "location_access_check_interval_millis"; + + /** + * Delay between granting location access and checking it. + * @hide + */ + @SystemApi + @TestApi + public static final String LOCATION_ACCESS_CHECK_DELAY_MILLIS = + "location_access_check_delay_millis"; + + /** * This are the settings to be backed up. * * NOTE: Settings are backed up and restored in the order they appear diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING new file mode 100644 index 000000000000..8e67ce7b2aa9 --- /dev/null +++ b/core/java/android/provider/TEST_MAPPING @@ -0,0 +1,12 @@ +{ + "presubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "android.provider.SettingsBackupTest" + } + ] + } + ] +} diff --git a/core/java/android/service/intelligence/ContentCaptureEventsRequest.aidl b/core/java/android/service/intelligence/ContentCaptureEventsRequest.aidl new file mode 100644 index 000000000000..23d607d9838c --- /dev/null +++ b/core/java/android/service/intelligence/ContentCaptureEventsRequest.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.intelligence; + +parcelable ContentCaptureEventsRequest; diff --git a/core/java/android/service/intelligence/ContentCaptureEventsRequest.java b/core/java/android/service/intelligence/ContentCaptureEventsRequest.java new file mode 100644 index 000000000000..bc5b92bce999 --- /dev/null +++ b/core/java/android/service/intelligence/ContentCaptureEventsRequest.java @@ -0,0 +1,73 @@ +/* + * 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.service.intelligence; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.intelligence.ContentCaptureEvent; + +import java.util.List; + +/** + * Batch of content capture events. + * + * @hide + */ +@SystemApi +public final class ContentCaptureEventsRequest implements Parcelable { + + private final List<ContentCaptureEvent> mEvents; + + /** @hide */ + public ContentCaptureEventsRequest(@NonNull List<ContentCaptureEvent> events) { + mEvents = events; + } + + /** + * Gets the events. + */ + @NonNull + public List<ContentCaptureEvent> getEvents() { + return mEvents; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeTypedList(mEvents, flags); + } + + public static final Parcelable.Creator<ContentCaptureEventsRequest> CREATOR = + new Parcelable.Creator<ContentCaptureEventsRequest>() { + + @Override + public ContentCaptureEventsRequest createFromParcel(Parcel parcel) { + return new ContentCaptureEventsRequest(parcel + .createTypedArrayList(ContentCaptureEvent.CREATOR)); + } + + @Override + public ContentCaptureEventsRequest[] newArray(int size) { + return new ContentCaptureEventsRequest[size]; + } + }; +} diff --git a/core/java/android/service/intelligence/FillController.java b/core/java/android/service/intelligence/FillController.java index c5e1242842bb..4a9c85de1fa2 100644 --- a/core/java/android/service/intelligence/FillController.java +++ b/core/java/android/service/intelligence/FillController.java @@ -15,12 +15,12 @@ */ package android.service.intelligence; -import static android.service.intelligence.IntelligenceService.DEBUG; +import static android.service.intelligence.SmartSuggestionsService.DEBUG; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.RemoteException; -import android.service.intelligence.IntelligenceService.AutofillProxy; +import android.service.intelligence.SmartSuggestionsService.AutofillProxy; import android.util.Log; import android.util.Pair; import android.view.autofill.AutofillId; diff --git a/core/java/android/service/intelligence/FillRequest.java b/core/java/android/service/intelligence/FillRequest.java index 95e922487906..f68db9df6fe3 100644 --- a/core/java/android/service/intelligence/FillRequest.java +++ b/core/java/android/service/intelligence/FillRequest.java @@ -18,7 +18,7 @@ package android.service.intelligence; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; -import android.service.intelligence.IntelligenceService.AutofillProxy; +import android.service.intelligence.SmartSuggestionsService.AutofillProxy; import android.view.autofill.AutofillId; /** diff --git a/core/java/android/service/intelligence/FillWindow.java b/core/java/android/service/intelligence/FillWindow.java index 4ea07bfc86cf..309f6a1b6f1b 100644 --- a/core/java/android/service/intelligence/FillWindow.java +++ b/core/java/android/service/intelligence/FillWindow.java @@ -15,7 +15,7 @@ */ package android.service.intelligence; -import static android.service.intelligence.IntelligenceService.DEBUG; +import static android.service.intelligence.SmartSuggestionsService.DEBUG; import android.annotation.LongDef; import android.annotation.NonNull; diff --git a/core/java/android/service/intelligence/IIntelligenceService.aidl b/core/java/android/service/intelligence/IIntelligenceService.aidl index e2260d7d3d76..d6b31079e34c 100644 --- a/core/java/android/service/intelligence/IIntelligenceService.aidl +++ b/core/java/android/service/intelligence/IIntelligenceService.aidl @@ -17,6 +17,7 @@ package android.service.intelligence; import android.os.IBinder; +import android.service.intelligence.ContentCaptureEventsRequest; import android.service.intelligence.InteractionSessionId; import android.service.intelligence.InteractionContext; import android.service.intelligence.SnapshotData; @@ -26,19 +27,19 @@ import android.view.intelligence.ContentCaptureEvent; import java.util.List; - /** * Interface from the system to an intelligence service. * * @hide */ + // TODO(b/111276913): rename / update javadoc (once the final name is defined) oneway interface IIntelligenceService { // Called when session is created (context not null) or destroyed (context null) void onSessionLifecycle(in InteractionContext context, in InteractionSessionId sessionId); - void onContentCaptureEvents(in InteractionSessionId sessionId, - in List<ContentCaptureEvent> events); + void onContentCaptureEventsRequest(in InteractionSessionId sessionId, + in ContentCaptureEventsRequest request); void onActivitySnapshot(in InteractionSessionId sessionId, in SnapshotData snapshotData); diff --git a/core/java/android/service/intelligence/InteractionContext.java b/core/java/android/service/intelligence/InteractionContext.java index 0cc377bb030b..7f4283db28d2 100644 --- a/core/java/android/service/intelligence/InteractionContext.java +++ b/core/java/android/service/intelligence/InteractionContext.java @@ -37,7 +37,7 @@ public final class InteractionContext implements Parcelable { /** * Flag used to indicate that the app explicitly disabled content capture for the activity * (using - * {@link android.view.intelligence.IntelligenceManager#setContentCaptureEnabled()}), + * {@link android.view.intelligence.ContentCaptureManager#setContentCaptureEnabled()}), * in which case the service will just receive activity-level events. */ public static final int FLAG_DISABLED_BY_APP = 0x1; diff --git a/core/java/android/service/intelligence/PresentationParams.java b/core/java/android/service/intelligence/PresentationParams.java index c59069bbb674..95303099793a 100644 --- a/core/java/android/service/intelligence/PresentationParams.java +++ b/core/java/android/service/intelligence/PresentationParams.java @@ -20,7 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.graphics.Rect; -import android.service.intelligence.IntelligenceService.AutofillProxy; +import android.service.intelligence.SmartSuggestionsService.AutofillProxy; import android.util.DebugUtils; import android.view.View; diff --git a/core/java/android/service/intelligence/IntelligenceService.java b/core/java/android/service/intelligence/SmartSuggestionsService.java index 040e25e977e4..0e29e70ecc3b 100644 --- a/core/java/android/service/intelligence/IntelligenceService.java +++ b/core/java/android/service/intelligence/SmartSuggestionsService.java @@ -19,8 +19,10 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa import android.annotation.CallSuper; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.app.Service; +import android.content.ComponentName; import android.content.Intent; import android.graphics.Rect; import android.os.CancellationSignal; @@ -43,31 +45,31 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.Set; /** - * A service used to capture the content of the screen. - * - * <p>The data collected by this service can be analyzed and combined with other sources to provide - * contextual data in other areas of the system such as Autofill. + * A service used to capture the content of the screen to provide contextual data in other areas of + * the system such as Autofill. * * @hide */ @SystemApi -public abstract class IntelligenceService extends Service { +public abstract class SmartSuggestionsService extends Service { - private static final String TAG = "IntelligenceService"; + private static final String TAG = "SmartSuggestionsService"; // TODO(b/111330312): STOPSHIP use dynamic value, or change to false static final boolean DEBUG = true; + static final boolean VERBOSE = false; /** * The {@link Intent} that must be declared as handled by the service. * To be supported, the service must also require the - * {@link android.Manifest.permission#BIND_INTELLIGENCE_SERVICE} permission so + * {@link android.Manifest.permission#BIND_SMART_SUGGESTIONS_SERVICE} permission so * that other applications can not abuse it. */ public static final String SERVICE_INTERFACE = - "android.service.intelligence.IntelligenceService"; + "android.service.intelligence.SmartSuggestionsService"; private Handler mHandler; @@ -80,21 +82,21 @@ public abstract class IntelligenceService extends Service { throws RemoteException { if (context != null) { mHandler.sendMessage( - obtainMessage(IntelligenceService::onCreateInteractionSession, - IntelligenceService.this, context, sessionId)); + obtainMessage(SmartSuggestionsService::onCreateInteractionSession, + SmartSuggestionsService.this, context, sessionId)); } else { mHandler.sendMessage( - obtainMessage(IntelligenceService::onDestroyInteractionSession, - IntelligenceService.this, sessionId)); + obtainMessage(SmartSuggestionsService::onDestroyInteractionSession, + SmartSuggestionsService.this, sessionId)); } } @Override - public void onContentCaptureEvents(InteractionSessionId sessionId, - List<ContentCaptureEvent> events) { + public void onContentCaptureEventsRequest(InteractionSessionId sessionId, + ContentCaptureEventsRequest request) { mHandler.sendMessage( - obtainMessage(IntelligenceService::onContentCaptureEvent, - IntelligenceService.this, sessionId, events)); + obtainMessage(SmartSuggestionsService::onContentCaptureEventsRequest, + SmartSuggestionsService.this, sessionId, request)); } @@ -102,22 +104,22 @@ public abstract class IntelligenceService extends Service { public void onActivitySnapshot(InteractionSessionId sessionId, SnapshotData snapshotData) { mHandler.sendMessage( - obtainMessage(IntelligenceService::onActivitySnapshot, - IntelligenceService.this, sessionId, snapshotData)); + obtainMessage(SmartSuggestionsService::onActivitySnapshot, + SmartSuggestionsService.this, sessionId, snapshotData)); } @Override public void onAutofillRequest(InteractionSessionId sessionId, IBinder client, int autofilSessionId, AutofillId focusedId) { - mHandler.sendMessage(obtainMessage(IntelligenceService::handleOnAutofillRequest, - IntelligenceService.this, sessionId, client, autofilSessionId, focusedId)); + mHandler.sendMessage(obtainMessage(SmartSuggestionsService::handleOnAutofillRequest, + SmartSuggestionsService.this, sessionId, client, autofilSessionId, focusedId)); } @Override public void onDestroyAutofillWindowsRequest(InteractionSessionId sessionId) { mHandler.sendMessage( - obtainMessage(IntelligenceService::handleOnDestroyAutofillWindowsRequest, - IntelligenceService.this, sessionId)); + obtainMessage(SmartSuggestionsService::handleOnDestroyAutofillWindowsRequest, + SmartSuggestionsService.this, sessionId)); } }; @@ -139,25 +141,92 @@ public abstract class IntelligenceService extends Service { } /** + * Explicitly limits content capture to the given packages and activities. + * + * <p>When the whitelist is set, it overrides the values passed to + * {@link #setActivityContentCaptureEnabled(ComponentName, boolean)} + * and {@link #setPackageContentCaptureEnabled(String, boolean)}. + * + * <p>To reset the whitelist, call it passing {@code null} to both arguments. + * + * <p>Useful when the service wants to restrict content capture to a category of apps, like + * chat apps. For example, if the service wants to support view captures on all activities of + * app {@code ChatApp1} and just activities {@code act1} and {@code act2} of {@code ChatApp2}, + * it would call: {@code setContentCaptureWhitelist(Arrays.asList("ChatApp1"), + * Arrays.asList(new ComponentName("ChatApp2", "act1"), + * new ComponentName("ChatApp2", "act2")));} + */ + public final void setContentCaptureWhitelist(@Nullable List<String> packages, + @Nullable List<ComponentName> activities) { + //TODO(b/111276913): implement + } + + /** + * Defines whether content capture should be enabled for activities with such + * {@link android.content.ComponentName}. + * + * <p>Useful to blacklist a particular activity. + */ + public final void setActivityContentCaptureEnabled(@NonNull ComponentName activity, + boolean enabled) { + //TODO(b/111276913): implement + } + + /** + * Defines whether content capture should be enabled for activities of the app with such + * {@code packageName}. + * + * <p>Useful to blacklist any activity from a particular app. + */ + public final void setPackageContentCaptureEnabled(@NonNull String packageName, + boolean enabled) { + //TODO(b/111276913): implement + } + + /** + * Gets the activities where content capture was disabled by + * {@link #setActivityContentCaptureEnabled(ComponentName, boolean)}. + */ + @NonNull + public final Set<ComponentName> getContentCaptureDisabledActivities() { + //TODO(b/111276913): implement + return null; + } + + /** + * Gets the apps where content capture was disabled by + * {@link #setPackageContentCaptureEnabled(String, boolean)}. + */ + @NonNull + public final Set<String> getContentCaptureDisabledPackages() { + //TODO(b/111276913): implement + return null; + } + + /** * Creates a new interaction session. * * @param context interaction context * @param sessionId the session's Id */ public void onCreateInteractionSession(@NonNull InteractionContext context, - @NonNull InteractionSessionId sessionId) {} + @NonNull InteractionSessionId sessionId) { + if (VERBOSE) { + Log.v(TAG, "onCreateInteractionSession(id=" + sessionId + ", ctx=" + context + ")"); + } + } /** * Notifies the service of {@link ContentCaptureEvent events} associated with a content capture * session. * * @param sessionId the session's Id - * @param events the events + * @param request the events */ // TODO(b/111276913): rename to onContentCaptureEvents or something like that; also, pass a // Request object so it can be extended - public abstract void onContentCaptureEvent(@NonNull InteractionSessionId sessionId, - @NonNull List<ContentCaptureEvent> events); + public abstract void onContentCaptureEventsRequest(@NonNull InteractionSessionId sessionId, + @NonNull ContentCaptureEventsRequest request); private void handleOnAutofillRequest(@NonNull InteractionSessionId sessionId, @NonNull IBinder client, int autofillSessionId, @NonNull AutofillId focusedId) { @@ -242,8 +311,7 @@ public abstract class IntelligenceService extends Service { } /** - * Notifies the service of {@link IntelligenceSnapshotData snapshot data} associated with a - * session. + * Notifies the service of {@link SnapshotData snapshot data} associated with a session. * * @param sessionId the session's Id * @param snapshotData the data @@ -256,7 +324,11 @@ public abstract class IntelligenceService extends Service { * * @param sessionId the id of the session to destroy */ - public void onDestroyInteractionSession(@NonNull InteractionSessionId sessionId) {} + public void onDestroyInteractionSession(@NonNull InteractionSessionId sessionId) { + if (VERBOSE) { + Log.v(TAG, "onDestroyInteractionSession(id=" + sessionId + ")"); + } + } /** @hide */ static final class AutofillProxy { diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 8371c31b5ac5..0e2ae837a141 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -806,7 +806,7 @@ public class ZenModeConfig implements Parcelable { if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CALLS, isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CALLS, defaultPolicy))) { priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS; - messageSenders = getNotificationPolicySenders(zenPolicy.getPriorityCallSenders()); + callSenders = getNotificationPolicySenders(zenPolicy.getPriorityCallSenders()); } if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS, diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java index 43ab8dc07000..194147c2c154 100644 --- a/core/java/android/service/notification/ZenPolicy.java +++ b/core/java/android/service/notification/ZenPolicy.java @@ -859,6 +859,27 @@ public final class ZenPolicy implements Parcelable { /** * @hide */ + public boolean areValuesSet() { + return getPriorityCategoryReminders() != STATE_UNSET + || getPriorityCategoryEvents() != STATE_UNSET + || getPriorityCategoryMessages() != STATE_UNSET + || getPriorityCategoryCalls() != STATE_UNSET + || getPriorityCategoryRepeatCallers() != STATE_UNSET + || getPriorityCategoryAlarms() != STATE_UNSET + || getPriorityCategoryMedia() != STATE_UNSET + || getPriorityCategorySystem() != STATE_UNSET + || getVisualEffectFullScreenIntent() != STATE_UNSET + || getVisualEffectLights() != STATE_UNSET + || getVisualEffectPeek() != STATE_UNSET + || getVisualEffectStatusBar() != STATE_UNSET + || getVisualEffectBadge() != STATE_UNSET + || getVisualEffectAmbient() != STATE_UNSET + || getVisualEffectNotificationList() != STATE_UNSET; + } + + /** + * @hide + */ public void writeToProto(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); diff --git a/core/java/android/service/oemlock/IOemLockService.aidl b/core/java/android/service/oemlock/IOemLockService.aidl index d5e10d6629ca..99cffc5c5b8b 100644 --- a/core/java/android/service/oemlock/IOemLockService.aidl +++ b/core/java/android/service/oemlock/IOemLockService.aidl @@ -22,6 +22,8 @@ package android.service.oemlock; * @hide */ interface IOemLockService { + String getLockName(); + void setOemUnlockAllowedByCarrier(boolean allowed, in byte[] signature); boolean isOemUnlockAllowedByCarrier(); diff --git a/core/java/android/service/oemlock/OemLockManager.java b/core/java/android/service/oemlock/OemLockManager.java index f0d660354167..029d645dda19 100644 --- a/core/java/android/service/oemlock/OemLockManager.java +++ b/core/java/android/service/oemlock/OemLockManager.java @@ -44,6 +44,23 @@ public class OemLockManager { } /** + * Returns a vendor specific name for the OEM lock. + * + * This value is used to identify the security protocol used by locks. + * + * @return The name of the OEM lock or {@code null} if failed to get the name. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_CARRIER_OEM_UNLOCK_STATE) + @Nullable + public String getLockName() { + try { + return mService.getLockName(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Sets whether the carrier has allowed this device to be OEM unlocked. * * Depending on the implementation, the validity of the request might need to be proved. This diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java index be47320b59bd..433483f717fa 100644 --- a/core/java/android/text/style/SuggestionSpan.java +++ b/core/java/android/text/style/SuggestionSpan.java @@ -369,10 +369,7 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { /** * @return The color of the underline for that span, or 0 if there is no underline - * - * @hide */ - @UnsupportedAppUsage public int getUnderlineColor() { // The order here should match what is used in updateDrawState final boolean misspelled = (mFlags & FLAG_MISSPELLED) != 0; diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 5f348c4a73ed..dee6d908c4c5 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -45,7 +45,6 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put("settings_systemui_theme", "true"); DEFAULT_FLAGS.put("settings_dynamic_homepage", "true"); DEFAULT_FLAGS.put("settings_mobile_network_v2", "true"); - DEFAULT_FLAGS.put("settings_data_usage_v2", "true"); DEFAULT_FLAGS.put("settings_seamless_transfer", "false"); DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false"); DEFAULT_FLAGS.put("settings_network_and_internet_v2", "false"); diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index af41b6942a5e..5e6d3d19fa2f 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -25,6 +25,7 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.DisplayCutout; import android.view.InsetsState; +import android.view.InsetsSourceControl; import com.android.internal.os.IResultReceiver; import android.util.MergedConfiguration; @@ -60,6 +61,11 @@ oneway interface IWindow { */ void insetsChanged(in InsetsState insetsState); + /** + * Called when this window retrieved control over a specified set of inset sources. + */ + void insetsControlChanged(in InsetsState insetsState, in InsetsSourceControl[] activeControls); + void moved(int newX, int newY); void dispatchAppVisibility(boolean visible); void dispatchGetNewSurface(); diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index dace388b66f6..c4be0e504c5b 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -304,7 +304,7 @@ interface IWindowManager /** * Get the position of the nav bar */ - int getNavBarPosition(); + int getNavBarPosition(int displayId); /** * Lock the device immediately with the specified options (can be null). @@ -551,4 +551,16 @@ interface IWindowManager * @see KeyguardManager#isDeviceLocked() */ void setShouldShowIme(int displayId, boolean shouldShow); + + /** + * Reparent the top layers for a display to the requested surfaceControl. The display that + * is going to be re-parented (the displayId passed in) needs to have been created by the same + * process that is requesting the re-parent. This is to ensure clients can't just re-parent + * display content info to any SurfaceControl, as this would be a security issue. + * + * @param displayId The id of the display. + * @param surfaceControlHandle The SurfaceControl handle that the top level layers for the + * display should be re-parented to. + */ + void reparentDisplayContent(int displayId, in IBinder surfaceControlHandle); } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 7841d0417a2b..ba5340c826d3 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -16,17 +16,30 @@ package android.view; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.Rect; +import android.util.ArraySet; +import android.util.SparseArray; +import android.view.SurfaceControl.Transaction; +import android.view.WindowInsets.Type.InsetType; +import android.view.InsetsState.InternalInsetType; + +import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; /** * Implements {@link WindowInsetsController} on the client. + * @hide */ -class InsetsController { +public class InsetsController implements WindowInsetsController { private final InsetsState mState = new InsetsState(); private final Rect mFrame = new Rect(); + private final SparseArray<InsetsSourceConsumer> mSourceConsumers = new SparseArray<>(); + + private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>(); void onFrameChanged(Rect frame) { mFrame.set(frame); @@ -48,6 +61,61 @@ class InsetsController { return mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeNavBar, cutout); } + /** + * Called when the server has dispatched us a new set of inset controls. + */ + public void onControlsChanged(InsetsSourceControl[] activeControls) { + if (activeControls != null) { + for (InsetsSourceControl activeControl : activeControls) { + mTmpControlArray.put(activeControl.getType(), activeControl); + } + } + + // Ensure to update all existing source consumers + for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { + final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); + final InsetsSourceControl control = mTmpControlArray.get(consumer.getType()); + + // control may be null, but we still need to update the control to null if it got + // revoked. + consumer.setControl(control); + } + + // Ensure to create source consumers if not available yet. + for (int i = mTmpControlArray.size() - 1; i >= 0; i--) { + final InsetsSourceControl control = mTmpControlArray.valueAt(i); + getSourceConsumer(control.getType()).setControl(control); + } + mTmpControlArray.clear(); + } + + @Override + public void show(@InsetType int types) { + final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); + for (int i = internalTypes.size() - 1; i >= 0; i--) { + getSourceConsumer(internalTypes.valueAt(i)).show(); + } + } + + @Override + public void hide(@InsetType int types) { + final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); + for (int i = internalTypes.size() - 1; i >= 0; i--) { + getSourceConsumer(internalTypes.valueAt(i)).hide(); + } + } + + @VisibleForTesting + public @NonNull InsetsSourceConsumer getSourceConsumer(@InternalInsetType int type) { + InsetsSourceConsumer controller = mSourceConsumers.get(type); + if (controller != null) { + return controller; + } + controller = new InsetsSourceConsumer(type, mState, Transaction::new); + mSourceConsumers.put(type, controller); + return controller; + } + void dump(String prefix, PrintWriter pw) { pw.println(prefix); pw.println("InsetsController:"); mState.dump(prefix + " ", pw); diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java new file mode 100644 index 000000000000..e74aa8dfcf4e --- /dev/null +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.view; + +import android.annotation.Nullable; +import android.view.SurfaceControl.Transaction; +import android.view.InsetsState.InternalInsetType; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.function.Supplier; + +/** + * Controls the visibility and animations of a single window insets source. + * @hide + */ +public class InsetsSourceConsumer { + + private final Supplier<Transaction> mTransactionSupplier; + private final @InternalInsetType int mType; + private final InsetsState mState; + private @Nullable InsetsSourceControl mControl; + private boolean mHidden; + + public InsetsSourceConsumer(@InternalInsetType int type, InsetsState state, + Supplier<Transaction> transactionSupplier) { + mType = type; + mState = state; + mTransactionSupplier = transactionSupplier; + } + + public void setControl(@Nullable InsetsSourceControl control) { + if (mControl == control) { + return; + } + mControl = control; + applyHiddenToControl(); + } + + @VisibleForTesting + public InsetsSourceControl getControl() { + return mControl; + } + + int getType() { + return mType; + } + + @VisibleForTesting + public void show() { + setHidden(false); + } + + @VisibleForTesting + public void hide() { + setHidden(true); + } + + private void setHidden(boolean hidden) { + if (mHidden == hidden) { + return; + } + mHidden = hidden; + applyHiddenToControl(); + } + + private void applyHiddenToControl() { + if (mControl == null) { + return; + } + + // TODO: Animation + final Transaction t = mTransactionSupplier.get(); + if (mHidden) { + t.hide(mControl.getLeash()); + } else { + t.show(mControl.getLeash()); + } + t.apply(); + } +} diff --git a/core/java/android/view/InsetsSourceControl.aidl b/core/java/android/view/InsetsSourceControl.aidl new file mode 100644 index 000000000000..755bf456658d --- /dev/null +++ b/core/java/android/view/InsetsSourceControl.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +parcelable InsetsSourceControl; diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java new file mode 100644 index 000000000000..9383e6c57854 --- /dev/null +++ b/core/java/android/view/InsetsSourceControl.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.graphics.Point; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.InsetsState.InternalInsetType; + +/** + * Represents a parcelable object to allow controlling a single {@link InsetsSource}. + * @hide + */ +public class InsetsSourceControl implements Parcelable { + + private final @InternalInsetType int mType; + private final SurfaceControl mLeash; + + public InsetsSourceControl(@InternalInsetType int type, SurfaceControl leash) { + mType = type; + mLeash = leash; + } + + public int getType() { + return mType; + } + + public SurfaceControl getLeash() { + return mLeash; + } + + public InsetsSourceControl(Parcel in) { + mType = in.readInt(); + mLeash = in.readParcelable(null /* loader */); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mType); + dest.writeParcelable(mLeash, 0 /* flags*/); + } + + public static final Creator<InsetsSourceControl> CREATOR + = new Creator<InsetsSourceControl>() { + public InsetsSourceControl createFromParcel(Parcel in) { + return new InsetsSourceControl(in); + } + + public InsetsSourceControl[] newArray(int size) { + return new InsetsSourceControl[size]; + } + }; +} diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 9895adcad23a..689b14fe29c6 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -22,6 +22,9 @@ import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; import android.util.ArrayMap; +import android.util.ArraySet; +import android.view.WindowInsets.Type; +import android.view.WindowInsets.Type.InsetType; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -148,6 +151,22 @@ public class InsetsState implements Parcelable { } } + public static @InternalInsetType ArraySet<Integer> toInternalType(@InsetType int insetTypes) { + final ArraySet<Integer> result = new ArraySet<>(); + if ((insetTypes & Type.TOP_BAR) != 0) { + result.add(TYPE_TOP_BAR); + } + if ((insetTypes & Type.SIDE_BARS) != 0) { + result.add(TYPE_SIDE_BAR_1); + result.add(TYPE_SIDE_BAR_2); + result.add(TYPE_SIDE_BAR_3); + } + if ((insetTypes & Type.IME) != 0) { + result.add(TYPE_IME); + } + return result; + } + public void dump(String prefix, PrintWriter pw) { pw.println(prefix + "InsetsState"); for (int i = mSources.size() - 1; i >= 0; i--) { diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 46f396a0b66b..ab010855b896 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -104,6 +104,8 @@ public class SurfaceControl implements Parcelable { int flags, int mask); private static native void nativeSetWindowCrop(long transactionObj, long nativeObject, int l, int t, int r, int b); + private static native void nativeSetCornerRadius(long transactionObj, long nativeObject, + float cornerRadius); private static native void nativeSetLayerStack(long transactionObj, long nativeObject, int layerStack); @@ -1006,6 +1008,18 @@ public class SurfaceControl implements Parcelable { } } + /** + * Sets the corner radius of a {@link SurfaceControl}. + * + * @param cornerRadius Corner radius in pixels. + */ + public void setCornerRadius(float cornerRadius) { + checkNotReleased(); + synchronized (SurfaceControl.class) { + sGlobalTransaction.setCornerRadius(this, cornerRadius); + } + } + public void setLayerStack(int layerStack) { checkNotReleased(); synchronized(SurfaceControl.class) { @@ -1529,6 +1543,20 @@ public class SurfaceControl implements Parcelable { return this; } + /** + * Sets the corner radius of a {@link SurfaceControl}. + * @param sc SurfaceControl + * @param cornerRadius Corner radius in pixels. + * @return Itself. + */ + @UnsupportedAppUsage + public Transaction setCornerRadius(SurfaceControl sc, float cornerRadius) { + sc.checkNotReleased(); + nativeSetCornerRadius(mNativeObject, sc.mNativeObject, cornerRadius); + + return this; + } + @UnsupportedAppUsage public Transaction setLayerStack(SurfaceControl sc, int layerStack) { sc.checkNotReleased(); diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 3c4ce8f7cfad..797d1c5f47aa 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1126,6 +1126,13 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb } }; + /** + * @hide + */ + public SurfaceControl getSurfaceControl() { + return mSurfaceControl; + } + class SurfaceControlWithBackground extends SurfaceControl { SurfaceControl mBackgroundControl; private boolean mOpaque = true; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 2767505aece3..4b9cbff8c161 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -112,7 +112,7 @@ import android.view.autofill.AutofillValue; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; -import android.view.intelligence.IntelligenceManager; +import android.view.intelligence.ContentCaptureManager; import android.widget.Checkable; import android.widget.FrameLayout; import android.widget.ScrollBarDrawable; @@ -4159,7 +4159,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, float mAlpha = 1f; /** - * The opacity of the view as manipulated by the Fade transition. This is a hidden + * The opacity of the view as manipulated by the Fade transition. This is a * property only used by transitions, which is composited with the other alpha * values to calculate the final visual alpha value. */ @@ -8138,7 +8138,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * is visible. * * <p>The populated structure is then passed to the service through - * {@link IntelligenceManager#notifyViewAppeared(ViewStructure)}. + * {@link ContentCaptureManager#notifyViewAppeared(ViewStructure)}. * * <p><b>Note: </b>the following methods of the {@code structure} will be ignored: * <ul> @@ -8915,7 +8915,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Helper used to notify the {@link IntelligenceManager} when the view is removed or + * Helper used to notify the {@link ContentCaptureManager} when the view is removed or * added, based on whether it's laid out and visible, and without knowing if the parent removed * it from the view hierarchy. * @@ -8931,11 +8931,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * </ol> */ private void notifyAppearedOrDisappearedForContentCaptureIfNeeded(boolean appeared) { + // First check if context has client, so it saves a service lookup when it doesn't + if (!mContext.isContentCaptureSupported()) return; - final IntelligenceManager im = mContext.getSystemService(IntelligenceManager.class); - if (im == null || !im.isContentCaptureEnabled()) return; + // Then check if it's enabled in the context... + final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class); + if (cm == null || !cm.isContentCaptureEnabled()) return; - // NOTE: isImportantForContentCapture() is more expensive than im.isContentCaptureEnabled() + // ... and finally at the view level + // NOTE: isImportantForContentCapture() is more expensive than cm.isContentCaptureEnabled() if (!isImportantForContentCapture()) return; if (appeared) { @@ -8950,9 +8954,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; } // All good: notify the manager... - final ViewStructure structure = im.newViewStructure(this); + final ViewStructure structure = cm.newViewStructure(this); onProvideContentCaptureStructure(structure, /* flags= */ 0); - im.notifyViewAppeared(structure); + cm.notifyViewAppeared(structure); // ...and set the flags mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED; mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED; @@ -8969,7 +8973,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; } // All good: notify the manager... - im.notifyViewDisappeared(getAutofillId()); + cm.notifyViewDisappeared(getAutofillId()); // ...and set the flags mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED; mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED; @@ -10337,6 +10341,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Retrieves the single {@link WindowInsetsController} of the window this view is attached to. + * + * @return The {@link WindowInsetsController} or {@code null} if the view isn't attached to a + * a window. + * @hide pending unhide + */ + public @Nullable WindowInsetsController getWindowInsetsController() { + if (mAttachInfo != null) { + return mAttachInfo.mViewRootImpl.getInsetsController(); + } + return null; + } + + /** * @hide Compute the insets that should be consumed by this view and the ones * that should propagate to those under it. * @@ -15729,15 +15747,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * This property is hidden and intended only for use by the Fade transition, which - * animates it to produce a visual translucency that does not side-effect (or get - * affected by) the real alpha property. This value is composited with the other - * alpha value (and the AlphaAnimation value, when that is present) to produce - * a final visual translucency result, which is what is passed into the DisplayList. - * - * @hide + * This property is intended only for use by the Fade transition, which animates it + * to produce a visual translucency that does not side-effect (or get affected by) + * the real alpha property. This value is composited with the other alpha value + * (and the AlphaAnimation value, when that is present) to produce a final visual + * translucency result, which is what is passed into the DisplayList. */ - @UnsupportedAppUsage public void setTransitionAlpha(float alpha) { ensureTransformationInfo(); if (mTransformationInfo.mTransitionAlpha != alpha) { @@ -15760,16 +15775,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * This property is hidden and intended only for use by the Fade transition, which - * animates it to produce a visual translucency that does not side-effect (or get - * affected by) the real alpha property. This value is composited with the other - * alpha value (and the AlphaAnimation value, when that is present) to produce - * a final visual translucency result, which is what is passed into the DisplayList. - * - * @hide + * This property is intended only for use by the Fade transition, which animates + * it to produce a visual translucency that does not side-effect (or get affected + * by) the real alpha property. This value is composited with the other alpha + * value (and the AlphaAnimation value, when that is present) to produce a final + * visual translucency result, which is what is passed into the DisplayList. */ @ViewDebug.ExportedProperty(category = "drawing") - @UnsupportedAppUsage public float getTransitionAlpha() { return mTransformationInfo != null ? mTransformationInfo.mTransitionAlpha : 1; } @@ -16288,9 +16300,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - /** @hide */ - @UnsupportedAppUsage - public void setAnimationMatrix(Matrix matrix) { + /** + * Changes the transformation matrix on the view. This is used in animation frameworks, + * such as {@link android.transition.Transition}. When the animation finishes, the matrix + * should be cleared by calling this method with <code>null</code> as the matrix parameter. + * Application developers should use transformation methods like {@link #setRotation(float)}, + * {@link #setScaleX(float)}, {@link #setScaleX(float)}, {@link #setTranslationX(float)}} + * and {@link #setTranslationY(float)} (float)}} instead. + * + * @param matrix The matrix, null indicates that the matrix should be cleared. + */ + public void setAnimationMatrix(@Nullable Matrix matrix) { invalidateViewProperty(true, false); mRenderNode.setAnimationMatrix(matrix); invalidateViewProperty(false, true); @@ -21460,7 +21480,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * previous ones * {@hide} */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) protected boolean setFrame(int left, int top, int right, int bottom) { boolean changed = false; @@ -21537,7 +21557,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see #setLeft(int), #setRight(int), #setTop(int), #setBottom(int) */ - public void setLeftTopRightBottom(int left, int top, int right, int bottom) { + public final void setLeftTopRightBottom(int left, int top, int right, int bottom) { setFrame(left, top, right, bottom); } @@ -23545,6 +23565,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Get the identifier used for this view by the drawing system. + * + * @see RenderNode#getUniqueId() + * @return A long that uniquely identifies this view's drawing component + */ + public long getUniqueDrawingId() { + return mRenderNode.getUniqueId(); + } + + /** * Returns this view's tag. * * @return the Object stored in this view as a tag, or {@code null} if not diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 1e91aa87bfb7..741510e67130 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -7042,10 +7042,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * suppression is disabled with a later call to suppressLayout(false). * When layout suppression is disabled, a requestLayout() call is sent * if layout() was attempted while layout was being suppressed. - * - * @hide */ - @UnsupportedAppUsage public void suppressLayout(boolean suppress) { mSuppressLayout = suppress; if (!suppress) { @@ -7061,8 +7058,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * suppressed, due to an earlier call to {@link #suppressLayout(boolean)}. * * @return true if layout calls are currently suppressed, false otherwise. - * - * @hide */ public boolean isLayoutSuppressed() { return mSuppressLayout; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 937e23813cec..cb4788624935 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -170,7 +170,12 @@ public final class ViewRootImpl implements ViewParent, * fully migrated over. */ private static final String USE_NEW_INSETS_PROPERTY = "persist.wm.new_insets"; - private static final boolean USE_NEW_INSETS = + + /** + * @see #USE_NEW_INSETS_PROPERTY + * @hide + */ + public static final boolean USE_NEW_INSETS = SystemProperties.getBoolean(USE_NEW_INSETS_PROPERTY, false); /** @@ -1847,6 +1852,10 @@ public final class ViewRootImpl implements ViewParent, host.dispatchApplyWindowInsets(insets); } + InsetsController getInsetsController() { + return mInsetsController; + } + private static boolean shouldUseDisplaySize(final WindowManager.LayoutParams lp) { return lp.type == TYPE_STATUS_BAR_PANEL || lp.type == TYPE_INPUT_METHOD @@ -1935,7 +1944,6 @@ public final class ViewRootImpl implements ViewParent, // PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888 // However, windows are now always 32 bits by default, so choose 32 bits mAttachInfo.mUse32BitDrawingCache = true; - mAttachInfo.mHasWindowFocus = false; mAttachInfo.mWindowVisibility = viewVisibility; mAttachInfo.mRecomputeGlobalAttributes = false; mLastConfigurationFromResources.setTo(config); @@ -4208,6 +4216,7 @@ public final class ViewRootImpl implements ViewParent, private final static int MSG_POINTER_CAPTURE_CHANGED = 28; private final static int MSG_DRAW_FINISHED = 29; private final static int MSG_INSETS_CHANGED = 30; + private final static int MSG_INSETS_CONTROL_CHANGED = 31; final class ViewRootHandler extends Handler { @Override @@ -4371,11 +4380,22 @@ public final class ViewRootImpl implements ViewParent, case MSG_INSETS_CHANGED: mPendingInsets = (InsetsState) msg.obj; - // TODO: Full traversal not needed here + // TODO: Full traversal not needed here. + if (USE_NEW_INSETS) { + requestLayout(); + } + break; + case MSG_INSETS_CONTROL_CHANGED: { + SomeArgs args = (SomeArgs) msg.obj; + mPendingInsets = (InsetsState) args.arg1; + mInsetsController.onControlsChanged((InsetsSourceControl[]) args.arg2); + + // TODO: Full traversal not necessarily needed here. if (USE_NEW_INSETS) { requestLayout(); } break; + } case MSG_WINDOW_MOVED: if (mAdded) { final int w = mWinFrame.width(); @@ -7116,6 +7136,14 @@ public final class ViewRootImpl implements ViewParent, mHandler.obtainMessage(MSG_INSETS_CHANGED, insetsState).sendToTarget(); } + private void dispatchInsetsControlChanged(InsetsState insetsState, + InsetsSourceControl[] activeControls) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = insetsState; + args.arg2 = activeControls; + mHandler.obtainMessage(MSG_INSETS_CONTROL_CHANGED, args).sendToTarget(); + } + public void dispatchMoved(int newX, int newY) { if (DEBUG_LAYOUT) Log.v(mTag, "Window moved " + this + ": newX=" + newX + " newY=" + newY); if (mTranslator != null) { @@ -8187,6 +8215,15 @@ public final class ViewRootImpl implements ViewParent, } @Override + public void insetsControlChanged(InsetsState insetsState, + InsetsSourceControl[] activeControls) { + final ViewRootImpl viewAncestor = mViewAncestor.get(); + if (viewAncestor != null) { + viewAncestor.dispatchInsetsControlChanged(insetsState, activeControls); + } + } + + @Override public void moved(int newX, int newY) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index c1e94d8ff97e..58ab817c6faf 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -2410,4 +2410,11 @@ public abstract class Window { public boolean isCloseOnSwipeEnabled() { return mCloseOnSwipeEnabled; } + + /** + * @return The {@link WindowInsetsController} associated with this window + * @see View#getWindowInsetsController() + * @hide pending unhide + */ + public abstract @NonNull WindowInsetsController getInsetsController(); } diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index a8debbd623f5..572d33103cf4 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -18,13 +18,17 @@ package android.view; import android.annotation.NonNull; +import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.graphics.Insets; import android.graphics.Rect; +import android.view.inputmethod.InputMethod; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -807,4 +811,69 @@ public final class WindowInsets { mIsRound, mAlwaysConsumeNavBar, mDisplayCutout); } } + + /** + * Class that defines different types of sources causing window insets. + * @hide pending unhide + */ + public static final class Type { + + static final int TOP_BAR = 0x1; + static final int IME = 0x2; + static final int SIDE_BARS = 0x4; + static final int WINDOW_DECOR = 0x8; + + private Type() { + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = { TOP_BAR, IME, SIDE_BARS, WINDOW_DECOR }) + public @interface InsetType { + } + + /** + * @return An inset type representing the top bar of a window, which can be the status + * bar on handheld-like devices as well as a caption bar. + */ + public static @InsetType int topBar() { + return TOP_BAR; + } + + /** + * @return An inset type representing the window of an {@link InputMethod}. + */ + public static @InsetType int ime() { + return IME; + } + + /** + * @return An inset type representing any system bars that are not {@link #topBar()}. + */ + public static @InsetType int sideBars() { + return SIDE_BARS; + } + + /** + * @return An inset type representing decor that is being app-controlled. + */ + public static @InsetType int windowDecor() { + return WINDOW_DECOR; + } + + /** + * @return All system bars. Includes {@link #topBar()} as well as {@link #sideBars()}, but + * not {@link #ime()}. + */ + public static @InsetType int systemBars() { + return TOP_BAR | SIDE_BARS; + } + + /** + * @return All inset types combined. + */ + public static @InsetType int all() { + return 0xFFFFFFFF; + } + } } diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java new file mode 100644 index 000000000000..7be5f2e7a0b0 --- /dev/null +++ b/core/java/android/view/WindowInsetsController.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.view.WindowInsets.Type.InsetType; + +/** + * Interface to control windows that generate insets. + * + * TODO Needs more information and examples once the API is more baked. + * @hide pending unhide + */ +public interface WindowInsetsController { + + /** + * Makes a set of windows that cause insets appear on screen. + * <p> + * Note that if the window currently doesn't have control over a certain type, it will apply the + * change as soon as the window gains control. The app can listen to the event by observing + * {@link View#onApplyWindowInsets} and checking visibility with "TODO at method" in + * {@link WindowInsets}. + * + * @param types A bitmask of {@link WindowInsets.Type.InsetType} specifying what windows the app + * would like to make appear on screen. + */ + void show(@InsetType int types); + + /** + * Makes a set of windows causing insets disappear. + * <p> + * Note that if the window currently doesn't have control over a certain type, it will apply the + * change as soon as the window gains control. The app can listen to the event by observing + * {@link View#onApplyWindowInsets} and checking visibility with "TODO at method" in + * {@link WindowInsets}. + * + * @param types A bitmask of {@link WindowInsets.Type.InsetType} specifying what windows the app + * would like to make disappear. + */ + void hide(@InsetType int types); +} diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java index 260e93890e3e..16bafe2c4f11 100644 --- a/core/java/android/view/WindowManagerPolicyConstants.java +++ b/core/java/android/view/WindowManagerPolicyConstants.java @@ -44,6 +44,7 @@ public interface WindowManagerPolicyConstants { int PRESENCE_EXTERNAL = 1 << 1; // Navigation bar position values + int NAV_BAR_INVALID = -1; int NAV_BAR_LEFT = 1 << 0; int NAV_BAR_RIGHT = 1 << 1; int NAV_BAR_BOTTOM = 1 << 2; diff --git a/core/java/android/view/inspector/ChildTraverser.java b/core/java/android/view/inspector/ChildTraverser.java deleted file mode 100644 index b775de503d98..000000000000 --- a/core/java/android/view/inspector/ChildTraverser.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.inspector; - -import android.annotation.NonNull; - -/** - * Interface for visiting all the child nodes of an inspectable object. - * - * Inspectable objects may return a collection of children as an array, an {@link Iterable} or an - * {@link java.util.Iterator}. This provides a unified API for traversing across all the children - * of an inspectable node. - * - * This interface is consumed by {@link InspectionHelper#traverseChildren(Object, ChildTraverser)} - * and may be implemented as a lambda. - * - * @see InspectionHelper#traverseChildren(Object, ChildTraverser) - * @hide - */ -@FunctionalInterface -public interface ChildTraverser { - /** - * Visit one child object of a parent inspectable object. - * - * The iteration interface will filter null values out before passing them to this method, but - * some child objects may not be inspectable. It is up to the implementor to determine their - * inspectablity and what to do with them. - * - * @param child A child object, guaranteed not to be null. - */ - void traverseChild(@NonNull Object child); -} diff --git a/core/java/android/view/inspector/InspectableChildren.java b/core/java/android/view/inspector/InspectableChildren.java deleted file mode 100644 index de8fa296e8c5..000000000000 --- a/core/java/android/view/inspector/InspectableChildren.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.inspector; - -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -/** - * Marks a getter for an inspectable node's inspectable children. - * - * This annotation can be applied to any getter that returns a collection of objects, either an - * array, an {@link Iterable} or a {@link java.util.Iterator}. The getter may return null, which - * will be treated as an empty collection. Additionally, the inspector will discard any null - * entries in the collection. - * - * By default, this annotation is inherited. At runtime, the inspector introspects on the class - * hierachy and uses the annotated getter from the bottommost class, if different from any - * annoated getters of the parent class. If a class inherits from a parent class with an annotated - * getter, but does not include this annotation, the child class will be traversed using the - * getter annotated on the parent. This holds true even if the child class overrides the getter. - * - * @see InspectionHelper#traverseChildren(Object, ChildTraverser) - * @see InspectionHelper#hasChildTraversal() - * @hide - */ -@Target({METHOD}) -@Retention(SOURCE) -public @interface InspectableChildren { -} diff --git a/core/java/android/view/inspector/InspectableNodeName.java b/core/java/android/view/inspector/InspectableNodeName.java index 716409c9af3a..ea94ad4c5df8 100644 --- a/core/java/android/view/inspector/InspectableNodeName.java +++ b/core/java/android/view/inspector/InspectableNodeName.java @@ -34,7 +34,7 @@ import java.lang.annotation.Target; * This annotation does not inherit. If a class extends an annotated parent class, but does not * annotate itself, its node name will be inferred from its Java name. * - * @see InspectionHelper#getNodeName() + * @see InspectionCompanion#getNodeName() * @hide */ @Target({TYPE}) diff --git a/core/java/android/view/inspector/InspectableProperty.java b/core/java/android/view/inspector/InspectableProperty.java index b0fd5032ba56..5b95715681fc 100644 --- a/core/java/android/view/inspector/InspectableProperty.java +++ b/core/java/android/view/inspector/InspectableProperty.java @@ -17,8 +17,11 @@ package android.view.inspector; import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.SOURCE; +import android.content.res.ResourceId; + import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -31,8 +34,8 @@ import java.lang.annotation.Target; * but on a different getter, the inspector will use the child's getter when inspecting instances * of the child, and the parent's otherwise. * - * @see InspectionHelper#mapProperties(PropertyMapper) - * @see InspectionHelper#readProperties(Object, PropertyReader) + * @see InspectionCompanion#mapProperties(PropertyMapper) + * @see InspectionCompanion#readProperties(Object, PropertyReader) * @hide */ @Target({METHOD}) @@ -46,5 +49,171 @@ public @interface InspectableProperty { * * @return The name of the property. */ - String value() default ""; + String name() default ""; + + /** + * If the property is inflated from XML, the resource ID of its XML attribute. + * + * If left as {ID_NULL}, and {@link #hasAttributeId()} is true, the attribute ID will be + * inferred from {@link #name()}. + * + * @return The attribute ID of the property or {@link ResourceId#ID_NULL} + */ + int attributeId() default ResourceId.ID_NULL; + + /** + * If this property has an attribute ID. + * + * Set to false if the annotated property does not have an attribute ID, that is, it is not + * inflated from an XML attribute. This will prevent the automatic inference of the attribute + * ID if {@link #attributeId()} is set to {@link ResourceId#ID_NULL}. + * + * @return Whether to infer an attribute ID if not supplied + */ + boolean hasAttributeId() default true; + + /** + * Specify how to interpret a value type packed into a primitive integer. + * + * @return A {@link ValueType} + */ + ValueType valueType() default ValueType.INFERRED; + + /** + * For enumerations packed into primitive {int} properties, map the values to string names. + * + * Note that {@link #enumMapping()} cannot be used simultaneously with {@link #flagMapping()}. + * + * @return An array of {@link EnumMap}, empty if not applicable + * @see android.annotation.IntDef + * @see IntEnumMapping + */ + EnumMap[] enumMapping() default {}; + + /** + * For flags packed into primitive {int} properties, model the string names of the flags. + * + * Note that {@link #flagMapping()} cannot be used simultaneously with {@link #enumMapping()}. + * + * @return An array of {@link FlagMap}, empty if not applicable + * @see android.annotation.IntDef + * @see IntFlagMapping + */ + FlagMap[] flagMapping() default {}; + + + /** + * One entry in an enumeration packed into a primitive {int}. + * + * @see IntEnumMapping + * @hide + */ + @Target({TYPE}) + @Retention(SOURCE) + @interface EnumMap { + /** + * The string name of this enumeration value. + * + * @return A string name + */ + String name(); + + /** + * The integer value of this enumeration value. + * + * @return An integer value + */ + int value(); + } + + /** + * One flag value of many that may be packed into a primitive {int}. + * + * @see IntFlagMapping + * @hide + */ + @Target({TYPE}) + @Retention(SOURCE) + @interface FlagMap { + /** + * The string name of this flag. + * + * @return A string name + */ + String name(); + + /** + * A target value that the property's value must equal after masking. + * + * If a mask is not supplied (i.e., {@link #mask()} is 0), the target will be reused as the + * mask. This handles the common case where no flags mutually exclude each other. + * + * @return The target value to compare against + */ + int target(); + + /** + * A mask that the property will be bitwise anded with before comparing to the target. + * + * If set to 0 (the default), the value of {@link #target()} will be used as a mask. Zero + * was chosen as the default since bitwise and with zero is always zero. + * + * @return A mask, or 0 to use the target as a mask + */ + int mask() default 0; + } + + /** + * The type of value packed into a primitive {int}. + * + * @hide + */ + enum ValueType { + /** + * No special handling, property is considered to be a numeric value. + */ + NONE, + + /** + * The default the annotation processor infers the value type from context. + */ + INFERRED, + + /** + * Value packs an enumeration. + * + * This is inferred if {@link #enumMapping()} is specified. + * + * @see EnumMap + */ + INT_ENUM, + + /** + * Value packs flags, of which many may be enabled at once. + * + * This is inferred if {@link #flagMapping()} is specified. + * + * @see FlagMap + */ + INT_FLAG, + + /** + * Value packs color information. + * + * This is inferred from {@link android.annotation.ColorInt}, or + * {@link android.annotation.ColorLong} on the getter method. + * + * @see android.graphics.Color + */ + COLOR, + + /** + * Value packs gravity information. + * + * This type is not inferred, and is non-trivial to represent using {@link FlagMap}. + * + * @see android.view.Gravity + */ + GRAVITY + } } diff --git a/core/java/android/view/inspector/InspectionCompanion.java b/core/java/android/view/inspector/InspectionCompanion.java new file mode 100644 index 000000000000..ce0aee8f2d84 --- /dev/null +++ b/core/java/android/view/inspector/InspectionCompanion.java @@ -0,0 +1,95 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inspector; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +/** + * An interface for companion objects used to inspect views. + * + * Inspection companions only need to handle the properties and node name of the specific class + * they are defined for, not anything from a parent class. At runtime, the inspector instantiates + * one instance of each inspection companion, and handles visiting them in the correct inheritance + * order for each type it inspects. + * + * Properties are read from the top of the type tree to the bottom, so that classes that override + * a property in their parent class can overwrite it in the reader. In general, properties will + * cleanly inherit through their getters, and the inspector runtime will read the properties of a + * parent class via the parent's inspection companion, and the child companion will only read + * properties added or changed since the parent was defined. + * + * Only one child traversal is considered for each class. If a descendant class defines a + * different child traversal than its parent, only the bottom traversal is used. If a class does + * not define its own child traversal, but one of its ancestors does, the bottom-most ancestor's + * traversal will be used. + * + * @param <T> The type of inspectable this is the companion to + */ +public interface InspectionCompanion<T> { + /** + * Map the string names of the properties this companion knows about to integer IDs. + * + * Each companion is responsible for storing the integer IDs of all its properties. This is the + * only method that is allowed to modify the stored IDs. + * + * Calling {@link #readProperties(T, PropertyReader)} before calling this results in + * undefined behavior. + * + * @param propertyMapper A {@link PropertyMapper} maps string names to IDs. + */ + void mapProperties(@NonNull PropertyMapper propertyMapper); + + /** + * Read the values of an instance of this companion's type into a {@link PropertyReader}. + * + * This method needs to return the property IDs stored by + * {@link #mapProperties(PropertyMapper)}. Implementations should track if their properties + * have been mapped and throw a {@link UninitializedPropertyMapException} if this method is + * called before {mapProperties}. + * + * @param inspectable A object of type {@link T} to read the properties of. + * @param propertyReader An object which receives the property IDs and values. + */ + void readProperties(@NonNull T inspectable, @NonNull PropertyReader propertyReader); + + /** + * Get an optional name to display to developers for inspection nodes of this companion's type. + * + * The default implementation returns null, which will cause the runtime to use the class's + * simple name as defined by {@link Class#getSimpleName()} as the node name. + * + * If the type of this companion is inflated from XML, this method should be overridden to + * return the string used as the tag name for this type in XML. + * + * @return A string to use as the node name, or null to use the simple class name fallback. + */ + @Nullable + default String getNodeName() { + return null; + } + + /** + * Thrown by {@link #readProperties(Object, PropertyReader)} if called before + * {@link #mapProperties(PropertyMapper)}. + */ + class UninitializedPropertyMapException extends RuntimeException { + public UninitializedPropertyMapException() { + super("Unable to read properties of an inspectable before mapping their IDs."); + } + } +} diff --git a/core/java/android/view/inspector/InspectionHelper.java b/core/java/android/view/inspector/InspectionHelper.java deleted file mode 100644 index 27a97040926c..000000000000 --- a/core/java/android/view/inspector/InspectionHelper.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.inspector; - -import android.annotation.NonNull; -import android.annotation.Nullable; - -/** - * An interface for companion objects used to inspect views. - * - * Inspection helpers only need to handle the properties, name and traversal of the specific class - * they are defined for, not anything from a parent class. At runtime, the inspector instantiates - * one instance of each inspection helper, and handles visiting them in the correct inheritance - * order for each type it inspects. - * - * Properties are read from the top of the type tree to the bottom, so that classes that override - * a property in their parent class can overwrite it in the reader. In general, properties will - * cleanly inherit through their getters, and the inspector runtime will read the properties of a - * parent class via the parent's inspection helper, and the child helper will only read properties - * added or changed since the parent was defined. - * - * Only one child traversal is considered for each class. If a descendant class defines a - * different child traversal than its parent, only the bottom traversal is used. If a class does - * not define its own child traversal, but one of its ancestors does, the bottom-most ancestor's - * traversal will be used. - * - * @param <T> The type of inspectable this helper operates on - * @hide - */ -public interface InspectionHelper<T> { - /** - * Map the string names of the properties this helper knows about to integer IDs. - * - * Each helper is responsible for storing the integer IDs of all its properties. This is the - * only method that is allowed to modify the stored IDs. - * - * Calling {@link #readProperties(T, PropertyReader)} before calling this results in - * undefined behavior. - * - * @param propertyMapper A {@link PropertyMapper} or lambda which maps string names to IDs. - */ - void mapProperties(@NonNull PropertyMapper propertyMapper); - - /** - * Read the values of an instance of this helper's type into a {@link PropertyReader}. - * - * This method needs to return the property IDs stored by - * {@link #mapProperties(PropertyMapper)}. Implementations should track if their properties - * have been mapped and throw a {@link UninitializedPropertyMapException} if this method is - * called before {mapProperties}. - * - * @param inspectable A object of type {@link T} to read the properties of. - * @param propertyReader An object which receives the property IDs and values. - */ - void readProperties(@NonNull T inspectable, @NonNull PropertyReader propertyReader); - - /** - * Query if this inspectable type can potentially have child nodes. - * - * E.g.: any descendant of {@link android.view.ViewGroup} can have child nodes, but a leaf - * view like {@link android.widget.ImageView} may not. - * - * The default implementation always returns false. If an implementing class overrides this, it - * should also define {@link #traverseChildren(T, ChildTraverser)}. - * - * @return True if this inspectable type can potentially have child nodes, false otherwise. - */ - default boolean hasChildTraversal() { - return false; - } - - /** - * Traverse the child nodes of an instance of this helper's type into a {@link ChildTraverser}. - * - * This provides the ability to traverse over a variety of collection APIs (e.g.: arrays, - * {@link Iterable}, or {@link java.util.Iterator}) in a uniform fashion. The traversal must be - * in the order defined by this helper's type. If the getter returns null, the helper must - * treat it as an empty collection. - * - * The default implementation throws a {@link NoChildTraversalException}. If - * {@link #hasChildTraversal()} returns is overriden to return true, it is expected that the - * implementing class will also override this method and provide a traversal. - * - * @param inspectable An object of type {@link T} to traverse the child nodes of. - * @param childTraverser A {@link ChildTraverser} or lamba to receive the children in order. - * @throws NoChildTraversalException If there is no defined child traversal - */ - default void traverseChildren( - @NonNull T inspectable, - @SuppressWarnings("unused") @NonNull ChildTraverser childTraverser) { - throw new NoChildTraversalException(inspectable.getClass()); - } - - /** - * Get an optional name to display to developers for inspection nodes of this helper's type. - * - * The default implementation returns null, which will cause the runtime to use the class's - * simple name as defined by {@link Class#getSimpleName()} as the node name. - * - * If the type of this helper is inflated from XML, this method should be overridden to return - * the string used as the tag name for this type in XML. - * - * @return A string to use as the node name, or null to use the simple class name fallback. - */ - @Nullable - default String getNodeName() { - return null; - } - - /** - * Thrown by {@link #readProperties(Object, PropertyReader)} if called before - * {@link #mapProperties(PropertyMapper)}. - */ - class UninitializedPropertyMapException extends RuntimeException { - public UninitializedPropertyMapException() { - super("Unable to read properties of an inspectable before mapping their IDs."); - } - } - - /** - * Thrown by {@link #traverseChildren(Object, ChildTraverser)} if no child traversal exists. - */ - class NoChildTraversalException extends RuntimeException { - public NoChildTraversalException(Class cls) { - super(String.format( - "Class %s does not have a defined child traversal. Cannot traverse children.", - cls.getCanonicalName() - )); - } - } -} diff --git a/core/java/android/view/inspector/IntEnumMapping.java b/core/java/android/view/inspector/IntEnumMapping.java new file mode 100644 index 000000000000..69f6dce94a81 --- /dev/null +++ b/core/java/android/view/inspector/IntEnumMapping.java @@ -0,0 +1,118 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inspector; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.util.ArrayList; + +/** + * Maps the values of an {int} property to string names for properties that encode enumerations. + * + * An {@link InspectionCompanion} may provide an instance of this class to a {@link PropertyMapper} + * for enumerations packed into primitive {int} properties. + * + * This class is immutable, and must be constructed by a {@link Builder}. + * + * @see PropertyMapper#mapIntEnum(String, int, IntEnumMapping) + */ +public final class IntEnumMapping { + private final Value[] mValues; + + /** + * Map from a property value to a string name. + * + * @param value The value of a property + * @return The name of the enumeration value, null if the value is not mapped + */ + @Nullable + public String nameOf(int value) { + for (Value valueTuple : mValues) { + if (valueTuple.mValue == value) { + return valueTuple.mName; + } + } + + return null; + } + + /** + * Create a new instance from a builder. + * + * This constructor is private, use {@link Builder#build()} instead. + * + * @param builder A builder to create from + */ + private IntEnumMapping(Builder builder) { + mValues = builder.mValues.toArray(new Value[builder.mValues.size()]); + } + + /** + * A builder for {@link IntEnumMapping} + */ + public static final class Builder { + private final ArrayList<Value> mValues; + + public Builder() { + mValues = new ArrayList<>(); + } + + /** + * Add a new entry to this mapping. + * + * @param name Name of the enumeration value + * @param value Int value of the enumeration value + * @return This builder + */ + @NonNull + public Builder addValue(@NonNull String name, int value) { + mValues.add(new Value(name, value)); + return this; + } + + /** + * Clear the builder, allowing for recycling. + */ + public void clear() { + mValues.clear(); + } + + /** + * Build a new {@link IntEnumMapping} from this builder + * + * @return A new mapping + */ + @NonNull + public IntEnumMapping build() { + return new IntEnumMapping(this); + } + } + + /** + * Inner class that holds the name and value of an enumeration value. + */ + private static final class Value { + @NonNull private final String mName; + private final int mValue; + + private Value(@NonNull String name, int value) { + mName = name; + mValue = value; + } + } +} diff --git a/core/java/android/view/inspector/IntFlagMapping.java b/core/java/android/view/inspector/IntFlagMapping.java new file mode 100644 index 000000000000..dcb87e18ae5e --- /dev/null +++ b/core/java/android/view/inspector/IntFlagMapping.java @@ -0,0 +1,155 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inspector; + +import android.annotation.NonNull; + +import java.util.ArrayList; + +/** + * Maps the values of an {int} property to arrays of string for properties that encode flags. + * + * An {@link InspectionCompanion} may provide an instance of this class to a {@link PropertyMapper} + * for flag values packed into primitive {int} properties. + * + * Each flag has a + * + * This class is immutable, and must be constructed by a {@link Builder}. + * + * @see PropertyMapper#mapIntFlag(String, int, IntFlagMapping) + */ +public final class IntFlagMapping { + private final Flag[] mFlags; + + /** + * Get an array of the names of enabled flags for a given property value. + * + * @param value The value of the property + * @return The names of the enabled flags + */ + @NonNull + public String[] namesOf(int value) { + ArrayList<String> enabledFlagNames = new ArrayList<>(mFlags.length); + + for (Flag flag : mFlags) { + if (flag.isEnabledFor(value)) { + enabledFlagNames.add(flag.mName); + } + } + + return enabledFlagNames.toArray(new String[enabledFlagNames.size()]); + } + + /** + * Create a new instance from a builder. + * + * This constructor is private, use {@link Builder#build()} instead. + * + * @param builder A builder to create from + */ + private IntFlagMapping(Builder builder) { + mFlags = builder.mFlags.toArray(new Flag[builder.mFlags.size()]); + } + + /** + * A builder for {@link IntFlagMapping}. + */ + public static final class Builder { + private ArrayList<Flag> mFlags; + + public Builder() { + mFlags = new ArrayList<>(); + } + + /** + * Add a new flag without a mask. + * + * The target value will be used as a mask, to handle the common case where flag values + * are not mutually exclusive. The flag will be considered enabled for a property value if + * the result of bitwise anding the target and the value equals the target, that is: + * {(value & target) == target}. + * + * @param name The name of the flag + * @param target The value to compare against + * @return This builder + */ + @NonNull + public Builder addFlag(@NonNull String name, int target) { + mFlags.add(new Flag(name, target, target)); + return this; + } + + /** + * Add a new flag with a mask. + * + * The flag will be considered enabled for a property value if the result of bitwise anding + * the value and the mask equals the target, that is: {(value & mask) == target}. + * + * @param name The name of the flag + * @param target The value to compare against + * @param mask A bit mask + * @return This builder + */ + @NonNull + public Builder addFlag(@NonNull String name, int target, int mask) { + mFlags.add(new Flag(name, target, mask)); + return this; + } + + /** + * Clear the builder, allowing for recycling. + */ + public void clear() { + mFlags.clear(); + } + + /** + * Build a new {@link IntFlagMapping} from this builder. + * + * @return A new mapping + */ + @NonNull + public IntFlagMapping build() { + return new IntFlagMapping(this); + } + } + + /** + * Inner class that holds the name, mask, and target value of a flag + */ + private static final class Flag { + @NonNull private final String mName; + private final int mTarget; + private final int mMask; + + private Flag(@NonNull String name, int target, int mask) { + mName = name; + mTarget = target; + mMask = mask; + } + + /** + * Compare the supplied property value against the mask and taget. + * + * @param value The value to check + * @return True if this flag is enabled + */ + private boolean isEnabledFor(int value) { + return (value & mMask) == mTarget; + } + } +} diff --git a/core/java/android/view/inspector/PropertyMapper.java b/core/java/android/view/inspector/PropertyMapper.java index 35550bd45b30..5fb291b34219 100644 --- a/core/java/android/view/inspector/PropertyMapper.java +++ b/core/java/android/view/inspector/PropertyMapper.java @@ -16,102 +16,160 @@ package android.view.inspector; +import android.annotation.AttrRes; import android.annotation.NonNull; /** * An interface for mapping the string names of inspectable properties to integer identifiers. * - * This interface is consumed by {@link InspectionHelper#mapProperties(PropertyMapper)}. + * This interface is consumed by {@link InspectionCompanion#mapProperties(PropertyMapper)}. * * Mapping properties to IDs enables quick comparisons against shadow copies of inspectable * objects without performing a large number of string comparisons. * - * @see InspectionHelper#mapProperties(PropertyMapper) - * @hide + * @see InspectionCompanion#mapProperties(PropertyMapper) */ public interface PropertyMapper { /** * Map a string name to an integer ID for a primitive boolean property. * * @param name The name of the property + * @param attributeId If the property is from an XML attribute, the resource ID of the property * @return An integer ID for the property * @throws PropertyConflictException If the property name is already mapped as another type. */ - int mapBoolean(@NonNull String name); + int mapBoolean(@NonNull String name, @AttrRes int attributeId); /** * Map a string name to an integer ID for a primitive byte property. * * @param name The name of the property + * @param attributeId If the property is from an XML attribute, the resource ID of the property * @return An integer ID for the property * @throws PropertyConflictException If the property name is already mapped as another type. */ - int mapByte(@NonNull String name); + int mapByte(@NonNull String name, @AttrRes int attributeId); /** * Map a string name to an integer ID for a primitive char property. * * @param name The name of the property + * @param attributeId If the property is from an XML attribute, the resource ID of the property * @return An integer ID for the property * @throws PropertyConflictException If the property name is already mapped as another type. */ - int mapChar(@NonNull String name); + int mapChar(@NonNull String name, @AttrRes int attributeId); /** * Map a string name to an integer ID for a primitive double property. * * @param name The name of the property + * @param attributeId If the property is from an XML attribute, the resource ID of the property * @return An integer ID for the property * @throws PropertyConflictException If the property name is already mapped as another type. */ - int mapDouble(@NonNull String name); + int mapDouble(@NonNull String name, @AttrRes int attributeId); /** * Map a string name to an integer ID for a primitive float property. * * @param name The name of the property + * @param attributeId If the property is from an XML attribute, the resource ID of the property * @return An integer ID for the property * @throws PropertyConflictException If the property name is already mapped as another type. */ - int mapFloat(@NonNull String name); + int mapFloat(@NonNull String name, @AttrRes int attributeId); /** * Map a string name to an integer ID for a primitive int property. * * @param name The name of the property + * @param attributeId If the property is from an XML attribute, the resource ID of the property * @return An integer ID for the property * @throws PropertyConflictException If the property name is already mapped as another type. */ - int mapInt(@NonNull String name); + int mapInt(@NonNull String name, @AttrRes int attributeId); /** * Map a string name to an integer ID for a primitive long property. * * @param name The name of the property + * @param attributeId If the property is from an XML attribute, the resource ID of the property * @return An integer ID for the property * @throws PropertyConflictException If the property name is already mapped as another type. */ - int mapLong(@NonNull String name); + int mapLong(@NonNull String name, @AttrRes int attributeId); /** * Map a string name to an integer ID for a primitive short property. * * @param name The name of the property + * @param attributeId If the property is from an XML attribute, the resource ID of the property * @return An integer ID for the property * @throws PropertyConflictException If the property name is already mapped as another type. */ - int mapShort(@NonNull String name); + int mapShort(@NonNull String name, @AttrRes int attributeId); /** * Map a string name to an integer ID for an object property. * * @param name The name of the property + * @param attributeId If the property is from an XML attribute, the resource ID of the property * @return An integer ID for the property * @throws PropertyConflictException If the property name is already mapped as another type. */ - int mapObject(@NonNull String name); + int mapObject(@NonNull String name, @AttrRes int attributeId); /** + * Map a string name to an integer ID for a color property. + * + * @param name The name of the property + * @param attributeId If the property is from an XML attribute, the resource ID of the property + * @return An integer ID for the property + * @throws PropertyConflictException If the property name is already mapped as another type. + * @see android.graphics.Color + */ + int mapColor(@NonNull String name, @AttrRes int attributeId); + + /** + * Map a string name to an integer ID for a gravity property. + * + * @param name The name of the property + * @param attributeId If the property is from an XML attribute, the resource ID of the property + * @return An integer ID for the property + * @throws PropertyConflictException If the property name is already mapped as another type. + * @see android.view.Gravity + */ + int mapGravity(@NonNull String name, @AttrRes int attributeId); + + /** + * Map a string name to an integer ID for an enumeration packed into an int property. + * + * @param name The name of the property + * @param attributeId If the property is from an XML attribute, the resource ID of the property + * @param mapping A mapping from int to String + * @return An integer ID for the property + * @throws PropertyConflictException If the property name is already mapped as another type. + */ + int mapIntEnum( + @NonNull String name, + @AttrRes int attributeId, + @NonNull IntEnumMapping mapping); + + /** + * Map a string name to an integer ID for a flag set packed into an int property. + * + * @param name The name of the property + * @param attributeId If the property is from an XML attribute, the resource ID of the property + * @param mapping A mapping from int to an array of strings + * @return An integer ID for the property + * @throws PropertyConflictException If the property name is already mapped as another type. + */ + int mapIntFlag( + @NonNull String name, + @AttrRes int attributeId, + @NonNull IntFlagMapping mapping); + /** * Thrown from a map method if a property name is already mapped as different type. */ class PropertyConflictException extends RuntimeException { diff --git a/core/java/android/view/inspector/PropertyReader.java b/core/java/android/view/inspector/PropertyReader.java index df81c102dbad..fd83e8df6c3a 100644 --- a/core/java/android/view/inspector/PropertyReader.java +++ b/core/java/android/view/inspector/PropertyReader.java @@ -16,19 +16,21 @@ package android.view.inspector; +import android.annotation.ColorInt; +import android.annotation.ColorLong; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.Color; /** * An interface for reading the properties of an inspectable object. * - * Used as the parameter for {@link InspectionHelper#readProperties(Object, PropertyReader)}. + * Used as the parameter for {@link InspectionCompanion#readProperties(Object, PropertyReader)}. * It has separate methods for all primitive types to avoid autoboxing overhead if a concrete * implementation is able to work with primitives. Implementations should be prepared to accept * {null} as the value of {@link PropertyReader#readObject(int, Object)}. * - * @see InspectionHelper#readProperties(Object, PropertyReader) - * @hide + * @see InspectionCompanion#readProperties(Object, PropertyReader) */ public interface PropertyReader { /** @@ -115,6 +117,60 @@ public interface PropertyReader { void readObject(int id, @Nullable Object value); /** + * Read a color packed into a {@link ColorInt} as a property. + * + * @param id Identifier of the property from a {@link PropertyMapper} + * @param value Value of the property + * @throws PropertyTypeMismatchException If the property ID is not mapped as a color + */ + void readColor(int id, @ColorInt int value); + + /** + * Read a color packed into a {@link ColorLong} as a property. + * + * @param id Identifier of the property from a {@link PropertyMapper} + * @param value Value of the property + * @throws PropertyTypeMismatchException If the property ID is not mapped as a color + */ + void readColor(int id, @ColorLong long value); + + /** + * Read a {@link Color} object as a property. + * + * @param id Identifier of the property from a {@link PropertyMapper} + * @param value Value of the property + * @throws PropertyTypeMismatchException If the property ID is not mapped as a color + */ + void readColor(int id, @Nullable Color value); + + /** + * Read {@link android.view.Gravity} packed into an primitive {int}. + * + * @param id Identifier of the property from a {@link PropertyMapper} + * @param value Value of the property + * @throws PropertyTypeMismatchException If the property ID is not mapped as a gravity property + */ + void readGravity(int id, int value); + + /** + * Read an enumeration packed into a primitive {int}. + * + * @param id Identifier of the property from a {@link PropertyMapper} + * @param value Value of the property + * @throws PropertyTypeMismatchException If the property ID is not mapped as an object + */ + void readIntEnum(int id, int value); + + /** + * Read a flag packed into a primitive {int}. + * + * @param id Identifier of the property from a {@link PropertyMapper} + * @param value Value of the property + * @throws PropertyTypeMismatchException If the property ID is not mapped as an object + */ + void readIntFlag(int id, int value); + + /** * Thrown if a client calls a typed read method for a property of a different type. */ class PropertyTypeMismatchException extends RuntimeException { diff --git a/core/java/android/view/intelligence/ContentCaptureEvent.java b/core/java/android/view/intelligence/ContentCaptureEvent.java index befcb55b1f73..f63628105cba 100644 --- a/core/java/android/view/intelligence/ContentCaptureEvent.java +++ b/core/java/android/view/intelligence/ContentCaptureEvent.java @@ -163,7 +163,7 @@ public final class ContentCaptureEvent implements Parcelable { * Gets optional flags associated with the event. * * @return either {@code 0} or - * {@link android.view.intelligence.IntelligenceManager#FLAG_USER_INPUT}. + * {@link android.view.intelligence.ContentCaptureManager#FLAG_USER_INPUT}. */ public int getFlags() { return mFlags; diff --git a/core/java/android/view/intelligence/IntelligenceManager.java b/core/java/android/view/intelligence/ContentCaptureManager.java index 2f3b4ef56aa1..45518d5e5943 100644 --- a/core/java/android/view/intelligence/IntelligenceManager.java +++ b/core/java/android/view/intelligence/ContentCaptureManager.java @@ -23,7 +23,6 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.ComponentName; import android.content.Context; @@ -44,13 +43,8 @@ import com.android.internal.util.Preconditions; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.List; -import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; -/** - * TODO(b/111276913): add javadocs / implement - */ /* * NOTE: all methods in this class should return right away, or do the real work in a handler * thread. @@ -58,10 +52,13 @@ import java.util.concurrent.atomic.AtomicBoolean; * Hence, the only field that must be thread-safe is mEnabled, which is called at the beginning * of every method. */ -@SystemService(Context.INTELLIGENCE_MANAGER_SERVICE) -public final class IntelligenceManager { +/** + * TODO(b/111276913): add javadocs / implement + */ +@SystemService(Context.CONTENT_CAPTURE_MANAGER_SERVICE) +public final class ContentCaptureManager { - private static final String TAG = "IntelligenceManager"; + private static final String TAG = "ContentCaptureManager"; // TODO(b/111276913): define a way to dynamically set them(for example, using settings?) private static final boolean VERBOSE = false; @@ -140,7 +137,7 @@ public final class IntelligenceManager { private final Handler mHandler; /** @hide */ - public IntelligenceManager(@NonNull Context context, @Nullable IIntelligenceManager service) { + public ContentCaptureManager(@NonNull Context context, @Nullable IIntelligenceManager service) { mContext = Preconditions.checkNotNull(context, "context cannot be null"); if (VERBOSE) { Log.v(TAG, "Constructor for " + context.getPackageName()); @@ -156,7 +153,7 @@ public final class IntelligenceManager { public void onActivityCreated(@NonNull IBinder token, @NonNull ComponentName componentName) { if (!isContentCaptureEnabled()) return; - mHandler.sendMessage(obtainMessage(IntelligenceManager::handleStartSession, this, + mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleStartSession, this, token, componentName)); } @@ -192,7 +189,7 @@ public final class IntelligenceManager { } } - private void handleSessionStarted(int resultCode) { + private void handleSessionStarted(int resultCode) { mState = resultCode; mDisabled.set(mState == STATE_DISABLED); if (VERBOSE) { @@ -264,7 +261,7 @@ public final class IntelligenceManager { Log.v(TAG, "onActivityLifecycleEvent() for " + getActivityDebugName() + ": " + ContentCaptureEvent.getTypeAsString(type)); } - mHandler.sendMessage(obtainMessage(IntelligenceManager::handleSendEvent, this, + mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleSendEvent, this, new ContentCaptureEvent(type), /* forceFlush= */ true)); } @@ -279,7 +276,7 @@ public final class IntelligenceManager { + ", mId=" + mId); } - mHandler.sendMessage(obtainMessage(IntelligenceManager::handleFinishSession, this)); + mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleFinishSession, this)); } private void handleFinishSession() { @@ -328,7 +325,7 @@ public final class IntelligenceManager { throw new IllegalArgumentException("Invalid node class: " + node.getClass()); } - mHandler.sendMessage(obtainMessage(IntelligenceManager::handleSendEvent, this, + mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleSendEvent, this, new ContentCaptureEvent(TYPE_VIEW_APPEARED) .setViewNode(((ViewNode.ViewStructureImpl) node).mNode), /* forceFlush= */ false)); @@ -346,7 +343,7 @@ public final class IntelligenceManager { Preconditions.checkNotNull(id); if (!isContentCaptureEnabled()) return; - mHandler.sendMessage(obtainMessage(IntelligenceManager::handleSendEvent, this, + mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleSendEvent, this, new ContentCaptureEvent(TYPE_VIEW_DISAPPEARED).setAutofillId(id), /* forceFlush= */ false)); } @@ -365,7 +362,7 @@ public final class IntelligenceManager { if (!isContentCaptureEnabled()) return; - mHandler.sendMessage(obtainMessage(IntelligenceManager::handleSendEvent, this, + mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleSendEvent, this, new ContentCaptureEvent(TYPE_VIEW_TEXT_CHANGED, flags).setAutofillId(id) .setText(text), /* forceFlush= */ false)); } @@ -396,11 +393,11 @@ public final class IntelligenceManager { } /** - * Returns the component name of the {@code android.service.intelligence.IntelligenceService} - * that is enabled for the current user. + * Returns the component name of the system service that is consuming the captured events for + * the current user. */ @Nullable - public ComponentName getIntelligenceServiceComponentName() { + public ComponentName getServiceComponentName() { //TODO(b/111276913): implement return null; } @@ -422,106 +419,6 @@ public final class IntelligenceManager { //TODO(b/111276913): implement } - /** - * Called by the the service {@link android.service.intelligence.IntelligenceService} - * to define whether content capture should be enabled for activities with such - * {@link android.content.ComponentName}. - * - * <p>Useful to blacklist a particular activity. - * - * @throws UnsupportedOperationException if not called by the UID that owns the - * {@link android.service.intelligence.IntelligenceService} associated with the - * current user. - * - * @hide - */ - @SystemApi - public void setActivityContentCaptureEnabled(@NonNull ComponentName activity, - boolean enabled) { - //TODO(b/111276913): implement - } - - /** - * Called by the the service {@link android.service.intelligence.IntelligenceService} - * to explicitly limit content capture to the given packages and activities. - * - * <p>When the whitelist is set, it overrides the values passed to - * {@link #setActivityContentCaptureEnabled(ComponentName, boolean)} - * and {@link #setPackageContentCaptureEnabled(String, boolean)}. - * - * <p>To reset the whitelist, call it passing {@code null} to both arguments. - * - * <p>Useful when the service wants to restrict content capture to a category of apps, like - * chat apps. For example, if the service wants to support view captures on all activities of - * app {@code ChatApp1} and just activities {@code act1} and {@code act2} of {@code ChatApp2}, - * it would call: {@code setContentCaptureWhitelist(Arrays.asList("ChatApp1"), - * Arrays.asList(new ComponentName("ChatApp2", "act1"), - * new ComponentName("ChatApp2", "act2")));} - * - * @throws UnsupportedOperationException if not called by the UID that owns the - * {@link android.service.intelligence.IntelligenceService} associated with the - * current user. - * - * @hide - */ - @SystemApi - public void setContentCaptureWhitelist(@Nullable List<String> packages, - @Nullable List<ComponentName> activities) { - //TODO(b/111276913): implement - } - - /** - * Called by the the service {@link android.service.intelligence.IntelligenceService} - * to define whether content capture should be enabled for activities of the app with such - * {@code packageName}. - * - * <p>Useful to blacklist any activity from a particular app. - * - * @throws UnsupportedOperationException if not called by the UID that owns the - * {@link android.service.intelligence.IntelligenceService} associated with the - * current user. - * - * @hide - */ - @SystemApi - public void setPackageContentCaptureEnabled(@NonNull String packageName, boolean enabled) { - //TODO(b/111276913): implement - } - - /** - * Gets the activities where content capture was disabled by - * {@link #setActivityContentCaptureEnabled(ComponentName, boolean)}. - * - * @throws UnsupportedOperationException if not called by the UID that owns the - * {@link android.service.intelligence.IntelligenceService} associated with the - * current user. - * - * @hide - */ - @SystemApi - @NonNull - public Set<ComponentName> getContentCaptureDisabledActivities() { - //TODO(b/111276913): implement - return null; - } - - /** - * Gets the apps where content capture was disabled by - * {@link #setPackageContentCaptureEnabled(String, boolean)}. - * - * @throws UnsupportedOperationException if not called by the UID that owns the - * {@link android.service.intelligence.IntelligenceService} associated with the - * current user. - * - * @hide - */ - @SystemApi - @NonNull - public Set<String> getContentCaptureDisabledPackages() { - //TODO(b/111276913): implement - return null; - } - /** @hide */ public void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.println("IntelligenceManager"); @@ -547,7 +444,7 @@ public final class IntelligenceManager { } if (mEvents != null) { final int numberEvents = mEvents.size(); - pw.print(prefix2); pw.print("batched events: "); pw.print(numberEvents); + pw.print(prefix2); pw.print("buffered events: "); pw.print(numberEvents); pw.print('/'); pw.println(MAX_BUFFER_SIZE); if (VERBOSE && numberEvents > 0) { final String prefix3 = prefix2 + " "; diff --git a/core/java/android/view/intelligence/IIntelligenceManager.aidl b/core/java/android/view/intelligence/IIntelligenceManager.aidl index 7518ff53b638..882fb2674bf1 100644 --- a/core/java/android/view/intelligence/IIntelligenceManager.aidl +++ b/core/java/android/view/intelligence/IIntelligenceManager.aidl @@ -28,6 +28,7 @@ import java.util.List; /** * {@hide} */ +// TODO(b/111276913): rename once the final name is defined oneway interface IIntelligenceManager { /** * Starts a session, sending the "remote" sessionId to the receiver. diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java index 9ccd3211768d..f78180276373 100644 --- a/core/java/android/widget/GridView.java +++ b/core/java/android/widget/GridView.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; import android.graphics.Rect; +import android.os.Build; import android.os.Bundle; import android.os.Trace; import android.util.AttributeSet; @@ -107,7 +108,7 @@ public class GridView extends AbsListView { */ public static final int AUTO_FIT = -1; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 117521080) private int mNumColumns = AUTO_FIT; @UnsupportedAppUsage @@ -117,7 +118,7 @@ public class GridView extends AbsListView { @UnsupportedAppUsage private int mVerticalSpacing = 0; private int mStretchMode = STRETCH_COLUMN_WIDTH; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 117521079) private int mColumnWidth; @UnsupportedAppUsage private int mRequestedColumnWidth; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 1deee8af3d75..085f8f1d678f 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -166,7 +166,7 @@ import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; -import android.view.intelligence.IntelligenceManager; +import android.view.intelligence.ContentCaptureManager; import android.view.textclassifier.TextClassification; import android.view.textclassifier.TextClassificationContext; import android.view.textclassifier.TextClassificationManager; @@ -10135,7 +10135,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Notify managers (such as {@link AutofillManager} and {@link IntelligenceManager}) that are + * Notify managers (such as {@link AutofillManager} and {@link ContentCaptureManager}) that are * interested on text changes. */ private void notifyListeningManagersAfterTextChanged() { @@ -10155,10 +10155,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // ContentCapture if (isImportantForContentCapture() && isTextEditable()) { - final IntelligenceManager im = mContext.getSystemService(IntelligenceManager.class); - if (im != null && im.isContentCaptureEnabled()) { + final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class); + if (cm != null && cm.isContentCaptureEnabled()) { // TODO(b/111276913): pass flags when edited by user / add CTS test - im.notifyViewTextChanged(getAutofillId(), getText(), /* flags= */ 0); + cm.notifyViewTextChanged(getAutofillId(), getText(), /* flags= */ 0); } } } diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java index 34e8ed406200..875d7c9ee7a6 100644 --- a/core/java/com/android/internal/os/BinderCallsStats.java +++ b/core/java/com/android/internal/os/BinderCallsStats.java @@ -52,17 +52,23 @@ public class BinderCallsStats implements BinderInternal.Observer { public static final boolean ENABLED_DEFAULT = false; public static final boolean DETAILED_TRACKING_DEFAULT = true; public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 100; + public static final int MAX_BINDER_CALL_STATS_COUNT_DEFAULT = 5000; + + private static class OverflowBinder extends Binder {} private static final String TAG = "BinderCallsStats"; private static final int CALL_SESSIONS_POOL_SIZE = 100; private static final int MAX_EXCEPTION_COUNT_SIZE = 50; private static final String EXCEPTION_COUNT_OVERFLOW_NAME = "overflow"; + private static final Class<? extends Binder> OVERFLOW_BINDER = OverflowBinder.class; + private static final int OVERFLOW_TRANSACTION_CODE = -1; // Whether to collect all the data: cpu + exceptions + reply/request sizes. private boolean mDetailedTracking = DETAILED_TRACKING_DEFAULT; // Sampling period to control how often to track CPU usage. 1 means all calls, 100 means ~1 out // of 100 requests. private int mPeriodicSamplingInterval = PERIODIC_SAMPLING_INTERVAL_DEFAULT; + private int mMaxBinderCallStatsCount = MAX_BINDER_CALL_STATS_COUNT_DEFAULT; @GuardedBy("mLock") private final SparseArray<UidEntry> mUidEntries = new SparseArray<>(); @GuardedBy("mLock") @@ -71,6 +77,7 @@ public class BinderCallsStats implements BinderInternal.Observer { private final Object mLock = new Object(); private final Random mRandom; private long mStartTime = System.currentTimeMillis(); + private long mCallStatsCount = 0; private CachedDeviceState.Readonly mDeviceState; @@ -158,7 +165,13 @@ public class BinderCallsStats implements BinderInternal.Observer { final CallStat callStat = uidEntry.getOrCreate( callingUid, s.binderClass, s.transactionCode, - mDeviceState.isScreenInteractive()); + mDeviceState.isScreenInteractive(), + mCallStatsCount >= mMaxBinderCallStatsCount); + final boolean isNewCallStat = callStat.callCount == 0; + if (isNewCallStat) { + mCallStatsCount++; + } + callStat.callCount++; callStat.recordedCallCount++; callStat.cpuTimeMicros += duration; @@ -444,6 +457,24 @@ public class BinderCallsStats implements BinderInternal.Observer { } } + /** + * Sets the maximum number of items to track. + */ + public void setMaxBinderCallStats(int maxKeys) { + if (maxKeys <= 0) { + Slog.w(TAG, "Ignored invalid max value (value must be positive): " + + maxKeys); + return; + } + + synchronized (mLock) { + if (maxKeys != mMaxBinderCallStatsCount) { + mMaxBinderCallStatsCount = maxKeys; + reset(); + } + } + } + public void setSamplingInterval(int samplingInterval) { if (samplingInterval <= 0) { Slog.w(TAG, "Ignored invalid sampling interval (value must be positive): " @@ -461,6 +492,7 @@ public class BinderCallsStats implements BinderInternal.Observer { public void reset() { synchronized (mLock) { + mCallStatsCount = 0; mUidEntries.clear(); mExceptionCounts.clear(); mStartTime = System.currentTimeMillis(); @@ -595,10 +627,21 @@ public class BinderCallsStats implements BinderInternal.Observer { } CallStat getOrCreate(int callingUid, Class<? extends Binder> binderClass, - int transactionCode, boolean screenInteractive) { + int transactionCode, boolean screenInteractive, boolean maxCallStatsReached) { CallStat mapCallStat = get(callingUid, binderClass, transactionCode, screenInteractive); - // Only create CallStat if it's a new entry, otherwise update existing instance + // Only create CallStat if it's a new entry, otherwise update existing instance. if (mapCallStat == null) { + if (maxCallStatsReached) { + mapCallStat = get(callingUid, OVERFLOW_BINDER, OVERFLOW_TRANSACTION_CODE, + screenInteractive); + if (mapCallStat != null) { + return mapCallStat; + } + + binderClass = OVERFLOW_BINDER; + transactionCode = OVERFLOW_TRANSACTION_CODE; + } + mapCallStat = new CallStat(callingUid, binderClass, transactionCode, screenInteractive); CallStatKey key = new CallStatKey(); diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index 031377ebe27b..d8ee643e27f0 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -41,6 +41,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Color; @@ -90,6 +91,7 @@ import android.view.ViewParent; import android.view.ViewRootImpl; import android.view.ViewRootImpl.ActivityConfigCallback; import android.view.Window; +import android.view.WindowInsetsController; import android.view.WindowManager; import android.view.animation.Animation; import android.view.animation.AnimationUtils; @@ -3876,4 +3878,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { mDecor.updateLogTag(params); } } + + @Override + public WindowInsetsController getInsetsController() { + return mDecor.getWindowInsetsController(); + } } diff --git a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java new file mode 100644 index 000000000000..100c6ee6763b --- /dev/null +++ b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.internal.policy; + +import com.android.internal.R; + +import android.content.res.Resources; + +/** + * Utility functions for screen decorations used by both window manager and System UI. + */ +public class ScreenDecorationsUtils { + + /** + * Corner radius that should be used on windows in order to cover the display. + * These values are expressed in pixels because they should not respect display or font + * scaling, this means that we don't have to reload them on config changes. + */ + public static float getWindowCornerRadius(Resources resources) { + // Radius that should be used in case top or bottom aren't defined. + float defaultRadius = resources.getDimension(R.dimen.rounded_corner_radius); + + float topRadius = resources.getDimension(R.dimen.rounded_corner_radius_top); + if (topRadius == 0) { + topRadius = defaultRadius; + } + float bottomRadius = resources.getDimension(R.dimen.rounded_corner_radius_bottom); + if (bottomRadius == 0) { + bottomRadius = defaultRadius; + } + + // Always use the smallest radius to make sure the rounded corners will + // completely cover the display. + return Math.min(topRadius, bottomRadius); + } +} diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 604537ffee03..600b1b324257 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -18,7 +18,7 @@ package com.android.internal.statusbar; import android.content.ComponentName; import android.graphics.Rect; -import android.hardware.biometrics.IBiometricPromptReceiver; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.os.Bundle; import android.service.notification.StatusBarNotification; @@ -141,7 +141,7 @@ oneway interface IStatusBar void showShutdownUi(boolean isReboot, String reason); // Used to show the dialog when BiometricService starts authentication - void showBiometricDialog(in Bundle bundle, IBiometricPromptReceiver receiver, int type, + void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, boolean requireConfirmation, int userId); // Used to hide the dialog when a biometric is authenticated void onBiometricAuthenticated(); @@ -151,4 +151,6 @@ oneway interface IStatusBar void onBiometricError(String error); // Used to hide the biometric dialog when the AuthenticationClient is stopped void hideBiometricDialog(); + // Used to request the "try again" button for authentications which requireConfirmation=true + void showBiometricTryAgain(); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index b7ffb5768a31..bf82dc610ad4 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -21,7 +21,7 @@ import android.content.ComponentName; import android.graphics.Rect; import android.os.Bundle; import android.service.notification.StatusBarNotification; -import android.hardware.biometrics.IBiometricPromptReceiver; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.StatusBarIcon; @@ -92,7 +92,7 @@ interface IStatusBarService void showPinningEscapeToast(); // Used to show the dialog when BiometricService starts authentication - void showBiometricDialog(in Bundle bundle, IBiometricPromptReceiver receiver, int type, + void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, boolean requireConfirmation, int userId); // Used to hide the dialog when a biometric is authenticated void onBiometricAuthenticated(); @@ -102,4 +102,6 @@ interface IStatusBarService void onBiometricError(String error); // Used to hide the biometric dialog when the AuthenticationClient is stopped void hideBiometricDialog(); + // Used to request the "try again" button for authentications which requireConfirmation=true + void showBiometricTryAgain(); } diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java index 36fe4fc5af49..c8834a889333 100644 --- a/core/java/com/android/internal/view/BaseIWindow.java +++ b/core/java/com/android/internal/view/BaseIWindow.java @@ -27,6 +27,7 @@ import android.view.DragEvent; import android.view.IWindow; import android.view.IWindowSession; import android.view.PointerIcon; +import android.view.InsetsSourceControl; import android.view.InsetsState; import com.android.internal.os.IResultReceiver; @@ -58,6 +59,11 @@ public class BaseIWindow extends IWindow.Stub { } @Override + public void insetsControlChanged(InsetsState insetsState, + InsetsSourceControl[] activeControls) throws RemoteException { + } + + @Override public void moved(int newX, int newY) { } diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 849585004bc3..b97a9fa8d1cc 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -934,7 +934,7 @@ public class SystemConfig { // If the storage model feature flag is disabled, we need to fiddle // around with permission definitions to return us to pre-Q behavior. // STOPSHIP(b/112545973): remove once feature enabled by default - if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + if (!StorageManager.hasIsolatedStorage()) { if (newPermissions.contains(android.Manifest.permission.READ_MEDIA_AUDIO) || newPermissions.contains(android.Manifest.permission.READ_MEDIA_VIDEO) || newPermissions.contains(android.Manifest.permission.READ_MEDIA_IMAGES)) { diff --git a/core/jni/Android.bp b/core/jni/Android.bp index bdd5f83aa4ea..31bb1d57d702 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -34,7 +34,6 @@ cc_library_shared { ], cppflags: ["-Wno-conversion-null"], - cpp_std: "c++17", srcs: [ "AndroidRuntime.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index bc1d5cc7e1c6..f9879ccc18c9 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -1056,12 +1056,18 @@ void AndroidRuntime::start(const char* className, const Vector<String8>& options if (rootDir == NULL) { rootDir = "/system"; if (!hasDir("/system")) { - LOG_FATAL("No root directory specified, and /android does not exist."); + LOG_FATAL("No root directory specified, and /system does not exist."); return; } setenv("ANDROID_ROOT", rootDir, 1); } + const char* runtimeRootDir = getenv("ANDROID_RUNTIME_ROOT"); + if (runtimeRootDir == NULL) { + LOG_FATAL("No runtime directory specified with ANDROID_RUNTIME_ROOT environment variable."); + return; + } + //const char* kernelHack = getenv("LD_ASSUME_KERNEL"); //ALOGD("Found LD_ASSUME_KERNEL='%s'\n", kernelHack); diff --git a/core/jni/android_text_AndroidCharacter.cpp b/core/jni/android_text_AndroidCharacter.cpp index 8885aac49064..c6ea4e10f63e 100644 --- a/core/jni/android_text_AndroidCharacter.cpp +++ b/core/jni/android_text_AndroidCharacter.cpp @@ -25,9 +25,10 @@ #include "unicode/uchar.h" #define PROPERTY_UNDEFINED (-1) +#define JAVA_LANG_CHARACTER_MAX_DIRECTIONALITY 18 // ICU => JDK mapping -static int directionality_map[U_CHAR_DIRECTION_COUNT] = { +static int directionality_map[JAVA_LANG_CHARACTER_MAX_DIRECTIONALITY + 1] = { 0, // U_LEFT_TO_RIGHT (0) => DIRECTIONALITY_LEFT_TO_RIGHT (0) 1, // U_RIGHT_TO_LEFT (1) => DIRECTIONALITY_RIGHT_TO_LEFT (1) 3, // U_EUROPEAN_NUMBER (2) => DIRECTIONALITY_EUROPEAN_NUMBER (3) @@ -75,7 +76,8 @@ static void getDirectionalities(JNIEnv* env, jobject obj, jcharArray srcArray, int c = 0x00010000 + ((src[i] - 0xD800) << 10) + (src[i + 1] & 0x3FF); int dir = u_charDirection(c); - if (dir < 0 || dir >= U_CHAR_DIRECTION_COUNT) + if (dir < 0 || dir > JAVA_LANG_CHARACTER_MAX_DIRECTIONALITY + || u_charType(c) == U_UNASSIGNED) dir = PROPERTY_UNDEFINED; else dir = directionality_map[dir]; @@ -85,7 +87,8 @@ static void getDirectionalities(JNIEnv* env, jobject obj, jcharArray srcArray, } else { int c = src[i]; int dir = u_charDirection(c); - if (dir < 0 || dir >= U_CHAR_DIRECTION_COUNT) + if (dir < 0 || dir > JAVA_LANG_CHARACTER_MAX_DIRECTIONALITY + || u_charType(c) == U_UNASSIGNED) dest[i] = PROPERTY_UNDEFINED; else dest[i] = directionality_map[dir]; @@ -96,7 +99,7 @@ static void getDirectionalities(JNIEnv* env, jobject obj, jcharArray srcArray, static jint getEastAsianWidth(JNIEnv* env, jobject obj, jchar input) { int width = u_getIntPropertyValue(input, UCHAR_EAST_ASIAN_WIDTH); - if (width < 0 || width >= U_EA_COUNT) + if (width < 0 || width > u_getIntPropertyMaxValue(UCHAR_EAST_ASIAN_WIDTH)) width = PROPERTY_UNDEFINED; return width; @@ -121,6 +124,7 @@ static void getEastAsianWidths(JNIEnv* env, jobject obj, jcharArray srcArray, return; } + int maxWidth = u_getIntPropertyMaxValue(UCHAR_EAST_ASIAN_WIDTH); for (int i = 0; i < count; i++) { const int srci = start + i; if (src[srci] >= 0xD800 && src[srci] <= 0xDBFF && @@ -129,7 +133,7 @@ static void getEastAsianWidths(JNIEnv* env, jobject obj, jcharArray srcArray, int c = 0x00010000 + ((src[srci] - 0xD800) << 10) + (src[srci + 1] & 0x3FF); int width = u_getIntPropertyValue(c, UCHAR_EAST_ASIAN_WIDTH); - if (width < 0 || width >= U_EA_COUNT) + if (width < 0 || width > maxWidth) width = PROPERTY_UNDEFINED; dest[i++] = width; @@ -137,7 +141,7 @@ static void getEastAsianWidths(JNIEnv* env, jobject obj, jcharArray srcArray, } else { int c = src[srci]; int width = u_getIntPropertyValue(c, UCHAR_EAST_ASIAN_WIDTH); - if (width < 0 || width >= U_EA_COUNT) + if (width < 0 || width > maxWidth) width = PROPERTY_UNDEFINED; dest[i] = width; diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index ec9c8606e8df..ea6e0178bd9c 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -377,6 +377,14 @@ static void nativeSetWindowCrop(JNIEnv* env, jclass clazz, jlong transactionObj, transaction->setCrop_legacy(ctrl, crop); } +static void nativeSetCornerRadius(JNIEnv* env, jclass clazz, jlong transactionObj, + jlong nativeObject, jfloat cornerRadius) { + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + + SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject); + transaction->setCornerRadius(ctrl, cornerRadius); +} + static void nativeSetLayerStack(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jint layerStack) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); @@ -883,6 +891,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetFlags }, {"nativeSetWindowCrop", "(JJIIII)V", (void*)nativeSetWindowCrop }, + {"nativeSetCornerRadius", "(JJF)V", + (void*)nativeSetCornerRadius }, {"nativeSetLayerStack", "(JJI)V", (void*)nativeSetLayerStack }, {"nativeGetBuiltInDisplay", "(I)Landroid/os/IBinder;", diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index 6e661e1ce5b2..0e052fe8d4b1 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -234,6 +234,18 @@ message SecureSettingsProto { } optional Location location = 31; + // How frequently will the user be reminded about location permission grants + message LocationAccessCheck { + option (android.msg_privacy).dest = DEST_EXPLICIT; + + // Time in between periodic checks + optional SettingProto interval_millis = 1 [ (android.privacy).dest = DEST_AUTOMATIC ]; + + // Time in between the user granting a location permission and a check + optional SettingProto delay_millis = 2 [ (android.privacy).dest = DEST_AUTOMATIC ]; + } + optional LocationAccessCheck location_access_check = 73; + message LockScreen { option (android.msg_privacy).dest = DEST_EXPLICIT; @@ -515,5 +527,5 @@ message SecureSettingsProto { // Please insert fields in alphabetical order and group them into messages // if possible (to avoid reaching the method limit). - // Next tag = 73; + // Next tag = 74; } diff --git a/core/proto/android/service/runtime.proto b/core/proto/android/service/runtime.proto new file mode 100644 index 000000000000..ecbccef7a94c --- /dev/null +++ b/core/proto/android/service/runtime.proto @@ -0,0 +1,40 @@ +/* + * 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. + */ + +syntax = "proto2"; +package android.service.runtime; + +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + +option java_multiple_files = true; +option java_outer_classname = "RuntimeServiceProto"; + +// Represents dumpsys info from RuntimeService. +message RuntimeServiceInfoProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + // Generic debug information to include. + repeated DebugEntryProto debug_entry = 1; +} + +// A piece of key / value debug information. +message DebugEntryProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string key = 1; + + optional string string_value = 2; +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 8b66be310abc..83f30573fda8 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -627,6 +627,13 @@ <!-- ====================================================================== --> <eat-comment /> + <!-- Grouping for platform runtime permissions is not accessible to apps + @hide + @SystemApi + --> + <permission-group android:name="android.permission-group.UNDEFINED" + android:priority="100" /> + <!-- ====================================================================== --> <!-- Permissions for accessing user's contacts including personal profile --> <!-- ====================================================================== --> @@ -645,14 +652,17 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.READ_CONTACTS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_readContacts" android:description="@string/permdesc_readContacts" - android:protectionLevel="dangerous" /> + android:protectionLevel="dangerous" + android:usageInfoRequired="true" /> <!-- Allows an application to write the user's contacts data. <p>Protection level: dangerous --> <permission android:name="android.permission.WRITE_CONTACTS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_writeContacts" android:description="@string/permdesc_writeContacts" android:protectionLevel="dangerous" /> @@ -674,14 +684,17 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.READ_CALENDAR" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_readCalendar" android:description="@string/permdesc_readCalendar" - android:protectionLevel="dangerous" /> + android:protectionLevel="dangerous" + android:usageInfoRequired="true" /> <!-- Allows an application to write the user's calendar data. <p>Protection level: dangerous --> <permission android:name="android.permission.WRITE_CALENDAR" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_writeCalendar" android:description="@string/permdesc_writeCalendar" android:protectionLevel="dangerous" /> @@ -703,6 +716,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.SEND_SMS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_sendSms" android:description="@string/permdesc_sendSms" android:permissionFlags="costsMoney" @@ -712,33 +726,41 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.RECEIVE_SMS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_receiveSms" android:description="@string/permdesc_receiveSms" - android:protectionLevel="dangerous"/> + android:protectionLevel="dangerous" + android:usageInfoRequired="true" /> <!-- Allows an application to read SMS messages. <p>Protection level: dangerous --> <permission android:name="android.permission.READ_SMS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_readSms" android:description="@string/permdesc_readSms" - android:protectionLevel="dangerous" /> + android:protectionLevel="dangerous" + android:usageInfoRequired="true" /> <!-- Allows an application to receive WAP push messages. <p>Protection level: dangerous --> <permission android:name="android.permission.RECEIVE_WAP_PUSH" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_receiveWapPush" android:description="@string/permdesc_receiveWapPush" - android:protectionLevel="dangerous" /> + android:protectionLevel="dangerous" + android:usageInfoRequired="true" /> <!-- Allows an application to monitor incoming MMS messages. <p>Protection level: dangerous --> <permission android:name="android.permission.RECEIVE_MMS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_receiveMms" android:description="@string/permdesc_receiveMms" - android:protectionLevel="dangerous" /> + android:protectionLevel="dangerous" + android:usageInfoRequired="true" /> <!-- @SystemApi @TestApi Allows an application to read previously received cell broadcast messages and to register a content observer to get notifications when @@ -753,9 +775,11 @@ <p>Protection level: dangerous @hide Pending API council approval --> <permission android:name="android.permission.READ_CELL_BROADCASTS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_readCellBroadcasts" android:description="@string/permdesc_readCellBroadcasts" - android:protectionLevel="dangerous" /> + android:protectionLevel="dangerous" + android:usageInfoRequired="true" /> <!-- ====================================================================== --> <!-- Permissions for accessing external storage --> @@ -794,6 +818,7 @@ @deprecated replaced by new strongly-typed permission groups in Q. --> <permission android:name="android.permission.READ_EXTERNAL_STORAGE" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_sdcardRead" android:description="@string/permdesc_sdcardRead" android:protectionLevel="dangerous" @@ -815,6 +840,7 @@ @deprecated replaced by new strongly-typed permission groups in Q. --> <permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_sdcardWrite" android:description="@string/permdesc_sdcardWrite" android:protectionLevel="dangerous" @@ -831,9 +857,11 @@ <!-- Allows an application to read the user's shared audio collection. --> <permission android:name="android.permission.READ_MEDIA_AUDIO" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_audioRead" android:description="@string/permdesc_audioRead" - android:protectionLevel="dangerous" /> + android:protectionLevel="dangerous" + android:usageInfoRequired="true" /> <!-- Runtime permission controlling access to the user's shared visual media collection, including images and videos. --> @@ -846,22 +874,28 @@ <!-- Allows an application to read the user's shared images collection. --> <permission android:name="android.permission.READ_MEDIA_IMAGES" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_imagesRead" android:description="@string/permdesc_imagesRead" - android:protectionLevel="dangerous" /> + android:protectionLevel="dangerous" + android:usageInfoRequired="true" /> <!-- Allows an application to read the user's shared video collection. --> <permission android:name="android.permission.READ_MEDIA_VIDEO" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_videoRead" android:description="@string/permdesc_videoRead" - android:protectionLevel="dangerous" /> + android:protectionLevel="dangerous" + android:usageInfoRequired="true" /> <!-- Allows an application to access any geographic locations persisted in the user's shared collection. --> <permission android:name="android.permission.ACCESS_MEDIA_LOCATION" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_mediaLocation" android:description="@string/permdesc_mediaLocation" - android:protectionLevel="dangerous" /> + android:protectionLevel="dangerous" + android:usageInfoRequired="true" /> <!-- @hide @SystemApi Allows an application to modify OBB files visible to other apps. --> @@ -889,20 +923,24 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.ACCESS_FINE_LOCATION" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_accessFineLocation" android:description="@string/permdesc_accessFineLocation" android:backgroundPermission="android.permission.ACCESS_BACKGROUND_LOCATION" - android:protectionLevel="dangerous|instant" /> + android:protectionLevel="dangerous|instant" + android:usageInfoRequired="true" /> <!-- Allows an app to access approximate location. Alternatively, you might want {@link #ACCESS_FINE_LOCATION}. <p>Protection level: dangerous --> <permission android:name="android.permission.ACCESS_COARSE_LOCATION" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_accessCoarseLocation" android:description="@string/permdesc_accessCoarseLocation" android:backgroundPermission="android.permission.ACCESS_BACKGROUND_LOCATION" - android:protectionLevel="dangerous|instant" /> + android:protectionLevel="dangerous|instant" + android:usageInfoRequired="true" /> <!-- Allows an app to access location in the background. If you are requesting this, you should also request {@link #ACCESS_FINE_LOCATION}. @@ -911,9 +949,11 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_accessBackgroundLocation" android:description="@string/permdesc_accessBackgroundLocation" - android:protectionLevel="dangerous|instant" /> + android:protectionLevel="dangerous|instant" + android:usageInfoRequired="true" /> <!-- ====================================================================== --> <!-- Permissions for accessing the call log --> @@ -951,9 +991,11 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.READ_CALL_LOG" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_readCallLog" android:description="@string/permdesc_readCallLog" - android:protectionLevel="dangerous" /> + android:protectionLevel="dangerous" + android:usageInfoRequired="true" /> <!-- Allows an application to write (but not read) the user's call log data. @@ -969,6 +1011,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.WRITE_CALL_LOG" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_writeCallLog" android:description="@string/permdesc_writeCallLog" android:protectionLevel="dangerous" /> @@ -979,9 +1022,11 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.PROCESS_OUTGOING_CALLS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_processOutgoingCalls" android:description="@string/permdesc_processOutgoingCalls" - android:protectionLevel="dangerous" /> + android:protectionLevel="dangerous" + android:usageInfoRequired="true" /> <!-- ====================================================================== --> <!-- Permissions for accessing the device telephony --> @@ -1010,23 +1055,28 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.READ_PHONE_STATE" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_readPhoneState" android:description="@string/permdesc_readPhoneState" - android:protectionLevel="dangerous" /> + android:protectionLevel="dangerous" + android:usageInfoRequired="true" /> <!-- Allows read access to the device's phone number(s). This is a subset of the capabilities granted by {@link #READ_PHONE_STATE} but is exposed to instant applications. <p>Protection level: dangerous--> <permission android:name="android.permission.READ_PHONE_NUMBERS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_readPhoneNumbers" android:description="@string/permdesc_readPhoneNumbers" - android:protectionLevel="dangerous|instant" /> + android:protectionLevel="dangerous|instant" + android:usageInfoRequired="true" /> <!-- Allows an application to initiate a phone call without going through the Dialer user interface for the user to confirm the call. <p>Protection level: dangerous --> <permission android:name="android.permission.CALL_PHONE" + android:permissionGroup="android.permission-group.UNDEFINED" android:permissionFlags="costsMoney" android:label="@string/permlab_callPhone" android:description="@string/permdesc_callPhone" @@ -1036,6 +1086,7 @@ <p>Protection level: dangerous --> <permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_addVoicemail" android:description="@string/permdesc_addVoicemail" android:protectionLevel="dangerous" /> @@ -1044,6 +1095,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.USE_SIP" + android:permissionGroup="android.permission-group.UNDEFINED" android:description="@string/permdesc_use_sip" android:label="@string/permlab_use_sip" android:protectionLevel="dangerous"/> @@ -1052,6 +1104,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.ANSWER_PHONE_CALLS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_answerPhoneCalls" android:description="@string/permdesc_answerPhoneCalls" android:protectionLevel="dangerous|runtime" /> @@ -1079,6 +1132,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.ACCEPT_HANDOVER" + android:permissionGroup="android.permission-group.UNDEFINED" android.label="@string/permlab_acceptHandover" android:description="@string/permdesc_acceptHandovers" android:protectionLevel="dangerous" /> @@ -1102,9 +1156,11 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.RECORD_AUDIO" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_recordAudio" android:description="@string/permdesc_recordAudio" - android:protectionLevel="dangerous|instant"/> + android:protectionLevel="dangerous|instant" + android:usageInfoRequired="true" /> <!-- ====================================================================== --> <!-- Permissions for activity recognition --> @@ -1123,9 +1179,11 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.ACTIVITY_RECOGNITION" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_activityRecognition" android:description="@string/permdesc_activityRecognition" - android:protectionLevel="dangerous|instant" /> + android:protectionLevel="dangerous|instant" + android:usageInfoRequired="true" /> <!-- ====================================================================== --> <!-- Permissions for accessing the UCE Service --> @@ -1171,9 +1229,11 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.CAMERA" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_camera" android:description="@string/permdesc_camera" - android:protectionLevel="dangerous|instant" /> + android:protectionLevel="dangerous|instant" + android:usageInfoRequired="true" /> <!-- ====================================================================== --> @@ -1194,9 +1254,11 @@ measure what is happening inside his/her body, such as heart rate. <p>Protection level: dangerous --> <permission android:name="android.permission.BODY_SENSORS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_bodySensors" android:description="@string/permdesc_bodySensors" - android:protectionLevel="dangerous" /> + android:protectionLevel="dangerous" + android:usageInfoRequired="true" /> <!-- Allows an app to use fingerprint hardware. <p>Protection level: normal @@ -1680,9 +1742,11 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.GET_ACCOUNTS" + android:permissionGroup="android.permission-group.UNDEFINED" android:protectionLevel="dangerous" android:description="@string/permdesc_getAccounts" - android:label="@string/permlab_getAccounts" /> + android:label="@string/permlab_getAccounts" + android:usageInfoRequired="true" /> <uses-permission android:name="android.permission.GET_ACCOUNTS"/> <!-- @SystemApi Allows applications to call into AccountAuthenticators. @@ -2983,12 +3047,12 @@ <permission android:name="android.permission.BIND_TEXTCLASSIFIER_SERVICE" android:protectionLevel="signature" /> - <!-- Must be required by a android.service.intelligence.IntelligenceService, + <!-- Must be required by a android.service.intelligence.SmartSuggestionsService, to ensure that only the system can bind to it. @SystemApi @hide This is not a third-party API (intended for OEMs and system apps). <p>Protection level: signature --> - <permission android:name="android.permission.BIND_INTELLIGENCE_SERVICE" + <permission android:name="android.permission.BIND_SMART_SUGGESTIONS_SERVICE" android:protectionLevel="signature" /> <!-- Must be required by hotword enrollment application, @@ -4206,6 +4270,12 @@ <permission android:name="android.permission.SMS_FINANCIAL_TRANSACTIONS" android:protectionLevel="signature|appop" /> + <!-- Required for apps targeting {@link android.os.Build.VERSION_CODES#P} that want to use + {@link android.app.Notification.Builder#setFullScreenIntent notification full screen + intents}. --> + <permission android:name="android.permission.USE_FULL_SCREEN_INTENT" + android:protectionLevel="normal" /> + <!-- @SystemApi Allows requesting the framework broadcast the {@link Intent#ACTION_DEVICE_CUSTOMIZATION_READY} intent. @hide --> @@ -4541,6 +4611,13 @@ </intent-filter> </receiver> + <receiver android:name="com.android.server.WallpaperUpdateReceiver" + android:permission="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY"> + <intent-filter> + <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/> + </intent-filter> + </receiver> + <service android:name="android.hardware.location.GeofenceHardwareService" android:permission="android.permission.LOCATION_HARDWARE" android:exported="false" /> diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index 433ae399c88c..4bf1ad6651ee 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -126,7 +126,6 @@ android:visibility="gone" android:contentDescription="@string/notification_alerted_content_description" android:src="@drawable/ic_notifications_alerted" - android:tint="@color/notification_secondary_text_color_light" /> <ImageView android:id="@+id/profile_badge" diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 244728954aa0..f55e48e5b140 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -1915,6 +1915,9 @@ <enum name="KEYCODE_SYSTEM_NAVIGATION_RIGHT" value="283" /> <enum name="KEYCODE_ALL_APPS" value="284" /> <enum name="KEYCODE_REFRESH" value="285" /> + <enum name="KEYCODE_THUMBS_UP" value="286" /> + <enum name="KEYCODE_THUMBS_DOWN" value="287" /> + <enum name="KEYCODE_PROFILE_SWITCH" value="288" /> </attr> <!-- ***************************************************************** --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 1d809613e2b2..0cd6bc5cd700 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2125,6 +2125,9 @@ during initialization when the setting is still null. --> <bool name="config_dozeAlwaysOnEnabled">true</bool> + <!-- If AOD can show an ambient version of the wallpaper --> + <bool name="config_dozeSupportsAodWallpaper">true</bool> + <!-- Whether the display blanks itself when transitioning from a doze to a non-doze state --> <bool name="config_displayBlanksAfterDoze">false</bool> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index f7b9961c39e8..05a156b69371 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -430,7 +430,7 @@ <dimen name="notification_badge_size">12dp</dimen> <!-- Size of the alerted icon for notifications --> - <dimen name="notification_alerted_size">18dp</dimen> + <dimen name="notification_alerted_size">12dp</dimen> <!-- Keyguard dimensions --> <!-- TEMP --> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index bd6d97622a4d..a33f6b2fbf9c 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1444,9 +1444,14 @@ <!-- Title shown when the system-provided biometric dialog is shown, asking the user to authenticate. [CHAR LIMIT=40] --> <string name="biometric_dialog_default_title">Application <xliff:g id="app" example="Gmail">%s</xliff:g> wants to authenticate.</string> - <!-- Message shown when biometric hardware is not available [CHAR LIMIT=50] --> <string name="biometric_error_hw_unavailable">Biometric hardware unavailable</string> + <!-- Message shown when biometric authentication was canceled by the user [CHAR LIMIT=50] --> + <string name="biometric_error_user_canceled">Authentication canceled</string> + <!-- Message shown by the biometric dialog when biometric is not recognized --> + <string name="biometric_not_recognized">Not recognized</string> + <!-- Message shown when biometric authentication has been canceled [CHAR LIMIT=50] --> + <string name="biometric_error_canceled">Authentication canceled</string> <!-- Message shown during fingerprint acquisision when the fingerprint cannot be recognized --> <string name="fingerprint_acquired_partial">Partial fingerprint detected. Please try again.</string> @@ -1462,8 +1467,6 @@ <string-array name="fingerprint_acquired_vendor"> </string-array> - <!-- Message shown by the biometric dialog when biometric is not recognized --> - <string name="biometric_not_recognized">Not recognized</string> <!-- Accessibility message announced when a fingerprint has been authenticated [CHAR LIMIT=NONE] --> <string name="fingerprint_authenticated">Fingerprint authenticated</string> <!-- Accessibility message announced when a face has been authenticated [CHAR LIMIT=NONE] --> @@ -1585,14 +1588,14 @@ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_readSyncStats">Allows an app to read the sync stats for an account, including the history of sync events and how much data is synced. </string> - <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=30] --> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] --> <string name="permlab_sdcardRead">read the contents of your shared storage</string> - <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=30] --> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] --> <string name="permdesc_sdcardRead">Allows the app to read the contents of your shared storage.</string> - <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=30] --> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] --> <string name="permlab_sdcardWrite">modify or delete the contents of your shared storage</string> - <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=30] --> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] --> <string name="permdesc_sdcardWrite">Allows the app to write the contents of your shared storage.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> @@ -2983,7 +2986,7 @@ <!-- Label for item in the text selection menu to translate selected text with a translation app. Should be a verb. [CHAR LIMIT=30] --> <string name="translate">Translate</string> - + <!-- Accessibility description for an item in the text selection menu to translate selected text with a translation app. [CHAR LIMIT=NONE] --> <string name="translate_desc">Translate selected text</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e251e27a5496..01422c8ffd97 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2406,7 +2406,9 @@ <!-- Biometric messages --> <java-symbol type="string" name="biometric_dialog_default_title" /> <java-symbol type="string" name="biometric_error_hw_unavailable" /> + <java-symbol type="string" name="biometric_error_user_canceled" /> <java-symbol type="string" name="biometric_not_recognized" /> + <java-symbol type="string" name="biometric_error_canceled" /> <!-- Fingerprint messages --> <java-symbol type="string" name="fingerprint_error_unable_to_process" /> @@ -3308,6 +3310,7 @@ <java-symbol type="integer" name="config_autoGroupAtCount" /> <java-symbol type="bool" name="config_dozeAlwaysOnDisplayAvailable" /> <java-symbol type="bool" name="config_dozeAlwaysOnEnabled" /> + <java-symbol type="bool" name="config_dozeSupportsAodWallpaper" /> <java-symbol type="bool" name="config_displayBlanksAfterDoze" /> <java-symbol type="bool" name="config_displayBrightnessBucketsInDoze" /> <java-symbol type="integer" name="config_storageManagerDaystoRetainDefault" /> @@ -3499,4 +3502,8 @@ <java-symbol type="string" name="config_defaultAssistantComponentName" /> <java-symbol type="id" name="transition_overlay_view_tag" /> + + <java-symbol type="dimen" name="rounded_corner_radius" /> + <java-symbol type="dimen" name="rounded_corner_radius_top" /> + <java-symbol type="dimen" name="rounded_corner_radius_bottom" /> </resources> diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 3b650e5ff777..46d4a4773389 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -91,6 +91,8 @@ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> <uses-permission android:name="android.permission.KILL_UID" /> + <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> + <!-- location test permissions --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/> diff --git a/core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java b/core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java index c8bc35c976a2..9e1523165925 100644 --- a/core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java +++ b/core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java @@ -16,6 +16,10 @@ package android.os; +import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; +import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; +import static android.os.RedactingFileDescriptor.removeRange; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -58,8 +62,8 @@ public class RedactingFileDescriptorTest { @Test public void testSingleByte() throws Exception { - final FileDescriptor fd = RedactingFileDescriptor - .open(mContext, mFile, new long[] { 10, 11 }).getFileDescriptor(); + final FileDescriptor fd = RedactingFileDescriptor.open(mContext, mFile, MODE_READ_ONLY, + new long[] { 10, 11 }).getFileDescriptor(); final byte[] buf = new byte[1_000]; assertEquals(buf.length, Os.read(fd, buf, 0, buf.length)); @@ -74,8 +78,8 @@ public class RedactingFileDescriptorTest { @Test public void testRanges() throws Exception { - final FileDescriptor fd = RedactingFileDescriptor - .open(mContext, mFile, new long[] { 100, 200, 300, 400 }).getFileDescriptor(); + final FileDescriptor fd = RedactingFileDescriptor.open(mContext, mFile, MODE_READ_ONLY, + new long[] { 100, 200, 300, 400 }).getFileDescriptor(); final byte[] buf = new byte[10]; assertEquals(buf.length, Os.pread(fd, buf, 0, 10, 90)); @@ -96,8 +100,8 @@ public class RedactingFileDescriptorTest { @Test public void testEntireFile() throws Exception { - final FileDescriptor fd = RedactingFileDescriptor - .open(mContext, mFile, new long[] { 0, 5_000_000 }).getFileDescriptor(); + final FileDescriptor fd = RedactingFileDescriptor.open(mContext, mFile, MODE_READ_ONLY, + new long[] { 0, 5_000_000 }).getFileDescriptor(); try (FileInputStream in = new FileInputStream(fd)) { int val; @@ -106,4 +110,61 @@ public class RedactingFileDescriptorTest { } } } + + @Test + public void testReadWrite() throws Exception { + final FileDescriptor fd = RedactingFileDescriptor.open(mContext, mFile, MODE_READ_WRITE, + new long[] { 100, 200, 300, 400 }).getFileDescriptor(); + + // Redacted at first + final byte[] buf = new byte[10]; + assertEquals(buf.length, Os.pread(fd, buf, 0, 10, 95)); + assertArrayEquals(new byte[] { 64, 64, 64, 64, 64, 0, 0, 0, 0, 0 }, buf); + + // But we can see data that we've written + Os.pwrite(fd, new byte[] { 32, 32 }, 0, 2, 102); + assertEquals(buf.length, Os.pread(fd, buf, 0, 10, 95)); + assertArrayEquals(new byte[] { 64, 64, 64, 64, 64, 0, 0, 32, 32, 0 }, buf); + } + + @Test + public void testRemoveRange() throws Exception { + // Removing outside ranges should have no changes + assertArrayEquals(new long[] { 100, 200, 300, 400 }, + removeRange(new long[] { 100, 200, 300, 400 }, 0, 100)); + assertArrayEquals(new long[] { 100, 200, 300, 400 }, + removeRange(new long[] { 100, 200, 300, 400 }, 200, 300)); + assertArrayEquals(new long[] { 100, 200, 300, 400 }, + removeRange(new long[] { 100, 200, 300, 400 }, 400, 500)); + + // Removing full regions + assertArrayEquals(new long[] { 100, 200 }, + removeRange(new long[] { 100, 200, 300, 400 }, 300, 400)); + assertArrayEquals(new long[] { 100, 200 }, + removeRange(new long[] { 100, 200, 300, 400 }, 250, 450)); + assertArrayEquals(new long[] { 300, 400 }, + removeRange(new long[] { 100, 200, 300, 400 }, 50, 250)); + assertArrayEquals(new long[] { }, + removeRange(new long[] { 100, 200, 300, 400 }, 0, 5_000_000)); + } + + @Test + public void testRemoveRange_Partial() throws Exception { + assertArrayEquals(new long[] { 150, 200, 300, 400 }, + removeRange(new long[] { 100, 200, 300, 400 }, 50, 150)); + assertArrayEquals(new long[] { 100, 150, 300, 400 }, + removeRange(new long[] { 100, 200, 300, 400 }, 150, 250)); + assertArrayEquals(new long[] { 100, 150, 350, 400 }, + removeRange(new long[] { 100, 200, 300, 400 }, 150, 350)); + assertArrayEquals(new long[] { 100, 150 }, + removeRange(new long[] { 100, 200, 300, 400 }, 150, 500)); + } + + @Test + public void testRemoveRange_Hole() throws Exception { + assertArrayEquals(new long[] { 100, 125, 175, 200, 300, 400 }, + removeRange(new long[] { 100, 200, 300, 400 }, 125, 175)); + assertArrayEquals(new long[] { 100, 200 }, + removeRange(new long[] { 100, 200 }, 150, 150)); + } } diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 305d2af2122d..a8f9e8a5891b 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -664,7 +664,9 @@ public class SettingsBackupTest { Settings.Secure.PACKAGES_TO_CLEAR_DATA_BEFORE_FULL_RESTORE, Settings.Secure.FLASHLIGHT_AVAILABLE, Settings.Secure.FLASHLIGHT_ENABLED, - Settings.Secure.CROSS_PROFILE_CALENDAR_ENABLED); + Settings.Secure.CROSS_PROFILE_CALENDAR_ENABLED, + Settings.Secure.LOCATION_ACCESS_CHECK_INTERVAL_MILLIS, + Settings.Secure.LOCATION_ACCESS_CHECK_DELAY_MILLIS); @Test public void systemSettingsBackedUpOrBlacklisted() { diff --git a/core/tests/coretests/src/android/text/AndroidCharacterTest.java b/core/tests/coretests/src/android/text/AndroidCharacterTest.java new file mode 100644 index 000000000000..0c7e730e78e4 --- /dev/null +++ b/core/tests/coretests/src/android/text/AndroidCharacterTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS,d + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.text; + +import static org.junit.Assert.assertArrayEquals; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; + +import org.junit.Test; + +@Presubmit +@SmallTest +public class AndroidCharacterTest { + + @Test + public void testGetDirectionalities_nonSupplementaryCharacters() { + int size = Character.MAX_VALUE + 1 + - (Character.MAX_SURROGATE - Character.MIN_SURROGATE + 1); + char[] chars = new char[size]; + byte[] java_lang_results = new byte[size]; + int index = 0; + for (int cp = 0; cp <= Character.MAX_VALUE; cp++) { + if (cp < Character.MIN_SURROGATE || cp > Character.MAX_SURROGATE) { + chars[index] = (char) cp; + java_lang_results[index] = Character.getDirectionality(cp); + index++; + } + } + + byte[] android_text_results = new byte[size]; + AndroidCharacter.getDirectionalities(chars, android_text_results, index); + assertArrayEquals(java_lang_results, android_text_results); + } + + @Test + public void testGetDirectionalities_supplementaryCharacters() { + int maxNumberOfChars = Character.MAX_CODE_POINT - Character.MIN_SUPPLEMENTARY_CODE_POINT + + 1; + int size = maxNumberOfChars * 2; + char[] chars = new char[size]; + byte[] java_lang_results = new byte[size]; + int index = 0; + for (int cp = Character.MIN_SUPPLEMENTARY_CODE_POINT; cp <= Character.MAX_CODE_POINT; + cp++) { + chars[index] = Character.highSurrogate(cp); + chars[index + 1] = Character.lowSurrogate(cp); + java_lang_results[index] = java_lang_results[index + 1] = Character + .getDirectionality(cp); + index += 2; + } + + byte[] android_text_results = new byte[size]; + AndroidCharacter.getDirectionalities(chars, android_text_results, index); + assertArrayEquals(java_lang_results, android_text_results); + } +} diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java new file mode 100644 index 000000000000..ed80cd7e375c --- /dev/null +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.view; + +import static android.view.InsetsState.TYPE_TOP_BAR; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNull; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.FlakyTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@FlakyTest(detail = "Promote once confirmed non-flaky") +@RunWith(AndroidJUnit4.class) +public class InsetsControllerTest { + + private InsetsController mController = new InsetsController(); + + private SurfaceSession mSession = new SurfaceSession(); + private SurfaceControl mLeash; + + @Before + public void setup() { + mLeash = new SurfaceControl.Builder(mSession) + .setName("testSurface") + .build(); + } + + @Test + public void testControlsChanged() { + InsetsSourceControl control = new InsetsSourceControl(TYPE_TOP_BAR, mLeash); + mController.onControlsChanged(new InsetsSourceControl[] { control }); + assertEquals(mLeash, + mController.getSourceConsumer(TYPE_TOP_BAR).getControl().getLeash()); + } + + @Test + public void testControlsRevoked() { + InsetsSourceControl control = new InsetsSourceControl(TYPE_TOP_BAR, mLeash); + mController.onControlsChanged(new InsetsSourceControl[] { control }); + mController.onControlsChanged(new InsetsSourceControl[0]); + assertNull(mController.getSourceConsumer(TYPE_TOP_BAR).getControl()); + } +} diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java new file mode 100644 index 000000000000..5a20ba2522b1 --- /dev/null +++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.view; + +import static android.view.InsetsState.TYPE_TOP_BAR; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.FlakyTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.SurfaceControl.Transaction; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@Presubmit +@FlakyTest(detail = "Promote once confirmed non-flaky") +@RunWith(AndroidJUnit4.class) +public class InsetsSourceConsumerTest { + + private InsetsSourceConsumer mConsumer; + + private SurfaceSession mSession = new SurfaceSession(); + private SurfaceControl mLeash; + @Mock Transaction mMockTransaction; + @Mock InsetsController mMockController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mLeash = new SurfaceControl.Builder(mSession) + .setName("testSurface") + .build(); + mConsumer = new InsetsSourceConsumer(TYPE_TOP_BAR, new InsetsState(), + () -> mMockTransaction); + mConsumer.setControl(new InsetsSourceControl(TYPE_TOP_BAR, mLeash)); + } + + @Test + public void testHide() { + mConsumer.hide(); + verify(mMockTransaction).hide(eq(mLeash)); + } + + @Test + public void testShow() { + mConsumer.hide(); + mConsumer.show(); + verify(mMockTransaction, atLeastOnce()).show(eq(mLeash)); + } + + @Test + public void testRestore() { + mConsumer.setControl(null); + reset(mMockTransaction); + mConsumer.hide(); + verifyZeroInteractions(mMockTransaction); + mConsumer.setControl(new InsetsSourceControl(TYPE_TOP_BAR, mLeash)); + verify(mMockTransaction).hide(eq(mLeash)); + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java index 30309cf64e96..8691e73f82fb 100644 --- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java @@ -557,6 +557,62 @@ public class BinderCallsStatsTest { assertEquals(0, bcs.getExceptionCounts().size()); } + @Test + public void testOverflow_sameEntry() { + TestBinderCallsStats bcs = new TestBinderCallsStats(); + bcs.setDetailedTracking(true); + bcs.setSamplingInterval(1); + bcs.setMaxBinderCallStats(2); + + Binder binder = new Binder(); + CallSession callSession = bcs.callStarted(binder, 1); + bcs.time += 10; + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + + callSession = bcs.callStarted(binder, 1); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + + callSession = bcs.callStarted(binder, 1); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + + BinderCallsStats.UidEntry uidEntry = bcs.getUidEntries().get(WORKSOURCE_UID); + List<BinderCallsStats.CallStat> callStatsList = new ArrayList(uidEntry.getCallStatsList()); + assertEquals(1, callStatsList.size()); + BinderCallsStats.CallStat callStats = callStatsList.get(0); + assertEquals(3, callStats.callCount); + } + + @Test + public void testOverflow_overflowEntry() { + TestBinderCallsStats bcs = new TestBinderCallsStats(); + bcs.setDetailedTracking(true); + bcs.setSamplingInterval(1); + bcs.setMaxBinderCallStats(1); + + Binder binder = new Binder(); + CallSession callSession = bcs.callStarted(binder, 1); + bcs.time += 10; + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + + callSession = bcs.callStarted(binder, 2); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + + List<BinderCallsStats.ExportedCallStat> callStatsList = bcs.getExportedCallStats(); + assertEquals(2, callStatsList.size()); + BinderCallsStats.ExportedCallStat callStats = callStatsList.get(0); + assertEquals(1, callStats.callCount); + assertEquals("1", callStats.methodName); + assertEquals("android.os.Binder", callStats.className); + assertEquals(CALLING_UID, callStats.callingUid); + + callStats = callStatsList.get(1); + assertEquals(1, callStats.callCount); + assertEquals("-1", callStats.methodName); + assertEquals("com.android.internal.os.BinderCallsStats$OverflowBinder", + callStats.className); + assertEquals(CALLING_UID, callStats.callingUid); + } + class TestBinderCallsStats extends BinderCallsStats { public int callingUid = CALLING_UID; public int workSourceUid = WORKSOURCE_UID; diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 3933f508fe84..9b86b77a9384 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -163,9 +163,6 @@ public class Typeface { private int[] mSupportedAxes; private static final int[] EMPTY_AXES = {}; - // The underlying font families. - private final FontFamily[] mFamilies; - @UnsupportedAppUsage private static void setDefault(Typeface t) { sDefaultTypeface = t; @@ -252,7 +249,22 @@ public class Typeface { if (familyBuilder == null) { return Typeface.DEFAULT; } - typeface = new Typeface.CustomFallbackBuilder(familyBuilder.build()).build(); + final FontFamily family = familyBuilder.build(); + final FontStyle normal = new FontStyle(FontStyle.FONT_WEIGHT_NORMAL, + FontStyle.FONT_SLANT_UPRIGHT); + Font bestFont = family.getFont(0); + int bestScore = normal.getMatchScore(bestFont.getStyle()); + for (int i = 1; i < family.getSize(); ++i) { + final Font candidate = family.getFont(i); + final int score = normal.getMatchScore(candidate.getStyle()); + if (score < bestScore) { + bestFont = candidate; + bestScore = score; + } + } + typeface = new Typeface.CustomFallbackBuilder(family) + .setStyle(bestFont.getStyle()) + .build(); } catch (IOException e) { typeface = Typeface.DEFAULT; } @@ -732,21 +744,17 @@ public class Typeface { public Typeface build() { final int userFallbackSize = mFamilies.size(); final FontFamily[] fallback = SystemFonts.getSystemFallback(mFallbackName); - final FontFamily[] fullFamilies = new FontFamily[fallback.length + userFallbackSize]; final long[] ptrArray = new long[fallback.length + userFallbackSize]; for (int i = 0; i < userFallbackSize; ++i) { ptrArray[i] = mFamilies.get(i).getNativePtr(); - fullFamilies[i] = mFamilies.get(i); } for (int i = 0; i < fallback.length; ++i) { ptrArray[i + userFallbackSize] = fallback[i].getNativePtr(); - fullFamilies[i + userFallbackSize] = fallback[i]; } final int weight = mStyle == null ? 400 : mStyle.getWeight(); final int italic = (mStyle == null || mStyle.getSlant() == FontStyle.FONT_SLANT_UPRIGHT) ? 0 : 1; - - return new Typeface(nativeCreateFromArray(ptrArray, weight, italic), fullFamilies); + return new Typeface(nativeCreateFromArray(ptrArray, weight, italic)); } } @@ -811,7 +819,7 @@ public class Typeface { } } - typeface = new Typeface(nativeCreateFromTypeface(ni, style), family.mFamilies); + typeface = new Typeface(nativeCreateFromTypeface(ni, style)); styles.put(style, typeface); } return typeface; @@ -879,8 +887,7 @@ public class Typeface { } typeface = new Typeface( - nativeCreateFromTypefaceWithExactStyle( - base.native_instance, weight, italic), base.mFamilies); + nativeCreateFromTypefaceWithExactStyle(base.native_instance, weight, italic)); innerCache.put(key, typeface); } return typeface; @@ -890,8 +897,7 @@ public class Typeface { public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family, @NonNull List<FontVariationAxis> axes) { final Typeface base = family == null ? Typeface.DEFAULT : family; - return new Typeface(nativeCreateFromTypefaceWithVariation(base.native_instance, axes), - base.mFamilies); + return new Typeface(nativeCreateFromTypefaceWithVariation(base.native_instance, axes)); } /** @@ -997,7 +1003,7 @@ public class Typeface { ptrArray[i] = families[i].getNativePtr(); } return new Typeface(nativeCreateFromArray(ptrArray, - RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE), families); + RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); } /** @@ -1045,19 +1051,6 @@ public class Typeface { } native_instance = ni; - mFamilies = new FontFamily[0]; - sRegistry.registerNativeAllocation(this, native_instance); - mStyle = nativeGetStyle(ni); - mWeight = nativeGetWeight(ni); - } - - private Typeface(long ni, @NonNull FontFamily[] families) { - if (ni == 0) { - throw new IllegalStateException("native typeface cannot be made"); - } - - native_instance = ni; - mFamilies = families; sRegistry.registerNativeAllocation(this, native_instance); mStyle = nativeGetStyle(ni); mWeight = nativeGetWeight(ni); @@ -1084,8 +1077,7 @@ public class Typeface { final Typeface base = systemFontMap.get(alias.getToName()); final int weight = alias.getWeight(); final Typeface newFace = weight == 400 ? base : - new Typeface(nativeCreateWeightAlias(base.native_instance, weight), - base.mFamilies); + new Typeface(nativeCreateWeightAlias(base.native_instance, weight)); systemFontMap.put(alias.getName(), newFace); } } diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index 87402342a3c9..991847ad27fa 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -1571,15 +1571,32 @@ public class GradientDrawable extends Drawable { st.mGradient = a.getInt( R.styleable.GradientDrawableGradient_type, st.mGradient); - // TODO: Update these to be themeable. + final boolean hasGradientColors = st.mGradientColors != null; + final boolean hasGradientCenter = st.hasCenterColor(); + final int prevStart = hasGradientColors ? st.mGradientColors[0] : 0; + final int prevCenter = hasGradientCenter ? st.mGradientColors[1] : 0; + final int prevEnd; + + if (st.hasCenterColor()) { + // if there is a center color, the end color is the last of the 3 values + prevEnd = st.mGradientColors[2]; + } else if (hasGradientColors) { + // if there is not a center color but there are already colors configured, then + // the end color is the 2nd value in the array + prevEnd = st.mGradientColors[1]; + } else { + // otherwise, there isn't a previously configured end color + prevEnd = 0; + } + final int startColor = a.getColor( - R.styleable.GradientDrawableGradient_startColor, 0); + R.styleable.GradientDrawableGradient_startColor, prevStart); final boolean hasCenterColor = a.hasValue( - R.styleable.GradientDrawableGradient_centerColor); + R.styleable.GradientDrawableGradient_centerColor) || hasGradientCenter; final int centerColor = a.getColor( - R.styleable.GradientDrawableGradient_centerColor, 0); + R.styleable.GradientDrawableGradient_centerColor, prevCenter); final int endColor = a.getColor( - R.styleable.GradientDrawableGradient_endColor, 0); + R.styleable.GradientDrawableGradient_endColor, prevEnd); if (hasCenterColor) { st.mGradientColors = new int[3]; @@ -1943,6 +1960,10 @@ public class GradientDrawable extends Drawable { } } + public boolean hasCenterColor() { + return mGradientColors != null && mGradientColors.length == 3; + } + private void applyDensityScaling(int sourceDensity, int targetDensity) { if (mInnerRadius > 0) { mInnerRadius = Drawable.scaleFromDensity( diff --git a/graphics/java/android/graphics/fonts/FontStyle.java b/graphics/java/android/graphics/fonts/FontStyle.java index 82fc7ac01772..af517d623b01 100644 --- a/graphics/java/android/graphics/fonts/FontStyle.java +++ b/graphics/java/android/graphics/fonts/FontStyle.java @@ -18,6 +18,7 @@ package android.graphics.fonts; import android.annotation.IntDef; import android.annotation.IntRange; +import android.annotation.NonNull; import android.annotation.Nullable; import com.android.internal.util.Preconditions; @@ -232,6 +233,16 @@ public final class FontStyle { return mSlant; } + /** + * Compute the matching score for another style. + * + * The smaller is better. + * @hide + */ + public int getMatchScore(@NonNull FontStyle o) { + return Math.abs((getWeight() - o.getWeight())) / 100 + (getSlant() == o.getSlant() ? 0 : 2); + } + @Override public boolean equals(@Nullable Object o) { if (o == this) { diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 5f27faed08f3..da77b99e6e53 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -9,8 +9,6 @@ cc_defaults { "hwui_lto", ], - cpp_std: "c++17", - cflags: [ "-DEGL_EGLEXT_PROTOTYPES", "-DGL_GLEXT_PROTOTYPES", @@ -179,7 +177,6 @@ cc_defaults { "renderthread/CanvasContext.cpp", "renderthread/DrawFrameTask.cpp", "renderthread/EglManager.cpp", - "renderthread/ReliableSurface.cpp", "renderthread/VulkanManager.cpp", "renderthread/RenderProxy.cpp", "renderthread/RenderTask.cpp", diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp index 06e937ab66f4..0cfaa8c61279 100644 --- a/libs/hwui/CanvasTransform.cpp +++ b/libs/hwui/CanvasTransform.cpp @@ -146,4 +146,4 @@ bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette pale return shouldInvert; } -}; // namespace android::uirenderer
\ No newline at end of file +} // namespace android::uirenderer diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h index a952cc23e1ef..dc63e5db4a70 100644 --- a/libs/hwui/DisplayList.h +++ b/libs/hwui/DisplayList.h @@ -31,5 +31,5 @@ typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot; */ using DisplayList = skiapipeline::SkiaDisplayList; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/FrameMetricsObserver.h b/libs/hwui/FrameMetricsObserver.h index ba72e937095f..237fc622dd2e 100644 --- a/libs/hwui/FrameMetricsObserver.h +++ b/libs/hwui/FrameMetricsObserver.h @@ -26,5 +26,5 @@ public: virtual void notify(const int64_t* buffer); }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/FrameMetricsReporter.h b/libs/hwui/FrameMetricsReporter.h index d920a99f5ee3..75b8038c5040 100644 --- a/libs/hwui/FrameMetricsReporter.h +++ b/libs/hwui/FrameMetricsReporter.h @@ -56,5 +56,5 @@ private: std::vector<sp<FrameMetricsObserver> > mObservers; }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/GlFunctorLifecycleListener.h b/libs/hwui/GlFunctorLifecycleListener.h index 5d07b46919d4..5adc46961c8b 100644 --- a/libs/hwui/GlFunctorLifecycleListener.h +++ b/libs/hwui/GlFunctorLifecycleListener.h @@ -28,5 +28,5 @@ public: virtual void onGlFunctorReleased(Functor* functor) = 0; }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp index 165fc4860fb2..a97c12cad9fd 100644 --- a/libs/hwui/HardwareBitmapUploader.cpp +++ b/libs/hwui/HardwareBitmapUploader.cpp @@ -256,4 +256,4 @@ sk_sp<Bitmap> HardwareBitmapUploader::allocateHardwareBitmap(const SkBitmap& sou return sk_sp<Bitmap>(new Bitmap(buffer.get(), bitmap.info(), Bitmap::computePalette(bitmap))); } -}; // namespace android::uirenderer +} // namespace android::uirenderer diff --git a/libs/hwui/HardwareBitmapUploader.h b/libs/hwui/HardwareBitmapUploader.h index c0113d81fefb..6298013bd263 100644 --- a/libs/hwui/HardwareBitmapUploader.h +++ b/libs/hwui/HardwareBitmapUploader.h @@ -25,4 +25,4 @@ public: static sk_sp<Bitmap> allocateHardwareBitmap(const SkBitmap& sourceBitmap); }; -}; // namespace android::uirenderer +} // namespace android::uirenderer diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp index d0df200d2fa6..a15ff2235db2 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -54,5 +54,5 @@ SkBlendMode Layer::getMode() const { } } -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h index 98600dbf1eea..ea3bfc9e80cb 100644 --- a/libs/hwui/Layer.h +++ b/libs/hwui/Layer.h @@ -144,5 +144,5 @@ private: }; // struct Layer -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/LayerUpdateQueue.h b/libs/hwui/LayerUpdateQueue.h index 6857999500f0..2c63af6aaab4 100644 --- a/libs/hwui/LayerUpdateQueue.h +++ b/libs/hwui/LayerUpdateQueue.h @@ -50,7 +50,7 @@ private: std::vector<Entry> mEntries; }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_LAYER_UPDATE_QUEUE_H diff --git a/libs/hwui/Lighting.h b/libs/hwui/Lighting.h index d972c2181aea..ccfbb93b00ef 100644 --- a/libs/hwui/Lighting.h +++ b/libs/hwui/Lighting.h @@ -34,5 +34,5 @@ struct LightInfo { uint8_t spotShadowAlpha; }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/Matrix.cpp b/libs/hwui/Matrix.cpp index d84ed321a4cb..d0dbff031e99 100644 --- a/libs/hwui/Matrix.cpp +++ b/libs/hwui/Matrix.cpp @@ -526,5 +526,5 @@ void Matrix4::dump(const char* label) const { ALOGD("]"); } -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h index 1b5cb60ca4bd..b33cfe2ec511 100644 --- a/libs/hwui/Matrix.h +++ b/libs/hwui/Matrix.h @@ -245,5 +245,5 @@ private: typedef Matrix4 mat4; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/NinePatchUtils.h b/libs/hwui/NinePatchUtils.h index 082e95fb1440..86d3cb9a5b11 100644 --- a/libs/hwui/NinePatchUtils.h +++ b/libs/hwui/NinePatchUtils.h @@ -103,5 +103,5 @@ static inline void SetLatticeFlags(SkCanvas::Lattice* lattice, SkCanvas::Lattice } } -}; // namespace NinePatchUtils -}; // namespace android +} // namespace NinePatchUtils +} // namespace android diff --git a/libs/hwui/PathParser.cpp b/libs/hwui/PathParser.cpp index ad599e9ec316..808921d344da 100644 --- a/libs/hwui/PathParser.cpp +++ b/libs/hwui/PathParser.cpp @@ -304,5 +304,5 @@ void PathParser::parseAsciiStringForSkPath(SkPath* skPath, ParseResult* result, return; } -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/PathParser.h b/libs/hwui/PathParser.h index 474eb97b53c6..f5bebce605fb 100644 --- a/libs/hwui/PathParser.h +++ b/libs/hwui/PathParser.h @@ -46,6 +46,6 @@ public: static void validateVerbAndPoints(char verb, size_t points, ParseResult* result); }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_PATHPARSER_H diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index 3f2c616eb8ff..4a3e10c54cef 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -223,5 +223,5 @@ void Properties::overrideRenderPipelineType(RenderPipelineType type) { sRenderPipelineType = type; } -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 542bc71f7c72..da53f6657ff7 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -289,7 +289,7 @@ private: static RenderPipelineType sRenderPipelineType; }; // class Caches -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_PROPERTIES_H diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index f928de9b92a6..c63e449319dd 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -1028,5 +1028,5 @@ void RecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { fDL->drawVectorDrawable(tree); } -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 099e0be433ea..08cfc6266f56 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -216,5 +216,5 @@ private: DisplayListData* fDL; }; -}; // namespace uirenderer -}; // namespace android
\ No newline at end of file +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h index 0715187e19ea..d6362ef10aad 100644 --- a/libs/hwui/Rect.h +++ b/libs/hwui/Rect.h @@ -262,5 +262,5 @@ public: } }; // class Rect -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/UvMapper.h b/libs/hwui/UvMapper.h index b495e3394bc9..833ca4a79ce5 100644 --- a/libs/hwui/UvMapper.h +++ b/libs/hwui/UvMapper.h @@ -124,7 +124,7 @@ private: float mMaxV; }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_UV_MAPPER_H diff --git a/libs/hwui/Vector.h b/libs/hwui/Vector.h index d2c15ad872a5..e6eea1c5c0ca 100644 --- a/libs/hwui/Vector.h +++ b/libs/hwui/Vector.h @@ -113,7 +113,7 @@ public: } }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_VECTOR_H diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp index 6cf04bf5f811..dd62bbbdc84f 100644 --- a/libs/hwui/VectorDrawable.cpp +++ b/libs/hwui/VectorDrawable.cpp @@ -691,7 +691,7 @@ BitmapPalette Tree::computePalette() { return BitmapPalette::Unknown; } -}; // namespace VectorDrawable +} // namespace VectorDrawable -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/Vertex.h b/libs/hwui/Vertex.h index f0912777e3d8..28cabb9be0f1 100644 --- a/libs/hwui/Vertex.h +++ b/libs/hwui/Vertex.h @@ -73,7 +73,7 @@ struct TextureVertex { REQUIRE_COMPATIBLE_LAYOUT(TextureVertex); -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_VERTEX_H diff --git a/libs/hwui/VertexBuffer.h b/libs/hwui/VertexBuffer.h index 613cf4af64b2..6543a2251f7a 100644 --- a/libs/hwui/VertexBuffer.h +++ b/libs/hwui/VertexBuffer.h @@ -174,7 +174,7 @@ private: void (*mCleanupIndexMethod)(void*); }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_VERTEX_BUFFER_H diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index e99742bc2eba..a5f21d8e6d73 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -76,8 +76,8 @@ typedef uint32_t Flags; namespace uirenderer { namespace VectorDrawable { class Tree; -}; -}; +} +} typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot; typedef std::function<void(uint16_t* text, float* positions)> ReadGlyphFunc; @@ -318,4 +318,4 @@ protected: friend class DrawTextOnPathFunctor; }; -}; // namespace android +} // namespace android diff --git a/libs/hwui/pipeline/skia/AnimatedDrawables.h b/libs/hwui/pipeline/skia/AnimatedDrawables.h index efef6de2a9e1..bf19655825b3 100644 --- a/libs/hwui/pipeline/skia/AnimatedDrawables.h +++ b/libs/hwui/pipeline/skia/AnimatedDrawables.h @@ -79,6 +79,6 @@ private: sp<uirenderer::CanvasPropertyPaint> mPaint; }; -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/DumpOpsCanvas.h b/libs/hwui/pipeline/skia/DumpOpsCanvas.h index e4ba13da709c..206219426bb0 100644 --- a/libs/hwui/pipeline/skia/DumpOpsCanvas.h +++ b/libs/hwui/pipeline/skia/DumpOpsCanvas.h @@ -172,6 +172,6 @@ private: std::string mIdent; }; -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/FunctorDrawable.h b/libs/hwui/pipeline/skia/FunctorDrawable.h index 162d13762e1a..af3a056864a7 100644 --- a/libs/hwui/pipeline/skia/FunctorDrawable.h +++ b/libs/hwui/pipeline/skia/FunctorDrawable.h @@ -48,6 +48,6 @@ protected: const SkRect mBounds; }; -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp index 90d5e715f8cd..4a87e7502c6f 100644 --- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp @@ -216,6 +216,6 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { } } -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.h b/libs/hwui/pipeline/skia/GLFunctorDrawable.h index b06f7f029f23..215979cba2e3 100644 --- a/libs/hwui/pipeline/skia/GLFunctorDrawable.h +++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.h @@ -41,6 +41,6 @@ protected: void onDraw(SkCanvas* canvas) override; }; -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp index 9b408fbc95ca..f08ac17e4082 100644 --- a/libs/hwui/pipeline/skia/LayerDrawable.cpp +++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp @@ -145,6 +145,6 @@ bool LayerDrawable::DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer return layerImage != nullptr; } -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/LayerDrawable.h b/libs/hwui/pipeline/skia/LayerDrawable.h index 5c125908ffb2..95dc6d0cf096 100644 --- a/libs/hwui/pipeline/skia/LayerDrawable.h +++ b/libs/hwui/pipeline/skia/LayerDrawable.h @@ -45,6 +45,6 @@ private: sp<DeferredLayerUpdater> mLayerUpdater; }; -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index d80cb6d1ab70..4494cb05df10 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -332,6 +332,6 @@ void RenderNodeDrawable::setViewProperties(const RenderProperties& properties, S } } -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h index d746978b0a61..6ba8e599818c 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.h +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.h @@ -150,6 +150,6 @@ private: SkiaDisplayList* mProjectedDisplayList = nullptr; }; -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp index dba97fe5ef9f..0a3c8f4347eb 100644 --- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp +++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp @@ -211,6 +211,6 @@ void EndReorderBarrierDrawable::drawShadow(SkCanvas* canvas, RenderNodeDrawable* casterAlpha < 1.0f ? SkShadowFlags::kTransparentOccluder_ShadowFlag : 0); } -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h index 26cfa908228c..cfc0f9b258da 100644 --- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h +++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h @@ -74,6 +74,6 @@ private: StartReorderBarrierDrawable* mStartBarrier; }; -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp index 38905138e332..ac6f6a3f776d 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp @@ -141,6 +141,6 @@ void SkiaDisplayList::output(std::ostream& output, uint32_t level) { mDisplayList.draw(&canvas); } -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h index ac7bb7b0950c..d7879e722a29 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.h +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h @@ -36,7 +36,7 @@ class Outline; namespace VectorDrawable { class Tree; -}; +} typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot; namespace skiapipeline { @@ -179,6 +179,6 @@ public: SkMatrix mParentMatrix; }; -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index 07979a22c988..142bca95e598 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -155,7 +155,7 @@ void SkiaOpenGLPipeline::onStop() { } } -bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior, +bool SkiaOpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior, ColorMode colorMode) { if (mEglSurface != EGL_NO_SURFACE) { mEglManager.destroySurface(mEglSurface); diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h index 479910697871..4ab3541d447b 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h @@ -42,7 +42,7 @@ public: bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) override; DeferredLayerUpdater* createTextureLayer() override; - bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior, + bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior, renderthread::ColorMode colorMode) override; void onStop() override; bool isSurfaceReady() override; diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index f5de1c8adfaf..b682ab0256dd 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -259,6 +259,6 @@ double SkiaRecordingCanvas::drawAnimatedImage(AnimatedImageDrawable* animatedIma return 0; } -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h index 988728dfe23e..d6107a9d9969 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h @@ -92,6 +92,6 @@ private: PaintCoW&& filterBitmap(PaintCoW&& paint, sk_sp<SkColorFilter> colorSpaceFilter); }; -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index e50ad1cd8c44..3607b23a633e 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -115,15 +115,16 @@ DeferredLayerUpdater* SkiaVulkanPipeline::createTextureLayer() { void SkiaVulkanPipeline::onStop() {} -bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior, +bool SkiaVulkanPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior, ColorMode colorMode) { if (mVkSurface) { mVkManager.destroySurface(mVkSurface); mVkSurface = nullptr; } + mSurfaceColorSpace = SkColorSpace::MakeSRGB(); if (surface) { - mVkSurface = mVkManager.createSurface(surface, colorMode); + mVkSurface = mVkManager.createSurface(surface, colorMode, mSurfaceColorSpace); } if (colorMode == ColorMode::SRGB) { diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index 02874c7d2c69..14c0d69dba33 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -38,7 +38,7 @@ public: bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) override; DeferredLayerUpdater* createTextureLayer() override; - bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior, + bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior, renderthread::ColorMode colorMode) override; void onStop() override; bool isSurfaceReady() override; diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp index a594206a2dd9..004a558dd9d0 100644 --- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp @@ -219,6 +219,6 @@ void VkInteropFunctorDrawable::syncFunctor() const { }); } -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.h b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.h index 3269cfbb8fe3..8fe52c5ef700 100644 --- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.h +++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.h @@ -51,6 +51,6 @@ private: SkImageInfo mFBInfo; }; -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h index efa9da27199d..9e1bb8e8e548 100644 --- a/libs/hwui/private/hwui/DrawGlInfo.h +++ b/libs/hwui/private/hwui/DrawGlInfo.h @@ -83,7 +83,7 @@ struct DrawGlInfo { }; }; // struct DrawGlInfo -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_DRAW_GL_INFO_H diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 182233fb3715..6869972b5e7f 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -142,12 +142,7 @@ void CanvasContext::destroy() { void CanvasContext::setSurface(sp<Surface>&& surface) { ATRACE_CALL(); - if (surface) { - mNativeSurface = new ReliableSurface{std::move(surface)}; - mNativeSurface->setDequeueTimeout(500_ms); - } else { - mNativeSurface = nullptr; - } + mNativeSurface = std::move(surface); ColorMode colorMode = mWideColorGamut ? ColorMode::WideColorGamut : ColorMode::SRGB; bool hasSurface = mRenderPipeline->setSurface(mNativeSurface.get(), mSwapBehavior, colorMode); @@ -290,7 +285,6 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy info.damageAccumulator = &mDamageAccumulator; info.layerUpdateQueue = &mLayerUpdateQueue; - info.out.canDrawThisFrame = true; mAnimationContext->startFrame(info.mode); mRenderPipeline->onPrepareTree(); @@ -310,7 +304,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy mIsDirty = true; - if (CC_UNLIKELY(!hasSurface())) { + if (CC_UNLIKELY(!mNativeSurface.get())) { mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); info.out.canDrawThisFrame = false; return; @@ -329,6 +323,8 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy // the deadline for RT animations info.out.canDrawThisFrame = false; } + } else { + info.out.canDrawThisFrame = true; } // TODO: Do we need to abort out if the backdrop is added but not ready? Should that even @@ -339,19 +335,6 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy if (!info.out.canDrawThisFrame) { mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); - return; - } - - int err = mNativeSurface->reserveNext(); - if (err != OK) { - mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); - info.out.canDrawThisFrame = false; - ALOGW("reserveNext failed, error = %d", err); - if (err != TIMED_OUT) { - // A timed out surface can still recover, but assume others are permanently dead. - setSurface(nullptr); - } - return; } bool postedFrameCallback = false; diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 9e7abf447cd6..70be4a6d7730 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -25,7 +25,6 @@ #include "IRenderPipeline.h" #include "LayerUpdateQueue.h" #include "RenderNode.h" -#include "ReliableSurface.h" #include "renderthread/RenderTask.h" #include "renderthread/RenderThread.h" #include "thread/Task.h" @@ -220,7 +219,7 @@ private: EGLint mLastFrameHeight = 0; RenderThread& mRenderThread; - sp<ReliableSurface> mNativeSurface; + sp<Surface> mNativeSurface; // stopped indicates the CanvasContext will reject actual redraw operations, // and defer repaint until it is un-stopped bool mStopped = false; diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 8230dfd44f9a..65ced6ad9316 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -31,8 +31,6 @@ #include <string> #include <vector> -#include <system/window.h> -#include <gui/Surface.h> #define GLES_VERSION 2 @@ -108,7 +106,7 @@ void EglManager::initialize() { LOG_ALWAYS_FATAL_IF(eglInitialize(mEglDisplay, &major, &minor) == EGL_FALSE, "Failed to initialize display %p! err=%s", mEglDisplay, eglErrorString()); - ALOGV("Initialized EGL, version %d.%d", (int)major, (int)minor); + ALOGI("Initialized EGL, version %d.%d", (int)major, (int)minor); initExtensions(); diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index 42e17b273bee..4972554c65cc 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -28,10 +28,10 @@ class GrContext; -struct ANativeWindow; - namespace android { +class Surface; + namespace uirenderer { class DeferredLayerUpdater; @@ -67,7 +67,7 @@ public: virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) = 0; virtual DeferredLayerUpdater* createTextureLayer() = 0; - virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior, ColorMode colorMode) = 0; + virtual bool setSurface(Surface* window, SwapBehavior swapBehavior, ColorMode colorMode) = 0; virtual void onStop() = 0; virtual bool isSurfaceReady() = 0; virtual bool isContextReady() = 0; diff --git a/libs/hwui/renderthread/ReliableSurface.cpp b/libs/hwui/renderthread/ReliableSurface.cpp deleted file mode 100644 index 0ab4cd29f1cd..000000000000 --- a/libs/hwui/renderthread/ReliableSurface.cpp +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "ReliableSurface.h" - -#include <private/android/AHardwareBufferHelpers.h> - -namespace android::uirenderer::renderthread { - -// TODO: Make surface less protected -// This exists because perform is a varargs, and ANativeWindow has no va_list perform. -// So wrapping/chaining that is hard. Telling the compiler to ignore protected is easy, so we do -// that instead -struct SurfaceExposer : Surface { - // Make warnings happy - SurfaceExposer() = delete; - - using Surface::setBufferCount; - using Surface::setSwapInterval; - using Surface::dequeueBuffer; - using Surface::queueBuffer; - using Surface::cancelBuffer; - using Surface::lockBuffer_DEPRECATED; - using Surface::perform; -}; - -#define callProtected(surface, func, ...) ((*surface).*&SurfaceExposer::func)(__VA_ARGS__) - -ReliableSurface::ReliableSurface(sp<Surface>&& surface) : mSurface(std::move(surface)) { - LOG_ALWAYS_FATAL_IF(!mSurface, "Error, unable to wrap a nullptr"); - - ANativeWindow::setSwapInterval = hook_setSwapInterval; - ANativeWindow::dequeueBuffer = hook_dequeueBuffer; - ANativeWindow::cancelBuffer = hook_cancelBuffer; - ANativeWindow::queueBuffer = hook_queueBuffer; - ANativeWindow::query = hook_query; - ANativeWindow::perform = hook_perform; - - ANativeWindow::dequeueBuffer_DEPRECATED = hook_dequeueBuffer_DEPRECATED; - ANativeWindow::cancelBuffer_DEPRECATED = hook_cancelBuffer_DEPRECATED; - ANativeWindow::lockBuffer_DEPRECATED = hook_lockBuffer_DEPRECATED; - ANativeWindow::queueBuffer_DEPRECATED = hook_queueBuffer_DEPRECATED; -} - -void ReliableSurface::perform(int operation, va_list args) { - std::lock_guard _lock{mMutex}; - - switch (operation) { - case NATIVE_WINDOW_SET_USAGE: - mUsage = va_arg(args, uint32_t); - break; - case NATIVE_WINDOW_SET_USAGE64: - mUsage = va_arg(args, uint64_t); - break; - case NATIVE_WINDOW_SET_BUFFERS_GEOMETRY: - /* width */ va_arg(args, uint32_t); - /* height */ va_arg(args, uint32_t); - mFormat = va_arg(args, PixelFormat); - break; - case NATIVE_WINDOW_SET_BUFFERS_FORMAT: - mFormat = va_arg(args, PixelFormat); - break; - } -} - -int ReliableSurface::reserveNext() { - { - std::lock_guard _lock{mMutex}; - if (mReservedBuffer) { - ALOGW("reserveNext called but there was already a buffer reserved?"); - return OK; - } - if (mInErrorState) { - return UNKNOWN_ERROR; - } - } - - int fenceFd = -1; - ANativeWindowBuffer* buffer = nullptr; - int result = callProtected(mSurface, dequeueBuffer, &buffer, &fenceFd); - - { - std::lock_guard _lock{mMutex}; - LOG_ALWAYS_FATAL_IF(mReservedBuffer, "race condition in reserveNext"); - mReservedBuffer = buffer; - mReservedFenceFd.reset(fenceFd); - if (result != OK) { - ALOGW("reserveNext failed, error %d", result); - } - } - - return result; -} - -void ReliableSurface::clearReservedBuffer() { - std::lock_guard _lock{mMutex}; - if (mReservedBuffer) { - ALOGW("Reserved buffer %p was never used", mReservedBuffer); - } - mReservedBuffer = nullptr; - mReservedFenceFd.reset(); -} - -int ReliableSurface::cancelBuffer(ANativeWindowBuffer* buffer, int fenceFd) { - clearReservedBuffer(); - if (isFallbackBuffer(buffer)) { - if (fenceFd > 0) { - close(fenceFd); - } - return OK; - } - int result = callProtected(mSurface, cancelBuffer, buffer, fenceFd); - return result; -} - -int ReliableSurface::dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd) { - { - std::lock_guard _lock{mMutex}; - if (mReservedBuffer) { - *buffer = mReservedBuffer; - *fenceFd = mReservedFenceFd.release(); - mReservedBuffer = nullptr; - return OK; - } - } - - int result = callProtected(mSurface, dequeueBuffer, buffer, fenceFd); - if (result != OK) { - ALOGW("dequeueBuffer failed, error = %d; switching to fallback", result); - *buffer = acquireFallbackBuffer(); - *fenceFd = -1; - return *buffer ? OK : INVALID_OPERATION; - } - return OK; -} - -int ReliableSurface::queueBuffer(ANativeWindowBuffer* buffer, int fenceFd) { - clearReservedBuffer(); - - if (isFallbackBuffer(buffer)) { - if (fenceFd > 0) { - close(fenceFd); - } - return OK; - } - - int result = callProtected(mSurface, queueBuffer, buffer, fenceFd); - return result; -} - -bool ReliableSurface::isFallbackBuffer(const ANativeWindowBuffer* windowBuffer) const { - if (!mScratchBuffer || !windowBuffer) { - return false; - } - ANativeWindowBuffer* scratchBuffer = - AHardwareBuffer_to_ANativeWindowBuffer(mScratchBuffer.get()); - return windowBuffer == scratchBuffer; -} - -ANativeWindowBuffer* ReliableSurface::acquireFallbackBuffer() { - std::lock_guard _lock{mMutex}; - mInErrorState = true; - - if (mScratchBuffer) { - return AHardwareBuffer_to_ANativeWindowBuffer(mScratchBuffer.get()); - } - - AHardwareBuffer_Desc desc; - desc.usage = mUsage; - desc.format = mFormat; - desc.width = 1; - desc.height = 1; - desc.layers = 1; - desc.rfu0 = 0; - desc.rfu1 = 0; - AHardwareBuffer* newBuffer = nullptr; - int err = AHardwareBuffer_allocate(&desc, &newBuffer); - if (err) { - // Allocate failed, that sucks - ALOGW("Failed to allocate scratch buffer, error=%d", err); - return nullptr; - } - mScratchBuffer.reset(newBuffer); - return AHardwareBuffer_to_ANativeWindowBuffer(newBuffer); -} - -Surface* ReliableSurface::getWrapped(const ANativeWindow* window) { - return getSelf(window)->mSurface.get(); -} - -int ReliableSurface::hook_setSwapInterval(ANativeWindow* window, int interval) { - return callProtected(getWrapped(window), setSwapInterval, interval); -} - -int ReliableSurface::hook_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer, - int* fenceFd) { - return getSelf(window)->dequeueBuffer(buffer, fenceFd); -} - -int ReliableSurface::hook_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, - int fenceFd) { - return getSelf(window)->cancelBuffer(buffer, fenceFd); -} - -int ReliableSurface::hook_queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, - int fenceFd) { - return getSelf(window)->queueBuffer(buffer, fenceFd); -} - -int ReliableSurface::hook_dequeueBuffer_DEPRECATED(ANativeWindow* window, - ANativeWindowBuffer** buffer) { - ANativeWindowBuffer* buf; - int fenceFd = -1; - int result = window->dequeueBuffer(window, &buf, &fenceFd); - if (result != OK) { - return result; - } - sp<Fence> fence(new Fence(fenceFd)); - int waitResult = fence->waitForever("dequeueBuffer_DEPRECATED"); - if (waitResult != OK) { - ALOGE("dequeueBuffer_DEPRECATED: Fence::wait returned an error: %d", waitResult); - window->cancelBuffer(window, buf, -1); - return waitResult; - } - *buffer = buf; - return result; -} - -int ReliableSurface::hook_cancelBuffer_DEPRECATED(ANativeWindow* window, - ANativeWindowBuffer* buffer) { - return window->cancelBuffer(window, buffer, -1); -} - -int ReliableSurface::hook_lockBuffer_DEPRECATED(ANativeWindow* window, - ANativeWindowBuffer* buffer) { - // This method is a no-op in Surface as well - return OK; -} - -int ReliableSurface::hook_queueBuffer_DEPRECATED(ANativeWindow* window, - ANativeWindowBuffer* buffer) { - return window->queueBuffer(window, buffer, -1); -} - -int ReliableSurface::hook_query(const ANativeWindow* window, int what, int* value) { - return getWrapped(window)->query(what, value); -} - -int ReliableSurface::hook_perform(ANativeWindow* window, int operation, ...) { - va_list args; - va_start(args, operation); - int result = callProtected(getWrapped(window), perform, operation, args); - va_end(args); - - switch (operation) { - case NATIVE_WINDOW_SET_BUFFERS_FORMAT: - case NATIVE_WINDOW_SET_USAGE: - case NATIVE_WINDOW_SET_USAGE64: - va_start(args, operation); - getSelf(window)->perform(operation, args); - va_end(args); - break; - default: - break; - } - - return result; -} - -}; // namespace android::uirenderer::renderthread
\ No newline at end of file diff --git a/libs/hwui/renderthread/ReliableSurface.h b/libs/hwui/renderthread/ReliableSurface.h deleted file mode 100644 index 9ae53a9798d3..000000000000 --- a/libs/hwui/renderthread/ReliableSurface.h +++ /dev/null @@ -1,85 +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. - */ - -#pragma once - -#include <gui/Surface.h> -#include <utils/Macros.h> -#include <utils/StrongPointer.h> - -#include <memory> - -namespace android::uirenderer::renderthread { - -class ReliableSurface : public ANativeObjectBase<ANativeWindow, ReliableSurface, RefBase> { - PREVENT_COPY_AND_ASSIGN(ReliableSurface); - -public: - ReliableSurface(sp<Surface>&& surface); - - void setDequeueTimeout(nsecs_t timeout) { mSurface->setDequeueTimeout(timeout); } - - int reserveNext(); - - void allocateBuffers() { mSurface->allocateBuffers(); } - - int query(int what, int* value) const { return mSurface->query(what, value); } - - nsecs_t getLastDequeueStartTime() const { return mSurface->getLastDequeueStartTime(); } - - uint64_t getNextFrameNumber() const { return mSurface->getNextFrameNumber(); } - -private: - const sp<Surface> mSurface; - - mutable std::mutex mMutex; - - uint64_t mUsage = AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER; - PixelFormat mFormat = PIXEL_FORMAT_RGBA_8888; - std::unique_ptr<AHardwareBuffer, void (*)(AHardwareBuffer*)> mScratchBuffer{ - nullptr, AHardwareBuffer_release}; - bool mInErrorState = false; - ANativeWindowBuffer* mReservedBuffer = nullptr; - base::unique_fd mReservedFenceFd; - - bool isFallbackBuffer(const ANativeWindowBuffer* windowBuffer) const; - ANativeWindowBuffer* acquireFallbackBuffer(); - void clearReservedBuffer(); - - void perform(int operation, va_list args); - int cancelBuffer(ANativeWindowBuffer* buffer, int fenceFd); - int dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd); - int queueBuffer(ANativeWindowBuffer* buffer, int fenceFd); - - static Surface* getWrapped(const ANativeWindow*); - - // ANativeWindow hooks - static int hook_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, int fenceFd); - static int hook_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer, - int* fenceFd); - static int hook_queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, int fenceFd); - - static int hook_perform(ANativeWindow* window, int operation, ...); - static int hook_query(const ANativeWindow* window, int what, int* value); - static int hook_setSwapInterval(ANativeWindow* window, int interval); - - static int hook_cancelBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer); - static int hook_dequeueBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer** buffer); - static int hook_lockBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer); - static int hook_queueBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer); -}; - -}; // namespace android::uirenderer::renderthread
\ No newline at end of file diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 6668c5840c3e..d9b789f28f8d 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -49,7 +49,7 @@ enum { Reset = 1 << 1, JankStats = 1 << 2, }; -}; +} /* * RenderProxy is strictly single threaded. All methods must be invoked on the owning diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 9a6df75fedd9..e1f8307343a7 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -472,8 +472,9 @@ SkSurface* VulkanManager::getBackbufferSurface(VulkanSurface** surfaceOut) { window->query(window, NATIVE_WINDOW_HEIGHT, &windowHeight); if (windowWidth != surface->mWindowWidth || windowHeight != surface->mWindowHeight) { ColorMode colorMode = surface->mColorMode; + sk_sp<SkColorSpace> colorSpace = surface->mColorSpace; destroySurface(surface); - *surfaceOut = createSurface(window, colorMode); + *surfaceOut = createSurface(window, colorMode, colorSpace); surface = *surfaceOut; } @@ -647,7 +648,7 @@ void VulkanManager::createBuffers(VulkanSurface* surface, VkFormat format, VkExt imageInfo.mSurface = SkSurface::MakeFromBackendRenderTarget( mRenderThread.getGrContext(), backendRT, kTopLeft_GrSurfaceOrigin, surface->mColorMode == ColorMode::WideColorGamut ? kRGBA_F16_SkColorType - : kRGBA_8888_SkColorType, nullptr, &props); + : kRGBA_8888_SkColorType, surface->mColorSpace, &props); } SkASSERT(mCommandPool != VK_NULL_HANDLE); @@ -833,14 +834,15 @@ bool VulkanManager::createSwapchain(VulkanSurface* surface) { return true; } -VulkanSurface* VulkanManager::createSurface(ANativeWindow* window, ColorMode colorMode) { +VulkanSurface* VulkanManager::createSurface(ANativeWindow* window, ColorMode colorMode, + sk_sp<SkColorSpace> surfaceColorSpace) { initialize(); if (!window) { return nullptr; } - VulkanSurface* surface = new VulkanSurface(colorMode, window); + VulkanSurface* surface = new VulkanSurface(colorMode, window, surfaceColorSpace); VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo; memset(&surfaceCreateInfo, 0, sizeof(VkAndroidSurfaceCreateInfoKHR)); diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h index 8594a1bd4339..d67d2c81e95c 100644 --- a/libs/hwui/renderthread/VulkanManager.h +++ b/libs/hwui/renderthread/VulkanManager.h @@ -38,8 +38,8 @@ class RenderThread; class VulkanSurface { public: - VulkanSurface(ColorMode colorMode, ANativeWindow* window) - : mColorMode(colorMode), mNativeWindow(window) {} + VulkanSurface(ColorMode colorMode, ANativeWindow* window, sk_sp<SkColorSpace> colorSpace) + : mColorMode(colorMode), mNativeWindow(window), mColorSpace(colorSpace) {} sk_sp<SkSurface> getBackBufferSurface() { return mBackbuffer; } @@ -79,6 +79,7 @@ private: ANativeWindow* mNativeWindow; int mWindowWidth = 0; int mWindowHeight = 0; + sk_sp<SkColorSpace> mColorSpace; }; // This class contains the shared global Vulkan objects, such as VkInstance, VkDevice and VkQueue, @@ -96,7 +97,8 @@ public: // Given a window this creates a new VkSurfaceKHR and VkSwapchain and stores them inside a new // VulkanSurface object which is returned. - VulkanSurface* createSurface(ANativeWindow* window, ColorMode colorMode); + VulkanSurface* createSurface(ANativeWindow* window, ColorMode colorMode, + sk_sp<SkColorSpace> surfaceColorSpace); // Destroy the VulkanSurface and all associated vulkan objects. void destroySurface(VulkanSurface* surface); diff --git a/libs/hwui/surfacetexture/EGLConsumer.cpp b/libs/hwui/surfacetexture/EGLConsumer.cpp index c8220c6cb0d4..85b3917809fa 100644 --- a/libs/hwui/surfacetexture/EGLConsumer.cpp +++ b/libs/hwui/surfacetexture/EGLConsumer.cpp @@ -672,4 +672,4 @@ EGLImageKHR EGLConsumer::EglImage::createImage(EGLDisplay dpy, return image; } -}; // namespace android +} // namespace android diff --git a/libs/hwui/surfacetexture/EGLConsumer.h b/libs/hwui/surfacetexture/EGLConsumer.h index eccb08298f6f..7dac3ef0f44a 100644 --- a/libs/hwui/surfacetexture/EGLConsumer.h +++ b/libs/hwui/surfacetexture/EGLConsumer.h @@ -308,4 +308,4 @@ protected: sp<EglImage> mReleasedTexImage; }; -}; // namespace android +} // namespace android diff --git a/libs/hwui/surfacetexture/ImageConsumer.h b/libs/hwui/surfacetexture/ImageConsumer.h index 5bab0ef58a9a..f0e55bbf19f8 100644 --- a/libs/hwui/surfacetexture/ImageConsumer.h +++ b/libs/hwui/surfacetexture/ImageConsumer.h @@ -97,4 +97,4 @@ private: ImageSlot mImageSlots[BufferQueueDefs::NUM_BUFFER_SLOTS]; }; -}; /* namespace android */ +} /* namespace android */ diff --git a/libs/hwui/surfacetexture/SurfaceTexture.cpp b/libs/hwui/surfacetexture/SurfaceTexture.cpp index 90f891265572..da094442684d 100644 --- a/libs/hwui/surfacetexture/SurfaceTexture.cpp +++ b/libs/hwui/surfacetexture/SurfaceTexture.cpp @@ -491,4 +491,4 @@ sk_sp<SkImage> SurfaceTexture::dequeueImage(SkMatrix& transformMatrix, bool* que return image; } -}; // namespace android +} // namespace android diff --git a/libs/hwui/surfacetexture/SurfaceTexture.h b/libs/hwui/surfacetexture/SurfaceTexture.h index 96afd82b0d40..b5d136ff3058 100644 --- a/libs/hwui/surfacetexture/SurfaceTexture.h +++ b/libs/hwui/surfacetexture/SurfaceTexture.h @@ -449,4 +449,4 @@ protected: }; // ---------------------------------------------------------------------------- -}; // namespace android +} // namespace android diff --git a/libs/hwui/tests/unit/LayerUpdateQueueTests.cpp b/libs/hwui/tests/unit/LayerUpdateQueueTests.cpp index 217d63f9c2e1..41714ebd84b1 100644 --- a/libs/hwui/tests/unit/LayerUpdateQueueTests.cpp +++ b/libs/hwui/tests/unit/LayerUpdateQueueTests.cpp @@ -81,5 +81,5 @@ TEST(LayerUpdateQueue, clear) { EXPECT_TRUE(queue.entries().empty()); } -}; -}; +} +} diff --git a/libs/hwui/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp index 02f740cee096..ee6beba847a0 100644 --- a/libs/hwui/tests/unit/VectorDrawableTests.cpp +++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp @@ -406,5 +406,5 @@ TEST(VectorDrawable, drawPathWithoutIncrementingShaderRefCount) { EXPECT_TRUE(shader->unique()); } -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/thread/Barrier.h b/libs/hwui/thread/Barrier.h index 8faeee6b391a..bb750ca0fa88 100644 --- a/libs/hwui/thread/Barrier.h +++ b/libs/hwui/thread/Barrier.h @@ -48,7 +48,7 @@ private: mutable Condition mCondition; }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_BARRIER_H diff --git a/libs/hwui/thread/Future.h b/libs/hwui/thread/Future.h index 45f3102492e3..df53348e58fb 100644 --- a/libs/hwui/thread/Future.h +++ b/libs/hwui/thread/Future.h @@ -53,7 +53,7 @@ private: T mResult; }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_FUTURE_H diff --git a/libs/hwui/thread/Signal.h b/libs/hwui/thread/Signal.h index ffcd4b675a85..6d33ac473ac4 100644 --- a/libs/hwui/thread/Signal.h +++ b/libs/hwui/thread/Signal.h @@ -53,7 +53,7 @@ private: mutable Condition mCondition; }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_SIGNAL_H diff --git a/libs/hwui/thread/Task.h b/libs/hwui/thread/Task.h index 276a22f941fe..228ce19a2fd5 100644 --- a/libs/hwui/thread/Task.h +++ b/libs/hwui/thread/Task.h @@ -48,7 +48,7 @@ private: sp<Future<T> > mFuture; }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_TASK_H diff --git a/libs/hwui/thread/TaskManager.cpp b/libs/hwui/thread/TaskManager.cpp index 54b55e472095..26ff6ebad3b4 100644 --- a/libs/hwui/thread/TaskManager.cpp +++ b/libs/hwui/thread/TaskManager.cpp @@ -129,5 +129,5 @@ void TaskManager::WorkerThread::exit() { mSignal.signal(); } -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/thread/TaskManager.h b/libs/hwui/thread/TaskManager.h index 29b4fcdbfde9..c4c1291e755c 100644 --- a/libs/hwui/thread/TaskManager.h +++ b/libs/hwui/thread/TaskManager.h @@ -101,7 +101,7 @@ private: std::vector<sp<WorkerThread> > mThreads; }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_TASK_MANAGER_H diff --git a/libs/hwui/utils/Blur.cpp b/libs/hwui/utils/Blur.cpp index 1bc5646993c9..763d1aa177b7 100644 --- a/libs/hwui/utils/Blur.cpp +++ b/libs/hwui/utils/Blur.cpp @@ -178,5 +178,5 @@ void Blur::vertical(float* weights, int32_t radius, const uint8_t* source, uint8 } } -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/utils/Blur.h b/libs/hwui/utils/Blur.h index bec3837106e8..d6b41b83def8 100644 --- a/libs/hwui/utils/Blur.h +++ b/libs/hwui/utils/Blur.h @@ -41,7 +41,7 @@ public: int32_t width, int32_t height); }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_BLUR_H diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp index 3fb6a31a7d97..dc347f615d98 100644 --- a/libs/hwui/utils/Color.cpp +++ b/libs/hwui/utils/Color.cpp @@ -221,5 +221,5 @@ SkColor LabToSRGB(const Lab& lab, SkAlpha alpha) { static_cast<uint8_t>(rgb.b * 255)); } -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/utils/FatVector.h b/libs/hwui/utils/FatVector.h index eafe2f13c16d..8cc4d1010ab6 100644 --- a/libs/hwui/utils/FatVector.h +++ b/libs/hwui/utils/FatVector.h @@ -99,7 +99,7 @@ private: typename InlineStdAllocator<T, SIZE>::Allocation mAllocation; }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_FAT_VECTOR_H diff --git a/libs/hwui/utils/GLUtils.cpp b/libs/hwui/utils/GLUtils.cpp index fcd036c451e9..c694e93f7e21 100644 --- a/libs/hwui/utils/GLUtils.cpp +++ b/libs/hwui/utils/GLUtils.cpp @@ -76,5 +76,5 @@ const char* GLUtils::getGLFramebufferError() { } } -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/utils/LinearAllocator.cpp b/libs/hwui/utils/LinearAllocator.cpp index 3e5021cd45d4..8baa4b770f85 100644 --- a/libs/hwui/utils/LinearAllocator.cpp +++ b/libs/hwui/utils/LinearAllocator.cpp @@ -249,5 +249,5 @@ void LinearAllocator::dumpMemoryStats(const char* prefix) { ALOGD("%sPages %zu (dedicated %zu)", prefix, mPageCount, mDedicatedPageCount); } -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/utils/LinearAllocator.h b/libs/hwui/utils/LinearAllocator.h index 03f685e8aca8..b401fcf58f76 100644 --- a/libs/hwui/utils/LinearAllocator.h +++ b/libs/hwui/utils/LinearAllocator.h @@ -201,7 +201,7 @@ public: : std::vector<T, LinearStdAllocator<T>>(allocator) {} }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_LINEARALLOCATOR_H diff --git a/libs/hwui/utils/Pair.h b/libs/hwui/utils/Pair.h index 4bcd57629e0c..76f93cbfeb92 100644 --- a/libs/hwui/utils/Pair.h +++ b/libs/hwui/utils/Pair.h @@ -36,7 +36,7 @@ struct Pair { inline const S& getSecond() const { return second; } }; -}; // namespace uirenderer +} // namespace uirenderer template <typename F, typename S> struct trait_trivial_ctor<uirenderer::Pair<F, S> > { @@ -55,6 +55,6 @@ struct trait_trivial_move<uirenderer::Pair<F, S> > { enum { value = aggregate_traits<F, S>::has_trivial_move }; }; -}; // namespace android +} // namespace android #endif // ANDROID_HWUI_PAIR_H diff --git a/libs/hwui/utils/Result.h b/libs/hwui/utils/Result.h index 7f33f2e3424d..bd20ba66d8a7 100644 --- a/libs/hwui/utils/Result.h +++ b/libs/hwui/utils/Result.h @@ -51,4 +51,4 @@ private: std::variant<R, Error<E>> result; }; -}; // namespace android::uirenderer +} // namespace android::uirenderer diff --git a/libs/hwui/utils/RingBuffer.h b/libs/hwui/utils/RingBuffer.h index b3e893139cf8..081386a7f671 100644 --- a/libs/hwui/utils/RingBuffer.h +++ b/libs/hwui/utils/RingBuffer.h @@ -61,7 +61,7 @@ private: size_t mCount = 0; }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif /* RINGBUFFER_H_ */ diff --git a/libs/hwui/utils/StringUtils.cpp b/libs/hwui/utils/StringUtils.cpp index 5304b762f3dc..304982e8e493 100644 --- a/libs/hwui/utils/StringUtils.cpp +++ b/libs/hwui/utils/StringUtils.cpp @@ -34,5 +34,5 @@ unordered_string_set StringUtils::split(const char* spacedList) { return set; } -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/utils/TypeLogic.h b/libs/hwui/utils/TypeLogic.h index dbdad33d8335..1689ccecd6b1 100644 --- a/libs/hwui/utils/TypeLogic.h +++ b/libs/hwui/utils/TypeLogic.h @@ -37,4 +37,4 @@ template <typename D, typename S> struct copy_cv { template <typename D, typename S> using same_cv = copy_cv<std::remove_cv_t<D>, S>; template <typename D, typename S> using same_cv_t = typename same_cv<D, S>::type; -}; // namespace android::uirenderer
\ No newline at end of file +} // namespace android::uirenderer diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index c226d49c6af7..71736dd38e91 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -2620,7 +2620,8 @@ public class AudioTrack extends PlayerBase * to the audio sink. * <BR>With {@link #WRITE_NON_BLOCKING}, the write will return immediately after * queuing as much audio data for playback as possible without blocking. - * @param timestamp The timestamp of the first decodable audio frame in the provided audioData. + * @param timestamp The timestamp, in nanoseconds, of the first decodable audio frame in the + * provided audioData. * @return zero or the positive number of bytes that were written, or one of the following * error codes. * <ul> diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 01a0cb619bf0..32c4643adad0 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -2635,13 +2635,18 @@ public class ExifInterface { if (size == 0) { return 0; } - // We don't allow read positions after the available bytes, - // the input stream won't be able to seek back then. - if (position < 0 || position >= in.available()) { + if (position < 0) { return -1; } try { if (mPosition != position) { + // We don't allow seek to positions after the available bytes, + // the input stream won't be able to seek back then. + // However, if we hit an exception before (mPosition set to -1), + // let it try the seek in hope it might recover. + if (mPosition >= 0 && position >= mPosition + in.available()) { + return -1; + } in.seek(position); mPosition = position; } @@ -2649,8 +2654,8 @@ public class ExifInterface { // If the read will cause us to go over the available bytes, // reduce the size so that we stay in the available range. // Otherwise the input stream may not be able to seek back. - if (mPosition + size > in.available()) { - size = in.available() - (int)mPosition; + if (size > in.available()) { + size = in.available(); } int bytesRead = in.read(buffer, offset, size); diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java index b047f8de515d..111dd0f0b63e 100644 --- a/media/java/android/media/MediaPlayer2.java +++ b/media/java/android/media/MediaPlayer2.java @@ -29,6 +29,7 @@ import android.content.Context; import android.content.res.AssetFileDescriptor; import android.graphics.Rect; import android.graphics.SurfaceTexture; +import android.media.MediaPlayer2.DrmInfo; import android.media.MediaPlayer2Proto.PlayerMessage; import android.media.MediaPlayer2Proto.Value; import android.net.Uri; @@ -72,7 +73,11 @@ import java.util.Map; import java.util.Queue; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -300,21 +305,7 @@ public class MediaPlayer2 implements AutoCloseable private volatile float mVolume = 1.0f; private VideoSize mVideoSize = new VideoSize(0, 0); - // TODO: create per-source drm fields in SourceInfo - // Modular DRM - private final Object mDrmLock = new Object(); - //--- guarded by |mDrmLock| start - private UUID mDrmUUID; - private DrmInfo mDrmInfo; - private MediaDrm mDrmObj; - private byte[] mDrmSessionId; - private boolean mDrmInfoResolved; - private boolean mActiveDrmScheme; - private boolean mDrmConfigAllowed; - private boolean mDrmProvisioningInProgress; - private boolean mPrepareDrmInProgress; - private ProvisioningThread mDrmProvisioningThread; - //--- guarded by |mDrmLock| end + private ExecutorService mDrmThreadPool = Executors.newCachedThreadPool(); // Creating a dummy audio track, used for keeping session id alive private final Object mSessionIdLock = new Object(); @@ -328,6 +319,7 @@ public class MediaPlayer2 implements AutoCloseable private final List<Task> mPendingTasks = new LinkedList<>(); @GuardedBy("mTaskLock") private Task mCurrentTask; + private final AtomicLong mTaskIdGenerator = new AtomicLong(0); @GuardedBy("mTaskLock") boolean mIsPreviousCommandSeekTo = false; @@ -413,15 +405,13 @@ public class MediaPlayer2 implements AutoCloseable mHandlerThread = null; } - setCurrentSourceInfo(null); - clearNextSourceInfos(); + clearSourceInfos(); // Modular DRM clean up mOnDrmConfigHelper = null; synchronized (mDrmEventCbLock) { mDrmEventCallbackRecords.clear(); } - resetDrmState(); native_release(); @@ -461,13 +451,8 @@ public class MediaPlayer2 implements AutoCloseable synchronized (mDrmEventCbLock) { mDrmEventCallbackRecords.clear(); } - setCurrentSourceInfo(null); - clearNextSourceInfos(); - synchronized (mTaskLock) { - mPendingTasks.clear(); - mIsPreviousCommandSeekTo = false; - } + clearSourceInfos(); stayAwake(false); native_reset(); @@ -481,7 +466,6 @@ public class MediaPlayer2 implements AutoCloseable mTaskHandler.removeCallbacksAndMessages(null); } - resetDrmState(); } private native void native_reset(); @@ -706,13 +690,14 @@ public class MediaPlayer2 implements AutoCloseable } synchronized (mSrcLock) { - setCurrentSourceInfo(new SourceInfo(dsd)); + setCurrentSourceInfo_l(new SourceInfo(dsd)); handleDataSource(true /* isCurrent */, dsd, mCurrentSourceInfo.mId); } } finally { dsd.close(); } } + }); } @@ -732,7 +717,7 @@ public class MediaPlayer2 implements AutoCloseable void process() { Media2Utils.checkArgument(dsd != null, "the DataSourceDesc cannot be null"); synchronized (mSrcLock) { - clearNextSourceInfos(); + clearNextSourceInfos_l(); mNextSourceInfos.add(new SourceInfo(dsd)); } prepareNextDataSource(); @@ -758,7 +743,7 @@ public class MediaPlayer2 implements AutoCloseable } synchronized (mSrcLock) { - clearNextSourceInfos(); + clearNextSourceInfos_l(); for (DataSourceDesc dsd : dsds) { if (dsd != null) { mNextSourceInfos.add(new SourceInfo(dsd)); @@ -781,7 +766,9 @@ public class MediaPlayer2 implements AutoCloseable return addTask(new Task(CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES, false) { @Override void process() { - clearNextSourceInfos(); + synchronized (mSrcLock) { + clearNextSourceInfos_l(); + } } }); } @@ -1073,7 +1060,7 @@ public class MediaPlayer2 implements AutoCloseable SourceInfo nextSourceInfo = mNextSourceInfos.peek(); if (nextSourceInfo.mStateAsNextSource == NEXT_SOURCE_STATE_PREPARED) { // Switch to next source only when it has been prepared. - setCurrentSourceInfo(mNextSourceInfos.poll()); + setCurrentSourceInfo_l(mNextSourceInfos.poll()); long srcId = mCurrentSourceInfo.mId; try { @@ -2173,7 +2160,7 @@ public class MediaPlayer2 implements AutoCloseable final int what = msg.arg1; final int extra = msg.arg2; - final SourceInfo sourceInfo = getSourceInfoById(srcId); + final SourceInfo sourceInfo = getSourceInfo(srcId); if (sourceInfo == null) { return; } @@ -2227,11 +2214,11 @@ public class MediaPlayer2 implements AutoCloseable Log.w(TAG, "MEDIA_DRM_INFO msg.obj=NULL"); } else if (msg.obj instanceof byte[]) { // The PlayerMessage was parsed already in postEventFromNative - final DrmInfo drmInfo; - synchronized (mDrmLock) { - if (mDrmInfo != null) { - drmInfo = mDrmInfo.makeCopy(); + final DrmInfo drmInfo; + synchronized (sourceInfo) { + if (sourceInfo.mDrmInfo != null) { + drmInfo = sourceInfo.mDrmInfo.makeCopy(); } else { drmInfo = null; } @@ -2303,7 +2290,7 @@ public class MediaPlayer2 implements AutoCloseable } }); - SourceInfo src = getSourceInfoById(srcId); + SourceInfo src = getSourceInfo(srcId); if (src != null) { src.mBufferedPercentage.set(percent); } @@ -2504,6 +2491,7 @@ public class MediaPlayer2 implements AutoCloseable return; } + final SourceInfo sourceInfo = mp.getSourceInfo(srcId); switch (what) { case MEDIA_DRM_INFO: // We need to derive mDrmInfo before prepare() returns so processing it here @@ -2511,7 +2499,7 @@ public class MediaPlayer2 implements AutoCloseable // notification looper so its handleMessage might process the event after prepare() // has returned. Log.v(TAG, "postEventFromNative MEDIA_DRM_INFO"); - if (obj != null) { + if (obj != null && sourceInfo != null) { PlayerMessage playerMsg; try { playerMsg = PlayerMessage.parseFrom(obj); @@ -2520,11 +2508,12 @@ public class MediaPlayer2 implements AutoCloseable break; } DrmInfo drmInfo = new DrmInfo(playerMsg); - synchronized (mp.mDrmLock) { - mp.mDrmInfo = drmInfo; + synchronized (sourceInfo) { + sourceInfo.mDrmInfo = drmInfo; } } else { - Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + obj); + Log.w(TAG, "MEDIA_DRM_INFO sourceInfo " + sourceInfo + + " msg.obj of unexpected type " + obj); } break; @@ -2533,8 +2522,10 @@ public class MediaPlayer2 implements AutoCloseable // mainly for prepare() use case. For prepare(), this still can run to a race // condition b/c MediaPlayerNative releases the prepare() lock before calling notify // so we also set mDrmInfoResolved in prepare(). - synchronized (mp.mDrmLock) { - mp.mDrmInfoResolved = true; + if (sourceInfo != null) { + synchronized (sourceInfo) { + sourceInfo.mDrmInfoResolved = true; + } } break; } @@ -3211,9 +3202,7 @@ public class MediaPlayer2 implements AutoCloseable */ // This is a synchronous call. public void setOnDrmConfigHelper(OnDrmConfigHelper listener) { - synchronized (mDrmLock) { - mOnDrmConfigHelper = listener; - } + mOnDrmConfigHelper = listener; } private OnDrmConfigHelper mOnDrmConfigHelper; @@ -3358,24 +3347,27 @@ public class MediaPlayer2 implements AutoCloseable * @throws IllegalStateException if called before being prepared */ public DrmInfo getDrmInfo(@NonNull DataSourceDesc dsd) { - // TODO: this implementation only works when dsd is the only data source - DrmInfo drmInfo = null; - - // there is not much point if the app calls getDrmInfo within an OnDrmInfoListenet; - // regardless below returns drmInfo anyway instead of raising an exception - synchronized (mDrmLock) { - if (!mDrmInfoResolved && mDrmInfo == null) { - final String msg = "The Player has not been prepared yet"; - Log.v(TAG, msg); - throw new IllegalStateException(msg); - } + final SourceInfo sourceInfo = getSourceInfo(dsd); + if (sourceInfo != null) { + DrmInfo drmInfo = null; + + // there is not much point if the app calls getDrmInfo within an OnDrmInfoListener; + // regardless below returns drmInfo anyway instead of raising an exception + synchronized (sourceInfo) { + if (!sourceInfo.mDrmInfoResolved && sourceInfo.mDrmInfo == null) { + final String msg = "The Player has not been prepared yet"; + Log.v(TAG, msg); + throw new IllegalStateException(msg); + } - if (mDrmInfo != null) { - drmInfo = mDrmInfo.makeCopy(); - } - } // synchronized + if (sourceInfo.mDrmInfo != null) { + drmInfo = sourceInfo.mDrmInfo.makeCopy(); + } + } // synchronized - return drmInfo; + return drmInfo; + } + return null; } /** @@ -3411,15 +3403,28 @@ public class MediaPlayer2 implements AutoCloseable */ // This is an asynchronous call. public Object prepareDrm(@NonNull DataSourceDesc dsd, @NonNull UUID uuid) { - // TODO: this implementation only works when dsd is the only data source return addTask(new Task(CALL_COMPLETED_PREPARE_DRM, true) { @Override void process() { - int status = PREPARE_DRM_STATUS_SUCCESS; + final SourceInfo sourceInfo = getSourceInfo(dsd); + int status = PREPARE_DRM_STATUS_PREPARATION_ERROR; boolean sendEvent = true; + if (sourceInfo == null) { + Log.e(TAG, "prepareDrm(): DataSource not found."); + } else if (sourceInfo.mDrmInfo == null) { + // only allowing if tied to a protected source; + // might relax for releasing offline keys + Log.e(TAG, "prepareDrm(): Wrong usage: The player must be prepared and " + + "DRM info be retrieved before this call."); + } else { + status = PREPARE_DRM_STATUS_SUCCESS; + } + try { - doPrepareDrm(dsd, uuid); + if (status == PREPARE_DRM_STATUS_SUCCESS) { + sourceInfo.mDrmHandle.prepare(uuid); + } } catch (ResourceBusyException e) { status = PREPARE_DRM_STATUS_RESOURCE_BUSY; } catch (UnsupportedSchemeException e) { @@ -3428,14 +3433,14 @@ public class MediaPlayer2 implements AutoCloseable Log.w(TAG, "prepareDrm: NotProvisionedException"); // handle provisioning internally; it'll reset mPrepareDrmInProgress - status = handleProvisioninig(dsd, uuid); + status = sourceInfo.mDrmHandle.handleProvisioninig(uuid, mTaskId); if (status == PREPARE_DRM_STATUS_SUCCESS) { // DrmEventCallback will be fired in provisioning sendEvent = false; } else { - synchronized (mDrmLock) { - cleanDrmObj(); + synchronized (sourceInfo.mDrmHandle) { + sourceInfo.mDrmHandle.cleanDrmObj(); } switch (status) { @@ -3478,95 +3483,6 @@ public class MediaPlayer2 implements AutoCloseable }); } - private void doPrepareDrm(@NonNull DataSourceDesc dsd, @NonNull UUID uuid) - throws UnsupportedSchemeException, ResourceBusyException, - NotProvisionedException { - Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + mOnDrmConfigHelper); - - synchronized (mDrmLock) { - // only allowing if tied to a protected source; might relax for releasing offline keys - if (mDrmInfo == null) { - final String msg = "prepareDrm(): Wrong usage: The player must be prepared and " - + "DRM info be retrieved before this call."; - Log.e(TAG, msg); - throw new IllegalStateException(msg); - } - - if (mActiveDrmScheme) { - final String msg = "prepareDrm(): Wrong usage: There is already " - + "an active DRM scheme with " + mDrmUUID; - Log.e(TAG, msg); - throw new IllegalStateException(msg); - } - - if (mPrepareDrmInProgress) { - final String msg = "prepareDrm(): Wrong usage: There is already " - + "a pending prepareDrm call."; - Log.e(TAG, msg); - throw new IllegalStateException(msg); - } - - if (mDrmProvisioningInProgress) { - final String msg = "prepareDrm(): Unexpectd: Provisioning is already in progress."; - Log.e(TAG, msg); - throw new IllegalStateException(msg); - } - - // shouldn't need this; just for safeguard - cleanDrmObj(); - - mPrepareDrmInProgress = true; - - try { - // only creating the DRM object to allow pre-openSession configuration - prepareDrm_createDrmStep(uuid); - } catch (Exception e) { - Log.w(TAG, "prepareDrm(): Exception ", e); - mPrepareDrmInProgress = false; - throw e; - } - - mDrmConfigAllowed = true; - } // synchronized - - // call the callback outside the lock - if (mOnDrmConfigHelper != null) { - mOnDrmConfigHelper.onDrmConfig(this, dsd); - } - - synchronized (mDrmLock) { - mDrmConfigAllowed = false; - boolean earlyExit = false; - - try { - prepareDrm_openSessionStep(uuid); - - mDrmUUID = uuid; - mActiveDrmScheme = true; - mPrepareDrmInProgress = false; - } catch (IllegalStateException e) { - final String msg = "prepareDrm(): Wrong usage: The player must be " - + "in the prepared state to call prepareDrm()."; - Log.e(TAG, msg); - earlyExit = true; - mPrepareDrmInProgress = false; - throw new IllegalStateException(msg); - } catch (NotProvisionedException e) { - Log.w(TAG, "prepareDrm: NotProvisionedException", e); - throw e; - } catch (Exception e) { - Log.e(TAG, "prepareDrm: Exception " + e); - earlyExit = true; - mPrepareDrmInProgress = false; - throw e; - } finally { - if (earlyExit) { // clean up object if didn't succeed - cleanDrmObj(); - } - } // finally - } // synchronized - } - /** * Releases the DRM session for the given data source * <p> @@ -3581,35 +3497,10 @@ public class MediaPlayer2 implements AutoCloseable // This is a synchronous call. public void releaseDrm(@NonNull DataSourceDesc dsd) throws NoDrmSchemeException { - // TODO: this implementation only works when dsd is the only data source - synchronized (mDrmLock) { - Log.v(TAG, "releaseDrm:"); - - if (!mActiveDrmScheme) { - Log.e(TAG, "releaseDrm(): No active DRM scheme to release."); - throw new NoDrmSchemeException( - "releaseDrm: No active DRM scheme to release."); - } - - try { - // we don't have the player's state in this layer. The below call raises - // exception if we're in a non-stopped/prepared state. - - // for cleaning native/mediaserver crypto object - native_releaseDrm(); - - // for cleaning client-side MediaDrm object; only called if above has succeeded - cleanDrmObj(); - - mActiveDrmScheme = false; - } catch (IllegalStateException e) { - Log.w(TAG, "releaseDrm: Exception ", e); - throw new IllegalStateException( - "releaseDrm: The player is not in a valid state."); - } catch (Exception e) { - Log.e(TAG, "releaseDrm: Exception ", e); - } - } // synchronized + final SourceInfo sourceInfo = getSourceInfo(dsd); + if (sourceInfo != null) { + sourceInfo.mDrmHandle.release(); + } } private native void native_releaseDrm(); @@ -3653,51 +3544,22 @@ public class MediaPlayer2 implements AutoCloseable * * @throws NoDrmSchemeException if there is no active DRM session */ - @NonNull public MediaDrm.KeyRequest getDrmKeyRequest( @NonNull DataSourceDesc dsd, @Nullable byte[] keySetId, @Nullable byte[] initData, @Nullable String mimeType, @MediaDrmKeyType int keyType, @Nullable Map<String, String> optionalParameters) throws NoDrmSchemeException { - // TODO: this implementation only works when dsd is the only data source - Log.v(TAG, "getDrmKeyRequest: " - + " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType - + " keyType: " + keyType + " optionalParameters: " + optionalParameters); - - synchronized (mDrmLock) { - if (!mActiveDrmScheme) { - Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException"); - throw new NoDrmSchemeException( - "getDrmKeyRequest: Has to set a DRM scheme first."); - } - - try { - byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE) - ? mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE - keySetId; // keySetId for KEY_TYPE_RELEASE - - HashMap<String, String> hmapOptionalParameters = - (optionalParameters != null) - ? new HashMap<String, String>(optionalParameters) : - null; - - MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(scope, initData, mimeType, - keyType, hmapOptionalParameters); - Log.v(TAG, "getDrmKeyRequest: --> request: " + request); - - return request; - - } catch (NotProvisionedException e) { - Log.w(TAG, "getDrmKeyRequest NotProvisionedException: " - + "Unexpected. Shouldn't have reached here."); - throw new IllegalStateException("getDrmKeyRequest: Unexpected provisioning error."); - } catch (Exception e) { - Log.w(TAG, "getDrmKeyRequest Exception " + e); - throw e; - } - - } // synchronized + Log.v(TAG, "getDrmKeyRequest: " + + " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType + + " keyType: " + keyType + " optionalParameters: " + optionalParameters); + + final SourceInfo sourceInfo = getSourceInfo(dsd); + if (sourceInfo != null) { + return sourceInfo.mDrmHandle.getDrmKeyRequest( + keySetId, initData, mimeType, keyType, optionalParameters); + } + return null; } /** @@ -3727,40 +3589,13 @@ public class MediaPlayer2 implements AutoCloseable @NonNull DataSourceDesc dsd, @Nullable byte[] keySetId, @NonNull byte[] response) throws NoDrmSchemeException, DeniedByServerException { - // TODO: this implementation only works when dsd is the only data source Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response); - synchronized (mDrmLock) { - - if (!mActiveDrmScheme) { - Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException"); - throw new NoDrmSchemeException( - "getDrmKeyRequest: Has to set a DRM scheme first."); - } - - try { - byte[] scope = (keySetId == null) - ? mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE - keySetId; // keySetId for KEY_TYPE_RELEASE - - byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response); - - Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response - + " --> " + keySetResult); - - - return keySetResult; - - } catch (NotProvisionedException e) { - Log.w(TAG, "provideDrmKeyResponse NotProvisionedException: " - + "Unexpected. Shouldn't have reached here."); - throw new IllegalStateException("provideDrmKeyResponse: " - + "Unexpected provisioning error."); - } catch (Exception e) { - Log.w(TAG, "provideDrmKeyResponse Exception " + e); - throw e; - } - } // synchronized + final SourceInfo sourceInfo = getSourceInfo(dsd); + if (sourceInfo != null) { + return sourceInfo.mDrmHandle.provideDrmKeyResponse(keySetId, response); + } + return null; } /** @@ -3779,23 +3614,12 @@ public class MediaPlayer2 implements AutoCloseable @NonNull DataSourceDesc dsd, @NonNull byte[] keySetId) throws NoDrmSchemeException { - // TODO: this implementation only works when dsd is the only data source Log.v(TAG, "restoreDrmKeys: keySetId: " + keySetId); - synchronized (mDrmLock) { - if (!mActiveDrmScheme) { - Log.w(TAG, "restoreDrmKeys NoDrmSchemeException"); - throw new NoDrmSchemeException( - "restoreDrmKeys: Has to set a DRM scheme first."); - } - - try { - mDrmObj.restoreKeys(mDrmSessionId, keySetId); - } catch (Exception e) { - Log.w(TAG, "restoreKeys Exception " + e); - throw e; - } - } // synchronized + final SourceInfo sourceInfo = getSourceInfo(dsd); + if (sourceInfo != null) { + sourceInfo.mDrmHandle.restoreDrmKeys(keySetId); + } } /** @@ -3812,34 +3636,17 @@ public class MediaPlayer2 implements AutoCloseable * * @throws NoDrmSchemeException if there is no active DRM session */ - @NonNull public String getDrmPropertyString( @NonNull DataSourceDesc dsd, @NonNull @MediaDrmStringProperty String propertyName) throws NoDrmSchemeException { - // TODO: this implementation only works when dsd is the only data source Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName); - String value; - synchronized (mDrmLock) { - - if (!mActiveDrmScheme && !mDrmConfigAllowed) { - Log.w(TAG, "getDrmPropertyString NoDrmSchemeException"); - throw new NoDrmSchemeException( - "getDrmPropertyString: Has to prepareDrm() first."); - } - - try { - value = mDrmObj.getPropertyString(propertyName); - } catch (Exception e) { - Log.w(TAG, "getDrmPropertyString Exception " + e); - throw e; - } - } // synchronized - - Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + value); - - return value; + final SourceInfo sourceInfo = getSourceInfo(dsd); + if (sourceInfo != null) { + return sourceInfo.mDrmHandle.getDrmPropertyString(propertyName); + } + return null; } /** @@ -3863,21 +3670,10 @@ public class MediaPlayer2 implements AutoCloseable // TODO: this implementation only works when dsd is the only data source Log.v(TAG, "setDrmPropertyString: propertyName: " + propertyName + " value: " + value); - synchronized (mDrmLock) { - - if (!mActiveDrmScheme && !mDrmConfigAllowed) { - Log.w(TAG, "setDrmPropertyString NoDrmSchemeException"); - throw new NoDrmSchemeException( - "setDrmPropertyString: Has to prepareDrm() first."); - } - - try { - mDrmObj.setPropertyString(propertyName, value); - } catch (Exception e) { - Log.w(TAG, "setDrmPropertyString Exception " + e); - throw e; - } - } // synchronized + final SourceInfo sourceInfo = getSourceInfo(dsd); + if (sourceInfo != null) { + sourceInfo.mDrmHandle.setDrmPropertyString(propertyName, value); + } } /** @@ -4029,43 +3825,6 @@ public class MediaPlayer2 implements AutoCloseable private native void native_prepareDrm(@NonNull byte[] uuid, @NonNull byte[] drmSessionId); - // Modular DRM helpers - - private void prepareDrm_createDrmStep(@NonNull UUID uuid) - throws UnsupportedSchemeException { - Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid); - - try { - mDrmObj = new MediaDrm(uuid); - Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj); - } catch (Exception e) { // UnsupportedSchemeException - Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e); - throw e; - } - } - - private void prepareDrm_openSessionStep(@NonNull UUID uuid) - throws NotProvisionedException, ResourceBusyException { - Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid); - - // TODO: don't need an open session for a future specialKeyReleaseDrm mode but we should do - // it anyway so it raises provisioning error if needed. We'd rather handle provisioning - // at prepareDrm/openSession rather than getDrmKeyRequest/provideDrmKeyResponse - try { - mDrmSessionId = mDrmObj.openSession(); - Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId); - - // Sending it down to native/mediaserver to create the crypto object - // This call could simply fail due to bad player state, e.g., after play(). - native_prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId); - Log.v(TAG, "prepareDrm_openSessionStep: native_prepareDrm/Crypto succeeded"); - - } catch (Exception e) { //ResourceBusyException, NotProvisionedException - Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e); - throw e; - } - } - // Instantiated from the native side @SuppressWarnings("unused") private static class StreamEventCallback extends AudioTrack.StreamEventCallback { @@ -4097,227 +3856,28 @@ public class MediaPlayer2 implements AutoCloseable } } - private class ProvisioningThread extends Thread { - public static final int TIMEOUT_MS = 60000; - - private final DataSourceDesc mDSD; - private UUID mUuid; - private String mUrlStr; - private Object mDrmLock; - private MediaPlayer2 mMediaPlayer; - private int mStatus; - public int status() { - return mStatus; - } - - public ProvisioningThread(MediaDrm.ProvisionRequest request, - DataSourceDesc dsd, - UUID uuid, MediaPlayer2 mediaPlayer) { - // lock is held by the caller - mDSD = dsd; - mDrmLock = mediaPlayer.mDrmLock; - mMediaPlayer = mediaPlayer; - - mUrlStr = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData()); - mUuid = uuid; - - mStatus = PREPARE_DRM_STATUS_PREPARATION_ERROR; - - Log.v(TAG, "handleProvisioninig: Thread is initialised url: " + mUrlStr); - } - - public void run() { - - byte[] response = null; - boolean provisioningSucceeded = false; - try { - URL url = new URL(mUrlStr); - final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - try { - connection.setRequestMethod("POST"); - connection.setDoOutput(false); - connection.setDoInput(true); - connection.setConnectTimeout(TIMEOUT_MS); - connection.setReadTimeout(TIMEOUT_MS); - - connection.connect(); - response = readInputStreamFully(connection.getInputStream()); - - Log.v(TAG, "handleProvisioninig: Thread run: response " - + response.length + " " + response); - } catch (Exception e) { - mStatus = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR; - Log.w(TAG, "handleProvisioninig: Thread run: connect " + e + " url: " + url); - } finally { - connection.disconnect(); - } - } catch (Exception e) { - mStatus = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR; - Log.w(TAG, "handleProvisioninig: Thread run: openConnection " + e); - } - - if (response != null) { - try { - mDrmObj.provideProvisionResponse(response); - Log.v(TAG, "handleProvisioninig: Thread run: " - + "provideProvisionResponse SUCCEEDED!"); - - provisioningSucceeded = true; - } catch (Exception e) { - mStatus = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR; - Log.w(TAG, "handleProvisioninig: Thread run: " - + "provideProvisionResponse " + e); - } - } - - boolean succeeded = false; - - synchronized (mDrmLock) { - // continuing with prepareDrm - if (provisioningSucceeded) { - succeeded = mMediaPlayer.resumePrepareDrm(mUuid); - mStatus = (succeeded) - ? PREPARE_DRM_STATUS_SUCCESS : - PREPARE_DRM_STATUS_PREPARATION_ERROR; - } - mMediaPlayer.mDrmProvisioningInProgress = false; - mMediaPlayer.mPrepareDrmInProgress = false; - if (!succeeded) { - cleanDrmObj(); // cleaning up if it hasn't gone through while in the lock - } - } // synchronized - - // calling the callback outside the lock - sendDrmEvent(new DrmEventNotifier() { - @Override - public void notify(DrmEventCallback callback) { - callback.onDrmPrepared( - mMediaPlayer, mDSD, mStatus); - } - }); - - synchronized (mTaskLock) { - if (mCurrentTask != null - && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE_DRM - && mCurrentTask.mNeedToWaitForEventToComplete) { - mCurrentTask = null; - processPendingTask_l(); - } - } - } - - /** - * Returns a byte[] containing the remainder of 'in', closing it when done. - */ - private byte[] readInputStreamFully(InputStream in) throws IOException { - try { - return readInputStreamFullyNoClose(in); - } finally { - in.close(); - } - } - - /** - * Returns a byte[] containing the remainder of 'in'. - */ - private byte[] readInputStreamFullyNoClose(InputStream in) throws IOException { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int count; - while ((count = in.read(buffer)) != -1) { - bytes.write(buffer, 0, count); - } - return bytes.toByteArray(); - } - } // ProvisioningThread - - private int handleProvisioninig(DataSourceDesc dsd, UUID uuid) { - synchronized (mDrmLock) { - if (mDrmProvisioningInProgress) { - Log.e(TAG, "handleProvisioninig: Unexpected mDrmProvisioningInProgress"); - return PREPARE_DRM_STATUS_PREPARATION_ERROR; - } - - MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest(); - if (provReq == null) { - Log.e(TAG, "handleProvisioninig: getProvisionRequest returned null."); - return PREPARE_DRM_STATUS_PREPARATION_ERROR; - } - - Log.v(TAG, "handleProvisioninig provReq " - + " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl()); - - // networking in a background thread - mDrmProvisioningInProgress = true; - - mDrmProvisioningThread = new ProvisioningThread(provReq, dsd, uuid, this); - mDrmProvisioningThread.start(); - - return PREPARE_DRM_STATUS_SUCCESS; - } - } - - private boolean resumePrepareDrm(UUID uuid) { - Log.v(TAG, "resumePrepareDrm: uuid: " + uuid); - - // mDrmLock is guaranteed to be held - boolean success = false; + /** + * Returns a byte[] containing the remainder of 'in', closing it when done. + */ + private static byte[] readInputStreamFully(InputStream in) throws IOException { try { - // resuming - prepareDrm_openSessionStep(uuid); - - mDrmUUID = uuid; - mActiveDrmScheme = true; - - success = true; - } catch (Exception e) { - Log.w(TAG, "handleProvisioninig: Thread run native_prepareDrm resume failed with " + e); - // mDrmObj clean up is done by the caller + return readInputStreamFullyNoClose(in); + } finally { + in.close(); } - - return success; - } - - private void resetDrmState() { - synchronized (mDrmLock) { - Log.v(TAG, "resetDrmState:" - + " mDrmInfo=" + mDrmInfo - + " mDrmProvisioningThread=" + mDrmProvisioningThread - + " mPrepareDrmInProgress=" + mPrepareDrmInProgress - + " mActiveDrmScheme=" + mActiveDrmScheme); - - mDrmInfoResolved = false; - mDrmInfo = null; - - if (mDrmProvisioningThread != null) { - // timeout; relying on HttpUrlConnection - try { - mDrmProvisioningThread.join(); - } catch (InterruptedException e) { - Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e); - } - mDrmProvisioningThread = null; - } - - mPrepareDrmInProgress = false; - mActiveDrmScheme = false; - - cleanDrmObj(); - } // synchronized } - private void cleanDrmObj() { - // the caller holds mDrmLock - Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId); - - if (mDrmSessionId != null) { - mDrmObj.closeSession(mDrmSessionId); - mDrmSessionId = null; - } - if (mDrmObj != null) { - mDrmObj.release(); - mDrmObj = null; + /** + * Returns a byte[] containing the remainder of 'in'. + */ + private static byte[] readInputStreamFullyNoClose(InputStream in) throws IOException { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int count; + while ((count = in.read(buffer)) != -1) { + bytes.write(buffer, 0, count); } + return bytes.toByteArray(); } private static byte[] getByteArrayFromUUID(@NonNull UUID uuid) { @@ -4333,8 +3893,6 @@ public class MediaPlayer2 implements AutoCloseable return uuidBytes; } - // Modular DRM end - private static class TimedTextUtil { // These keys must be in sync with the keys in TextDescription2.h private static final int KEY_START_TIME = 7; // int @@ -4410,6 +3968,7 @@ public class MediaPlayer2 implements AutoCloseable } private abstract class Task implements Runnable { + final long mTaskId = mTaskIdGenerator.getAndIncrement(); private final int mMediaCallType; private final boolean mNeedToWaitForEventToComplete; private DataSourceDesc mDSD; @@ -4501,7 +4060,503 @@ public class MediaPlayer2 implements AutoCloseable } }; - private final class SourceInfo { + // Modular DRM + final class DrmHandle { + + static final int PROVISION_TIMEOUT_MS = 60000; + + final DataSourceDesc mDSD; + + //--- guarded by |this| start + MediaDrm mDrmObj; + byte[] mDrmSessionId; + UUID mActiveDrmUUID; + boolean mDrmConfigAllowed; + boolean mDrmProvisioningInProgress; + boolean mPrepareDrmInProgress; + Future<?> mProvisionResult; + //--- guarded by |this| end + + DrmHandle(DataSourceDesc dsd) { + mDSD = dsd; + } + + void prepare(UUID uuid) throws UnsupportedSchemeException, + ResourceBusyException, NotProvisionedException { + final OnDrmConfigHelper onDrmConfigHelper = mOnDrmConfigHelper; + Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + onDrmConfigHelper); + + synchronized (this) { + if (mActiveDrmUUID != null) { + final String msg = "prepareDrm(): Wrong usage: There is already " + + "an active DRM scheme with " + uuid; + Log.e(TAG, msg); + throw new IllegalStateException(msg); + } + + if (mPrepareDrmInProgress) { + final String msg = "prepareDrm(): Wrong usage: There is already " + + "a pending prepareDrm call."; + Log.e(TAG, msg); + throw new IllegalStateException(msg); + } + + if (mDrmProvisioningInProgress) { + final String msg = "prepareDrm(): Unexpectd: Provisioning already in progress"; + Log.e(TAG, msg); + throw new IllegalStateException(msg); + } + + // shouldn't need this; just for safeguard + cleanDrmObj(); + + mPrepareDrmInProgress = true; + + try { + // only creating the DRM object to allow pre-openSession configuration + prepareDrm_createDrmStep(uuid); + } catch (Exception e) { + Log.w(TAG, "prepareDrm(): Exception ", e); + mPrepareDrmInProgress = false; + throw e; + } + + mDrmConfigAllowed = true; + } // synchronized + + // call the callback outside the lock + if (onDrmConfigHelper != null) { + onDrmConfigHelper.onDrmConfig(MediaPlayer2.this, mDSD); + } + + synchronized (this) { + mDrmConfigAllowed = false; + boolean earlyExit = false; + + try { + prepareDrm_openSessionStep(uuid); + + this.mActiveDrmUUID = uuid; + mPrepareDrmInProgress = false; + } catch (IllegalStateException e) { + final String msg = "prepareDrm(): Wrong usage: The player must be " + + "in the prepared state to call prepareDrm()."; + Log.e(TAG, msg); + earlyExit = true; + mPrepareDrmInProgress = false; + throw new IllegalStateException(msg); + } catch (NotProvisionedException e) { + Log.w(TAG, "prepareDrm: NotProvisionedException", e); + throw e; + } catch (Exception e) { + Log.e(TAG, "prepareDrm: Exception " + e); + earlyExit = true; + mPrepareDrmInProgress = false; + throw e; + } finally { + if (earlyExit) { // clean up object if didn't succeed + cleanDrmObj(); + } + } // finally + } // synchronized + } + + void prepareDrm_createDrmStep(UUID uuid) + throws UnsupportedSchemeException { + Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid); + + try { + mDrmObj = new MediaDrm(uuid); + Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj); + } catch (Exception e) { // UnsupportedSchemeException + Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e); + throw e; + } + } + + void prepareDrm_openSessionStep(UUID uuid) + throws NotProvisionedException, ResourceBusyException { + Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid); + + // TODO: + // don't need an open session for a future specialKeyReleaseDrm mode but we should do + // it anyway so it raises provisioning error if needed. We'd rather handle provisioning + // at prepareDrm/openSession rather than getDrmKeyRequest/provideDrmKeyResponse + try { + mDrmSessionId = mDrmObj.openSession(); + Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId); + + // Sending it down to native/mediaserver to create the crypto object + // This call could simply fail due to bad player state, e.g., after play(). + MediaPlayer2.this.native_prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId); + Log.v(TAG, "prepareDrm_openSessionStep: native_prepareDrm/Crypto succeeded"); + + } catch (Exception e) { //ResourceBusyException, NotProvisionedException + Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e); + throw e; + } + + } + + int handleProvisioninig(UUID uuid, long taskId) { + synchronized (this) { + if (mDrmProvisioningInProgress) { + Log.e(TAG, "handleProvisioninig: Unexpected mDrmProvisioningInProgress"); + return PREPARE_DRM_STATUS_PREPARATION_ERROR; + } + + MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest(); + if (provReq == null) { + Log.e(TAG, "handleProvisioninig: getProvisionRequest returned null."); + return PREPARE_DRM_STATUS_PREPARATION_ERROR; + } + + Log.v(TAG, "handleProvisioninig provReq " + + " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl()); + + // networking in a background thread + mDrmProvisioningInProgress = true; + + mProvisionResult = mDrmThreadPool.submit(newProvisioningTask(uuid, taskId)); + + return PREPARE_DRM_STATUS_SUCCESS; + } + } + + void provision(UUID uuid, long taskId) { + + MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest(); + String urlStr = provReq.getDefaultUrl(); + urlStr += "&signedRequest=" + new String(provReq.getData()); + Log.v(TAG, "handleProvisioninig: Thread is initialised url: " + urlStr); + + byte[] response = null; + boolean provisioningSucceeded = false; + int status = PREPARE_DRM_STATUS_PREPARATION_ERROR; + try { + URL url = new URL(urlStr); + final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + try { + connection.setRequestMethod("POST"); + connection.setDoOutput(false); + connection.setDoInput(true); + connection.setConnectTimeout(PROVISION_TIMEOUT_MS); + connection.setReadTimeout(PROVISION_TIMEOUT_MS); + + connection.connect(); + response = readInputStreamFully(connection.getInputStream()); + + Log.v(TAG, "handleProvisioninig: Thread run: response " + + response.length + " " + response); + } catch (Exception e) { + status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR; + Log.w(TAG, "handleProvisioninig: Thread run: connect " + e + " url: " + url); + } finally { + connection.disconnect(); + } + } catch (Exception e) { + status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR; + Log.w(TAG, "handleProvisioninig: Thread run: openConnection " + e); + } + + if (response != null) { + try { + mDrmObj.provideProvisionResponse(response); + Log.v(TAG, "handleProvisioninig: Thread run: " + + "provideProvisionResponse SUCCEEDED!"); + + provisioningSucceeded = true; + } catch (Exception e) { + status = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR; + Log.w(TAG, "handleProvisioninig: Thread run: " + + "provideProvisionResponse " + e); + } + } + + boolean succeeded = false; + + synchronized (this) { + // continuing with prepareDrm + if (provisioningSucceeded) { + succeeded = resumePrepare(uuid); + status = (succeeded) ? + PREPARE_DRM_STATUS_SUCCESS : + PREPARE_DRM_STATUS_PREPARATION_ERROR; + } + mDrmProvisioningInProgress = false; + mPrepareDrmInProgress = false; + if (!succeeded) { + cleanDrmObj(); // cleaning up if it hasn't gone through while in the lock + } + } // synchronized + + // calling the callback outside the lock + final int finalStatus = status; + sendDrmEvent(new DrmEventNotifier() { + @Override + public void notify(DrmEventCallback callback) { + callback.onDrmPrepared( + MediaPlayer2.this, mDSD, finalStatus); + } + }); + + synchronized (mTaskLock) { + if (mCurrentTask != null + && mCurrentTask.mTaskId == taskId + && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE_DRM + && mCurrentTask.mNeedToWaitForEventToComplete) { + mCurrentTask = null; + processPendingTask_l(); + } + } + } + + Runnable newProvisioningTask(UUID uuid, long taskId) { + return new Runnable() { + @Override + public void run() { + provision(uuid, taskId); + } + }; + } + + boolean resumePrepare(UUID uuid) { + Log.v(TAG, "resumePrepareDrm: uuid: " + uuid); + + // mDrmLock is guaranteed to be held + boolean success = false; + try { + // resuming + prepareDrm_openSessionStep(uuid); + + this.mActiveDrmUUID = uuid; + + success = true; + } catch (Exception e) { + Log.w(TAG, "handleProvisioninig: Thread run native_prepareDrm resume failed:" + e); + // mDrmObj clean up is done by the caller + } + + return success; + } + + void cleanDrmObj() { + // the caller holds mDrmLock + Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId); + + if (mDrmSessionId != null) { + mDrmObj.closeSession(mDrmSessionId); + mDrmSessionId = null; + } + if (mDrmObj != null) { + mDrmObj.close(); + mDrmObj = null; + } + } + + void release() throws NoDrmSchemeException { + synchronized (this) { + Log.v(TAG, "releaseDrm:"); + + if (mActiveDrmUUID == null) { + Log.e(TAG, "releaseDrm(): No active DRM scheme to release."); + throw new NoDrmSchemeException( + "releaseDrm: No active DRM scheme to release."); + } + + try { + // we don't have the player's state in this layer. The below call raises + // exception if we're in a non-stopped/prepared state. + + // for cleaning native/mediaserver crypto object + native_releaseDrm(); + + // for cleaning client-side MediaDrm object; only called if above has succeeded + cleanDrmObj(); + + this.mActiveDrmUUID = null; + } catch (IllegalStateException e) { + Log.w(TAG, "releaseDrm: Exception ", e); + throw new IllegalStateException( + "releaseDrm: The player is not in a valid state."); + } catch (Exception e) { + Log.e(TAG, "releaseDrm: Exception ", e); + } + } // synchronized + } + + void cleanup() { + synchronized (this) { + Log.v(TAG, "cleanupDrm: " + + " mProvisioningTask=" + mProvisionResult + + " mPrepareDrmInProgress=" + mPrepareDrmInProgress + + " mActiveDrmScheme=" + mActiveDrmUUID); + + if (mProvisionResult != null) { + // timeout; relying on HttpUrlConnection + try { + mProvisionResult.get(); + } + catch (InterruptedException | ExecutionException e) { + Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e); + } + } + + // set to false to avoid duplicate release calls + this.mActiveDrmUUID = null; + + cleanDrmObj(); + } // synchronized + } + + Runnable newCleanupTask() { + return new Runnable() { + @Override + public void run() { + cleanup(); + } + }; + } + + MediaDrm.KeyRequest getDrmKeyRequest( + byte[] keySetId, byte[] initData, + String mimeType, int keyType, + Map<String, String> optionalParameters) + throws NoDrmSchemeException { + synchronized (this) { + if (mActiveDrmUUID == null) { + Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException"); + throw new NoDrmSchemeException( + "getDrmKeyRequest: Has to set a DRM scheme first."); + } + + try { + byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE) ? + mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE + keySetId; // keySetId for KEY_TYPE_RELEASE + + HashMap<String, String> hmapOptionalParameters = + (optionalParameters != null) + ? new HashMap<String, String>(optionalParameters) + : null; + + MediaDrm.KeyRequest request = mDrmObj.getKeyRequest( + scope, initData, mimeType, keyType, hmapOptionalParameters); + Log.v(TAG, "getDrmKeyRequest: --> request: " + request); + + return request; + + } catch (NotProvisionedException e) { + Log.w(TAG, "getDrmKeyRequest NotProvisionedException: " + + "Unexpected. Shouldn't have reached here."); + throw new IllegalStateException("getDrmKeyRequest: provisioning error."); + } catch (Exception e) { + Log.w(TAG, "getDrmKeyRequest Exception " + e); + throw e; + } + + } + } + + byte[] provideDrmKeyResponse(byte[] keySetId, byte[] response) + throws NoDrmSchemeException, DeniedByServerException { + synchronized (this) { + + if (mActiveDrmUUID == null) { + Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException"); + throw new NoDrmSchemeException( + "getDrmKeyRequest: Has to set a DRM scheme first."); + } + + try { + byte[] scope = (keySetId == null) ? + mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE + keySetId; // keySetId for KEY_TYPE_RELEASE + + byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response); + + Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + + " response: " + response + " --> " + keySetResult); + + + return keySetResult; + + } catch (NotProvisionedException e) { + Log.w(TAG, "provideDrmKeyResponse NotProvisionedException: " + + "Unexpected. Shouldn't have reached here."); + throw new IllegalStateException("provideDrmKeyResponse: " + + "Unexpected provisioning error."); + } catch (Exception e) { + Log.w(TAG, "provideDrmKeyResponse Exception " + e); + throw e; + } + } + } + + void restoreDrmKeys(byte[] keySetId) + throws NoDrmSchemeException { + synchronized (this) { + if (mActiveDrmUUID == null) { + Log.w(TAG, "restoreDrmKeys NoDrmSchemeException"); + throw new NoDrmSchemeException( + "restoreDrmKeys: Has to set a DRM scheme first."); + } + + try { + mDrmObj.restoreKeys(mDrmSessionId, keySetId); + } catch (Exception e) { + Log.w(TAG, "restoreKeys Exception " + e); + throw e; + } + } + } + + String getDrmPropertyString(String propertyName) + throws NoDrmSchemeException { + String v; + synchronized (this) { + + if (mActiveDrmUUID == null && !mDrmConfigAllowed) { + Log.w(TAG, "getDrmPropertyString NoDrmSchemeException"); + throw new NoDrmSchemeException( + "getDrmPropertyString: Has to prepareDrm() first."); + } + + try { + v = mDrmObj.getPropertyString(propertyName); + } catch (Exception e) { + Log.w(TAG, "getDrmPropertyString Exception " + e); + throw e; + } + } // synchronized + + Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + v); + + return v; + } + + void setDrmPropertyString(String propertyName, String value) + throws NoDrmSchemeException { + synchronized (this) { + + if ( mActiveDrmUUID == null && !mDrmConfigAllowed ) { + Log.w(TAG, "setDrmPropertyString NoDrmSchemeException"); + throw new NoDrmSchemeException( + "setDrmPropertyString: Has to prepareDrm() first."); + } + + try { + mDrmObj.setPropertyString(propertyName, value); + } catch ( Exception e ) { + Log.w(TAG, "setDrmPropertyString Exception " + e); + throw e; + } + } + } + + } + + final class SourceInfo { final DataSourceDesc mDSD; final long mId = mSrcIdGenerator.getAndIncrement(); AtomicInteger mBufferedPercentage = new AtomicInteger(0); @@ -4513,8 +4568,14 @@ public class MediaPlayer2 implements AutoCloseable int mStateAsNextSource = NEXT_SOURCE_STATE_INIT; boolean mPlayPendingAsNextSource = false; + // Modular DRM + final DrmHandle mDrmHandle; + DrmInfo mDrmInfo; + boolean mDrmInfoResolved; + SourceInfo(DataSourceDesc dsd) { this.mDSD = dsd; + mDrmHandle = new DrmHandle(dsd); } void close() { @@ -4535,7 +4596,7 @@ public class MediaPlayer2 implements AutoCloseable } - private SourceInfo getSourceInfoById(long srcId) { + private SourceInfo getSourceInfo(long srcId) { synchronized (mSrcLock) { if (isCurrentSource(srcId)) { return mCurrentSourceInfo; @@ -4547,34 +4608,65 @@ public class MediaPlayer2 implements AutoCloseable return null; } + private SourceInfo getSourceInfo(DataSourceDesc dsd) { + synchronized (mSrcLock) { + if (isCurrentSource(dsd)) { + return mCurrentSourceInfo; + } + if (isNextSource(dsd)) { + return mNextSourceInfos.peek(); + } + } + return null; + } + private boolean isCurrentSource(long srcId) { synchronized (mSrcLock) { return mCurrentSourceInfo != null && mCurrentSourceInfo.mId == srcId; } } + private boolean isCurrentSource(DataSourceDesc dsd) { + synchronized (mSrcLock) { + return mCurrentSourceInfo != null && mCurrentSourceInfo.mDSD == dsd; + } + } + private boolean isNextSource(long srcId) { SourceInfo nextSourceInfo = mNextSourceInfos.peek(); return nextSourceInfo != null && nextSourceInfo.mId == srcId; } - private void setCurrentSourceInfo(SourceInfo newSourceInfo) { - synchronized (mSrcLock) { - if (mCurrentSourceInfo != null) { - mCurrentSourceInfo.close(); - } - mCurrentSourceInfo = newSourceInfo; + private boolean isNextSource(DataSourceDesc dsd) { + SourceInfo nextSourceInfo = mNextSourceInfos.peek(); + return nextSourceInfo != null && nextSourceInfo.mDSD == dsd; + } + + @GuardedBy("mSrcLock") + private void setCurrentSourceInfo_l(SourceInfo sourceInfo) { + cleanupSourceInfo(mCurrentSourceInfo); + mCurrentSourceInfo = sourceInfo; + } + + @GuardedBy("mSrcLock") + private void clearNextSourceInfos_l() { + while (!mNextSourceInfos.isEmpty()) { + cleanupSourceInfo(mNextSourceInfos.poll()); + } + } + + private void cleanupSourceInfo(SourceInfo sourceInfo) { + if (sourceInfo != null) { + sourceInfo.close(); + Runnable task = sourceInfo.mDrmHandle.newCleanupTask(); + mDrmThreadPool.submit(task); } } - private void clearNextSourceInfos() { + private void clearSourceInfos() { synchronized (mSrcLock) { - for (SourceInfo sourceInfo : mNextSourceInfos) { - if (sourceInfo != null) { - sourceInfo.close(); - } - } - mNextSourceInfos.clear(); + setCurrentSourceInfo_l(null); + clearNextSourceInfos_l(); } } diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index 3a64f43f61de..0950a2497866 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -156,7 +156,8 @@ public class MediaScanner implements AutoCloseable { private static final String NOTIFICATIONS_DIR = "/notifications/"; private static final String ALARMS_DIR = "/alarms/"; private static final String MUSIC_DIR = "/music/"; - private static final String PODCAST_DIR = "/podcasts/"; + private static final String PODCASTS_DIR = "/podcasts/"; + private static final String AUDIOBOOKS_DIR = "/audiobooks/"; public static final String SCANNED_BUILD_PREFS_NAME = "MediaScanBuild"; public static final String LAST_INTERNAL_SCAN_FINGERPRINT = "lastScanFingerprint"; @@ -654,7 +655,7 @@ public class MediaScanner implements AutoCloseable { // rescan for metadata if file was modified since last scan if (entry != null && (entry.mLastModifiedChanged || scanAlways)) { if (noMedia) { - result = endFile(entry, false, false, false, false, false); + result = endFile(entry, false, false, false, false, false, false); } else { boolean isaudio = MediaFile.isAudioMimeType(mMimeType); boolean isvideo = MediaFile.isVideoMimeType(mMimeType); @@ -679,11 +680,13 @@ public class MediaScanner implements AutoCloseable { boolean notifications = mScanSuccess && (lowpath.indexOf(NOTIFICATIONS_DIR) > 0); boolean alarms = mScanSuccess && (lowpath.indexOf(ALARMS_DIR) > 0); - boolean podcasts = mScanSuccess && (lowpath.indexOf(PODCAST_DIR) > 0); + boolean podcasts = mScanSuccess && (lowpath.indexOf(PODCASTS_DIR) > 0); + boolean audiobooks = mScanSuccess && (lowpath.indexOf(AUDIOBOOKS_DIR) > 0); boolean music = mScanSuccess && ((lowpath.indexOf(MUSIC_DIR) > 0) || - (!ringtones && !notifications && !alarms && !podcasts)); + (!ringtones && !notifications && !alarms && !podcasts && !audiobooks)); - result = endFile(entry, ringtones, notifications, alarms, music, podcasts); + result = endFile(entry, ringtones, notifications, alarms, podcasts, + audiobooks, music); } } } catch (RemoteException e) { @@ -957,7 +960,7 @@ public class MediaScanner implements AutoCloseable { @UnsupportedAppUsage private Uri endFile(FileEntry entry, boolean ringtones, boolean notifications, - boolean alarms, boolean music, boolean podcasts) + boolean alarms, boolean podcasts, boolean audiobooks, boolean music) throws RemoteException { // update database @@ -1003,6 +1006,7 @@ public class MediaScanner implements AutoCloseable { values.put(Audio.Media.IS_ALARM, alarms); values.put(Audio.Media.IS_MUSIC, music); values.put(Audio.Media.IS_PODCAST, podcasts); + values.put(Audio.Media.IS_AUDIOBOOK, audiobooks); } else if (MediaFile.isExifMimeType(mMimeType) && !mNoMedia) { ExifInterface exif = null; try { diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 436897fbf8b0..874f21ea8c28 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -18,6 +18,7 @@ package android.media; import android.Manifest; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; @@ -1029,7 +1030,7 @@ public class RingtoneManager { * @throws FileNotFoundException if the provided URI could not be opened. * @see #getDefaultUri */ - public static AssetFileDescriptor openDefaultRingtoneUri( + public static @Nullable AssetFileDescriptor openDefaultRingtoneUri( @NonNull Context context, @NonNull Uri uri) throws FileNotFoundException { // Try cached ringtone first since the actual provider may not be // encryption aware, or it may be stored on CE media storage diff --git a/native/webview/plat_support/draw_gl.h b/native/webview/plat_support/draw_gl.h index c8434b61eba5..de13ed0dec6f 100644 --- a/native/webview/plat_support/draw_gl.h +++ b/native/webview/plat_support/draw_gl.h @@ -43,9 +43,9 @@ struct AwDrawGLInfo { // Input: tells the draw function what action to perform. enum Mode { kModeDraw = 0, - kModeProcess, - kModeProcessNoContext, - kModeSync, + kModeProcess = 1, + kModeProcessNoContext = 2, + kModeSync = 3, } mode; // Input: current clip rect in surface coordinates. Reflects the current state @@ -93,9 +93,9 @@ typedef void (AwDrawGLFunction)(long view_context, AwDrawGLInfo* draw_info, void* spare); enum AwMapMode { - MAP_READ_ONLY, - MAP_WRITE_ONLY, - MAP_READ_WRITE, + MAP_READ_ONLY = 0, + MAP_WRITE_ONLY = 1, + MAP_READ_WRITE = 2, }; // Called to create a GraphicBuffer diff --git a/packages/CarSystemUI/res/xml/car_volume_items.xml b/packages/CarSystemUI/res/xml/car_volume_items.xml index 8715946aac26..922b1a79d45a 100644 --- a/packages/CarSystemUI/res/xml/car_volume_items.xml +++ b/packages/CarSystemUI/res/xml/car_volume_items.xml @@ -23,8 +23,8 @@ car:icon="@drawable/car_ic_music"/> <item car:usage="media" car:icon="@drawable/car_ic_music"/> - <item car:usage="voice_communication" - car:icon="@*android:drawable/ic_audio_ring_notif"/> + <item car:usage="assistance_navigation_guidance" + car:icon="@drawable/car_ic_navigation"/> <item car:usage="voice_communication_signalling" car:icon="@*android:drawable/ic_audio_ring_notif"/> <item car:usage="alarm" @@ -43,8 +43,8 @@ car:icon="@drawable/car_ic_notification"/> <item car:usage="assistance_accessibility" car:icon="@drawable/car_ic_notification"/> - <item car:usage="assistance_navigation_guidance" - car:icon="@drawable/car_ic_navigation"/> + <item car:usage="voice_communication" + car:icon="@*android:drawable/ic_audio_ring_notif"/> <item car:usage="assistance_sonification" car:icon="@drawable/car_ic_notification"/> <item car:usage="game" diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java index b2fc41783516..892267b22058 100644 --- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java +++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java @@ -48,7 +48,7 @@ public class SmartActionsHelper { | Notification.FLAG_NO_CLEAR; private static final int MAX_ACTION_EXTRACTION_TEXT_LENGTH = 400; private static final int MAX_ACTIONS_PER_LINK = 1; - private static final int MAX_SMART_ACTIONS = Notification.MAX_ACTION_BUTTONS; + private static final int MAX_SMART_ACTIONS = 3; private static final int MAX_SUGGESTED_REPLIES = 3; private static final ConversationActions.TypeConfig TYPE_CONFIG = @@ -81,12 +81,9 @@ public class SmartActionsHelper { if (tcm == null) { return EMPTY_ACTION_LIST; } - Notification.Action[] actions = entry.getNotification().actions; - int numOfExistingActions = actions == null ? 0: actions.length; - int maxSmartActions = MAX_SMART_ACTIONS - numOfExistingActions; return suggestActionsFromText( tcm, - getMostSalientActionText(entry.getNotification()), maxSmartActions); + getMostSalientActionText(entry.getNotification()), MAX_SMART_ACTIONS); } ArrayList<CharSequence> suggestReplies( diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp deleted file mode 100644 index bc06cab7aef5..000000000000 --- a/packages/PackageInstaller/Android.bp +++ /dev/null @@ -1,14 +0,0 @@ -android_app { - name: "PackageInstaller", - - srcs: ["src/**/*.java"], - - static_libs: [ - "androidx.leanback_leanback", - "xz-java", - ], - - certificate: "platform", - privileged: true, - platform_apis: true, -}
\ No newline at end of file diff --git a/packages/PackageInstaller/Android.mk b/packages/PackageInstaller/Android.mk new file mode 100644 index 000000000000..ab5483c8afb8 --- /dev/null +++ b/packages/PackageInstaller/Android.mk @@ -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. + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := PackageInstaller + +LOCAL_CERTIFICATE := platform +LOCAL_PRIVILEGED_MODULE := true +LOCAL_PRIVATE_PLATFORM_APIS := true + +LOCAL_STATIC_JAVA_LIBRARIES := xz-java +LOCAL_STATIC_ANDROID_LIBRARIES := androidx.leanback_leanback + +include $(BUILD_PACKAGE) + +include $(call all-makefiles-under, $(LOCAL_PATH)) diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml index 4801f62bae67..eb9ec82c9519 100644 --- a/packages/PackageInstaller/AndroidManifest.xml +++ b/packages/PackageInstaller/AndroidManifest.xml @@ -2,6 +2,8 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.packageinstaller"> + <original-package android:name="com.android.packageinstaller" /> + <uses-permission android:name="android.permission.MANAGE_USERS" /> <uses-permission android:name="android.permission.INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.DELETE_PACKAGES" /> diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml index 91e23ddeaedf..03eafc4ab836 100644 --- a/packages/PrintSpooler/AndroidManifest.xml +++ b/packages/PrintSpooler/AndroidManifest.xml @@ -34,15 +34,23 @@ <uses-permission android:name="com.android.printspooler.permission.ACCESS_ALL_PRINT_JOBS"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY"/> - <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> - <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.READ_PRINT_SERVICES" /> <uses-permission android:name="android.permission.READ_PRINT_SERVICE_RECOMMENDATIONS" /> + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" + android:dataSentOffDevice="no" + android:dataSharedWithThirdParty="no" + android:dataUsedForMonetization="no" + android:dataRetentionTime="unlimited"/> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" + android:dataSentOffDevice="no" + android:dataSharedWithThirdParty="no" + android:dataUsedForMonetization="no" + android:dataRetentionTime="unlimited"/> + <application android:allowClearUserData="true" android:label="@string/app_label" - android:allowBackup= "false" android:supportsRtl="true"> <service diff --git a/packages/PrintSpooler/res/layout/print_activity.xml b/packages/PrintSpooler/res/layout/print_activity.xml index 0ccf13e23aaf..9e16f5ef40a7 100644 --- a/packages/PrintSpooler/res/layout/print_activity.xml +++ b/packages/PrintSpooler/res/layout/print_activity.xml @@ -107,7 +107,7 @@ android:layout_height="wrap_content" android:layout_marginStart="16dip" android:elevation="@dimen/preview_controls_elevation" - android:tint="?android:attr/textColorPrimaryInverse" + android:tint="@android:color/white" android:background="@drawable/print_button"> </ImageButton> diff --git a/packages/SettingsLib/ActionButtonsPreference/Android.bp b/packages/SettingsLib/ActionButtonsPreference/Android.bp new file mode 100644 index 000000000000..e518e0b97c4b --- /dev/null +++ b/packages/SettingsLib/ActionButtonsPreference/Android.bp @@ -0,0 +1,13 @@ +android_library { + name: "ActionButtonsPreference", + + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + + static_libs: [ + "androidx.preference_preference", + ], + + sdk_version: "system_current", + min_sdk_version: "21", +} diff --git a/packages/SettingsLib/ActionButtonsPreference/AndroidManifest.xml b/packages/SettingsLib/ActionButtonsPreference/AndroidManifest.xml new file mode 100644 index 000000000000..4b9f1ab8d6cc --- /dev/null +++ b/packages/SettingsLib/ActionButtonsPreference/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.widget"> + + <uses-sdk android:minSdkVersion="21" /> + +</manifest> diff --git a/packages/SettingsLib/ActionButtonsPreference/res/layout/settings_action_buttons.xml b/packages/SettingsLib/ActionButtonsPreference/res/layout/settings_action_buttons.xml new file mode 100644 index 000000000000..4f47113bbafa --- /dev/null +++ b/packages/SettingsLib/ActionButtonsPreference/res/layout/settings_action_buttons.xml @@ -0,0 +1,52 @@ +<?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. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingStart="8dp" + android:orientation="horizontal"> + + <Button + android:id="@+id/button1" + style="@style/SettingsActionButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1"/> + + <Button + android:id="@+id/button2" + style="@style/SettingsActionButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1"/> + + <Button + android:id="@+id/button3" + style="@style/SettingsActionButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1"/> + + <Button + android:id="@+id/button4" + style="@style/SettingsActionButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1"/> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/ActionButtonsPreference/res/values/styles.xml b/packages/SettingsLib/ActionButtonsPreference/res/values/styles.xml new file mode 100644 index 000000000000..efa508dcde62 --- /dev/null +++ b/packages/SettingsLib/ActionButtonsPreference/res/values/styles.xml @@ -0,0 +1,26 @@ +<?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. + --> + +<resources> + <style name="SettingsActionButton" parent="android:Widget.DeviceDefault.Button.Borderless.Colored"> + <item name="android:drawablePadding">4dp</item> + <item name="android:drawableTint">@*android:color/btn_colored_borderless_text_material</item> + <item name="android:layout_marginEnd">8dp</item> + <item name="android:paddingTop">20dp</item> + <item name="android:paddingBottom">20dp</item> + </style> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java new file mode 100644 index 000000000000..8b46cc608fd1 --- /dev/null +++ b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java @@ -0,0 +1,393 @@ +/* + * 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.settingslib.widget; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.widget.Button; + +import androidx.annotation.DrawableRes; +import androidx.annotation.StringRes; +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +/** + * This preference provides a four buttons layout with Settings style. + * It looks like below + * + * -------------------------------------------------- + * button1 | button2 | button3 | button4 | + * -------------------------------------------------- + * + * User can set title / icon / click listener for each button. + * + * By default, four buttons are visible. + * However, there are two cases which button should be invisible(View.GONE). + * + * 1. User sets invisible for button. ex: ActionButtonPreference.setButton1Visible(false) + * 2. User doesn't set any title or icon for button. + */ +public class ActionButtonsPreference extends Preference { + + private static final String TAG = "ActionButtonPreference"; + private final ButtonInfo mButton1Info = new ButtonInfo(); + private final ButtonInfo mButton2Info = new ButtonInfo(); + private final ButtonInfo mButton3Info = new ButtonInfo(); + private final ButtonInfo mButton4Info = new ButtonInfo(); + + public ActionButtonsPreference(Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + public ActionButtonsPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + public ActionButtonsPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public ActionButtonsPreference(Context context) { + super(context); + init(); + } + + private void init() { + setLayoutResource(R.layout.settings_action_buttons); + setSelectable(false); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + holder.setDividerAllowedAbove(true); + holder.setDividerAllowedBelow(true); + + mButton1Info.mButton = (Button) holder.findViewById(R.id.button1); + mButton2Info.mButton = (Button) holder.findViewById(R.id.button2); + mButton3Info.mButton = (Button) holder.findViewById(R.id.button3); + mButton4Info.mButton = (Button) holder.findViewById(R.id.button4); + + mButton1Info.setUpButton(); + mButton2Info.setUpButton(); + mButton3Info.setUpButton(); + mButton4Info.setUpButton(); + } + + /** + * Set the visibility state of button1. + */ + public ActionButtonsPreference setButton1Visible(boolean isVisible) { + if (isVisible != mButton1Info.mIsVisible) { + mButton1Info.mIsVisible = isVisible; + notifyChanged(); + } + return this; + } + + /** + * Sets the text to be displayed in button1. + */ + public ActionButtonsPreference setButton1Text(@StringRes int textResId) { + final String newText = getContext().getString(textResId); + if (!TextUtils.equals(newText, mButton1Info.mText)) { + mButton1Info.mText = newText; + notifyChanged(); + } + return this; + } + + /** + * Sets the drawable to be displayed above of text in button1. + */ + public ActionButtonsPreference setButton1Icon(@DrawableRes int iconResId) { + if (iconResId == 0) { + return this; + } + + final Drawable icon; + try { + icon = getContext().getDrawable(iconResId); + mButton1Info.mIcon = icon; + notifyChanged(); + } catch (Resources.NotFoundException exception) { + Log.e(TAG, "Resource does not exist: " + iconResId); + } + return this; + } + + /** + * Set the enabled state of button1. + */ + public ActionButtonsPreference setButton1Enabled(boolean isEnabled) { + if (isEnabled != mButton1Info.mIsEnabled) { + mButton1Info.mIsEnabled = isEnabled; + notifyChanged(); + } + return this; + } + + /** + * Register a callback to be invoked when button1 is clicked. + */ + public ActionButtonsPreference setButton1OnClickListener( + View.OnClickListener listener) { + if (listener != mButton1Info.mListener) { + mButton1Info.mListener = listener; + notifyChanged(); + } + return this; + } + + /** + * Set the visibility state of button2. + */ + public ActionButtonsPreference setButton2Visible(boolean isVisible) { + if (isVisible != mButton2Info.mIsVisible) { + mButton2Info.mIsVisible = isVisible; + notifyChanged(); + } + return this; + } + + /** + * Sets the text to be displayed in button2. + */ + public ActionButtonsPreference setButton2Text(@StringRes int textResId) { + final String newText = getContext().getString(textResId); + if (!TextUtils.equals(newText, mButton2Info.mText)) { + mButton2Info.mText = newText; + notifyChanged(); + } + return this; + } + + /** + * Sets the drawable to be displayed above of text in button2. + */ + public ActionButtonsPreference setButton2Icon(@DrawableRes int iconResId) { + if (iconResId == 0) { + return this; + } + + final Drawable icon; + try { + icon = getContext().getDrawable(iconResId); + mButton2Info.mIcon = icon; + notifyChanged(); + } catch (Resources.NotFoundException exception) { + Log.e(TAG, "Resource does not exist: " + iconResId); + } + return this; + } + + /** + * Set the enabled state of button2. + */ + public ActionButtonsPreference setButton2Enabled(boolean isEnabled) { + if (isEnabled != mButton2Info.mIsEnabled) { + mButton2Info.mIsEnabled = isEnabled; + notifyChanged(); + } + return this; + } + + /** + * Register a callback to be invoked when button2 is clicked. + */ + public ActionButtonsPreference setButton2OnClickListener( + View.OnClickListener listener) { + if (listener != mButton2Info.mListener) { + mButton2Info.mListener = listener; + notifyChanged(); + } + return this; + } + + /** + * Set the visibility state of button3. + */ + public ActionButtonsPreference setButton3Visible(boolean isVisible) { + if (isVisible != mButton3Info.mIsVisible) { + mButton3Info.mIsVisible = isVisible; + notifyChanged(); + } + return this; + } + + /** + * Sets the text to be displayed in button3. + */ + public ActionButtonsPreference setButton3Text(@StringRes int textResId) { + final String newText = getContext().getString(textResId); + if (!TextUtils.equals(newText, mButton3Info.mText)) { + mButton3Info.mText = newText; + notifyChanged(); + } + return this; + } + + /** + * Sets the drawable to be displayed above of text in button3. + */ + public ActionButtonsPreference setButton3Icon(@DrawableRes int iconResId) { + if (iconResId == 0) { + return this; + } + + final Drawable icon; + try { + icon = getContext().getDrawable(iconResId); + mButton3Info.mIcon = icon; + notifyChanged(); + } catch (Resources.NotFoundException exception) { + Log.e(TAG, "Resource does not exist: " + iconResId); + } + return this; + } + + /** + * Set the enabled state of button3. + */ + public ActionButtonsPreference setButton3Enabled(boolean isEnabled) { + if (isEnabled != mButton3Info.mIsEnabled) { + mButton3Info.mIsEnabled = isEnabled; + notifyChanged(); + } + return this; + } + + /** + * Register a callback to be invoked when button3 is clicked. + */ + public ActionButtonsPreference setButton3OnClickListener( + View.OnClickListener listener) { + if (listener != mButton3Info.mListener) { + mButton3Info.mListener = listener; + notifyChanged(); + } + return this; + } + + /** + * Set the visibility state of button4. + */ + public ActionButtonsPreference setButton4Visible(boolean isVisible) { + if (isVisible != mButton4Info.mIsVisible) { + mButton4Info.mIsVisible = isVisible; + notifyChanged(); + } + return this; + } + + /** + * Sets the text to be displayed in button4. + */ + public ActionButtonsPreference setButton4Text(@StringRes int textResId) { + final String newText = getContext().getString(textResId); + if (!TextUtils.equals(newText, mButton4Info.mText)) { + mButton4Info.mText = newText; + notifyChanged(); + } + return this; + } + + /** + * Sets the drawable to be displayed above of text in button4. + */ + public ActionButtonsPreference setButton4Icon(@DrawableRes int iconResId) { + if (iconResId == 0) { + return this; + } + + final Drawable icon; + try { + icon = getContext().getDrawable(iconResId); + mButton4Info.mIcon = icon; + notifyChanged(); + } catch (Resources.NotFoundException exception) { + Log.e(TAG, "Resource does not exist: " + iconResId); + } + return this; + } + + /** + * Set the enabled state of button4. + */ + public ActionButtonsPreference setButton4Enabled(boolean isEnabled) { + if (isEnabled != mButton4Info.mIsEnabled) { + mButton4Info.mIsEnabled = isEnabled; + notifyChanged(); + } + return this; + } + + /** + * Register a callback to be invoked when button4 is clicked. + */ + public ActionButtonsPreference setButton4OnClickListener( + View.OnClickListener listener) { + if (listener != mButton4Info.mListener) { + mButton4Info.mListener = listener; + notifyChanged(); + } + return this; + } + + static class ButtonInfo { + private Button mButton; + private CharSequence mText; + private Drawable mIcon; + private View.OnClickListener mListener; + private boolean mIsEnabled = true; + private boolean mIsVisible = true; + + void setUpButton() { + mButton.setText(mText); + mButton.setOnClickListener(mListener); + mButton.setEnabled(mIsEnabled); + mButton.setCompoundDrawablesWithIntrinsicBounds( + null /* left */, mIcon /* top */, null /* right */, null /* bottom */); + + if (shouldBeVisible()) { + mButton.setVisibility(View.VISIBLE); + } else { + mButton.setVisibility(View.GONE); + } + } + + /** + * By default, four buttons are visible. + * However, there are two cases which button should be invisible. + * + * 1. User set invisible for this button. ex: mIsVisible = false. + * 2. User didn't set any title or icon. + */ + private boolean shouldBeVisible() { + return mIsVisible && (!TextUtils.isEmpty(mText) || mIcon != null); + } + } +} diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 444e72459510..cc17b25d9a40 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -17,6 +17,7 @@ android_library { "SettingsLibSearchWidget", "SettingsLibSettingsSpinner", "SettingsLayoutPreference", + "ActionButtonsPreference", ], // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES diff --git a/packages/SettingsLib/res/layout/preference_checkable_two_target.xml b/packages/SettingsLib/res/layout/preference_checkable_two_target.xml new file mode 100644 index 000000000000..1a47afc81d55 --- /dev/null +++ b/packages/SettingsLib/res/layout/preference_checkable_two_target.xml @@ -0,0 +1,92 @@ +<?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. + --> + +<!-- Based off preference_material_settings.xml except that ripple on only on the left side. --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:settings="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:gravity="center_vertical" + android:background="@android:color/transparent" + android:clipToPadding="false"> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:background="?android:attr/selectableItemBackground" + android:gravity="start|center_vertical" + android:clipToPadding="false" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + + <LinearLayout + android:id="@+id/checkbox_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="start|center_vertical" + android:minWidth="56dp" + android:orientation="horizontal" + android:clipToPadding="false" + android:paddingTop="4dp" + android:paddingBottom="4dp"> + <include layout="@layout/preference_widget_checkbox" /> + </LinearLayout> + + <RelativeLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:paddingTop="16dp" + android:paddingBottom="16dp"> + + <TextView + android:id="@android:id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:textAppearance="?android:attr/textAppearanceListItem" + android:ellipsize="marquee" /> + + <TextView + android:id="@android:id/summary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@android:id/title" + android:layout_alignStart="@android:id/title" + android:textAppearance="?android:attr/textAppearanceListItemSecondary" + android:textColor="?android:attr/textColorSecondary" + android:maxLines="10" /> + + </RelativeLayout> + + </LinearLayout> + + <include layout="@layout/preference_two_target_divider" /> + + <!-- Preference should place its actual preference widget here. --> + <LinearLayout + android:id="@android:id/widget_frame" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:minWidth="64dp" + android:gravity="center" + android:orientation="vertical" /> + +</LinearLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index a97e05442b6d..9270d13002d7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -208,6 +208,10 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> mHiSyncId = id; } + public boolean isHearingAidDevice() { + return mHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID; + } + void onBondingDockConnect() { // Attempt to connect if UUIDs are available. Otherwise, // we will connect when the ACTION_UUID intent arrives. diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java index 92fd86894d56..12b8efb1461c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java @@ -53,6 +53,8 @@ public final class CategoryKey { "com.android.settings.category.ia.night_display"; public static final String CATEGORY_PRIVACY = "com.android.settings.category.ia.privacy"; + public static final String CATEGORY_ENTERPRISE_PRIVACY = + "com.android.settings.category.ia.enterprise_privacy"; public static final Map<String, String> KEY_COMPAT_MAP; diff --git a/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoaderCompat.java b/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoaderCompat.java deleted file mode 100644 index e9c523881373..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoaderCompat.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settingslib.net; - -import static android.net.NetworkStats.SET_DEFAULT; -import static android.net.NetworkStats.SET_FOREGROUND; -import static android.net.NetworkStats.TAG_NONE; -import static android.net.NetworkStatsHistory.FIELD_RX_BYTES; -import static android.net.NetworkStatsHistory.FIELD_TX_BYTES; -import static android.text.format.DateUtils.HOUR_IN_MILLIS; - -import android.content.Context; -import android.net.INetworkStatsSession; -import android.net.NetworkStatsHistory; -import android.net.NetworkTemplate; -import android.os.Bundle; -import android.os.RemoteException; - -import androidx.loader.content.AsyncTaskLoader; - -import com.android.settingslib.AppItem; - -/** - * Loader for historical chart data for both network and UID details. - * - * Deprecated in favor of {@link NetworkCycleChartDataLoader} and - * {@link NetworkCycleDataForUidLoader} - * - * @deprecated - */ -@Deprecated -public class ChartDataLoaderCompat extends AsyncTaskLoader<ChartData> { - private static final String KEY_TEMPLATE = "template"; - private static final String KEY_APP = "app"; - private static final String KEY_FIELDS = "fields"; - - private final INetworkStatsSession mSession; - private final Bundle mArgs; - - public static Bundle buildArgs(NetworkTemplate template, AppItem app) { - return buildArgs(template, app, FIELD_RX_BYTES | FIELD_TX_BYTES); - } - - public static Bundle buildArgs(NetworkTemplate template, AppItem app, int fields) { - final Bundle args = new Bundle(); - args.putParcelable(KEY_TEMPLATE, template); - args.putParcelable(KEY_APP, app); - args.putInt(KEY_FIELDS, fields); - return args; - } - - public ChartDataLoaderCompat(Context context, INetworkStatsSession session, Bundle args) { - super(context); - mSession = session; - mArgs = args; - } - - @Override - protected void onStartLoading() { - super.onStartLoading(); - forceLoad(); - } - - @Override - public ChartData loadInBackground() { - final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE); - final AppItem app = mArgs.getParcelable(KEY_APP); - final int fields = mArgs.getInt(KEY_FIELDS); - - try { - return loadInBackground(template, app, fields); - } catch (RemoteException e) { - // since we can't do much without history, and we don't want to - // leave with half-baked UI, we bail hard. - throw new RuntimeException("problem reading network stats", e); - } - } - - private ChartData loadInBackground(NetworkTemplate template, AppItem app, int fields) - throws RemoteException { - final ChartData data = new ChartData(); - data.network = mSession.getHistoryForNetwork(template, fields); - - if (app != null) { - // load stats for current uid and template - final int size = app.uids.size(); - for (int i = 0; i < size; i++) { - final int uid = app.uids.keyAt(i); - data.detailDefault = collectHistoryForUid( - template, uid, SET_DEFAULT, data.detailDefault); - data.detailForeground = collectHistoryForUid( - template, uid, SET_FOREGROUND, data.detailForeground); - } - - if (size > 0) { - data.detail = new NetworkStatsHistory(data.detailForeground.getBucketDuration()); - data.detail.recordEntireHistory(data.detailDefault); - data.detail.recordEntireHistory(data.detailForeground); - } else { - data.detailDefault = new NetworkStatsHistory(HOUR_IN_MILLIS); - data.detailForeground = new NetworkStatsHistory(HOUR_IN_MILLIS); - data.detail = new NetworkStatsHistory(HOUR_IN_MILLIS); - } - } - - return data; - } - - @Override - protected void onStopLoading() { - super.onStopLoading(); - cancelLoad(); - } - - @Override - protected void onReset() { - super.onReset(); - cancelLoad(); - } - - /** - * Collect {@link NetworkStatsHistory} for the requested UID, combining with - * an existing {@link NetworkStatsHistory} if provided. - */ - private NetworkStatsHistory collectHistoryForUid( - NetworkTemplate template, int uid, int set, NetworkStatsHistory existing) - throws RemoteException { - final NetworkStatsHistory history = mSession.getHistoryForUid( - template, uid, set, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES); - - if (existing != null) { - existing.recordEntireHistory(history); - return existing; - } else { - return history; - } - } -} diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java index 183d4856ac6f..180b77e7503b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java +++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java @@ -32,30 +32,24 @@ import android.net.INetworkStatsService; import android.net.INetworkStatsSession; import android.net.NetworkPolicy; import android.net.NetworkPolicyManager; -import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.os.RemoteException; import android.os.ServiceManager; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.format.DateUtils; -import android.util.FeatureFlagUtils; import android.util.Log; import android.util.Range; import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; import java.time.ZonedDateTime; -import java.util.Date; import java.util.Iterator; import java.util.Locale; public class DataUsageController { private static final String TAG = "DataUsageController"; - @VisibleForTesting - static final String DATA_USAGE_V2 = "settings_data_usage_v2"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final int FIELDS = FIELD_RX_BYTES | FIELD_TX_BYTES; private static final StringBuilder PERIOD_BUILDER = new StringBuilder(50); @@ -95,21 +89,6 @@ public class DataUsageController { * mContext.getResources().getInteger(R.integer.default_data_warning_level_mb); } - @VisibleForTesting - @Deprecated - INetworkStatsSession getSession() { - if (mSession == null) { - try { - mSession = mStatsService.openSession(); - } catch (RemoteException e) { - Log.w(TAG, "Failed to open stats session", e); - } catch (RuntimeException e) { - Log.w(TAG, "Failed to open stats session", e); - } - } - return mSession; - } - public void setCallback(Callback callback) { mCallback = callback; } @@ -149,13 +128,7 @@ public class DataUsageController { end = now; start = now - DateUtils.WEEK_IN_MILLIS * 4; } - final long totalBytes; - final long callStart = System.currentTimeMillis(); - if (FeatureFlagUtils.isEnabled(mContext, DATA_USAGE_V2)) { - totalBytes = getUsageLevel(template, start, end); - } else { - totalBytes = getUsageLevel(template, start, end, now); - } + final long totalBytes = getUsageLevel(template, start, end); if (totalBytes < 0L) { return warn("no entry data"); } @@ -185,32 +158,7 @@ public class DataUsageController { * retrieving the data. */ public long getHistoricalUsageLevel(NetworkTemplate template) { - if (FeatureFlagUtils.isEnabled(mContext, DATA_USAGE_V2)) { - return getUsageLevel(template, 0L /* start */, System.currentTimeMillis() /* end */); - } else { - final long now = System.currentTimeMillis(); - return getUsageLevel(template, 0L /* start */, now /* end */, now); - } - } - - @Deprecated - private long getUsageLevel(NetworkTemplate template, long start, long end, long now) { - final INetworkStatsSession session = getSession(); - if (session != null) { - try { - final NetworkStatsHistory history = - session.getHistoryForNetwork(template, FIELDS); - final NetworkStatsHistory.Entry entry = history.getValues( - start, end, System.currentTimeMillis() /* now */, null /* recycle */); - if (entry != null) { - return entry.rxBytes + entry.txBytes; - } - Log.w(TAG, "Failed to get data usage, no entry data"); - } catch (RemoteException e) { - Log.w(TAG, "Failed to get data usage, remote call failed"); - } - } - return -1L; + return getUsageLevel(template, 0L /* start */, System.currentTimeMillis() /* end */); } private long getUsageLevel(NetworkTemplate template, long start, long end) { @@ -241,20 +189,6 @@ public class DataUsageController { return null; } - @Deprecated - private static String historyEntryToString(NetworkStatsHistory.Entry entry) { - return entry == null ? null : new StringBuilder("Entry[") - .append("bucketDuration=").append(entry.bucketDuration) - .append(",bucketStart=").append(entry.bucketStart) - .append(",activeTime=").append(entry.activeTime) - .append(",rxBytes=").append(entry.rxBytes) - .append(",rxPackets=").append(entry.rxPackets) - .append(",txBytes=").append(entry.txBytes) - .append(",txPackets=").append(entry.txPackets) - .append(",operations=").append(entry.operations) - .append(']').toString(); - } - private static String statsBucketToString(Bucket bucket) { return bucket == null ? null : new StringBuilder("Entry[") .append("bucketDuration=").append(bucket.getEndTimeStamp() - bucket.getStartTimeStamp()) diff --git a/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoaderCompat.java b/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoaderCompat.java deleted file mode 100644 index 82bb0115c66f..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoaderCompat.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settingslib.net; - -import android.content.Context; -import android.net.INetworkStatsSession; -import android.net.NetworkStats; -import android.net.NetworkTemplate; -import android.os.Bundle; -import android.os.RemoteException; - -import androidx.loader.content.AsyncTaskLoader; - -/** - * Deprecated in favor of {@link NetworkStatsDetailLoader} - * - * @deprecated - */ -@Deprecated -public class SummaryForAllUidLoaderCompat extends AsyncTaskLoader<NetworkStats> { - private static final String KEY_TEMPLATE = "template"; - private static final String KEY_START = "start"; - private static final String KEY_END = "end"; - - private final INetworkStatsSession mSession; - private final Bundle mArgs; - - public static Bundle buildArgs(NetworkTemplate template, long start, long end) { - final Bundle args = new Bundle(); - args.putParcelable(KEY_TEMPLATE, template); - args.putLong(KEY_START, start); - args.putLong(KEY_END, end); - return args; - } - - public SummaryForAllUidLoaderCompat(Context context, INetworkStatsSession session, - Bundle args) { - super(context); - mSession = session; - mArgs = args; - } - - @Override - protected void onStartLoading() { - super.onStartLoading(); - forceLoad(); - } - - @Override - public NetworkStats loadInBackground() { - final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE); - final long start = mArgs.getLong(KEY_START); - final long end = mArgs.getLong(KEY_END); - - try { - return mSession.getSummaryForAllUid(template, start, end, false); - } catch (RemoteException e) { - return null; - } - } - - @Override - protected void onStopLoading() { - super.onStopLoading(); - cancelLoad(); - } - - @Override - protected void onReset() { - super.onReset(); - cancelLoad(); - } -} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java index 72ed5e123add..acf99a24e96c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java @@ -18,7 +18,6 @@ package com.android.settingslib.net; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyLong; @@ -35,12 +34,10 @@ import android.content.Context; import android.net.ConnectivityManager; import android.net.INetworkStatsSession; import android.net.NetworkStatsHistory; -import android.net.NetworkStatsHistory.Entry; import android.net.NetworkTemplate; import android.os.RemoteException; import android.telephony.TelephonyManager; import android.text.format.DateUtils; -import android.util.FeatureFlagUtils; import org.junit.Before; import org.junit.Test; @@ -48,7 +45,6 @@ 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 DataUsageControllerTest { @@ -61,16 +57,18 @@ public class DataUsageControllerTest { private TelephonyManager mTelephonyManager; @Mock private NetworkStatsManager mNetworkStatsManager; - + @Mock private Context mContext; + private DataUsageController mController; private NetworkStatsHistory mNetworkStatsHistory; @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); - mContext = RuntimeEnvironment.application; - mController = spy(new DataUsageController(mContext)); + when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager); + when(mContext.getSystemService(NetworkStatsManager.class)).thenReturn(mNetworkStatsManager); + mController = new DataUsageController(mContext); mNetworkStatsHistory = spy( new NetworkStatsHistory(DateUtils.DAY_IN_MILLIS /* bucketDuration */)); doReturn(mNetworkStatsHistory) @@ -79,75 +77,25 @@ public class DataUsageControllerTest { } @Test - public void getHistoricalUsageLevel_v1_noNetworkSession_shouldReturnNegative1() { - FeatureFlagUtils.setEnabled(mContext, DataUsageController.DATA_USAGE_V2, false); - doReturn(null).when(mController).getSession(); - - assertThat(mController.getHistoricalUsageLevel(null /* template */)).isEqualTo(-1L); - } - - @Test - public void getHistoriclUsageLevel_v1_noUsageData_shouldReturn0() { - FeatureFlagUtils.setEnabled(mContext, DataUsageController.DATA_USAGE_V2, false); - doReturn(mSession).when(mController).getSession(); + public void getHistoricalUsageLevel_shouldQuerySummaryForDevice() throws Exception { - assertThat(mController.getHistoricalUsageLevel(NetworkTemplate.buildTemplateWifiWildcard())) - .isEqualTo(0L); - } - - @Test - public void getHistoricalUsageLevel_v1_hasUsageData_shouldReturnTotalUsage() { - FeatureFlagUtils.setEnabled(mContext, DataUsageController.DATA_USAGE_V2, false); - doReturn(mSession).when(mController).getSession(); - final long receivedBytes = 743823454L; - final long transmittedBytes = 16574289L; - final Entry entry = new Entry(); - entry.bucketStart = 1521583200000L; - entry.rxBytes = receivedBytes; - entry.txBytes = transmittedBytes; - when(mNetworkStatsHistory.getValues(eq(0L), anyLong(), anyLong(), nullable(Entry.class))) - .thenReturn(entry); - - assertThat(mController.getHistoricalUsageLevel(NetworkTemplate.buildTemplateWifiWildcard())) - .isEqualTo(receivedBytes + transmittedBytes); - } - - @Test - public void getHistoricalUsageLevel_v2_shouldQuerySummaryForDevice() throws Exception { - final Context context = mock(Context.class); - FeatureFlagUtils.setEnabled(context, DataUsageController.DATA_USAGE_V2, true); - when(context.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager); - when(context.getSystemService(NetworkStatsManager.class)).thenReturn(mNetworkStatsManager); - final DataUsageController controller = new DataUsageController(context); - - controller.getHistoricalUsageLevel(NetworkTemplate.buildTemplateWifiWildcard()); + mController.getHistoricalUsageLevel(NetworkTemplate.buildTemplateWifiWildcard()); verify(mNetworkStatsManager).querySummaryForDevice(eq(ConnectivityManager.TYPE_WIFI), eq(SUB_ID), eq(0L) /* startTime */, anyLong() /* endTime */); } @Test - public void getHistoricalUsageLevel_v2NoUsageData_shouldReturn0() throws Exception { - final Context context = mock(Context.class); - FeatureFlagUtils.setEnabled(context, DataUsageController.DATA_USAGE_V2, true); - when(context.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager); - when(context.getSystemService(NetworkStatsManager.class)).thenReturn(mNetworkStatsManager); + public void getHistoricalUsageLevel_noUsageData_shouldReturn0() throws Exception { when(mNetworkStatsManager.querySummaryForDevice(eq(ConnectivityManager.TYPE_WIFI), eq(SUB_ID), eq(0L) /* startTime */, anyLong() /* endTime */)) .thenReturn(mock(NetworkStats.Bucket.class)); - final DataUsageController controller = new DataUsageController(context); - - assertThat(controller.getHistoricalUsageLevel(NetworkTemplate.buildTemplateWifiWildcard())) + assertThat(mController.getHistoricalUsageLevel(NetworkTemplate.buildTemplateWifiWildcard())) .isEqualTo(0L); } @Test - public void getHistoricalUsageLevel_v2HasUsageData_shouldReturnTotalUsage() - throws Exception { - final Context context = mock(Context.class); - FeatureFlagUtils.setEnabled(context, DataUsageController.DATA_USAGE_V2, true); - when(context.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager); - when(context.getSystemService(NetworkStatsManager.class)).thenReturn(mNetworkStatsManager); + public void getHistoricalUsageLevel_hasUsageData_shouldReturnTotalUsage() throws Exception { final long receivedBytes = 743823454L; final long transmittedBytes = 16574289L; final NetworkStats.Bucket bucket = mock(NetworkStats.Bucket.class); @@ -155,9 +103,8 @@ public class DataUsageControllerTest { when(bucket.getTxBytes()).thenReturn(transmittedBytes); when(mNetworkStatsManager.querySummaryForDevice(eq(ConnectivityManager.TYPE_WIFI), eq(SUB_ID), eq(0L) /* startTime */, anyLong() /* endTime */)).thenReturn(bucket); - final DataUsageController controller = new DataUsageController(context); - assertThat(controller.getHistoricalUsageLevel(NetworkTemplate.buildTemplateWifiWildcard())) + assertThat(mController.getHistoricalUsageLevel(NetworkTemplate.buildTemplateWifiWildcard())) .isEqualTo(receivedBytes + transmittedBytes); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java new file mode 100644 index 000000000000..88fef08bfcb7 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java @@ -0,0 +1,281 @@ +/* + * 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.settingslib.widget; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.widget.Button; + +import androidx.preference.PreferenceViewHolder; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class ActionButtonsPreferenceTest { + + private Context mContext; + private View mRootView; + private ActionButtonsPreference mPref; + private PreferenceViewHolder mHolder; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mRootView = View.inflate(mContext, R.layout.settings_action_buttons, null /* parent */); + mHolder = PreferenceViewHolder.createInstanceForTests(mRootView); + mPref = new ActionButtonsPreference(mContext); + } + + @Test + public void onBindViewHolder_setTitle_shouldShowButtonByDefault() { + mPref.setButton1Text(R.string.install_other_apps); + mPref.setButton2Text(R.string.install_other_apps); + mPref.setButton3Text(R.string.install_other_apps); + mPref.setButton4Text(R.string.install_other_apps); + + mPref.onBindViewHolder(mHolder); + + assertThat(mRootView.findViewById(R.id.button1).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(mRootView.findViewById(R.id.button2).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(mRootView.findViewById(R.id.button3).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(mRootView.findViewById(R.id.button4).getVisibility()) + .isEqualTo(View.VISIBLE); + } + + @Test + public void onBindViewHolder_setIcon_shouldShowButtonByDefault() { + mPref.setButton1Icon(R.drawable.ic_plus); + mPref.setButton2Icon(R.drawable.ic_plus); + mPref.setButton3Icon(R.drawable.ic_plus); + mPref.setButton4Icon(R.drawable.ic_plus); + + mPref.onBindViewHolder(mHolder); + + assertThat(mRootView.findViewById(R.id.button1).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(mRootView.findViewById(R.id.button2).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(mRootView.findViewById(R.id.button3).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(mRootView.findViewById(R.id.button4).getVisibility()) + .isEqualTo(View.VISIBLE); + } + + @Test + public void onBindViewHolder_notSetTitleOrIcon_shouldNotShowButtonByDefault() { + mPref.onBindViewHolder(mHolder); + + assertThat(mRootView.findViewById(R.id.button1).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button2).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button3).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button4).getVisibility()) + .isEqualTo(View.GONE); + } + + @Test + public void onBindViewHolder_setVisibleIsGoneAndSetTitle_shouldNotShowButton() { + mPref.setButton1Text(R.string.install_other_apps).setButton1Visible(false); + mPref.setButton2Text(R.string.install_other_apps).setButton2Visible(false); + mPref.setButton3Text(R.string.install_other_apps).setButton3Visible(false); + mPref.setButton4Text(R.string.install_other_apps).setButton4Visible(false); + + mPref.onBindViewHolder(mHolder); + + assertThat(mRootView.findViewById(R.id.button1).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button2).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button3).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button4).getVisibility()) + .isEqualTo(View.GONE); + } + + @Test + public void onBindViewHolder_setVisibleIsGoneAndSetIcon_shouldNotShowButton() { + mPref.setButton1Icon(R.drawable.ic_plus).setButton1Visible(false); + mPref.setButton2Icon(R.drawable.ic_plus).setButton2Visible(false); + mPref.setButton3Icon(R.drawable.ic_plus).setButton3Visible(false); + mPref.setButton4Icon(R.drawable.ic_plus).setButton4Visible(false); + + mPref.onBindViewHolder(mHolder); + + assertThat(mRootView.findViewById(R.id.button1).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button2).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button3).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button4).getVisibility()) + .isEqualTo(View.GONE); + } + + @Test + public void onBindViewHolder_setVisibility_shouldUpdateButtonVisibility() { + mPref.setButton1Text(R.string.install_other_apps).setButton1Visible(false); + mPref.setButton2Text(R.string.install_other_apps).setButton2Visible(false); + mPref.setButton3Text(R.string.install_other_apps).setButton3Visible(false); + mPref.setButton4Text(R.string.install_other_apps).setButton4Visible(false); + + mPref.onBindViewHolder(mHolder); + + assertThat(mRootView.findViewById(R.id.button1).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button2).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button3).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button4).getVisibility()) + .isEqualTo(View.GONE); + + mPref.setButton1Visible(true); + mPref.setButton2Visible(true); + mPref.setButton3Visible(true); + mPref.setButton4Visible(true); + + mPref.onBindViewHolder(mHolder); + + assertThat(mRootView.findViewById(R.id.button1).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(mRootView.findViewById(R.id.button2).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(mRootView.findViewById(R.id.button3).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(mRootView.findViewById(R.id.button4).getVisibility()) + .isEqualTo(View.VISIBLE); + } + + @Test + public void onBindViewHolder_setEnabled_shouldEnableButton() { + mPref.setButton1Enabled(true); + mPref.setButton2Enabled(false); + mPref.setButton3Enabled(true); + mPref.setButton4Enabled(false); + + mPref.onBindViewHolder(mHolder); + + assertThat(mRootView.findViewById(R.id.button1).isEnabled()).isTrue(); + assertThat(mRootView.findViewById(R.id.button2).isEnabled()).isFalse(); + assertThat(mRootView.findViewById(R.id.button3).isEnabled()).isTrue(); + assertThat(mRootView.findViewById(R.id.button4).isEnabled()).isFalse(); + } + + @Test + public void onBindViewHolder_setText_shouldShowSameText() { + mPref.setButton1Text(R.string.install_other_apps); + mPref.setButton2Text(R.string.install_other_apps); + mPref.setButton3Text(R.string.install_other_apps); + mPref.setButton4Text(R.string.install_other_apps); + + mPref.onBindViewHolder(mHolder); + + assertThat(((Button) mRootView.findViewById(R.id.button1)).getText()) + .isEqualTo(mContext.getText(R.string.install_other_apps)); + assertThat(((Button) mRootView.findViewById(R.id.button2)).getText()) + .isEqualTo(mContext.getText(R.string.install_other_apps)); + assertThat(((Button) mRootView.findViewById(R.id.button3)).getText()) + .isEqualTo(mContext.getText(R.string.install_other_apps)); + assertThat(((Button) mRootView.findViewById(R.id.button4)).getText()) + .isEqualTo(mContext.getText(R.string.install_other_apps)); + } + + @Test + public void onBindViewHolder_setButtonIcon_iconMustDisplayAboveText() { + mPref.setButton1Text(R.string.install_other_apps); + mPref.setButton1Icon(R.drawable.ic_plus); + + mPref.onBindViewHolder(mHolder); + final Drawable[] drawablesAroundText = + ((Button) mRootView.findViewById(R.id.button1)) + .getCompoundDrawables(); + + assertThat(drawablesAroundText[1 /* top */]).isNotNull(); + } + + @Test + public void setButtonIcon_iconResourceIdIsZero_shouldNotDisplayIcon() { + mPref.setButton1Text(R.string.install_other_apps); + mPref.setButton1Icon(0); + + mPref.onBindViewHolder(mHolder); + final Drawable[] drawablesAroundText = + ((Button) mRootView.findViewById(R.id.button1)) + .getCompoundDrawables(); + + assertThat(drawablesAroundText[1 /* top */]).isNull(); + } + + @Test + public void setButtonIcon_iconResourceIdNotExisting_shouldNotDisplayIconAndCrash() { + mPref.setButton1Text(R.string.install_other_apps); + mPref.setButton1Icon(999999999 /* not existing id */); + // Should not crash here + mPref.onBindViewHolder(mHolder); + final Drawable[] drawablesAroundText = + ((Button) mRootView.findViewById(R.id.button1)) + .getCompoundDrawables(); + + assertThat(drawablesAroundText[1 /* top */]).isNull(); + } + + public static ActionButtonsPreference createMock() { + final ActionButtonsPreference pref = mock(ActionButtonsPreference.class); + when(pref.setButton1Text(anyInt())).thenReturn(pref); + when(pref.setButton1Icon(anyInt())).thenReturn(pref); + when(pref.setButton1Enabled(anyBoolean())).thenReturn(pref); + when(pref.setButton1Visible(anyBoolean())).thenReturn(pref); + when(pref.setButton1OnClickListener(any(View.OnClickListener.class))).thenReturn(pref); + + when(pref.setButton2Text(anyInt())).thenReturn(pref); + when(pref.setButton2Icon(anyInt())).thenReturn(pref); + when(pref.setButton2Enabled(anyBoolean())).thenReturn(pref); + when(pref.setButton2Visible(anyBoolean())).thenReturn(pref); + when(pref.setButton2OnClickListener(any(View.OnClickListener.class))).thenReturn(pref); + + when(pref.setButton3Text(anyInt())).thenReturn(pref); + when(pref.setButton3Icon(anyInt())).thenReturn(pref); + when(pref.setButton3Enabled(anyBoolean())).thenReturn(pref); + when(pref.setButton3Visible(anyBoolean())).thenReturn(pref); + when(pref.setButton3OnClickListener(any(View.OnClickListener.class))).thenReturn(pref); + + when(pref.setButton4Text(anyInt())).thenReturn(pref); + when(pref.setButton4Icon(anyInt())).thenReturn(pref); + when(pref.setButton4Enabled(anyBoolean())).thenReturn(pref); + when(pref.setButton4Visible(anyBoolean())).thenReturn(pref); + when(pref.setButton4OnClickListener(any(View.OnClickListener.class))).thenReturn(pref); + return pref; + } +} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index b2c12b277dc0..56b768feee23 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1914,6 +1914,15 @@ class SettingsProtoDumpUtil { SecureSettingsProto.Location.CHANGER); p.end(locationToken); + final long locationAccessCheckToken = p.start(SecureSettingsProto.LOCATION_ACCESS_CHECK); + dumpSetting(s, p, + Settings.Secure.LOCATION_ACCESS_CHECK_INTERVAL_MILLIS, + SecureSettingsProto.LocationAccessCheck.INTERVAL_MILLIS); + dumpSetting(s, p, + Settings.Secure.LOCATION_ACCESS_CHECK_DELAY_MILLIS, + SecureSettingsProto.LocationAccessCheck.DELAY_MILLIS); + p.end(locationAccessCheckToken); + final long lockScreenToken = p.start(SecureSettingsProto.LOCK_SCREEN); // Settings.Secure.LOCK_BIOMETRIC_WEAK_FLAGS intentionally excluded since it's deprecated. // Settings.Secure.LOCK_PATTERN_ENABLED intentionally excluded since it's deprecated. diff --git a/packages/SystemUI/res/anim/dismiss_all_shape_animation_1.xml b/packages/SystemUI/res/anim/dismiss_all_shape_animation_1.xml deleted file mode 100644 index 3cc98d8f7ed9..000000000000 --- a/packages/SystemUI/res/anim/dismiss_all_shape_animation_1.xml +++ /dev/null @@ -1,22 +0,0 @@ -<!-- 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. ---> -<set xmlns:android="http://schemas.android.com/apk/res/android" > - <objectAnimator - android:duration="500" - android:propertyXName="translateX" - android:propertyYName="translateY" - android:pathData="M 0,0 c 31.33333,0 156.66667,0 188,0 " - android:interpolator="@android:interpolator/fast_out_slow_in" /> -</set> diff --git a/packages/SystemUI/res/anim/dismiss_all_shape_animation_2.xml b/packages/SystemUI/res/anim/dismiss_all_shape_animation_2.xml deleted file mode 100644 index eda843d03ba2..000000000000 --- a/packages/SystemUI/res/anim/dismiss_all_shape_animation_2.xml +++ /dev/null @@ -1,30 +0,0 @@ -<!-- 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. ---> -<set xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="33" - android:propertyXName="translateX" - android:propertyYName="translateY" - android:pathData="M -12,18 L -12,18" /> - <objectAnimator - android:duration="500" - android:propertyXName="translateX" - android:propertyYName="translateY" - android:pathData="M -12,18 c 31.33333,0 156.66667,0 188,0 " - android:interpolator="@android:interpolator/fast_out_slow_in" /> - </set> -</set> diff --git a/packages/SystemUI/res/anim/dismiss_all_shape_animation_3.xml b/packages/SystemUI/res/anim/dismiss_all_shape_animation_3.xml deleted file mode 100644 index cab3d5cc2367..000000000000 --- a/packages/SystemUI/res/anim/dismiss_all_shape_animation_3.xml +++ /dev/null @@ -1,30 +0,0 @@ -<!-- 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. ---> -<set xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="67" - android:propertyXName="translateX" - android:propertyYName="translateY" - android:pathData="M -24,36 L -24,36" /> - <objectAnimator - android:duration="500" - android:propertyXName="translateX" - android:propertyYName="translateY" - android:pathData="M -24,36 c 31.33333,0 156.66667,0 188,0 " - android:interpolator="@android:interpolator/fast_out_slow_in" /> - </set> -</set> diff --git a/packages/SystemUI/res/anim/dismiss_all_shape_animation_rectangle_path_1.xml b/packages/SystemUI/res/anim/dismiss_all_shape_animation_rectangle_path_1.xml deleted file mode 100644 index e435d9a9a69e..000000000000 --- a/packages/SystemUI/res/anim/dismiss_all_shape_animation_rectangle_path_1.xml +++ /dev/null @@ -1,31 +0,0 @@ -<!-- 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. ---> -<set xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="150" - android:propertyName="fillAlpha" - android:valueFrom="1" - android:valueTo="1" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="100" - android:propertyName="fillAlpha" - android:valueFrom="1" - android:valueTo="0" - android:interpolator="@android:interpolator/linear" /> - </set> -</set> diff --git a/packages/SystemUI/res/anim/dismiss_all_shape_animation_rectangle_path_1_1.xml b/packages/SystemUI/res/anim/dismiss_all_shape_animation_rectangle_path_1_1.xml deleted file mode 100644 index e31a7dbcebc5..000000000000 --- a/packages/SystemUI/res/anim/dismiss_all_shape_animation_rectangle_path_1_1.xml +++ /dev/null @@ -1,31 +0,0 @@ -<!-- 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. ---> -<set xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="183" - android:propertyName="fillAlpha" - android:valueFrom="1" - android:valueTo="1" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="100" - android:propertyName="fillAlpha" - android:valueFrom="1" - android:valueTo="0" - android:interpolator="@android:interpolator/linear" /> - </set> -</set> diff --git a/packages/SystemUI/res/anim/dismiss_all_shape_animation_rectangle_path_1_2.xml b/packages/SystemUI/res/anim/dismiss_all_shape_animation_rectangle_path_1_2.xml deleted file mode 100644 index 2409612cb250..000000000000 --- a/packages/SystemUI/res/anim/dismiss_all_shape_animation_rectangle_path_1_2.xml +++ /dev/null @@ -1,31 +0,0 @@ -<!-- 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. ---> -<set xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="217" - android:propertyName="fillAlpha" - android:valueFrom="1" - android:valueTo="1" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="100" - android:propertyName="fillAlpha" - android:valueFrom="1" - android:valueTo="0" - android:interpolator="@android:interpolator/linear" /> - </set> -</set> diff --git a/packages/SystemUI/res/anim/fab_elevation.xml b/packages/SystemUI/res/anim/fab_elevation.xml deleted file mode 100644 index 2c76a865a470..000000000000 --- a/packages/SystemUI/res/anim/fab_elevation.xml +++ /dev/null @@ -1,35 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="true" android:state_pressed="true"> - <set> - <objectAnimator - android:duration="@android:integer/config_shortAnimTime" - android:propertyName="translationZ" - android:valueTo="@dimen/fab_press_translation_z" - android:valueType="floatType" /> - </set> - </item> - <item> - <set> - <objectAnimator - android:duration="@android:integer/config_shortAnimTime" - android:propertyName="translationZ" - android:valueTo="0" - android:valueType="floatType" /> - </set> - </item> -</selector> diff --git a/packages/SystemUI/res/anim/ic_landscape_to_rotate_arrows_animation.xml b/packages/SystemUI/res/anim/ic_landscape_to_rotate_arrows_animation.xml deleted file mode 100644 index 8fdad809f0d1..000000000000 --- a/packages/SystemUI/res/anim/ic_landscape_to_rotate_arrows_animation.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <objectAnimator - android:duration="616" - android:propertyName="rotation" - android:valueFrom="-90.0" - android:valueTo="0.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/fast_out_slow_in" /> -</set> diff --git a/packages/SystemUI/res/anim/ic_landscape_to_rotate_bottom_merged_animation.xml b/packages/SystemUI/res/anim/ic_landscape_to_rotate_bottom_merged_animation.xml deleted file mode 100644 index 3c3c131ef16b..000000000000 --- a/packages/SystemUI/res/anim/ic_landscape_to_rotate_bottom_merged_animation.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="50" - android:propertyName="fillAlpha" - android:valueFrom="0.0" - android:valueTo="0.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="83" - android:propertyName="fillAlpha" - android:valueFrom="0.0" - android:valueTo="1.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/linear" /> - </set> -</set> diff --git a/packages/SystemUI/res/anim/ic_landscape_to_rotate_landscape_animation.xml b/packages/SystemUI/res/anim/ic_landscape_to_rotate_landscape_animation.xml deleted file mode 100644 index 57132e19dcc5..000000000000 --- a/packages/SystemUI/res/anim/ic_landscape_to_rotate_landscape_animation.xml +++ /dev/null @@ -1,50 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <objectAnimator - android:duration="466" - android:propertyName="scaleX" - android:valueFrom="1.0" - android:valueTo="0.909" - android:valueType="floatType" - android:interpolator="@interpolator/ic_landscape_to_rotate_animation_interpolator_0" /> - <objectAnimator - android:duration="466" - android:propertyName="scaleY" - android:valueFrom="1.0" - android:valueTo="0.909" - android:valueType="floatType" - android:interpolator="@interpolator/ic_landscape_to_rotate_animation_interpolator_0" /> - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="50" - android:propertyName="rotation" - android:valueFrom="0.0" - android:valueTo="0.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="400" - android:propertyName="rotation" - android:valueFrom="0.0" - android:valueTo="45.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/fast_out_slow_in" /> - </set> -</set> diff --git a/packages/SystemUI/res/anim/ic_portrait_to_rotate_arrows_0_animation.xml b/packages/SystemUI/res/anim/ic_portrait_to_rotate_arrows_0_animation.xml deleted file mode 100644 index ad2a5fad5268..000000000000 --- a/packages/SystemUI/res/anim/ic_portrait_to_rotate_arrows_0_animation.xml +++ /dev/null @@ -1,53 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="116" - android:propertyName="scaleX" - android:valueFrom="1.0" - android:valueTo="1.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="333" - android:propertyName="scaleX" - android:valueFrom="1.0" - android:valueTo="0.9" - android:valueType="floatType" - android:interpolator="@android:interpolator/linear" /> - </set> - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="116" - android:propertyName="scaleY" - android:valueFrom="1.0" - android:valueTo="1.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="333" - android:propertyName="scaleY" - android:valueFrom="1.0" - android:valueTo="0.9" - android:valueType="floatType" - android:interpolator="@android:interpolator/linear" /> - </set> -</set> diff --git a/packages/SystemUI/res/anim/ic_portrait_to_rotate_arrows_animation.xml b/packages/SystemUI/res/anim/ic_portrait_to_rotate_arrows_animation.xml deleted file mode 100644 index cdb7890dc170..000000000000 --- a/packages/SystemUI/res/anim/ic_portrait_to_rotate_arrows_animation.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <objectAnimator - android:duration="616" - android:propertyName="rotation" - android:valueFrom="0.0" - android:valueTo="-221.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/fast_out_slow_in" /> -</set> diff --git a/packages/SystemUI/res/anim/ic_portrait_to_rotate_bottom_merged_animation.xml b/packages/SystemUI/res/anim/ic_portrait_to_rotate_bottom_merged_animation.xml deleted file mode 100644 index 46100b407831..000000000000 --- a/packages/SystemUI/res/anim/ic_portrait_to_rotate_bottom_merged_animation.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="400" - android:propertyName="fillAlpha" - android:valueFrom="1.0" - android:valueTo="1.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="83" - android:propertyName="fillAlpha" - android:valueFrom="1.0" - android:valueTo="0.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/linear" /> - </set> -</set> diff --git a/packages/SystemUI/res/anim/ic_portrait_to_rotate_device_0_animation.xml b/packages/SystemUI/res/anim/ic_portrait_to_rotate_device_0_animation.xml deleted file mode 100644 index 8f6d24d0d61d..000000000000 --- a/packages/SystemUI/res/anim/ic_portrait_to_rotate_device_0_animation.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <objectAnimator - android:duration="400" - android:propertyName="rotation" - android:valueFrom="0.0" - android:valueTo="-135.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/fast_out_slow_in" /> -</set> diff --git a/packages/SystemUI/res/anim/ic_portrait_to_rotate_device_merged_animation.xml b/packages/SystemUI/res/anim/ic_portrait_to_rotate_device_merged_animation.xml deleted file mode 100644 index 300ed53052a8..000000000000 --- a/packages/SystemUI/res/anim/ic_portrait_to_rotate_device_merged_animation.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="66" - android:propertyName="pathData" - android:valueFrom="M -3.5,-20.5 c -1.19999694824,-1.19999694824 -3.10000610352,-1.19999694824 -4.19999694824,0.0 c 0.0,0.0 -12.8000030518,12.6999969482 -12.8000030518,12.6999969482 c -1.19999694824,1.19999694824 -1.19999694824,3.10000610352 0.0,4.19999694824 c 0.0,0.0 24.0,24.0000152588 24.0,24.0000152588 c 1.19999694824,1.19999694824 3.10000610352,1.19999694824 4.19999694824,0.0 c 0.0,0.0 12.6999969482,-12.700012207 12.6999969482,-12.700012207 c 1.20001220703,-1.19999694824 1.20001220703,-3.09999084473 0.0,-4.19999694824 c 0.0,0.0 -23.8999938965,-24.0 -23.8999938965,-24.0 Z M 2.84999084473,15.5500183105 c 0.0,0.0 -18.6000061035,-18.5000457764 -18.6000061035,-18.5000457764 c 0.0,0.0 12.5999908447,-12.8000030518 12.5999908447,-12.8000030518 c 0.0,0.0 18.6000213623,18.5000457764 18.6000213623,18.5000457764 c 0.0,0.0 -12.6000061035,12.8000030518 -12.6000061035,12.8000030518 Z" - android:valueTo="M -3.5,-20.5 c -1.19999694824,-1.19999694824 -3.10000610352,-1.19999694824 -4.19999694824,0.0 c 0.0,0.0 -12.8000030518,12.6999969482 -12.8000030518,12.6999969482 c -1.19999694824,1.19999694824 -1.19999694824,3.10000610352 0.0,4.19999694824 c 0.0,0.0 24.0,24.0000152588 24.0,24.0000152588 c 1.19999694824,1.19999694824 3.10000610352,1.19999694824 4.19999694824,0.0 c 0.0,0.0 12.6999969482,-12.700012207 12.6999969482,-12.700012207 c 1.20001220703,-1.19999694824 1.20001220703,-3.09999084473 0.0,-4.19999694824 c 0.0,0.0 -23.8999938965,-24.0 -23.8999938965,-24.0 Z M 2.84999084473,15.5500183105 c 0.0,0.0 -18.6000061035,-18.5000457764 -18.6000061035,-18.5000457764 c 0.0,0.0 12.5999908447,-12.8000030518 12.5999908447,-12.8000030518 c 0.0,0.0 18.6000213623,18.5000457764 18.6000213623,18.5000457764 c 0.0,0.0 -12.6000061035,12.8000030518 -12.6000061035,12.8000030518 Z" - android:valueType="pathType" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="216" - android:propertyName="pathData" - android:valueFrom="M -3.5,-20.5 c -1.19999694824,-1.19999694824 -3.10000610352,-1.19999694824 -4.19999694824,0.0 c 0.0,0.0 -12.8000030518,12.6999969482 -12.8000030518,12.6999969482 c -1.19999694824,1.19999694824 -1.19999694824,3.10000610352 0.0,4.19999694824 c 0.0,0.0 24.0,24.0000152588 24.0,24.0000152588 c 1.19999694824,1.19999694824 3.10000610352,1.19999694824 4.19999694824,0.0 c 0.0,0.0 12.6999969482,-12.700012207 12.6999969482,-12.700012207 c 1.20001220703,-1.19999694824 1.20001220703,-3.09999084473 0.0,-4.19999694824 c 0.0,0.0 -23.8999938965,-24.0 -23.8999938965,-24.0 Z M 2.84999084473,15.5500183105 c 0.0,0.0 -18.6000061035,-18.5000457764 -18.6000061035,-18.5000457764 c 0.0,0.0 12.5999908447,-12.8000030518 12.5999908447,-12.8000030518 c 0.0,0.0 18.6000213623,18.5000457764 18.6000213623,18.5000457764 c 0.0,0.0 -12.6000061035,12.8000030518 -12.6000061035,12.8000030518 Z" - android:valueTo="M -3.34053039551,-22.9980926514 c -1.3207244873,-1.3207244873 -3.46876525879,-1.26383972168 -4.74829101563,0.125762939453 c 0.0,0.0 -14.8512420654,14.7411804199 -14.8512420654,14.7411804199 c -1.39259338379,1.392578125 -1.44947814941,3.54061889648 -0.125762939453,4.74827575684 c 0.0,0.0 26.4143981934,26.4144134521 26.4143981934,26.4144134521 c 1.3207244873,1.3207244873 3.46876525879,1.26382446289 4.74829101562,-0.125762939453 c 0.0,0.0 14.7381896973,-14.7381896973 14.7381896973,-14.7381896973 c 1.392578125,-1.39259338379 1.44947814941,-3.54061889648 0.125762939453,-4.74829101562 c 0.0,0.0 -26.3013458252,-26.417388916 -26.3013458252,-26.417388916 Z M 2.87156677246,16.9857940674 c 0.0,0.0 -19.7573547363,-19.7573699951 -19.7573547363,-19.7573699951 c 0.0,0.0 14.0142059326,-14.2142181396 14.0142059326,-14.2142181396 c 0.0,0.0 19.7573699951,19.7573699951 19.7573699951,19.7573699951 c 0.0,0.0 -14.0142211914,14.2142181396 -14.0142211914,14.2142181396 Z" - android:valueType="pathType" - android:interpolator="@android:interpolator/fast_out_slow_in" /> - </set> -</set> diff --git a/packages/SystemUI/res/anim/ic_rotate_to_landscape_arrows_0_animation.xml b/packages/SystemUI/res/anim/ic_rotate_to_landscape_arrows_0_animation.xml deleted file mode 100644 index ad2a5fad5268..000000000000 --- a/packages/SystemUI/res/anim/ic_rotate_to_landscape_arrows_0_animation.xml +++ /dev/null @@ -1,53 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="116" - android:propertyName="scaleX" - android:valueFrom="1.0" - android:valueTo="1.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="333" - android:propertyName="scaleX" - android:valueFrom="1.0" - android:valueTo="0.9" - android:valueType="floatType" - android:interpolator="@android:interpolator/linear" /> - </set> - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="116" - android:propertyName="scaleY" - android:valueFrom="1.0" - android:valueTo="1.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="333" - android:propertyName="scaleY" - android:valueFrom="1.0" - android:valueTo="0.9" - android:valueType="floatType" - android:interpolator="@android:interpolator/linear" /> - </set> -</set> diff --git a/packages/SystemUI/res/anim/ic_rotate_to_landscape_arrows_animation.xml b/packages/SystemUI/res/anim/ic_rotate_to_landscape_arrows_animation.xml deleted file mode 100644 index c1521520c427..000000000000 --- a/packages/SystemUI/res/anim/ic_rotate_to_landscape_arrows_animation.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <objectAnimator - android:duration="616" - android:propertyName="rotation" - android:valueFrom="0.0" - android:valueTo="-180.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/fast_out_slow_in" /> -</set> diff --git a/packages/SystemUI/res/anim/ic_rotate_to_landscape_bottom_merged_animation.xml b/packages/SystemUI/res/anim/ic_rotate_to_landscape_bottom_merged_animation.xml deleted file mode 100644 index b2c1eb8b93a3..000000000000 --- a/packages/SystemUI/res/anim/ic_rotate_to_landscape_bottom_merged_animation.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="200" - android:propertyName="fillAlpha" - android:valueFrom="1.0" - android:valueTo="1.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="83" - android:propertyName="fillAlpha" - android:valueFrom="1.0" - android:valueTo="0.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/linear" /> - </set> -</set> diff --git a/packages/SystemUI/res/anim/ic_rotate_to_landscape_landscape_animation.xml b/packages/SystemUI/res/anim/ic_rotate_to_landscape_landscape_animation.xml deleted file mode 100644 index 2a9bbe32975e..000000000000 --- a/packages/SystemUI/res/anim/ic_rotate_to_landscape_landscape_animation.xml +++ /dev/null @@ -1,60 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="116" - android:propertyName="scaleX" - android:valueFrom="0.909" - android:valueTo="0.909" - android:valueType="floatType" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="166" - android:propertyName="scaleX" - android:valueFrom="0.909" - android:valueTo="1.0" - android:valueType="floatType" - android:interpolator="@interpolator/ic_rotate_to_landscape_animation_interpolator_0" /> - </set> - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="116" - android:propertyName="scaleY" - android:valueFrom="0.909" - android:valueTo="0.909" - android:valueType="floatType" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="166" - android:propertyName="scaleY" - android:valueFrom="0.909" - android:valueTo="1.0" - android:valueType="floatType" - android:interpolator="@interpolator/ic_rotate_to_landscape_animation_interpolator_0" /> - </set> - <objectAnimator - android:duration="616" - android:propertyName="rotation" - android:valueFrom="45.0" - android:valueTo="0.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/fast_out_slow_in" /> -</set> diff --git a/packages/SystemUI/res/anim/ic_rotate_to_portrait_arrows_0_animation.xml b/packages/SystemUI/res/anim/ic_rotate_to_portrait_arrows_0_animation.xml deleted file mode 100644 index ce267704dac5..000000000000 --- a/packages/SystemUI/res/anim/ic_rotate_to_portrait_arrows_0_animation.xml +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <objectAnimator - android:duration="466" - android:propertyName="scaleX" - android:valueFrom="0.9" - android:valueTo="1.0" - android:valueType="floatType" - android:interpolator="@interpolator/ic_rotate_to_portrait_animation_interpolator_0" /> - <objectAnimator - android:duration="466" - android:propertyName="scaleY" - android:valueFrom="0.9" - android:valueTo="1.0" - android:valueType="floatType" - android:interpolator="@interpolator/ic_rotate_to_portrait_animation_interpolator_0" /> -</set> diff --git a/packages/SystemUI/res/anim/ic_rotate_to_portrait_arrows_animation.xml b/packages/SystemUI/res/anim/ic_rotate_to_portrait_arrows_animation.xml deleted file mode 100644 index 6e8941d608cd..000000000000 --- a/packages/SystemUI/res/anim/ic_rotate_to_portrait_arrows_animation.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <objectAnimator - android:duration="616" - android:propertyName="rotation" - android:valueFrom="-221.0" - android:valueTo="0.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/fast_out_slow_in" /> -</set> diff --git a/packages/SystemUI/res/anim/ic_rotate_to_portrait_bottom_merged_animation.xml b/packages/SystemUI/res/anim/ic_rotate_to_portrait_bottom_merged_animation.xml deleted file mode 100644 index 3c3c131ef16b..000000000000 --- a/packages/SystemUI/res/anim/ic_rotate_to_portrait_bottom_merged_animation.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="50" - android:propertyName="fillAlpha" - android:valueFrom="0.0" - android:valueTo="0.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="83" - android:propertyName="fillAlpha" - android:valueFrom="0.0" - android:valueTo="1.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/linear" /> - </set> -</set> diff --git a/packages/SystemUI/res/anim/ic_rotate_to_portrait_device_0_animation.xml b/packages/SystemUI/res/anim/ic_rotate_to_portrait_device_0_animation.xml deleted file mode 100644 index fd8e4f881160..000000000000 --- a/packages/SystemUI/res/anim/ic_rotate_to_portrait_device_0_animation.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="50" - android:propertyName="rotation" - android:valueFrom="-135.0" - android:valueTo="-135.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="400" - android:propertyName="rotation" - android:valueFrom="-135.0" - android:valueTo="0.0" - android:valueType="floatType" - android:interpolator="@android:interpolator/fast_out_slow_in" /> - </set> -</set> diff --git a/packages/SystemUI/res/anim/ic_rotate_to_portrait_device_merged_animation.xml b/packages/SystemUI/res/anim/ic_rotate_to_portrait_device_merged_animation.xml deleted file mode 100644 index a77a536e46c0..000000000000 --- a/packages/SystemUI/res/anim/ic_rotate_to_portrait_device_merged_animation.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="50" - android:propertyName="pathData" - android:valueFrom="M -3.34053039551,-22.9980926514 c -1.3207244873,-1.3207244873 -3.46876525879,-1.26383972168 -4.74829101563,0.125762939453 c 0.0,0.0 -14.8512420654,14.7411804199 -14.8512420654,14.7411804199 c -1.39259338379,1.392578125 -1.44947814941,3.54061889648 -0.125762939453,4.74827575684 c 0.0,0.0 26.4143981934,26.4144134521 26.4143981934,26.4144134521 c 1.3207244873,1.3207244873 3.46876525879,1.26382446289 4.74829101562,-0.125762939453 c 0.0,0.0 14.7381896973,-14.7381896973 14.7381896973,-14.7381896973 c 1.392578125,-1.39259338379 1.44947814941,-3.54061889648 0.125762939453,-4.74829101562 c 0.0,0.0 -26.3013458252,-26.417388916 -26.3013458252,-26.417388916 Z M 2.87156677246,16.9857940674 c 0.0,0.0 -19.7573547363,-19.7573699951 -19.7573547363,-19.7573699951 c 0.0,0.0 14.0142059326,-14.2142181396 14.0142059326,-14.2142181396 c 0.0,0.0 19.7573699951,19.7573699951 19.7573699951,19.7573699951 c 0.0,0.0 -14.0142211914,14.2142181396 -14.0142211914,14.2142181396 Z" - android:valueTo="M -3.34053039551,-22.9980926514 c -1.3207244873,-1.3207244873 -3.46876525879,-1.26383972168 -4.74829101563,0.125762939453 c 0.0,0.0 -14.8512420654,14.7411804199 -14.8512420654,14.7411804199 c -1.39259338379,1.392578125 -1.44947814941,3.54061889648 -0.125762939453,4.74827575684 c 0.0,0.0 26.4143981934,26.4144134521 26.4143981934,26.4144134521 c 1.3207244873,1.3207244873 3.46876525879,1.26382446289 4.74829101562,-0.125762939453 c 0.0,0.0 14.7381896973,-14.7381896973 14.7381896973,-14.7381896973 c 1.392578125,-1.39259338379 1.44947814941,-3.54061889648 0.125762939453,-4.74829101562 c 0.0,0.0 -26.3013458252,-26.417388916 -26.3013458252,-26.417388916 Z M 2.87156677246,16.9857940674 c 0.0,0.0 -19.7573547363,-19.7573699951 -19.7573547363,-19.7573699951 c 0.0,0.0 14.0142059326,-14.2142181396 14.0142059326,-14.2142181396 c 0.0,0.0 19.7573699951,19.7573699951 19.7573699951,19.7573699951 c 0.0,0.0 -14.0142211914,14.2142181396 -14.0142211914,14.2142181396 Z" - android:valueType="pathType" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="500" - android:propertyName="pathData" - android:valueFrom="M -3.34053039551,-22.9980926514 c -1.3207244873,-1.3207244873 -3.46876525879,-1.26383972168 -4.74829101563,0.125762939453 c 0.0,0.0 -14.8512420654,14.7411804199 -14.8512420654,14.7411804199 c -1.39259338379,1.392578125 -1.44947814941,3.54061889648 -0.125762939453,4.74827575684 c 0.0,0.0 26.4143981934,26.4144134521 26.4143981934,26.4144134521 c 1.3207244873,1.3207244873 3.46876525879,1.26382446289 4.74829101562,-0.125762939453 c 0.0,0.0 14.7381896973,-14.7381896973 14.7381896973,-14.7381896973 c 1.392578125,-1.39259338379 1.44947814941,-3.54061889648 0.125762939453,-4.74829101562 c 0.0,0.0 -26.3013458252,-26.417388916 -26.3013458252,-26.417388916 Z M 2.87156677246,16.9857940674 c 0.0,0.0 -19.7573547363,-19.7573699951 -19.7573547363,-19.7573699951 c 0.0,0.0 14.0142059326,-14.2142181396 14.0142059326,-14.2142181396 c 0.0,0.0 19.7573699951,19.7573699951 19.7573699951,19.7573699951 c 0.0,0.0 -14.0142211914,14.2142181396 -14.0142211914,14.2142181396 Z" - android:valueTo="M -3.5,-20.5 c -1.19999694824,-1.19999694824 -3.10000610352,-1.19999694824 -4.19999694824,0.0 c 0.0,0.0 -12.8000030518,12.6999969482 -12.8000030518,12.6999969482 c -1.19999694824,1.19999694824 -1.19999694824,3.10000610352 0.0,4.19999694824 c 0.0,0.0 24.0,24.0000152588 24.0,24.0000152588 c 1.19999694824,1.19999694824 3.10000610352,1.19999694824 4.19999694824,0.0 c 0.0,0.0 12.6999969482,-12.700012207 12.6999969482,-12.700012207 c 1.20001220703,-1.19999694824 1.20001220703,-3.09999084473 0.0,-4.19999694824 c 0.0,0.0 -23.8999938965,-24.0 -23.8999938965,-24.0 Z M 2.84999084473,15.5500183105 c 0.0,0.0 -18.6000061035,-18.5000457764 -18.6000061035,-18.5000457764 c 0.0,0.0 12.5999908447,-12.8000030518 12.5999908447,-12.8000030518 c 0.0,0.0 18.6000213623,18.5000457764 18.6000213623,18.5000457764 c 0.0,0.0 -12.6000061035,12.8000030518 -12.6000061035,12.8000030518 Z" - android:valueType="pathType" - android:interpolator="@android:interpolator/fast_out_slow_in" /> - </set> -</set> diff --git a/packages/SystemUI/res/anim/ic_signal_blink_1.xml b/packages/SystemUI/res/anim/ic_signal_blink_1.xml deleted file mode 100644 index 64580d189c0d..000000000000 --- a/packages/SystemUI/res/anim/ic_signal_blink_1.xml +++ /dev/null @@ -1,38 +0,0 @@ -<!-- - Copyright (C) 2015 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@android:anim/linear_interpolator" - android:duration="@integer/carrier_network_change_anim_time" - android:repeatCount="-1"> - - <propertyValuesHolder - android:propertyName="fillColor" - android:valueType="colorType"> - <keyframe - android:fraction="0.0" - android:value="?attr/fillColor"/> - <keyframe - android:fraction="0.32" - android:value="?attr/fillColor"/> - <keyframe - android:fraction="0.33" - android:value="?attr/backgroundColor"/> - <keyframe - android:fraction="1.0" - android:value="?attr/backgroundColor"/> - </propertyValuesHolder> - -</objectAnimator> diff --git a/packages/SystemUI/res/anim/ic_signal_blink_2.xml b/packages/SystemUI/res/anim/ic_signal_blink_2.xml deleted file mode 100644 index f055cd078558..000000000000 --- a/packages/SystemUI/res/anim/ic_signal_blink_2.xml +++ /dev/null @@ -1,44 +0,0 @@ -<!-- - Copyright (C) 2015 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@android:anim/linear_interpolator" - android:duration="@integer/carrier_network_change_anim_time" - android:repeatCount="-1"> - - <propertyValuesHolder - android:propertyName="fillColor" - android:valueType="colorType"> - <keyframe - android:fraction="0.0" - android:value="?attr/backgroundColor"/> - <keyframe - android:fraction="0.32" - android:value="?attr/backgroundColor"/> - <keyframe - android:fraction="0.33" - android:value="?attr/fillColor"/> - <keyframe - android:fraction="0.66" - android:value="?attr/fillColor"/> - <keyframe - android:fraction="0.67" - android:value="?attr/backgroundColor"/> - <keyframe - android:fraction="1.0" - android:value="?attr/backgroundColor"/> - </propertyValuesHolder> - -</objectAnimator> diff --git a/packages/SystemUI/res/anim/ic_signal_blink_3.xml b/packages/SystemUI/res/anim/ic_signal_blink_3.xml deleted file mode 100644 index abcd77410220..000000000000 --- a/packages/SystemUI/res/anim/ic_signal_blink_3.xml +++ /dev/null @@ -1,38 +0,0 @@ -<!-- - Copyright (C) 2015 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@android:anim/linear_interpolator" - android:duration="@integer/carrier_network_change_anim_time" - android:repeatCount="-1"> - - <propertyValuesHolder - android:propertyName="fillColor" - android:valueType="colorType"> - <keyframe - android:fraction="0.0" - android:value="?attr/backgroundColor"/> - <keyframe - android:fraction="0.66" - android:value="?attr/backgroundColor"/> - <keyframe - android:fraction="0.67" - android:value="?attr/fillColor"/> - <keyframe - android:fraction="1.0" - android:value="?attr/fillColor"/> - </propertyValuesHolder> - -</objectAnimator> diff --git a/packages/SystemUI/res/anim/system_in.xml b/packages/SystemUI/res/anim/system_in.xml deleted file mode 100644 index 630fd72189cd..000000000000 --- a/packages/SystemUI/res/anim/system_in.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2010 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<set xmlns:android="http://schemas.android.com/apk/res/android" - > - <alpha android:fromAlpha="0.0" android:toAlpha="1.0" - android:duration="@android:integer/config_longAnimTime" - /> -</set> diff --git a/packages/SystemUI/res/anim/system_out.xml b/packages/SystemUI/res/anim/system_out.xml deleted file mode 100644 index 4717e47784b9..000000000000 --- a/packages/SystemUI/res/anim/system_out.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2010 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<set xmlns:android="http://schemas.android.com/apk/res/android" - > - <alpha android:toAlpha="0.0" android:fromAlpha="1.0" - android:duration="@android:integer/config_longAnimTime" - /> -</set> diff --git a/packages/SystemUI/res/drawable/dismiss_all_shape_animation.xml b/packages/SystemUI/res/drawable/dismiss_all_shape_animation.xml deleted file mode 100644 index 9e71cbeebea4..000000000000 --- a/packages/SystemUI/res/drawable/dismiss_all_shape_animation.xml +++ /dev/null @@ -1,21 +0,0 @@ -<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" - android:drawable="@drawable/dismiss_all_shape" > - <target - android:name="3" - android:animation="@anim/dismiss_all_shape_animation_3" /> - <target - android:name="rectangle_path_1_2" - android:animation="@anim/dismiss_all_shape_animation_rectangle_path_1_2" /> - <target - android:name="2" - android:animation="@anim/dismiss_all_shape_animation_2" /> - <target - android:name="rectangle_path_1_1" - android:animation="@anim/dismiss_all_shape_animation_rectangle_path_1_1" /> - <target - android:name="1" - android:animation="@anim/dismiss_all_shape_animation_1" /> - <target - android:name="rectangle_path_1" - android:animation="@anim/dismiss_all_shape_animation_rectangle_path_1" /> -</animated-vector> diff --git a/packages/SystemUI/res/layout/biometric_dialog.xml b/packages/SystemUI/res/layout/biometric_dialog.xml index 5ca34b033f0d..1e8cd5a7e13b 100644 --- a/packages/SystemUI/res/layout/biometric_dialog.xml +++ b/packages/SystemUI/res/layout/biometric_dialog.xml @@ -160,6 +160,15 @@ android:maxLines="2" android:text="@string/biometric_dialog_confirm" android:visibility="gone"/> + <!-- Try Again Button --> + <Button android:id="@+id/button_try_again" + android:layout_width="wrap_content" + android:layout_height="match_parent" + style="@*android:style/Widget.DeviceDefault.Button.Colored" + android:gravity="center" + android:maxLines="2" + android:text="@string/biometric_dialog_try_again" + android:visibility="gone"/> <Space android:id="@+id/rightSpacer" android:layout_width="12dip" android:layout_height="match_parent" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 2148e274d506..9917257bba8a 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -272,6 +272,8 @@ <string name="accessibility_biometric_dialog_help_area">Help message area</string> <!-- Message shown when a biometric is authenticated, asking the user to confirm authentication [CHAR LIMIT=30] --> <string name="biometric_dialog_confirm">Confirm</string> + <!-- Button name on BiometricPrompt shown when a biometric is detected but not authenticated. Tapping the button resumes authentication [CHAR_LIMIT=30] --> + <string name="biometric_dialog_try_again">Try again</string> <!-- Message shown when the system-provided fingerprint dialog is shown, asking for authentication --> <string name="fingerprint_dialog_touch_sensor">Touch the fingerprint sensor</string> @@ -700,6 +702,8 @@ <string name="quick_settings_bluetooth_secondary_label_headset">Headset</string> <!-- QuickSettings: Bluetooth secondary label for an input/IO device being connected [CHAR LIMIT=20]--> <string name="quick_settings_bluetooth_secondary_label_input">Input</string> + <!-- QuickSettings: Bluetooth secondary label for a Hearing Aids device being connected [CHAR LIMIT=20]--> + <string name="quick_settings_bluetooth_secondary_label_hearing_aids">Hearing Aids</string> <!-- QuickSettings: Bluetooth secondary label shown when bluetooth is being enabled [CHAR LIMIT=NONE] --> <string name="quick_settings_bluetooth_secondary_label_transient">Turning on…</string> <!-- QuickSettings: Brightness [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index ece2bb9a9507..f3bdbae97e42 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -71,4 +71,9 @@ interface ISystemUiProxy { */ void onStatusBarMotionEvent(in MotionEvent event) = 9; + /** + * Get the corner radius of windows in pixels. + */ + float getWindowCornerRadius() = 10; + } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java index 65c52200a7d8..a9cf857c3e50 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java @@ -83,6 +83,7 @@ public class SyncRtSurfaceTransactionApplier { t.setWindowCrop(params.surface, params.windowCrop); t.setAlpha(params.surface, params.alpha); t.setLayer(params.surface, params.layer); + t.setCornerRadius(params.surface, params.cornerRadius); t.show(params.surface); } @@ -98,12 +99,13 @@ public class SyncRtSurfaceTransactionApplier { * @param windowCrop Crop to apply. */ public SurfaceParams(SurfaceControlCompat surface, float alpha, Matrix matrix, - Rect windowCrop, int layer) { + Rect windowCrop, int layer, float cornerRadius) { this.surface = surface.mSurfaceControl; this.alpha = alpha; this.matrix = new Matrix(matrix); this.windowCrop = new Rect(windowCrop); this.layer = layer; + this.cornerRadius = cornerRadius; } final SurfaceControl surface; @@ -111,5 +113,6 @@ public class SyncRtSurfaceTransactionApplier { final Matrix matrix; final Rect windowCrop; final int layer; + final float cornerRadius; } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java index ff9e84ca29c1..8a251ae51f38 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java @@ -18,6 +18,7 @@ package com.android.systemui.shared.system; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID; import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT; @@ -61,7 +62,7 @@ public class WindowManagerWrapper { public static final int TRANSIT_KEYGUARD_OCCLUDE = WindowManager.TRANSIT_KEYGUARD_OCCLUDE; public static final int TRANSIT_KEYGUARD_UNOCCLUDE = WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; - public static final int NAV_BAR_POS_INVALID = -1; + public static final int NAV_BAR_POS_INVALID = NAV_BAR_INVALID; public static final int NAV_BAR_POS_LEFT = NAV_BAR_LEFT; public static final int NAV_BAR_POS_RIGHT = NAV_BAR_RIGHT; public static final int NAV_BAR_POS_BOTTOM = NAV_BAR_BOTTOM; @@ -178,10 +179,9 @@ public class WindowManagerWrapper { * @see #NAV_BAR_POS_BOTTOM * @see #NAV_BAR_POS_INVALID */ - public int getNavBarPosition() { + public int getNavBarPosition(int displayId) { try { - // TODO: Use WindowManagerService.getNavBarPosition(int displayId) - return WindowManagerGlobal.getWindowManagerService().getNavBarPosition(); + return WindowManagerGlobal.getWindowManagerService().getNavBarPosition(displayId); } catch (RemoteException e) { Log.w(TAG, "Failed to get nav bar position"); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java index c0047c015813..a90a7d231dc1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java @@ -21,7 +21,7 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricPrompt; -import android.hardware.biometrics.IBiometricPromptReceiver; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -52,15 +52,20 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba private static final int MSG_BUTTON_NEGATIVE = 6; private static final int MSG_USER_CANCELED = 7; private static final int MSG_BUTTON_POSITIVE = 8; + private static final int MSG_BIOMETRIC_SHOW_TRY_AGAIN = 9; + private static final int MSG_TRY_AGAIN_PRESSED = 10; private Map<Integer, BiometricDialogView> mDialogs; // BiometricAuthenticator type, view private SomeArgs mCurrentDialogArgs; private BiometricDialogView mCurrentDialog; private WindowManager mWindowManager; - private IBiometricPromptReceiver mReceiver; + private IBiometricServiceReceiverInternal mReceiver; private boolean mDialogShowing; private Callback mCallback = new Callback(); + private boolean mTryAgainShowing; // No good place to save state before config change :/ + private boolean mConfirmShowing; // No good place to save state before config change :/ + private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -89,6 +94,15 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba case MSG_BUTTON_POSITIVE: handleButtonPositive(); break; + case MSG_BIOMETRIC_SHOW_TRY_AGAIN: + handleShowTryAgain(); + break; + case MSG_TRY_AGAIN_PRESSED: + handleTryAgainPressed(); + break; + default: + Log.w(TAG, "Unknown message: " + msg.what); + break; } } }; @@ -96,7 +110,7 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba private class Callback implements DialogViewCallback { @Override public void onUserCanceled() { - mHandler.obtainMessage(BiometricDialogImpl.MSG_USER_CANCELED).sendToTarget(); + mHandler.obtainMessage(MSG_USER_CANCELED).sendToTarget(); } @Override @@ -107,12 +121,17 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba @Override public void onNegativePressed() { - mHandler.obtainMessage(BiometricDialogImpl.MSG_BUTTON_NEGATIVE).sendToTarget(); + mHandler.obtainMessage(MSG_BUTTON_NEGATIVE).sendToTarget(); } @Override public void onPositivePressed() { - mHandler.obtainMessage(BiometricDialogImpl.MSG_BUTTON_POSITIVE).sendToTarget(); + mHandler.obtainMessage(MSG_BUTTON_POSITIVE).sendToTarget(); + } + + @Override + public void onTryAgainPressed() { + mHandler.obtainMessage(MSG_TRY_AGAIN_PRESSED).sendToTarget(); } } @@ -139,13 +158,14 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } @Override - public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type, - boolean requireConfirmation, int userId) { + public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, + int type, boolean requireConfirmation, int userId) { if (DEBUG) Log.d(TAG, "showBiometricDialog, type: " + type); // Remove these messages as they are part of the previous client mHandler.removeMessages(MSG_BIOMETRIC_ERROR); mHandler.removeMessages(MSG_BIOMETRIC_HELP); mHandler.removeMessages(MSG_BIOMETRIC_AUTHENTICATED); + mHandler.removeMessages(MSG_HIDE_DIALOG); SomeArgs args = SomeArgs.obtain(); args.arg1 = bundle; args.arg2 = receiver; @@ -179,6 +199,12 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget(); } + @Override + public void showBiometricTryAgain() { + if (DEBUG) Log.d(TAG, "showBiometricTryAgain"); + mHandler.obtainMessage(MSG_BIOMETRIC_SHOW_TRY_AGAIN).sendToTarget(); + } + private void handleShowDialog(SomeArgs args, boolean skipAnimation) { mCurrentDialogArgs = args; final int type = args.argi1; @@ -193,11 +219,13 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba Log.w(TAG, "Dialog already showing"); return; } - mReceiver = (IBiometricPromptReceiver) args.arg2; + mReceiver = (IBiometricServiceReceiverInternal) args.arg2; mCurrentDialog.setBundle((Bundle)args.arg1); mCurrentDialog.setRequireConfirmation((boolean) args.arg3); mCurrentDialog.setUserId(args.argi2); mCurrentDialog.setSkipIntro(skipAnimation); + mCurrentDialog.setPendingTryAgain(mTryAgainShowing); + mCurrentDialog.setPendingConfirm(mConfirmShowing); mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams()); mDialogShowing = true; } @@ -209,7 +237,8 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba mContext.getResources() .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId())); if (mCurrentDialog.requiresConfirmation()) { - mCurrentDialog.showConfirmationButton(); + mConfirmShowing = true; + mCurrentDialog.showConfirmationButton(true /* show */); } else { handleHideDialog(false /* userCanceled */); } @@ -226,6 +255,7 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba if (DEBUG) Log.d(TAG, "Dialog already dismissed"); return; } + mTryAgainShowing = false; mCurrentDialog.showErrorMessage(error); } @@ -246,6 +276,8 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } mReceiver = null; mDialogShowing = false; + mConfirmShowing = false; + mTryAgainShowing = false; mCurrentDialog.startDismiss(); } @@ -259,6 +291,7 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } catch (RemoteException e) { Log.e(TAG, "Remote exception when handling negative button", e); } + mTryAgainShowing = false; handleHideDialog(false /* userCanceled */); } @@ -272,13 +305,31 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } catch (RemoteException e) { Log.e(TAG, "Remote exception when handling positive button", e); } + mConfirmShowing = false; handleHideDialog(false /* userCanceled */); } private void handleUserCanceled() { + mTryAgainShowing = false; + mConfirmShowing = false; handleHideDialog(true /* userCanceled */); } + private void handleShowTryAgain() { + mCurrentDialog.showTryAgainButton(true /* show */); + mTryAgainShowing = true; + } + + private void handleTryAgainPressed() { + try { + mCurrentDialog.clearTemporaryMessage(); + mTryAgainShowing = false; + mReceiver.onTryAgainPressed(); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException when handling try again", e); + } + } + @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java index 38427adfd42a..e085f2368214 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java @@ -87,6 +87,9 @@ public abstract class BiometricDialogView extends LinearLayout { protected boolean mRequireConfirmation; private int mUserId; // used to determine if we should show work background + private boolean mPendingShowTryAgain; + private boolean mPendingShowConfirm; + protected abstract void updateIcon(int lastState, int newState); protected abstract int getHintStringResourceId(); protected abstract int getAuthenticatedAccessibilityResourceId(); @@ -178,6 +181,7 @@ public abstract class BiometricDialogView extends LinearLayout { final Button negative = mLayout.findViewById(R.id.button2); final Button positive = mLayout.findViewById(R.id.button1); final ImageView icon = mLayout.findViewById(R.id.biometric_icon); + final Button tryAgain = mLayout.findViewById(R.id.button_try_again); icon.setContentDescription(getResources().getString(getIconDescriptionResourceId())); @@ -193,6 +197,11 @@ public abstract class BiometricDialogView extends LinearLayout { mCallback.onPositivePressed(); }); + tryAgain.setOnClickListener((View v) -> { + showTryAgainButton(false /* show */); + mCallback.onTryAgainPressed(); + }); + mLayout.setFocusableInTouchMode(true); mLayout.requestFocus(); } @@ -207,7 +216,6 @@ public abstract class BiometricDialogView extends LinearLayout { final TextView subtitle = mLayout.findViewById(R.id.subtitle); final TextView description = mLayout.findViewById(R.id.description); final Button negative = mLayout.findViewById(R.id.button2); - final Button positive = mLayout.findViewById(R.id.button1); final ImageView backgroundView = mLayout.findViewById(R.id.background); if (mUserManager.isManagedProfile(mUserId)) { @@ -233,8 +241,6 @@ public abstract class BiometricDialogView extends LinearLayout { title.setText(titleText); title.setSelected(true); - positive.setVisibility(View.INVISIBLE); - final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE); if (TextUtils.isEmpty(subtitleText)) { subtitle.setVisibility(View.GONE); @@ -243,7 +249,8 @@ public abstract class BiometricDialogView extends LinearLayout { subtitle.setText(subtitleText); } - final CharSequence descriptionText = mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION); + final CharSequence descriptionText = + mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION); if (TextUtils.isEmpty(descriptionText)) { description.setVisibility(View.GONE); } else { @@ -253,6 +260,9 @@ public abstract class BiometricDialogView extends LinearLayout { negative.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT)); + showTryAgainButton(mPendingShowTryAgain); + showConfirmationButton(mPendingShowConfirm); + if (mWasForceRemoved || mSkipIntro) { // Show the dialog immediately mLayout.animate().cancel(); @@ -281,11 +291,17 @@ public abstract class BiometricDialogView extends LinearLayout { public void startDismiss() { mAnimatingAway = true; + // This is where final cleanup should occur. final Runnable endActionRunnable = new Runnable() { @Override public void run() { mWindowManager.removeView(BiometricDialogView.this); mAnimatingAway = false; + // Set the icons / text back to normal state + handleClearMessage(); + showTryAgainButton(false /* show */); + mPendingShowTryAgain = false; + mPendingShowConfirm = false; } }; @@ -345,9 +361,13 @@ public abstract class BiometricDialogView extends LinearLayout { return mRequireConfirmation; } - public void showConfirmationButton() { + public void showConfirmationButton(boolean show) { final Button positive = mLayout.findViewById(R.id.button1); - positive.setVisibility(View.VISIBLE); + if (show) { + positive.setVisibility(View.VISIBLE); + } else { + positive.setVisibility(View.GONE); + } } public void setUserId(int userId) { @@ -376,12 +396,18 @@ public abstract class BiometricDialogView extends LinearLayout { BiometricPrompt.HIDE_DIALOG_DELAY); } + public void clearTemporaryMessage() { + mHandler.removeMessages(MSG_CLEAR_MESSAGE); + mHandler.obtainMessage(MSG_CLEAR_MESSAGE).sendToTarget(); + } + public void showHelpMessage(String message) { showTemporaryMessage(message); } public void showErrorMessage(String error) { showTemporaryMessage(error); + showTryAgainButton(false /* show */); mCallback.onErrorShown(); } @@ -390,6 +416,25 @@ public abstract class BiometricDialogView extends LinearLayout { mLastState = newState; } + public void showTryAgainButton(boolean show) { + final Button tryAgain = mLayout.findViewById(R.id.button_try_again); + if (show) { + tryAgain.setVisibility(View.VISIBLE); + } else { + tryAgain.setVisibility(View.GONE); + } + } + + // Set the state before the window is attached, so we know if the dialog should be started + // with or without the button. This is because there's no good onPause signal + public void setPendingTryAgain(boolean show) { + mPendingShowTryAgain = show; + } + + public void setPendingConfirm(boolean show) { + mPendingShowConfirm = show; + } + public WindowManager.LayoutParams getLayoutParams() { final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java index f388d9c67718..24fd22e2ee80 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java @@ -43,4 +43,9 @@ public interface DialogViewCallback { * should be dismissed. */ void onPositivePressed(); + + /** + * Invoked when the "try again" button is pressed. + */ + void onTryAgainPressed(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java index 591e9e015897..9d2be39b28fc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java @@ -26,6 +26,7 @@ import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Handler; +import android.provider.Settings; import android.service.quicksettings.TileService; import android.text.TextUtils; import android.util.ArraySet; @@ -69,7 +70,8 @@ public class TileQueryHelper { mSpecs.clear(); mFinished = false; // Enqueue jobs to fetch every system tile and then ever package tile. - addStockTiles(host); + addCurrentAndStockTiles(host); + addPackageTiles(host); } @@ -77,16 +79,28 @@ public class TileQueryHelper { return mFinished; } - private void addStockTiles(QSTileHost host) { - String possible = mContext.getString(R.string.quick_settings_tiles_stock); + private void addCurrentAndStockTiles(QSTileHost host) { + String stock = mContext.getString(R.string.quick_settings_tiles_stock); + String current = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.QS_TILES); final ArrayList<String> possibleTiles = new ArrayList<>(); - possibleTiles.addAll(Arrays.asList(possible.split(","))); - if (Build.IS_DEBUGGABLE) { + if (current != null) { + // The setting QS_TILES is not populated immediately upon Factory Reset + possibleTiles.addAll(Arrays.asList(current.split(","))); + } + String[] stockSplit = stock.split(","); + for (String spec : stockSplit) { + if (!current.contains(spec)) { + possibleTiles.add(spec); + } + } + if (Build.IS_DEBUGGABLE && !current.contains(GarbageMonitor.MemoryTile.TILE_SPEC)) { possibleTiles.add(GarbageMonitor.MemoryTile.TILE_SPEC); } final ArrayList<QSTile> tilesToAdd = new ArrayList<>(); for (String spec : possibleTiles) { + // Only add current and stock tiles that can be created from QSFactoryImpl final QSTile tile = host.createTile(spec); if (tile == null) { continue; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index b2f60436fecd..7d52f0b2763d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -114,7 +114,7 @@ public class QSFactoryImpl implements QSFactory { } // Broken tiles. - Log.w(TAG, "Bad tile spec: " + tileSpec); + Log.w(TAG, "No stock tile spec: " + tileSpec); return null; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index c62a592be8e2..3ab1c21b5066 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -205,7 +205,10 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { } else { final BluetoothClass bluetoothClass = lastDevice.getBtClass(); if (bluetoothClass != null) { - if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) { + if (lastDevice.isHearingAidDevice()) { + return mContext.getString( + R.string.quick_settings_bluetooth_secondary_label_hearing_aids); + } else if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) { return mContext.getString( R.string.quick_settings_bluetooth_secondary_label_audio); } else if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 1b89324209de..12b6f673de1c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -43,6 +43,7 @@ import android.provider.Settings; import android.util.Log; import android.view.MotionEvent; +import com.android.internal.policy.ScreenDecorationsUtils; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.Prefs; @@ -97,6 +98,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private int mCurrentBoundedUserId = -1; private float mBackButtonAlpha; private MotionEvent mStatusBarGestureDownEvent; + private float mWindowCornerRadius; private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() { @@ -228,6 +230,18 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + public float getWindowCornerRadius() { + if (!verifyCaller("getWindowCornerRadius")) { + return 0; + } + long token = Binder.clearCallingIdentity(); + try { + return mWindowCornerRadius; + } finally { + Binder.restoreCallingIdentity(token); + } + } + private boolean verifyCaller(String reason) { final int callerId = Binder.getCallingUserHandle().getIdentifier(); if (callerId != mCurrentBoundedUserId) { @@ -334,6 +348,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis .setPackage(mRecentsComponentName.getPackageName()); mInteractionFlags = Prefs.getInt(mContext, Prefs.Key.QUICK_STEP_INTERACTION_FLAGS, getDefaultInteractionFlags()); + mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext.getResources()); // Listen for the package update changes. if (mDeviceProvisionedController.getCurrentUser() == UserHandle.USER_SYSTEM) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java index a5e7f04be281..8821679aadf1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java @@ -33,7 +33,7 @@ import com.android.systemui.statusbar.notification.row.NotificationInflater.Infl * dozing and/or in AOD. The pulse uses the notification's ambient view and pops in briefly * before automatically dismissing the alert. */ -public final class AmbientPulseManager extends AlertingNotificationManager { +public class AmbientPulseManager extends AlertingNotificationManager { protected final ArraySet<OnAmbientChangedListener> mListeners = new ArraySet<>(); @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 0c8f48752455..8b9399536969 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -21,7 +21,7 @@ import static com.android.systemui.statusbar.phone.StatusBar.ONLY_CORE_APPS; import android.app.StatusBarManager; import android.content.ComponentName; import android.graphics.Rect; -import android.hardware.biometrics.IBiometricPromptReceiver; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -96,6 +96,7 @@ public class CommandQueue extends IStatusBar.Stub { private static final int MSG_SHOW_CHARGING_ANIMATION = 44 << MSG_SHIFT; private static final int MSG_SHOW_PINNING_TOAST_ENTER_EXIT = 45 << MSG_SHIFT; private static final int MSG_SHOW_PINNING_TOAST_ESCAPE = 46 << MSG_SHIFT; + private static final int MSG_BIOMETRIC_TRY_AGAIN = 47 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -163,12 +164,13 @@ public class CommandQueue extends IStatusBar.Stub { default void onRotationProposal(int rotation, boolean isValid) { } - default void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, + default void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, boolean requireConfirmation, int userId) { } default void onBiometricAuthenticated() { } default void onBiometricHelp(String message) { } default void onBiometricError(String error) { } default void hideBiometricDialog() { } + default void showBiometricTryAgain() { } } @VisibleForTesting @@ -523,8 +525,8 @@ public class CommandQueue extends IStatusBar.Stub { } @Override - public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type, - boolean requireConfirmation, int userId) { + public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, + int type, boolean requireConfirmation, int userId) { synchronized (mLock) { SomeArgs args = SomeArgs.obtain(); args.arg1 = bundle; @@ -565,6 +567,13 @@ public class CommandQueue extends IStatusBar.Stub { } } + @Override + public void showBiometricTryAgain() { + synchronized (mLock) { + mHandler.obtainMessage(MSG_BIOMETRIC_TRY_AGAIN).sendToTarget(); + } + } + private final class H extends Handler { private H(Looper l) { super(l); @@ -774,7 +783,7 @@ public class CommandQueue extends IStatusBar.Stub { for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).showBiometricDialog( (Bundle) someArgs.arg1, - (IBiometricPromptReceiver) someArgs.arg2, + (IBiometricServiceReceiverInternal) someArgs.arg2, someArgs.argi1 /* type */, (boolean) someArgs.arg3 /* requireConfirmation */, someArgs.argi2 /* userId */); @@ -816,6 +825,11 @@ public class CommandQueue extends IStatusBar.Stub { mCallbacks.get(i).showPinningEscapeToast(); } break; + case MSG_BIOMETRIC_TRY_AGAIN: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).showBiometricTryAgain(); + } + break; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java index f0e5462bc167..f506753379a4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java @@ -29,6 +29,7 @@ import android.view.IRemoteAnimationRunner; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; +import com.android.internal.policy.ScreenDecorationsUtils; import com.android.systemui.Interpolators; import com.android.systemui.shared.system.SurfaceControlCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier; @@ -55,6 +56,7 @@ public class ActivityLaunchAnimator { private final NotificationPanelView mNotificationPanel; private final NotificationListContainer mNotificationContainer; private final StatusBarWindowView mStatusBarWindow; + private final float mWindowCornerRadius; private Callback mCallback; private final Runnable mTimeoutRunnable = () -> { setAnimationPending(false); @@ -72,6 +74,8 @@ public class ActivityLaunchAnimator { mNotificationContainer = container; mStatusBarWindow = statusBarWindow; mCallback = callback; + mWindowCornerRadius = ScreenDecorationsUtils + .getWindowCornerRadius(statusBarWindow.getResources()); } public RemoteAnimationAdapter getLaunchAnimation( @@ -124,6 +128,8 @@ public class ActivityLaunchAnimator { private final ExpandableNotificationRow mSourceNotification; private final ExpandAnimationParameters mParams; private final Rect mWindowCrop = new Rect(); + private final float mNotificationCornerRadius; + private float mCornerRadius; private boolean mIsFullScreenLaunch = true; private final SyncRtSurfaceTransactionApplier mSyncRtTransactionApplier; @@ -131,6 +137,8 @@ public class ActivityLaunchAnimator { mSourceNotification = sourceNofitication; mParams = new ExpandAnimationParameters(); mSyncRtTransactionApplier = new SyncRtSurfaceTransactionApplier(mSourceNotification); + mNotificationCornerRadius = Math.max(mSourceNotification.getCurrentTopRoundness(), + mSourceNotification.getCurrentBottomRoundness()); } @Override @@ -181,8 +189,7 @@ public class ActivityLaunchAnimator { @Override public void onAnimationUpdate(ValueAnimator animation) { mParams.linearProgress = animation.getAnimatedFraction(); - float progress - = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( + float progress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( mParams.linearProgress); int newWidth = (int) MathUtils.lerp(notificationWidth, targetWidth, progress); @@ -194,6 +201,8 @@ public class ActivityLaunchAnimator { + notificationHeight, primary.position.y + primary.sourceContainerBounds.bottom, progress); + mCornerRadius = MathUtils.lerp(mNotificationCornerRadius, + mWindowCornerRadius, progress); applyParamsToWindow(primary); applyParamsToNotification(mParams); applyParamsToNotificationList(mParams); @@ -259,7 +268,7 @@ public class ActivityLaunchAnimator { m.postTranslate(0, (float) (mParams.top - app.position.y)); mWindowCrop.set(mParams.left, 0, mParams.right, mParams.getHeight()); SurfaceParams params = new SurfaceParams(new SurfaceControlCompat(app.leash), - 1f /* alpha */, m, mWindowCrop, app.prefixOrderIndex); + 1f /* alpha */, m, mWindowCrop, app.prefixOrderIndex, mCornerRadius); mSyncRtTransactionApplier.scheduleApply(params); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 626e68850c12..5d640e0216e0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.stack; import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator .ExpandAnimationParameters; +import static com.android.systemui.statusbar.notification.stack.StackStateAnimator + .ANIMATION_DURATION_SWIPE; import static com.android.systemui.statusbar.phone.NotificationIconAreaController.LOW_PRIORITY; import android.animation.Animator; @@ -5093,7 +5095,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd if (i == 0) { endRunnable = animationFinishAction; } - dismissViewAnimated(view, endRunnable, totalDelay, 260); + dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE); currentDelay = Math.max(50, currentDelay - rowDelayDecrement); totalDelay += currentDelay; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 713bd90b30c3..d6905478a043 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -44,6 +44,7 @@ public class StackStateAnimator { public static final int ANIMATION_DURATION_WAKEUP = 500; public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448; public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464; + public static final int ANIMATION_DURATION_SWIPE = 260; public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220; public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150; public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 550; @@ -375,12 +376,9 @@ public class StackStateAnimator { // Find the amount to translate up. This is needed in order to understand the // direction of the remove animation (either downwards or upwards) - ExpandableViewState viewState = - ((ExpandableView) event.viewAfterChangingView).getViewState(); - int actualHeight = changingView.getActualHeight(); // upwards by default float translationDirection = -1.0f; - if (viewState != null) { + if (event.viewAfterChangingView != null) { float ownPosition = changingView.getTranslationY(); if (changingView instanceof ExpandableNotificationRow && event.viewAfterChangingView instanceof ExpandableNotificationRow) { @@ -396,8 +394,11 @@ public class StackStateAnimator { ownPosition = changingRow.getTranslationWhenRemoved(); } } + int actualHeight = changingView.getActualHeight(); // there was a view after this one, Approximate the distance the next child // travelled + ExpandableViewState viewState = + ((ExpandableView) event.viewAfterChangingView).getViewState(); translationDirection = ((viewState.yTranslation - (ownPosition + actualHeight / 2.0f)) * 2 / actualHeight); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 33d022c83c16..cd6e1d794428 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone; import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID; import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB; import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON; @@ -950,10 +951,10 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private void updateTaskSwitchHelper() { if (mGestureHelper == null) return; boolean isRtl = (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL); - int navBarPos = 0; + int navBarPos = NAV_BAR_INVALID; try { - // TODO: Use WindowManagerService.getNavBarPosition(int displayId) - navBarPos = WindowManagerGlobal.getWindowManagerService().getNavBarPosition(); + navBarPos = WindowManagerGlobal.getWindowManagerService().getNavBarPosition( + mDisplay.getDisplayId()); } catch (RemoteException e) { Slog.e(TAG, "Failed to get nav bar position.", e); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java index 83067f6cff87..a8d00c454548 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java @@ -127,6 +127,15 @@ public abstract class NavigationGestureAction { } /** + * Decide if the controller should not send the current motion event to launcher via + * {@link OverviewProxyService} + * @return if controller should not proxy + */ + public boolean disableProxyEvents() { + return false; + } + + /** * Tell if action is enabled. Compared to {@link #canPerformAction()} this is based on settings * if the action is disabled for a particular gesture. For example a back action can be enabled * however if there is nothing to back to then {@link #canPerformAction()} should return false. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubAction.java index 74744f1408fb..2b202eb83431 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubAction.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubAction.java @@ -212,6 +212,11 @@ public class QuickScrubAction extends NavigationGestureAction { } @Override + public boolean disableProxyEvents() { + return true; + } + + @Override protected void onGestureStart(MotionEvent event) { updateHighlight(); ObjectAnimator trackAnimator = ObjectAnimator.ofPropertyValuesHolder(this, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java index 497fdfb2deb1..2cbf27c6e61c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone; import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID; import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT; @@ -274,25 +275,21 @@ public class QuickStepController implements GestureHelper { if (mDragVPositive ? (posV < touchDownV) : (posV > touchDownV)) { // Swiping up gesture tryToStartGesture(mGestureActions[ACTION_SWIPE_UP_INDEX], - false /* alignedWithNavBar */, false /* positiveDirection */, - event); + false /* alignedWithNavBar */, event); } else { // Swiping down gesture tryToStartGesture(mGestureActions[ACTION_SWIPE_DOWN_INDEX], - false /* alignedWithNavBar */, true /* positiveDirection */, - event); + false /* alignedWithNavBar */, event); } } else if (exceededSwipeHorizontalTouchSlop) { if (mDragHPositive ? (posH < touchDownH) : (posH > touchDownH)) { // Swiping left (ltr) gesture tryToStartGesture(mGestureActions[ACTION_SWIPE_LEFT_INDEX], - true /* alignedWithNavBar */, false /* positiveDirection */, - event); + true /* alignedWithNavBar */, event); } else { // Swiping right (ltr) gesture tryToStartGesture(mGestureActions[ACTION_SWIPE_RIGHT_INDEX], - true /* alignedWithNavBar */, true /* positiveDirection */, - event); + true /* alignedWithNavBar */, event); } } } @@ -305,7 +302,6 @@ public class QuickStepController implements GestureHelper { case MotionEvent.ACTION_UP: if (mCurrentAction != null) { mCurrentAction.endGesture(); - mCurrentAction = null; } // Return the hit target back to its original position @@ -328,6 +324,11 @@ public class QuickStepController implements GestureHelper { if (shouldProxyEvents(action)) { proxyMotionEvents(event); } + + // Clear action when gesture and event proxy finishes + if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { + mCurrentAction = null; + } return mCurrentAction != null || deadZoneConsumed; } @@ -353,8 +354,7 @@ public class QuickStepController implements GestureHelper { private boolean shouldProxyEvents(int action) { final boolean actionValid = (mCurrentAction == null - || (mGestureActions[ACTION_SWIPE_UP_INDEX] != null - && mGestureActions[ACTION_SWIPE_UP_INDEX].isActive())); + || !mCurrentAction.disableProxyEvents()); if (actionValid && !mIsInScreenPinning) { // Allow down, cancel and up events, move and other events are passed if notifications // are not showing and disabled gestures (such as long press) are not executed @@ -421,6 +421,9 @@ public class QuickStepController implements GestureHelper { mDragHPositive = !isRTL; mDragVPositive = true; break; + case NAV_BAR_INVALID: + Log.e(TAG, "Invalid nav bar position"); + break; } for (NavigationGestureAction action: mGestureActions) { @@ -451,7 +454,7 @@ public class QuickStepController implements GestureHelper { } private void tryToStartGesture(NavigationGestureAction action, boolean alignedWithNavBar, - boolean positiveDirection, MotionEvent event) { + MotionEvent event) { if (action == null) { return; } 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 408ab42a6f06..5723948bc293 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -474,8 +474,10 @@ public class StatusBar extends SystemUI implements DemoMode, return; } WallpaperInfo info = wallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT); - final boolean supportsAmbientMode = info != null && - info.supportsAmbientMode(); + final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_dozeSupportsAodWallpaper); + final boolean supportsAmbientMode = deviceSupportsAodWallpaper + && info != null && info.supportsAmbientMode(); mStatusBarWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode); mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode); @@ -1211,7 +1213,8 @@ public class StatusBar extends SystemUI implements DemoMode, } int dockSide = WindowManagerProxy.getInstance().getDockSide(); if (dockSide == WindowManager.DOCKED_INVALID) { - final int navbarPos = WindowManagerWrapper.getInstance().getNavBarPosition(); + final int navbarPos = WindowManagerWrapper.getInstance().getNavBarPosition( + mDisplay.getDisplayId()); if (navbarPos == NAV_BAR_POS_INVALID) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index 978a72dcb4b7..53e461db3dd1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -49,6 +49,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.Window; +import android.view.WindowInsetsController; import android.widget.FrameLayout; import com.android.internal.annotations.VisibleForTesting; @@ -785,6 +786,11 @@ public class StatusBarWindowView extends FrameLayout { @Override public void reportActivityRelaunched() { } + + @Override + public WindowInsetsController getInsetsController() { + return null; + } }; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java index f63d2360d976..26fa20de4e86 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java @@ -14,41 +14,76 @@ package com.android.systemui.qs.customize; -import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.content.pm.PackageManager; +import android.provider.Settings; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import android.text.TextUtils; +import android.util.ArraySet; import com.android.systemui.Dependency; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSTileHost; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper public class TileQueryHelperTest extends SysuiTestCase { - @Mock private TileQueryHelper.TileStateListener mListener; - @Mock private QSTileHost mQSTileHost; + private static final String CURRENT_TILES = "wifi,dnd,nfc"; + private static final String ONLY_STOCK_TILES = "wifi,dnd"; + private static final String WITH_OTHER_TILES = "wifi,dnd,other"; + // Note no nfc in stock tiles + private static final String STOCK_TILES = "wifi,dnd,cell,battery"; + private static final String ALL_TILES = "wifi,dnd,nfc,cell,battery"; + private static final Set<String> FACTORY_TILES = new ArraySet<>(); + + static { + FACTORY_TILES.addAll(Arrays.asList( + new String[]{"wifi", "bt", "cell", "dnd", "inversion", "airplane", "work", + "rotation", "flashlight", "location", "cast", "hotspot", "user", "battery", + "saver", "night", "nfc"})); + } - private TestableLooper mBGLooper; + @Mock + private TileQueryHelper.TileStateListener mListener; + @Mock + private QSTileHost mQSTileHost; + @Mock + private PackageManager mPackageManager; + private QSTile.State mState; + private TestableLooper mBGLooper; private TileQueryHelper mTileQueryHelper; @Before @@ -56,6 +91,23 @@ public class TileQueryHelperTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mBGLooper = TestableLooper.get(this); mDependency.injectTestDependency(Dependency.BG_LOOPER, mBGLooper.getLooper()); + mContext.setMockPackageManager(mPackageManager); + + mState = new QSTile.State(); + doAnswer(invocation -> { + String spec = (String) invocation.getArguments()[0]; + if (FACTORY_TILES.contains(spec)) { + QSTile m = mock(QSTile.class); + when(m.isAvailable()).thenReturn(true); + when(m.getTileSpec()).thenReturn(spec); + when(m.getState()).thenReturn(mState); + return m; + } else { + return null; + } + } + ).when(mQSTileHost).createTile(anyString()); + mTileQueryHelper = new TileQueryHelper(mContext, mListener); } @@ -98,4 +150,72 @@ public class TileQueryHelperTest extends SysuiTestCase { assertTrue(mTileQueryHelper.isFinished()); } + + @Test + public void testQueryTiles_correctTilesAndOrderOnlyStockTiles() { + ArgumentCaptor<List<TileQueryHelper.TileInfo>> captor = ArgumentCaptor.forClass(List.class); + + Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, + ONLY_STOCK_TILES); + mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock, + STOCK_TILES); + + mTileQueryHelper.queryTiles(mQSTileHost); + + mBGLooper.processAllMessages(); + waitForIdleSync(Dependency.get(Dependency.MAIN_HANDLER)); + + verify(mListener, atLeastOnce()).onTilesChanged(captor.capture()); + List<String> specs = new ArrayList<>(); + for (TileQueryHelper.TileInfo t : captor.getValue()) { + specs.add(t.spec); + } + String tiles = TextUtils.join(",", specs); + assertThat(tiles, is(equalTo(STOCK_TILES))); + } + + @Test + public void testQueryTiles_correctTilesAndOrderOtherFactoryTiles() { + ArgumentCaptor<List<TileQueryHelper.TileInfo>> captor = ArgumentCaptor.forClass(List.class); + + Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, + CURRENT_TILES); + mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock, + STOCK_TILES); + + mTileQueryHelper.queryTiles(mQSTileHost); + + mBGLooper.processAllMessages(); + waitForIdleSync(Dependency.get(Dependency.MAIN_HANDLER)); + + verify(mListener, atLeastOnce()).onTilesChanged(captor.capture()); + List<String> specs = new ArrayList<>(); + for (TileQueryHelper.TileInfo t : captor.getValue()) { + specs.add(t.spec); + } + String tiles = TextUtils.join(",", specs); + assertThat(tiles, is(equalTo(ALL_TILES))); + } + + @Test + public void testQueryTiles_otherTileNotIncluded() { + ArgumentCaptor<List<TileQueryHelper.TileInfo>> captor = ArgumentCaptor.forClass(List.class); + + Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, + WITH_OTHER_TILES); + mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock, + STOCK_TILES); + + mTileQueryHelper.queryTiles(mQSTileHost); + + mBGLooper.processAllMessages(); + waitForIdleSync(Dependency.get(Dependency.MAIN_HANDLER)); + + verify(mListener, atLeastOnce()).onTilesChanged(captor.capture()); + List<String> specs = new ArrayList<>(); + for (TileQueryHelper.TileInfo t : captor.getValue()) { + specs.add(t.spec); + } + assertFalse(specs.contains("other")); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java index 435ede4d8ff0..55583931af43 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java @@ -62,6 +62,7 @@ public class ActivityLaunchAnimatorTest extends SysuiTestCase { @Before public void setUp() throws Exception { + when(mStatusBarWindowView.getResources()).thenReturn(mContext.getResources()); when(mCallback.areLaunchAnimationsEnabled()).thenReturn(true); mLaunchAnimator = new ActivityLaunchAnimator( mStatusBarWindowView, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java index 4177cd16c8bf..8fc15b247ab9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java @@ -38,12 +38,12 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import android.content.Context; import com.android.systemui.R; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.SysuiTestCase; +import android.content.Context; import android.content.res.Resources; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; @@ -395,6 +395,7 @@ public class QuickStepControllerTest extends SysuiTestCase { verify(mProxy, times(1)).onQuickScrubStart(); verify(mProxyService, times(1)).notifyQuickScrubStarted(); verify(mNavigationBarView, times(1)).updateSlippery(); + verify(mProxy, never()).onMotionEvent(moveEvent1); // Move again for scrub MotionEvent moveEvent2 = event(MotionEvent.ACTION_MOVE, 200, y); @@ -402,6 +403,7 @@ public class QuickStepControllerTest extends SysuiTestCase { assertEquals(action, mController.getCurrentAction()); verify(action, times(1)).onGestureMove(200, y); verify(mProxy, times(1)).onQuickScrubProgress(1f / 2); + verify(mProxy, never()).onMotionEvent(moveEvent2); // Action up MotionEvent upEvent = event(MotionEvent.ACTION_UP, 1, y); @@ -409,6 +411,7 @@ public class QuickStepControllerTest extends SysuiTestCase { assertNull(mController.getCurrentAction()); verify(action, times(1)).onGestureEnd(); verify(mProxy, times(1)).onQuickScrubEnd(); + verify(mProxy, never()).onMotionEvent(upEvent); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index 21047217e942..59a49378e9e7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone; import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import android.app.Notification; import android.app.StatusBarManager; @@ -63,9 +64,11 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { mContext.putComponent(CommandQueue.class, mCommandQueue); mDependency.injectTestDependency(ShadeController.class, mShadeController); + StatusBarWindowView statusBarWindowView = mock(StatusBarWindowView.class); + when(statusBarWindowView.getResources()).thenReturn(mContext.getResources()); mStatusBar = new StatusBarNotificationPresenter(mContext, mock(NotificationPanelView.class), mock(HeadsUpManagerPhone.class), - mock(StatusBarWindowView.class), mock(NotificationListContainerViewGroup.class), + statusBarWindowView, mock(NotificationListContainerViewGroup.class), mock(DozeScrimController.class), mock(ScrimController.class), mock(ActivityLaunchAnimator.Callback.class)); } diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java index 9aa9d7c52818..af6575954842 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java @@ -40,9 +40,9 @@ import android.service.autofill.SaveRequest; import android.text.format.DateUtils; import android.util.Slog; -import com.android.server.AbstractRemoteService; +import com.android.server.AbstractSinglePendingRequestRemoteService; -final class RemoteFillService extends AbstractRemoteService { +final class RemoteFillService extends AbstractSinglePendingRequestRemoteService<RemoteFillService> { private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS; private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS; @@ -69,8 +69,8 @@ final class RemoteFillService extends AbstractRemoteService { mCallbacks = callbacks; } - @Override - protected void onConnectedStateChanged(boolean state) { + @Override // from AbstractRemoteService + protected void handleOnConnectedStateChanged(boolean state) { if (mAutoFillService == null) { Slog.w(mTag, "onConnectedStateChanged(): null service"); return; @@ -82,18 +82,18 @@ final class RemoteFillService extends AbstractRemoteService { } } - @Override + @Override // from AbstractRemoteService protected IInterface getServiceInterface(IBinder service) { mAutoFillService = IAutoFillService.Stub.asInterface(service); return mAutoFillService; } - @Override + @Override // from AbstractRemoteService protected long getTimeoutIdleBindMillis() { return TIMEOUT_IDLE_BIND_MILLIS; } - @Override + @Override // from AbstractRemoteService protected long getRemoteRequestMillis() { return TIMEOUT_REMOTE_REQUEST_MILLIS; } @@ -136,6 +136,19 @@ final class RemoteFillService extends AbstractRemoteService { scheduleRequest(new PendingSaveRequest(request, this)); } + private boolean handleResponseCallbackCommon( + @NonNull PendingRequest<RemoteFillService> pendingRequest) { + if (isDestroyed()) return false; + + if (mPendingRequest == pendingRequest) { + mPendingRequest = null; + } + if (mPendingRequest == null) { + scheduleUnbind(); + } + return true; + } + private void dispatchOnFillRequestSuccess(@NonNull PendingFillRequest pendingRequest, @Nullable FillResponse response, int requestFlags) { mHandler.post(() -> { diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 8676f7f5bea0..4c645076eb95 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -902,7 +902,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // VultureCallback @Override - public void onServiceDied(AbstractRemoteService service) { + public void onServiceDied(AbstractRemoteService<? extends AbstractRemoteService<?>> service) { Slog.w(TAG, "removing session because service died"); forceRemoveSelfLocked(); } diff --git a/services/core/java/com/android/server/AbstractMultiplePendingRequestsRemoteService.java b/services/core/java/com/android/server/AbstractMultiplePendingRequestsRemoteService.java new file mode 100644 index 000000000000..f532b22cb8d4 --- /dev/null +++ b/services/core/java/com/android/server/AbstractMultiplePendingRequestsRemoteService.java @@ -0,0 +1,96 @@ +/* + * 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; + +import android.annotation.NonNull; +import android.content.ComponentName; +import android.content.Context; +import android.util.Slog; + +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Base class representing a remote service that can queue multiple pending requests while not + * bound. + * + * @param <S> the concrete remote service class + * + * @hide + */ +public abstract class AbstractMultiplePendingRequestsRemoteService< + S extends AbstractMultiplePendingRequestsRemoteService<S>> + extends AbstractRemoteService<S> { + + private final int mInitialCapacity; + + protected ArrayList<PendingRequest<S>> mPendingRequests; + + public AbstractMultiplePendingRequestsRemoteService(@NonNull Context context, + @NonNull String serviceInterface, @NonNull ComponentName componentName, int userId, + @NonNull VultureCallback callback, boolean bindInstantServiceAllowed, boolean verbose, + int initialCapacity) { + super(context, serviceInterface, componentName, userId, callback, bindInstantServiceAllowed, + verbose); + mInitialCapacity = initialCapacity; + } + + @Override // from AbstractRemoteService + void handlePendingRequests() { + if (mPendingRequests != null) { + final int size = mPendingRequests.size(); + if (mVerbose) Slog.v(mTag, "Sending " + size + " pending requests"); + for (int i = 0; i < size; i++) { + mPendingRequests.get(i).run(); + } + mPendingRequests = null; + } + } + + @Override // from AbstractRemoteService + protected void handleOnDestroy() { + if (mPendingRequests != null) { + final int size = mPendingRequests.size(); + if (mVerbose) Slog.v(mTag, "Canceling " + size + " pending requests"); + for (int i = 0; i < size; i++) { + mPendingRequests.get(i).cancel(); + } + mPendingRequests = null; + } + } + + @Override // from AbstractRemoteService + public void dump(@NonNull String prefix, @NonNull PrintWriter pw) { + super.dump(prefix, pw); + + pw.append(prefix).append("initialCapacity=").append(String.valueOf(mInitialCapacity)) + .println(); + final int size = mPendingRequests == null ? 0 : mPendingRequests.size(); + pw.append(prefix).append("pendingRequests=").append(String.valueOf(size)).println(); + } + + @Override // from AbstractRemoteService + void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S> pendingRequest) { + if (mPendingRequests == null) { + mPendingRequests = new ArrayList<>(mInitialCapacity); + } + mPendingRequests.add(pendingRequest); + if (mVerbose) { + Slog.v(mTag, "queued " + mPendingRequests.size() + " requests; last=" + pendingRequest); + } + } +} diff --git a/services/core/java/com/android/server/AbstractPerUserSystemService.java b/services/core/java/com/android/server/AbstractPerUserSystemService.java index 71d261c7f5b5..001d85f1b798 100644 --- a/services/core/java/com/android/server/AbstractPerUserSystemService.java +++ b/services/core/java/com/android/server/AbstractPerUserSystemService.java @@ -108,7 +108,8 @@ public abstract class AbstractPerUserSystemService<S extends AbstractPerUserSyst * <p>Typically called when the service {@link Settings} property or {@link UserManager} * restriction changed, which includes the initial creation of the service. * - * <p>Subclasses can extend this method to provide extra initialization. + * <p>Subclasses can extend this method to provide extra initialization, like clearing up + * previous state. * * @param disabled whether the service is disabled (due to {@link UserManager} restrictions). * diff --git a/services/core/java/com/android/server/AbstractRemoteService.java b/services/core/java/com/android/server/AbstractRemoteService.java index 0d4cf6b01ba8..f636487c666b 100644 --- a/services/core/java/com/android/server/AbstractRemoteService.java +++ b/services/core/java/com/android/server/AbstractRemoteService.java @@ -45,13 +45,20 @@ import java.lang.ref.WeakReference; * * <p>All state of this class is modified on a handler thread. * + * <p><b>NOTE: </b>this class should not be extended directly, you should extend either + * {@link AbstractSinglePendingRequestRemoteService} or + * {@link AbstractMultiplePendingRequestsRemoteService}. + * * <p>See {@code com.android.server.autofill.RemoteFillService} for a concrete * (no pun intended) example of how to use it. * + * @param <S> the concrete remote service class + * * @hide */ //TODO(b/117779333): improve javadoc above instead of using Autofill as an example -public abstract class AbstractRemoteService implements DeathRecipient { +public abstract class AbstractRemoteService<S extends AbstractRemoteService<S>> + implements DeathRecipient { private static final int MSG_UNBIND = 1; @@ -64,8 +71,6 @@ public abstract class AbstractRemoteService implements DeathRecipient { protected final Handler mHandler; protected final ComponentName mComponentName; - protected PendingRequest<? extends AbstractRemoteService> mPendingRequest; - private final Context mContext; private final Intent mIntent; private final VultureCallback mVultureCallback; @@ -88,10 +93,11 @@ public abstract class AbstractRemoteService implements DeathRecipient { * * @param service service that died! */ - void onServiceDied(AbstractRemoteService service); + void onServiceDied(AbstractRemoteService<? extends AbstractRemoteService<?>> service); } - public AbstractRemoteService(@NonNull Context context, @NonNull String serviceInterface, + // NOTE: must be package-protected so this class is not extend outside + AbstractRemoteService(@NonNull Context context, @NonNull String serviceInterface, @NonNull ComponentName componentName, int userId, @NonNull VultureCallback callback, boolean bindInstantServiceAllowed, boolean verbose) { mContext = context; @@ -118,12 +124,25 @@ public abstract class AbstractRemoteService implements DeathRecipient { return mDestroyed; } + private void handleOnConnectedStateChangedInternal(boolean connected) { + if (connected) { + handlePendingRequests(); + } + handleOnConnectedStateChanged(connected); + } + + /** + * Handles the pending requests when the connection it bounds to the remote service. + */ + abstract void handlePendingRequests(); + /** - * Callback called when the system connected / disconnected to the service. + * Callback called when the system connected / disconnected to the service and the pending + * requests have been handled. * * @param state {@code true} when connected, {@code false} when disconnected. */ - protected void onConnectedStateChanged(boolean state) { + protected void handleOnConnectedStateChanged(boolean state) { } /** @@ -144,14 +163,18 @@ public abstract class AbstractRemoteService implements DeathRecipient { private void handleDestroy() { if (checkIfDestroyed()) return; - if (mPendingRequest != null) { - mPendingRequest.cancel(); - mPendingRequest = null; - } - ensureUnbound(); + handleOnDestroy(); + handleEnsureUnbound(); mDestroyed = true; } + /** + * Clears the state when this object is destroyed. + * + * <p>Typically used to cancel the pending requests. + */ + protected abstract void handleOnDestroy(); + @Override // from DeathRecipient public void binderDied() { mHandler.sendMessage(obtainMessage(AbstractRemoteService::handleBinderDied, this)); @@ -183,9 +206,7 @@ public abstract class AbstractRemoteService implements DeathRecipient { pw.append(prefix).append(tab).append("destroyed=") .append(String.valueOf(mDestroyed)).println(); pw.append(prefix).append(tab).append("bound=") - .append(String.valueOf(isBound())).println(); - pw.append(prefix).append(tab).append("hasPendingRequest=") - .append(String.valueOf(mPendingRequest != null)).println(); + .append(String.valueOf(handleIsBound())).println(); pw.append(prefix).append("mBindInstantServiceAllowed=").println(mBindInstantServiceAllowed); pw.append(prefix).append("idleTimeout=") .append(Long.toString(getTimeoutIdleBindMillis() / 1000)).append("s").println(); @@ -194,7 +215,7 @@ public abstract class AbstractRemoteService implements DeathRecipient { pw.println(); } - protected void scheduleRequest(PendingRequest<? extends AbstractRemoteService> pendingRequest) { + protected void scheduleRequest(@NonNull PendingRequest<S> pendingRequest) { mHandler.sendMessage(obtainMessage( AbstractRemoteService::handlePendingRequest, this, pendingRequest)); } @@ -215,19 +236,20 @@ public abstract class AbstractRemoteService implements DeathRecipient { private void handleUnbind() { if (checkIfDestroyed()) return; - ensureUnbound(); + handleEnsureUnbound(); } - private void handlePendingRequest( - PendingRequest<? extends AbstractRemoteService> pendingRequest) { + /** + * Handles a request, either processing it right now when bound, or saving it to be handled when + * bound. + */ + protected final void handlePendingRequest(@NonNull PendingRequest<S> pendingRequest) { if (checkIfDestroyed() || mCompleted) return; - if (!isBound()) { - if (mPendingRequest != null) { - mPendingRequest.cancel(); - } - mPendingRequest = pendingRequest; - ensureBound(); + if (!handleIsBound()) { + if (mVerbose) Slog.v(mTag, "handlePendingRequest(): queuing" + pendingRequest); + handlePendingRequestWhileUnBound(pendingRequest); + handleEnsureBound(); } else { if (mVerbose) Slog.v(mTag, "handlePendingRequest(): " + pendingRequest); pendingRequest.run(); @@ -237,12 +259,17 @@ public abstract class AbstractRemoteService implements DeathRecipient { } } - private boolean isBound() { + /** + * Defines what to do with a request that arrives while not bound to the service. + */ + abstract void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S> pendingRequest); + + private boolean handleIsBound() { return mServiceInterface != null; } - private void ensureBound() { - if (isBound() || mBinding) return; + private void handleEnsureBound() { + if (handleIsBound() || mBinding) return; if (mVerbose) Slog.v(mTag, "ensureBound()"); mBinding = true; @@ -265,13 +292,13 @@ public abstract class AbstractRemoteService implements DeathRecipient { } } - private void ensureUnbound() { - if (!isBound() && !mBinding) return; + private void handleEnsureUnbound() { + if (!handleIsBound() && !mBinding) return; if (mVerbose) Slog.v(mTag, "ensureUnbound()"); mBinding = false; - if (isBound()) { - onConnectedStateChanged(false); + if (handleIsBound()) { + handleOnConnectedStateChangedInternal(false); if (mServiceInterface != null) { mServiceInterface.asBinder().unlinkToDeath(this, 0); mServiceInterface = null; @@ -283,6 +310,7 @@ public abstract class AbstractRemoteService implements DeathRecipient { private class RemoteServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { + if (mVerbose) Slog.v(mTag, "onServiceConnected()"); if (mDestroyed || !mBinding) { // This is abnormal. Unbinding the connection has been requested already. Slog.wtf(mTag, "onServiceConnected() was dispatched after unbindService."); @@ -296,15 +324,7 @@ public abstract class AbstractRemoteService implements DeathRecipient { handleBinderDied(); return; } - onConnectedStateChanged(true); - - if (mPendingRequest != null) { - final PendingRequest<? extends AbstractRemoteService> pendingRequest = - mPendingRequest; - mPendingRequest = null; - handlePendingRequest(pendingRequest); - } - + handleOnConnectedStateChangedInternal(true); mServiceDied = false; } @@ -325,25 +345,12 @@ public abstract class AbstractRemoteService implements DeathRecipient { return mDestroyed; } - protected boolean handleResponseCallbackCommon( - PendingRequest<? extends AbstractRemoteService> pendingRequest) { - if (isDestroyed()) return false; - - if (mPendingRequest == pendingRequest) { - mPendingRequest = null; - } - if (mPendingRequest == null) { - scheduleUnbind(); - } - return true; - } - /** * Base class for the requests serviced by the remote service. * * @param <S> the remote service class */ - public abstract static class PendingRequest<S extends AbstractRemoteService> + public abstract static class PendingRequest<S extends AbstractRemoteService<S>> implements Runnable { protected final String mTag = getClass().getSimpleName(); protected final Object mLock = new Object(); diff --git a/services/core/java/com/android/server/AbstractSinglePendingRequestRemoteService.java b/services/core/java/com/android/server/AbstractSinglePendingRequestRemoteService.java new file mode 100644 index 000000000000..8e1f540a4d5e --- /dev/null +++ b/services/core/java/com/android/server/AbstractSinglePendingRequestRemoteService.java @@ -0,0 +1,82 @@ +/* + * 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; + +import android.annotation.NonNull; +import android.content.ComponentName; +import android.content.Context; +import android.util.Slog; + +import java.io.PrintWriter; + +/** + * Base class representing a remote service that can have only one pending requests while not bound. + * + * <p>If another request is received while not bound, the previous one will be canceled. + * + * @param <S> the concrete remote service class + * + * @hide + */ +public abstract class AbstractSinglePendingRequestRemoteService< + S extends AbstractSinglePendingRequestRemoteService<S>> extends AbstractRemoteService<S> { + + protected PendingRequest<S> mPendingRequest; + + public AbstractSinglePendingRequestRemoteService(@NonNull Context context, + @NonNull String serviceInterface, @NonNull ComponentName componentName, int userId, + @NonNull VultureCallback callback, boolean bindInstantServiceAllowed, + boolean verbose) { + super(context, serviceInterface, componentName, userId, callback, bindInstantServiceAllowed, + verbose); + } + + @Override // from AbstractRemoteService + void handlePendingRequests() { + if (mPendingRequest != null) { + final PendingRequest<S> pendingRequest = mPendingRequest; + mPendingRequest = null; + handlePendingRequest(pendingRequest); + } + } + + @Override // from AbstractRemoteService + protected void handleOnDestroy() { + if (mPendingRequest != null) { + mPendingRequest.cancel(); + mPendingRequest = null; + } + } + + @Override // from AbstractRemoteService + public void dump(@NonNull String prefix, @NonNull PrintWriter pw) { + super.dump(prefix, pw); + pw.append(prefix).append("hasPendingRequest=") + .append(String.valueOf(mPendingRequest != null)).println(); + } + + @Override // from AbstractRemoteService + void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S> pendingRequest) { + if (mPendingRequest != null) { + if (mVerbose) { + Slog.v(mTag, "handlePendingRequestWhileUnBound(): cancelling " + mPendingRequest); + } + mPendingRequest.cancel(); + } + mPendingRequest = pendingRequest; + } +} diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java index 356a4daba66e..8d912fadf6d1 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -692,7 +692,7 @@ public class AppOpsService extends IAppOpsService.Stub { } }); - if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + if (!StorageManager.hasIsolatedStorage()) { StorageManagerInternal storageManagerInternal = LocalServices.getService( StorageManagerInternal.class); storageManagerInternal.addExternalStoragePolicy( diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java index dd960751ab21..01716a0ea40a 100644 --- a/services/core/java/com/android/server/BinderCallsStatsService.java +++ b/services/core/java/com/android/server/BinderCallsStatsService.java @@ -56,6 +56,7 @@ public class BinderCallsStatsService extends Binder { private static final String SETTINGS_DETAILED_TRACKING_KEY = "detailed_tracking"; private static final String SETTINGS_UPLOAD_DATA_KEY = "upload_data"; private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval"; + private static final String SETTINGS_MAX_CALL_STATS_KEY = "max_call_stats_count"; private boolean mEnabled; private final Uri mUri = Settings.Global.getUriFor(Settings.Global.BINDER_CALLS_STATS); @@ -97,6 +98,9 @@ public class BinderCallsStatsService extends Binder { mBinderCallsStats.setSamplingInterval(mParser.getInt( SETTINGS_SAMPLING_INTERVAL_KEY, BinderCallsStats.PERIODIC_SAMPLING_INTERVAL_DEFAULT)); + mBinderCallsStats.setMaxBinderCallStats(mParser.getInt( + SETTINGS_MAX_CALL_STATS_KEY, + BinderCallsStats.MAX_BINDER_CALL_STATS_COUNT_DEFAULT)); final boolean enabled = diff --git a/services/core/java/com/android/server/RuntimeService.java b/services/core/java/com/android/server/RuntimeService.java new file mode 100644 index 000000000000..ccfac80d22a7 --- /dev/null +++ b/services/core/java/com/android/server/RuntimeService.java @@ -0,0 +1,173 @@ +/* + * 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; + +import android.content.Context; +import android.os.Binder; +import android.service.runtime.DebugEntryProto; +import android.service.runtime.RuntimeServiceInfoProto; +import android.util.Slog; +import android.util.proto.ProtoOutputStream; + +import libcore.timezone.TimeZoneDataFiles; +import libcore.util.CoreLibraryDebug; +import libcore.util.DebugInfo; + +import com.android.internal.util.DumpUtils; +import com.android.timezone.distro.DistroException; +import com.android.timezone.distro.DistroVersion; +import com.android.timezone.distro.FileUtils; +import com.android.timezone.distro.TimeZoneDistro; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * This service exists only as a "dumpsys" target which reports information about the status of the + * runtime and related libraries. + */ +public class RuntimeService extends Binder { + + private static final String TAG = "RuntimeService"; + + private final Context mContext; + + public RuntimeService(Context context) { + mContext = context; + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) { + return; + } + + boolean protoFormat = hasOption(args, "--proto"); + ProtoOutputStream proto = null; + + DebugInfo coreLibraryDebugInfo = CoreLibraryDebug.getDebugInfo(); + addTimeZoneApkDebugInfo(coreLibraryDebugInfo); + + if (protoFormat) { + proto = new ProtoOutputStream(fd); + reportTimeZoneInfoProto(coreLibraryDebugInfo, proto); + } else { + reportTimeZoneInfo(coreLibraryDebugInfo, pw); + } + + if (protoFormat) { + proto.flush(); + } + } + + /** Returns {@code true} if {@code args} contains {@code arg}. */ + private static boolean hasOption(String[] args, String arg) { + for (String opt : args) { + if (arg.equals(opt)) { + return true; + } + } + return false; + } + + /** + * Add information to {@link DebugInfo} about the time zone data supplied by the + * "Time zone updates via APK" feature. + */ + private static void addTimeZoneApkDebugInfo(DebugInfo coreLibraryDebugInfo) { + // Add /data tz data set using the DistroVersion class (which libcore cannot use). + // This update mechanism will be removed after the time zone APEX is launched so this + // untidiness will disappear with it. + String debugKeyPrefix = "core_library.timezone.data_"; + String versionFileName = TimeZoneDataFiles.getDataTimeZoneFile( + TimeZoneDistro.DISTRO_VERSION_FILE_NAME); + addDistroVersionDebugInfo(versionFileName, debugKeyPrefix, coreLibraryDebugInfo); + } + + /** + * Prints {@code coreLibraryDebugInfo} to {@code pw}. + * + * <p>If you change this method, make sure to modify + * {@link #reportTimeZoneInfoProto(DebugInfo, ProtoOutputStream)} as well. + */ + private static void reportTimeZoneInfo(DebugInfo coreLibraryDebugInfo, + PrintWriter pw) { + pw.println("Core Library Debug Info: "); + for (DebugInfo.DebugEntry debugEntry : coreLibraryDebugInfo.getDebugEntries()) { + pw.print(debugEntry.getKey()); + pw.print(": \""); + pw.print(debugEntry.getStringValue()); + pw.println("\""); + } + } + + /** + * Adds {@code coreLibraryDebugInfo} to {@code protoStream}. + * + * <p>If you change this method, make sure to modify + * {@link #reportTimeZoneInfo(DebugInfo, PrintWriter)}. + */ + private static void reportTimeZoneInfoProto( + DebugInfo coreLibraryDebugInfo, ProtoOutputStream protoStream) { + for (DebugInfo.DebugEntry debugEntry : coreLibraryDebugInfo.getDebugEntries()) { + long entryToken = protoStream.start(RuntimeServiceInfoProto.DEBUG_ENTRY); + protoStream.write(DebugEntryProto.KEY, debugEntry.getKey()); + protoStream.write(DebugEntryProto.STRING_VALUE, debugEntry.getStringValue()); + protoStream.end(entryToken); + } + } + + /** + * Adds version information to {@code debugInfo} from the distro_version file that may exist + * at {@code distroVersionFileName}. If the file does not exist or cannot be read this is + * reported as debug information too. + */ + private static void addDistroVersionDebugInfo(String distroVersionFileName, + String debugKeyPrefix, DebugInfo debugInfo) { + File file = new File(distroVersionFileName); + String statusKey = debugKeyPrefix + "status"; + if (file.exists()) { + try { + byte[] versionBytes = + FileUtils.readBytes(file, DistroVersion.DISTRO_VERSION_FILE_LENGTH); + DistroVersion distroVersion = DistroVersion.fromBytes(versionBytes); + String formatVersionString = distroVersion.formatMajorVersion + "." + + distroVersion.formatMinorVersion; + debugInfo.addStringEntry(statusKey, "OK") + .addStringEntry(debugKeyPrefix + "formatVersion", formatVersionString) + .addStringEntry(debugKeyPrefix + "rulesVersion", + distroVersion.rulesVersion) + .addStringEntry(debugKeyPrefix + "revision", + distroVersion.revision); + } catch (IOException | DistroException e) { + debugInfo.addStringEntry(statusKey, "ERROR"); + debugInfo.addStringEntry(debugKeyPrefix + "exception_class", + e.getClass().getName()); + debugInfo.addStringEntry(debugKeyPrefix + "exception_msg", e.getMessage()); + logMessage("Error reading " + file, e); + } + } else { + debugInfo.addStringEntry(statusKey, "NOT_FOUND"); + } + } + + private static void logMessage(String msg, Throwable t) { + Slog.v(TAG, msg, t); + } +} diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 0e6f8dda44f6..e933bd0bc7ff 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -183,8 +183,7 @@ class StorageManagerService extends IStorageManager.Stub private static final String ZRAM_ENABLED_PROPERTY = "persist.sys.zram_enabled"; - private static final boolean ENABLE_ISOLATED_STORAGE = SystemProperties - .getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false); + private static final boolean ENABLE_ISOLATED_STORAGE = StorageManager.hasIsolatedStorage(); public static class Lifecycle extends SystemService { private StorageManagerService mStorageManagerService; diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index f0b472be1472..a2cbfaa02bfb 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -47,6 +47,7 @@ import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.telephony.emergency.EmergencyNumber; import android.util.LocalLog; import android.util.StatsLog; @@ -1664,6 +1665,14 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } @Override + public void notifyEmergencyNumberList(List<EmergencyNumber> emergencyNumberList) { + // TODO checkPermission, modify Listener constent documentation + // TODO implement multisim emergency number list update in listener + // TODO implement PhoneStateListenerTest + } + + + @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); diff --git a/services/core/java/com/android/server/WallpaperUpdateReceiver.java b/services/core/java/com/android/server/WallpaperUpdateReceiver.java new file mode 100644 index 000000000000..629e88250a11 --- /dev/null +++ b/services/core/java/com/android/server/WallpaperUpdateReceiver.java @@ -0,0 +1,62 @@ +/* + * 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; + +import android.app.ActivityThread; +import android.app.WallpaperManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.os.AsyncTask; +import android.util.Slog; + +/** + * Receiver responsible for updating the wallpaper when the device + * configuration has changed. + * + * @hide + */ +public class WallpaperUpdateReceiver extends BroadcastReceiver { + + private static final String TAG = "WallpaperUpdateReceiver"; + private static final boolean DEBUG = false; + + @Override + public void onReceive(final Context context, final Intent intent) { + if (DEBUG) Slog.d(TAG, "onReceive: " + intent); + + if (intent != null && Intent.ACTION_DEVICE_CUSTOMIZATION_READY.equals(intent.getAction())) { + AsyncTask.execute(this::updateWallpaper); + } + } + + private void updateWallpaper() { + try { + ActivityThread currentActivityThread = ActivityThread.currentActivityThread(); + Context uiContext = currentActivityThread.getSystemUiContext(); + WallpaperManager wallpaperManager = WallpaperManager.getInstance(uiContext); + if (DEBUG) Slog.d(TAG, "Set customized default_wallpaper."); + Bitmap blank = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8); + // set a blank wallpaper to force a redraw of default_wallpaper + wallpaperManager.setBitmap(blank); + wallpaperManager.setResource(com.android.internal.R.drawable.default_wallpaper); + } catch (Exception e) { + Slog.w(TAG, "Failed to customize system wallpaper." + e); + } + } +} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 80f47d5a000b..562e80d906b1 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -328,6 +328,7 @@ import com.android.server.AlarmManagerInternal; import com.android.server.AppOpsService; import com.android.server.AttributeCache; import com.android.server.DeviceIdleController; +import com.android.server.DisplayThread; import com.android.server.IntentResolver; import com.android.server.IoThread; import com.android.server.LocalServices; @@ -665,16 +666,50 @@ public class ActivityManagerService extends IActivityManager.Stub final class PidMap { private final SparseArray<ProcessRecord> mPidMap = new SparseArray<>(); + /** + * Puts the process record in the map. + * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this + * method. + */ void put(int key, ProcessRecord value) { - mPidMap.put(key, value); + synchronized (this) { + mPidMap.put(key, value); + } mAtmInternal.onProcessMapped(key, value.getWindowProcessController()); } + /** + * Removes the process record from the map. + * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this + * method. + */ void remove(int pid) { - mPidMap.remove(pid); + synchronized (this) { + mPidMap.remove(pid); + } mAtmInternal.onProcessUnMapped(pid); } + /** + * Removes the process record from the map if it has a thread. + * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this + * method. + */ + boolean removeIfNoThread(int pid) { + boolean removed = false; + synchronized (this) { + final ProcessRecord app = get(pid); + if (app != null && app.thread == null) { + mPidMap.remove(pid); + removed = true; + } + } + if (removed) { + mAtmInternal.onProcessUnMapped(pid); + } + return removed; + } + ProcessRecord get(int pid) { return mPidMap.get(pid); } @@ -1888,9 +1923,7 @@ public class ActivityManagerService extends IActivityManager.Stub app.getWindowProcessController().setPid(MY_PID); app.maxAdj = ProcessList.SYSTEM_ADJ; app.makeActive(mSystemThread.getApplicationThread(), mProcessStats); - synchronized (mPidsSelfLocked) { - mPidsSelfLocked.put(app.pid, app); - } + mPidsSelfLocked.put(app.pid, app); mProcessList.updateLruProcessLocked(app, false, null); updateOomAdjLocked(); } @@ -2262,7 +2295,8 @@ public class ActivityManagerService extends IActivityManager.Stub mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler); mActivityTaskManager = atm; - mActivityTaskManager.setActivityManagerService(mIntentFirewall, mPendingIntentController); + mActivityTaskManager.initialize(mIntentFirewall, mPendingIntentController, + DisplayThread.get().getLooper()); mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class); mProcessCpuThread = new Thread("CpuTracker") { @@ -4252,14 +4286,7 @@ public class ActivityManagerService extends IActivityManager.Stub private final void processStartTimedOutLocked(ProcessRecord app) { final int pid = app.pid; - boolean gone = false; - synchronized (mPidsSelfLocked) { - ProcessRecord knownApp = mPidsSelfLocked.get(pid); - if (knownApp != null && knownApp.thread == null) { - mPidsSelfLocked.remove(pid); - gone = true; - } - } + boolean gone = mPidsSelfLocked.removeIfNoThread(pid); if (gone) { Slog.w(TAG, "Process " + app + " failed to attach"); @@ -13111,11 +13138,8 @@ public class ActivityManagerService extends IActivityManager.Stub return true; } else if (app.pid > 0 && app.pid != MY_PID) { // Goodbye! - boolean removed; - synchronized (mPidsSelfLocked) { - mPidsSelfLocked.remove(app.pid); - mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); - } + mPidsSelfLocked.remove(app.pid); + mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid); if (app.isolated) { mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid); @@ -19346,7 +19370,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public boolean isAppStorageSandboxed(int pid, int uid) { - if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + if (!StorageManager.hasIsolatedStorage()) { return false; } if (uid == SHELL_UID || uid == ROOT_UID) { diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java index 2541352b6daa..24543b7974df 100644 --- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java @@ -25,10 +25,12 @@ import android.net.wifi.IWifiManager; import android.net.wifi.WifiActivityEnergyInfo; import android.os.BatteryStats; import android.os.Parcelable; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SynchronousResultReceiver; import android.os.SystemClock; +import android.os.ThreadLocalWorkSource; import android.telephony.ModemActivityInfo; import android.telephony.TelephonyManager; import android.util.IntArray; @@ -43,11 +45,9 @@ import com.android.internal.util.function.pooled.PooledLambda; import libcore.util.EmptyArray; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -74,7 +74,12 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { private final ScheduledExecutorService mExecutorService = Executors.newSingleThreadScheduledExecutor( (ThreadFactory) r -> { - Thread t = new Thread(r, "batterystats-worker"); + Thread t = new Thread( + () -> { + ThreadLocalWorkSource.setUid(Process.myUid()); + r.run(); + }, + "batterystats-worker"); t.setPriority(Thread.NORM_PRIORITY); return t; }); diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index a0977be0e25d..8c39d75ea6a4 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -58,8 +58,9 @@ import java.util.Set; /** * BROADCASTS * - * We keep two broadcast queues and associated bookkeeping, one for those at - * foreground priority, and one for normal (background-priority) broadcasts. + * We keep three broadcast queues and associated bookkeeping, one for those at + * foreground priority, and one for normal (background-priority) broadcasts, and one to + * offload special broadcasts that we know take a long time, such as BOOT_COMPLETED. */ public final class BroadcastQueue { private static final String TAG = "BroadcastQueue"; diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 4b19398cc73d..62f100926581 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -27,7 +27,6 @@ import static android.os.Process.getFreeMemory; import static android.os.Process.getTotalMemory; import static android.os.Process.killProcessQuiet; import static android.os.Process.startWebView; -import static android.os.storage.StorageManager.PROP_ISOLATED_STORAGE; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES; @@ -73,6 +72,7 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; +import android.os.storage.StorageManager; import android.os.storage.StorageManagerInternal; import android.text.TextUtils; import android.util.EventLog; @@ -1246,10 +1246,8 @@ public final class ProcessList { long startTime = SystemClock.elapsedRealtime(); if (app.pid > 0 && app.pid != ActivityManagerService.MY_PID) { checkSlow(startTime, "startProcess: removing from pids map"); - synchronized (mService.mPidsSelfLocked) { - mService.mPidsSelfLocked.remove(app.pid); - mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); - } + mService.mPidsSelfLocked.remove(app.pid); + mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); checkSlow(startTime, "startProcess: done removing from pids map"); app.setPid(0); } @@ -1281,8 +1279,7 @@ public final class ProcessList { final IPackageManager pm = AppGlobals.getPackageManager(); permGids = pm.getPackageGids(app.info.packageName, MATCH_DIRECT_BOOT_AUTO, app.userId); - if (SystemProperties.getBoolean(PROP_ISOLATED_STORAGE, false) - && mountExtStorageFull) { + if (StorageManager.hasIsolatedStorage() && mountExtStorageFull) { mountExternal = Zygote.MOUNT_EXTERNAL_FULL; } else { StorageManagerInternal storageManagerInternal = LocalServices.getService( @@ -1768,8 +1765,8 @@ public final class ProcessList { mService.cleanUpApplicationRecordLocked(oldApp, false, false, -1, true /*replacingPid*/); } + mService.mPidsSelfLocked.put(pid, app); synchronized (mService.mPidsSelfLocked) { - mService.mPidsSelfLocked.put(pid, app); if (!procAttached) { Message msg = mService.mHandler.obtainMessage(PROC_START_TIMEOUT_MSG); msg.obj = app; @@ -1929,10 +1926,8 @@ public final class ProcessList { .pendingStart)) { int pid = app.pid; if (pid > 0) { - synchronized (mService.mPidsSelfLocked) { - mService.mPidsSelfLocked.remove(pid); - mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); - } + mService.mPidsSelfLocked.remove(pid); + mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); mService.mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid); if (app.isolated) { mService.mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid); diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java index 2c2d4045b205..eaa7a83fab0d 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java @@ -19,19 +19,11 @@ package com.android.server.biometrics; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; -import android.hardware.biometrics.BiometricPrompt; -import android.hardware.biometrics.IBiometricPromptReceiver; -import android.os.Bundle; -import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.os.RemoteException; import android.security.KeyStore; -import android.text.TextUtils; import android.util.Slog; -import com.android.internal.statusbar.IStatusBarService; - import java.util.ArrayList; /** @@ -39,88 +31,15 @@ import java.util.ArrayList; */ public abstract class AuthenticationClient extends ClientMonitor { private long mOpId; - private Handler mHandler; public abstract int handleFailedAttempt(); public abstract void resetFailedAttempts(); - public abstract String getErrorString(int error, int vendorCode); - public abstract String getAcquiredString(int acquireInfo, int vendorCode); - /** - * @return one of {@link #TYPE_FINGERPRINT} {@link #TYPE_IRIS} or {@link #TYPE_FACE} - */ - public abstract int getBiometricType(); public static final int LOCKOUT_NONE = 0; public static final int LOCKOUT_TIMED = 1; public static final int LOCKOUT_PERMANENT = 2; private final boolean mRequireConfirmation; - // Callback mechanism received from the client - // (BiometricPrompt -> BiometricPromptService -> <Biometric>Service -> AuthenticationClient) - private IBiometricPromptReceiver mDialogReceiverFromClient; - private Bundle mBundle; - private IStatusBarService mStatusBarService; - private boolean mInLockout; - private TokenEscrow mEscrow; - protected boolean mDialogDismissed; - - /** - * Container that holds the identifier and authToken. For biometrics that require user - * confirmation, these should not be sent to their final destinations until the user confirms. - */ - class TokenEscrow { - final BiometricAuthenticator.Identifier mIdentifier; - final ArrayList<Byte> mToken; - - TokenEscrow(BiometricAuthenticator.Identifier identifier, ArrayList<Byte> token) { - mIdentifier = identifier; - mToken = token; - } - - BiometricAuthenticator.Identifier getIdentifier() { - return mIdentifier; - } - - ArrayList<Byte> getToken() { - return mToken; - } - } - - // Receives events from SystemUI and handles them before forwarding them to BiometricDialog - protected IBiometricPromptReceiver mDialogReceiver = new IBiometricPromptReceiver.Stub() { - @Override // binder call - public void onDialogDismissed(int reason) { - if (mBundle != null && mDialogReceiverFromClient != null) { - try { - if (reason != BiometricPrompt.DISMISSED_REASON_POSITIVE) { - // Positive button is used by passive modalities as a "confirm" button, - // do not send to client - mDialogReceiverFromClient.onDialogDismissed(reason); - } - if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) { - onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED, - 0 /* vendorCode */); - } else if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) { - // Have the service send the token to KeyStore, and send onAuthenticated - // to the application. - if (mEscrow != null) { - if (DEBUG) Slog.d(getLogTag(), "Confirmed"); - addTokenToKeyStore(mEscrow.getToken()); - notifyClientAuthenticationSucceeded(mEscrow.getIdentifier()); - mEscrow = null; - onAuthenticationConfirmed(); - } else { - Slog.e(getLogTag(), "Escrow is null!!!"); - } - } - mDialogDismissed = true; - } catch (RemoteException e) { - Slog.e(getLogTag(), "Remote exception", e); - } - stop(true /* initiatedByClient */); - } - } - }; /** * This method is called when authentication starts. @@ -133,25 +52,13 @@ public abstract class AuthenticationClient extends ClientMonitor { */ public abstract void onStop(); - /** - * This method is called when biometric authentication was confirmed by the user. The client - * should be removed. - */ - public abstract void onAuthenticationConfirmed(); - public AuthenticationClient(Context context, Metrics metrics, BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token, BiometricServiceBase.ServiceListener listener, int targetUserId, int groupId, long opId, - boolean restricted, String owner, Bundle bundle, - IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService, - boolean requireConfirmation) { + boolean restricted, String owner, int cookie, boolean requireConfirmation) { super(context, metrics, daemon, halDeviceId, token, listener, targetUserId, groupId, - restricted, owner); + restricted, owner, cookie); mOpId = opId; - mBundle = bundle; - mDialogReceiverFromClient = dialogReceiver; - mStatusBarService = statusBarService; - mHandler = new Handler(Looper.getMainLooper()); mRequireConfirmation = requireConfirmation; } @@ -164,175 +71,99 @@ public abstract class AuthenticationClient extends ClientMonitor { stop(false /* initiatedByClient */); } - @Override - public boolean onAcquired(int acquiredInfo, int vendorCode) { - // If the dialog is showing, the client doesn't need to receive onAcquired messages. - if (mBundle != null) { - try { - if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) { - mStatusBarService.onBiometricHelp(getAcquiredString(acquiredInfo, vendorCode)); - } - return false; // acquisition continues - } catch (RemoteException e) { - Slog.e(getLogTag(), "Remote exception when sending acquired message", e); - return true; // client failed - } finally { - // Good scans will keep the device awake - if (acquiredInfo == BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) { - notifyUserActivity(); - } - } - } else { - return super.onAcquired(acquiredInfo, vendorCode); - } - } - - @Override - public boolean onError(long deviceId, int error, int vendorCode) { - if (mDialogDismissed) { - // If user cancels authentication, the application has already received the - // ERROR_USER_CANCELED message from onDialogDismissed() - // and stopped the biometric hardware, so there is no need to send a - // ERROR_CANCELED message. - return true; - } - if (mBundle != null && error != BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED) { - try { - mStatusBarService.onBiometricError(getErrorString(error, vendorCode)); - } catch (RemoteException e) { - Slog.e(getLogTag(), "Remote exception when sending error", e); - } - } - return super.onError(deviceId, error, vendorCode); - } - - public void setTitleIfEmpty(CharSequence title) { - if (TextUtils.isEmpty(mBundle.getCharSequence(BiometricPrompt.KEY_TITLE))) { - mBundle.putCharSequence(BiometricPrompt.KEY_TITLE, title); - } - } - public boolean isBiometricPrompt() { - return mBundle != null; - } - - private void notifyClientAuthenticationSucceeded(BiometricAuthenticator.Identifier identifier) - throws RemoteException { - final BiometricServiceBase.ServiceListener listener = getListener(); - // Explicitly have if/else here to make it super obvious in case the code is - // touched in the future. - if (!getIsRestricted()) { - listener.onAuthenticationSucceeded( - getHalDeviceId(), identifier, getTargetUserId()); - } else { - listener.onAuthenticationSucceeded( - getHalDeviceId(), null, getTargetUserId()); - } + return getCookie() != 0; } - private void addTokenToKeyStore(ArrayList<Byte> token) { - // Send the token to KeyStore - final byte[] byteToken = new byte[token.size()]; - for (int i = 0; i < token.size(); i++) { - byteToken[i] = token.get(i); - } - KeyStore.getInstance().addAuthToken(byteToken); + public boolean getRequireConfirmation() { + return mRequireConfirmation; } @Override public boolean onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated, ArrayList<Byte> token) { - if (authenticated) { - mAlreadyDone = true; - if (mRequireConfirmation) { - // Store the token so it can be sent to keystore after the user presses confirm - mEscrow = new TokenEscrow(identifier, token); - } else { - addTokenToKeyStore(token); - } - } + final BiometricServiceBase.ServiceListener listener = getListener(); + mMetricsLogger.action(mMetrics.actionBiometricAuth(), authenticated); boolean result = false; - // If the biometric dialog is showing, notify authentication succeeded - if (mBundle != null) { - try { - if (authenticated) { - mStatusBarService.onBiometricAuthenticated(); - } else { - mStatusBarService.onBiometricHelp(getContext().getResources().getString( - com.android.internal.R.string.biometric_not_recognized)); + try { + if (authenticated) { + mAlreadyDone = true; + if (DEBUG) Slog.v(getLogTag(), "onAuthenticated(" + getOwnerString() + + ", ID:" + identifier.getBiometricId() + + ", isBP: " + isBiometricPrompt() + + ", listener: " + listener + + ", requireConfirmation: " + mRequireConfirmation); + if (listener != null) { + vibrateSuccess(); } - } catch (RemoteException e) { - Slog.e(getLogTag(), "Failed to notify Authenticated:", e); - } - } + result = true; + resetFailedAttempts(); + onStop(); - final BiometricServiceBase.ServiceListener listener = getListener(); - if (listener != null) { - try { - mMetricsLogger.action(mMetrics.actionBiometricAuth(), authenticated); - if (!authenticated) { - listener.onAuthenticationFailed(getHalDeviceId()); - } else { - if (DEBUG) { - Slog.v(getLogTag(), "onAuthenticated(owner=" + getOwnerString() - + ", id=" + identifier.getBiometricId()); - } - if (!mRequireConfirmation) { - notifyClientAuthenticationSucceeded(identifier); + final byte[] byteToken = new byte[token.size()]; + for (int i = 0; i < token.size(); i++) { + byteToken[i] = token.get(i); + } + if (isBiometricPrompt() && listener != null) { + // BiometricService will add the token to keystore + listener.onAuthenticationSucceededInternal(mRequireConfirmation, byteToken); + } else if (!isBiometricPrompt() && listener != null) { + KeyStore.getInstance().addAuthToken(byteToken); + try { + // Explicitly have if/else here to make it super obvious in case the code is + // touched in the future. + if (!getIsRestricted()) { + listener.onAuthenticationSucceeded( + getHalDeviceId(), identifier, getTargetUserId()); + } else { + listener.onAuthenticationSucceeded( + getHalDeviceId(), null, getTargetUserId()); + } + } catch (RemoteException e) { + Slog.e(getLogTag(), "Remote exception", e); } + } else { + // Client not listening + Slog.w(getLogTag(), "Client not listening"); + result = true; } - } catch (RemoteException e) { - Slog.w(getLogTag(), "Failed to notify Authenticated:", e); - result = true; // client failed - } - } else { - result = true; // client not listening - } - if (!authenticated) { - if (listener != null) { - vibrateError(); - } - // allow system-defined limit of number of attempts before giving up - int lockoutMode = handleFailedAttempt(); - if (lockoutMode != LOCKOUT_NONE) { - try { - mInLockout = true; - Slog.w(getLogTag(), "Forcing lockout (fp driver code should do this!), mode(" + - lockoutMode + ")"); + } else { + if (listener != null) { + vibrateError(); + } + // Allow system-defined limit of number of attempts before giving up + final int lockoutMode = handleFailedAttempt(); + if (lockoutMode != LOCKOUT_NONE) { + Slog.w(getLogTag(), "Forcing lockout (driver code should do this!), mode(" + + lockoutMode + ")"); stop(false); - int errorCode = lockoutMode == LOCKOUT_TIMED ? - BiometricConstants.BIOMETRIC_ERROR_LOCKOUT : - BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; - - // Send the lockout message to the system dialog - if (mBundle != null) { - mStatusBarService.onBiometricError( - getErrorString(errorCode, 0 /* vendorCode */)); - mHandler.postDelayed(() -> { - try { - listener.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */); - } catch (RemoteException e) { - Slog.w(getLogTag(), "RemoteException while sending error"); - } - }, BiometricPrompt.HIDE_DIALOG_DELAY); - } else { - listener.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */); + final int errorCode = lockoutMode == LOCKOUT_TIMED + ? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT + : BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; + if (listener != null) { + listener.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */, + getCookie()); + } + } else { + // Don't send onAuthenticationFailed if we're in lockout, it causes a + // janky UI on Keyguard/BiometricPrompt since "authentication failed" + // will show briefly and be replaced by "device locked out" message. + if (listener != null) { + if (isBiometricPrompt()) { + listener.onAuthenticationFailedInternal(getCookie(), + getRequireConfirmation()); + } else { + listener.onAuthenticationFailed(getHalDeviceId()); + } } - } catch (RemoteException e) { - Slog.w(getLogTag(), "Failed to notify lockout:", e); } + result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode } - result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode - } else { - if (listener != null) { - vibrateSuccess(); - } - // we have a valid biometric that doesn't require confirmation, done - result |= !mRequireConfirmation; - resetFailedAttempts(); - onStop(); + } catch (RemoteException e) { + Slog.e(getLogTag(), "Remote exception", e); + result = true; } return result; } @@ -353,16 +184,6 @@ public abstract class AuthenticationClient extends ClientMonitor { return result; } if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + " is authenticating..."); - - // If authenticating with system dialog, show the dialog - if (mBundle != null) { - try { - mStatusBarService.showBiometricDialog(mBundle, mDialogReceiver, - getBiometricType(), mRequireConfirmation, getTargetUserId()); - } catch (RemoteException e) { - Slog.e(getLogTag(), "Unable to show biometric dialog", e); - } - } } catch (RemoteException e) { Slog.e(getLogTag(), "startAuthentication failed", e); return ERROR_ESRCH; @@ -390,18 +211,6 @@ public abstract class AuthenticationClient extends ClientMonitor { } catch (RemoteException e) { Slog.e(getLogTag(), "stopAuthentication failed", e); return ERROR_ESRCH; - } finally { - // If the user already cancelled authentication (via some interaction with the - // dialog, we do not need to hide it since it's already hidden. - // If the device is in lockout, don't hide the dialog - it will automatically hide - // after BiometricPrompt.HIDE_DIALOG_DELAY - if (mBundle != null && !mDialogDismissed && !mInLockout) { - try { - mStatusBarService.hideBiometricDialog(); - } catch (RemoteException e) { - Slog.e(getLogTag(), "Unable to hide biometric dialog", e); - } - } } mAlreadyCancelled = true; diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 5f09189bd84a..add55eaad166 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -19,9 +19,17 @@ package com.android.server.biometrics; import static android.Manifest.permission.USE_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.Manifest.permission.USE_FINGERPRINT; +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE; import android.app.ActivityManager; +import android.app.ActivityTaskManager; import android.app.AppOpsManager; +import android.app.IActivityTaskManager; +import android.app.TaskStackListener; import android.app.UserSwitchObserver; import android.content.ContentResolver; import android.content.Context; @@ -32,9 +40,9 @@ import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; -import android.hardware.biometrics.IBiometricPromptReceiver; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.face.FaceManager; import android.hardware.face.IFaceService; import android.hardware.fingerprint.FingerprintManager; @@ -50,14 +58,21 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.provider.Settings; +import android.security.KeyStore; +import android.text.TextUtils; import android.util.Pair; import android.util.Slog; import com.android.internal.R; +import com.android.internal.statusbar.IStatusBarService; import com.android.server.SystemService; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Random; /** * System service that arbitrates the modality for BiometricPrompt to use. @@ -66,32 +81,10 @@ public class BiometricService extends SystemService { private static final String TAG = "BiometricService"; - /** - * No biometric methods or nothing has been enrolled. - * Move/expose these in BiometricPrompt if we ever want to allow applications to "blacklist" - * modalities when calling authenticate(). - */ - private static final int BIOMETRIC_NONE = 0; - - /** - * Constant representing fingerprint. - */ - private static final int BIOMETRIC_FINGERPRINT = 1 << 0; - - /** - * Constant representing iris. - */ - private static final int BIOMETRIC_IRIS = 1 << 1; - - /** - * Constant representing face. - */ - private static final int BIOMETRIC_FACE = 1 << 2; - private static final int[] FEATURE_ID = { - BIOMETRIC_FINGERPRINT, - BIOMETRIC_IRIS, - BIOMETRIC_FACE + TYPE_FINGERPRINT, + TYPE_IRIS, + TYPE_FACE }; private final AppOpsManager mAppOps; @@ -242,10 +235,367 @@ public class BiometricService extends SystemService { */ private final class BiometricServiceWrapper extends IBiometricService.Stub { + /** + * Authentication either just called and we have not transitioned to the CALLED state, or + * authentication terminated (success or error). + */ + private static final int STATE_AUTH_IDLE = 0; + /** + * Authentication was called and we are waiting for the <Biometric>Services to return their + * cookies before starting the hardware and showing the BiometricPrompt. + */ + private static final int STATE_AUTH_CALLED = 1; + /** + * Authentication started, BiometricPrompt is showing and the hardware is authenticating. + */ + private static final int STATE_AUTH_STARTED = 2; + /** + * Authentication is paused, waiting for the user to press "try again" button. Since the + * try again button requires us to cancel authentication, this represents the state where + * ERROR_CANCELED is not received yet. + */ + private static final int STATE_AUTH_PAUSED = 3; + /** + * Same as above, except the ERROR_CANCELED has been received. + */ + private static final int STATE_AUTH_PAUSED_CANCELED = 4; + /** + * Authentication is successful, but we're waiting for the user to press "confirm" button. + */ + private static final int STATE_AUTH_PENDING_CONFIRM = 5; + + final class AuthSession { + // Map of Authenticator/Cookie pairs. We expect to receive the cookies back from + // <Biometric>Services before we can start authenticating. Pairs that have been returned + // are moved to mModalitiesMatched. + final HashMap<Integer, Integer> mModalitiesWaiting; + // Pairs that have been matched. + final HashMap<Integer, Integer> mModalitiesMatched = new HashMap<>(); + + // The following variables are passed to authenticateInternal, which initiates the + // appropriate <Biometric>Services. + final IBinder mToken; + final long mSessionId; + final int mUserId; + // Original receiver from BiometricPrompt. + final IBiometricServiceReceiver mClientReceiver; + final String mOpPackageName; + // Info to be shown on BiometricDialog when all cookies are returned. + final Bundle mBundle; + final int mCallingUid; + final int mCallingPid; + final int mCallingUserId; + // Continue authentication with the same modality/modalities after "try again" is + // pressed + final int mModality; + + // The current state, which can be either idle, called, or started + private int mState = STATE_AUTH_IDLE; + // For explicit confirmation, do not send to keystore until the user has confirmed + // the authentication. + byte[] mTokenEscrow; + + AuthSession(HashMap<Integer, Integer> modalities, IBinder token, long sessionId, + int userId, IBiometricServiceReceiver receiver, String opPackageName, + Bundle bundle, int callingUid, int callingPid, int callingUserId, + int modality) { + mModalitiesWaiting = modalities; + mToken = token; + mSessionId = sessionId; + mUserId = userId; + mClientReceiver = receiver; + mOpPackageName = opPackageName; + mBundle = bundle; + mCallingUid = callingUid; + mCallingPid = callingPid; + mCallingUserId = callingUserId; + mModality = modality; + } + + boolean containsCookie(int cookie) { + if (mModalitiesWaiting != null && mModalitiesWaiting.containsValue(cookie)) { + return true; + } + if (mModalitiesMatched != null && mModalitiesMatched.containsValue(cookie)) { + return true; + } + return false; + } + } + + final class BiometricTaskStackListener extends TaskStackListener { + @Override + public void onTaskStackChanged() { + try { + final List<ActivityManager.RunningTaskInfo> runningTasks = + mActivityTaskManager.getTasks(1); + if (!runningTasks.isEmpty()) { + final String topPackage = runningTasks.get(0).topActivity.getPackageName(); + if (mCurrentAuthSession != null + && !topPackage.contentEquals(mCurrentAuthSession.mOpPackageName) + && mCurrentAuthSession.mState != STATE_AUTH_STARTED) { + // We only care about this state, since <Biometric>Service will + // cancel any client that's still in STATE_AUTH_STARTED + mStatusBarService.hideBiometricDialog(); + mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); + mCurrentAuthSession.mClientReceiver.onError( + BiometricConstants.BIOMETRIC_ERROR_CANCELED, + getContext().getString( + com.android.internal.R.string.biometric_error_canceled) + ); + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + } + } + } catch (RemoteException e) { + Slog.e(TAG, "Unable to get running tasks", e); + } + } + } + + private final IActivityTaskManager mActivityTaskManager = getContext().getSystemService( + ActivityTaskManager.class).getService(); + private final IStatusBarService mStatusBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + private final BiometricTaskStackListener mTaskStackListener = + new BiometricTaskStackListener(); + private final Random mRandom = new Random(); + + // The current authentication session, null if idle/done. We need to track both the current + // and pending sessions since errors may be sent to either. + private AuthSession mCurrentAuthSession; + private AuthSession mPendingAuthSession; + + // Wrap the client's receiver so we can do things with the BiometricDialog first + private final IBiometricServiceReceiverInternal mInternalReceiver = + new IBiometricServiceReceiverInternal.Stub() { + @Override + public void onAuthenticationSucceeded(boolean requireConfirmation, byte[] token) + throws RemoteException { + try { + if (!requireConfirmation) { + mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); + KeyStore.getInstance().addAuthToken(token); + mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(); + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + } else { + // Store the auth token and submit it to keystore after the confirmation + // button has been pressed. + mCurrentAuthSession.mTokenEscrow = token; + mCurrentAuthSession.mState = STATE_AUTH_PENDING_CONFIRM; + } + + // Notify SysUI that the biometric has been authenticated. SysUI already knows + // the implicit/explicit state and will react accordingly. + mStatusBarService.onBiometricAuthenticated(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + + @Override + public void onAuthenticationFailed(int cookie, boolean requireConfirmation) + throws RemoteException { + try { + mStatusBarService.onBiometricHelp(getContext().getResources().getString( + com.android.internal.R.string.biometric_not_recognized)); + if (requireConfirmation) { + mCurrentAuthSession.mState = STATE_AUTH_PAUSED; + mStatusBarService.showBiometricTryAgain(); + // Cancel authentication. Skip the token/package check since we are + // cancelling from system server. The interface is permission protected so + // this is fine. + cancelInternal(null /* token */, null /* package */, + false /* fromClient */); + } + mCurrentAuthSession.mClientReceiver.onAuthenticationFailed(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + + @Override + public void onError(int cookie, int error, String message) throws RemoteException { + Slog.d(TAG, "Error: " + error + " cookie: " + cookie); + // Errors can either be from the current auth session or the pending auth session. + // The pending auth session may receive errors such as ERROR_LOCKOUT before + // it becomes the current auth session. Similarly, the current auth session may + // receive errors such as ERROR_CANCELED while the pending auth session is preparing + // to be started. Thus we must match error messages with their cookies to be sure + // of their intended receivers. + try { + if (mCurrentAuthSession != null && mCurrentAuthSession.containsCookie(cookie)) { + if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) { + mStatusBarService.onBiometricError(message); + mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); + if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) { + mCurrentAuthSession.mClientReceiver.onError(error, message); + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + mStatusBarService.hideBiometricDialog(); + } else { + // Send errors after the dialog is dismissed. + mHandler.postDelayed(() -> { + try { + mCurrentAuthSession.mClientReceiver.onError(error, message); + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + }, BiometricPrompt.HIDE_DIALOG_DELAY); + } + } else if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED + || mCurrentAuthSession.mState == STATE_AUTH_PAUSED_CANCELED) { + if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED + && error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) { + // Skip the first ERROR_CANCELED message when this happens, since + // "try again" requires us to cancel authentication but keep + // the prompt showing. + mCurrentAuthSession.mState = STATE_AUTH_PAUSED_CANCELED; + } else { + // In the "try again" state, we should forward canceled errors to + // the client and and clean up. + mCurrentAuthSession.mClientReceiver.onError(error, message); + mStatusBarService.onBiometricError(message); + mActivityTaskManager.unregisterTaskStackListener( + mTaskStackListener); + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + } + } else { + Slog.e(TAG, "Impossible session error state: " + + mCurrentAuthSession.mState); + } + } else if (mPendingAuthSession != null + && mPendingAuthSession.containsCookie(cookie)) { + if (mPendingAuthSession.mState == STATE_AUTH_CALLED) { + mPendingAuthSession.mClientReceiver.onError(error, message); + mPendingAuthSession.mState = STATE_AUTH_IDLE; + mPendingAuthSession = null; + } else { + Slog.e(TAG, "Impossible pending session error state: " + + mPendingAuthSession.mState); + } + } + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + + @Override + public void onAcquired(int acquiredInfo, String message) throws RemoteException { + if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) { + try { + mStatusBarService.onBiometricHelp(message); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + } + + @Override + public void onDialogDismissed(int reason) throws RemoteException { + if (reason != BiometricPrompt.DISMISSED_REASON_POSITIVE) { + // Positive button is used by passive modalities as a "confirm" button, + // do not send to client + mCurrentAuthSession.mClientReceiver.onDialogDismissed(reason); + // Cancel authentication. Skip the token/package check since we are cancelling + // from system server. The interface is permission protected so this is fine. + cancelInternal(null /* token */, null /* package */, false /* fromClient */); + } + if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) { + mCurrentAuthSession.mClientReceiver.onError( + BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED, + getContext().getString( + com.android.internal.R.string.biometric_error_user_canceled)); + } else if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) { + // Have the service send the token to KeyStore, and send onAuthenticated + // to the application + KeyStore.getInstance().addAuthToken(mCurrentAuthSession.mTokenEscrow); + mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(); + } + mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + } + + @Override + public void onTryAgainPressed() { + Slog.d(TAG, "onTryAgainPressed"); + // No need to check permission, since it can only be invoked by SystemUI + // (or system server itself). + mHandler.post(() -> { + authenticateInternal(mCurrentAuthSession.mToken, + mCurrentAuthSession.mSessionId, + mCurrentAuthSession.mUserId, + mCurrentAuthSession.mClientReceiver, + mCurrentAuthSession.mOpPackageName, + mCurrentAuthSession.mBundle, + mCurrentAuthSession.mCallingUid, + mCurrentAuthSession.mCallingPid, + mCurrentAuthSession.mCallingUserId, + mCurrentAuthSession.mModality); + }); + } + }; + + @Override // Binder call + public void onReadyForAuthentication(int cookie, boolean requireConfirmation, int userId) { + checkInternalPermission(); + + Iterator it = mPendingAuthSession.mModalitiesWaiting.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<Integer, Integer> pair = (Map.Entry) it.next(); + if (pair.getValue() == cookie) { + mPendingAuthSession.mModalitiesMatched.put(pair.getKey(), pair.getValue()); + mPendingAuthSession.mModalitiesWaiting.remove(pair.getKey()); + Slog.d(TAG, "Matched cookie: " + cookie + ", " + + mPendingAuthSession.mModalitiesWaiting.size() + " remaining"); + break; + } + } + + if (mPendingAuthSession.mModalitiesWaiting.isEmpty()) { + final boolean mContinuing = mCurrentAuthSession != null + && mCurrentAuthSession.mState == STATE_AUTH_PAUSED; + mCurrentAuthSession = mPendingAuthSession; + mPendingAuthSession = null; + + mCurrentAuthSession.mState = STATE_AUTH_STARTED; + try { + int modality = TYPE_NONE; + it = mCurrentAuthSession.mModalitiesMatched.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<Integer, Integer> pair = (Map.Entry) it.next(); + if (pair.getKey() == TYPE_FINGERPRINT) { + mFingerprintService.startPreparedClient(pair.getValue()); + } else if (pair.getKey() == TYPE_IRIS) { + Slog.e(TAG, "Iris unsupported"); + } else if (pair.getKey() == TYPE_FACE) { + mFaceService.startPreparedClient(pair.getValue()); + } else { + Slog.e(TAG, "Unknown modality: " + pair.getKey()); + } + modality |= pair.getKey(); + } + + if (!mContinuing) { + mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle, + mInternalReceiver, modality, requireConfirmation, userId); + mActivityTaskManager.registerTaskStackListener(mTaskStackListener); + } + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + } + @Override // Binder call public void authenticate(IBinder token, long sessionId, int userId, - IBiometricServiceReceiver receiver, int flags, String opPackageName, - Bundle bundle, IBiometricPromptReceiver dialogReceiver) throws RemoteException { + IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle) + throws RemoteException { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final int callingUserId = UserHandle.getCallingUserId(); @@ -261,16 +611,45 @@ public class BiometricService extends SystemService { checkInternalPermission(); } - if (token == null || receiver == null || opPackageName == null || bundle == null - || dialogReceiver == null) { + if (token == null || receiver == null || opPackageName == null || bundle == null) { Slog.e(TAG, "Unable to authenticate, one or more null arguments"); return; } // Check the usage of this in system server. Need to remove this check if it becomes // a public API. - if (bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false)) { + final boolean useDefaultTitle = + bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false); + if (useDefaultTitle) { checkInternalPermission(); + // Set the default title if necessary + try { + if (useDefaultTitle) { + final List<ActivityManager.RunningAppProcessInfo> procs = + ActivityManager.getService().getRunningAppProcesses(); + for (int i = 0; i < procs.size(); i++) { + final ActivityManager.RunningAppProcessInfo info = procs.get(i); + if (info.uid == callingUid + && info.importance == IMPORTANCE_FOREGROUND) { + PackageManager pm = getContext().getPackageManager(); + final CharSequence label = pm.getApplicationLabel( + pm.getApplicationInfo(info.processName, + PackageManager.GET_META_DATA)); + final String title = getContext() + .getString(R.string.biometric_dialog_default_title, label); + if (TextUtils.isEmpty( + bundle.getCharSequence(BiometricPrompt.KEY_TITLE))) { + bundle.putCharSequence(BiometricPrompt.KEY_TITLE, title); + } + break; + } + } + } + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } catch (PackageManager.NameNotFoundException e) { + Slog.e(TAG, "Name not found", e); + } } mHandler.post(() -> { @@ -285,13 +664,13 @@ public class BiometricService extends SystemService { getContext().getString(R.string.biometric_error_hw_unavailable); switch (error) { case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT: - receiver.onError(0 /* deviceId */, error, hardwareUnavailable); + receiver.onError(error, hardwareUnavailable); break; case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE: - receiver.onError(0 /* deviceId */, error, hardwareUnavailable); + receiver.onError(error, hardwareUnavailable); break; case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS: - receiver.onError(0 /* deviceId */, error, + receiver.onError(error, getErrorString(modality, error, 0 /* vendorCode */)); break; default: @@ -304,60 +683,91 @@ public class BiometricService extends SystemService { return; } - // Actually start authentication mCurrentModality = modality; - try { - // No polymorphism :( - if (mCurrentModality == BIOMETRIC_FINGERPRINT) { - mFingerprintService.authenticateFromService(token, sessionId, userId, - receiver, flags, opPackageName, bundle, dialogReceiver, - callingUid, callingPid, callingUserId); - } else if (mCurrentModality == BIOMETRIC_IRIS) { - Slog.w(TAG, "Unsupported modality"); - } else if (mCurrentModality == BIOMETRIC_FACE) { - mFaceService.authenticateFromService(true /* requireConfirmation */, - token, sessionId, userId, receiver, flags, opPackageName, - bundle, dialogReceiver, callingUid, callingPid, callingUserId); - } else { - Slog.w(TAG, "Unsupported modality"); - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to start authentication", e); - } + + // Actually start authentication + authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle, + callingUid, callingPid, callingUserId, modality); }); } + /** + * authenticate() (above) which is called from BiometricPrompt determines which + * modality/modalities to start authenticating with. authenticateInternal() should only be + * used for: + * 1) Preparing <Biometric>Services for authentication when BiometricPrompt#authenticate is, + * invoked, shortly after which BiometricPrompt is shown and authentication starts + * 2) Preparing <Biometric>Services for authentication when BiometricPrompt is already shown + * and the user has pressed "try again" + */ + private void authenticateInternal(IBinder token, long sessionId, int userId, + IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, + int callingUid, int callingPid, int callingUserId, int modality) { + try { + // Generate random cookies to pass to the services that should prepare to start + // authenticating. Store the cookie here and wait for all services to "ack" + // with the cookie. Once all cookies are received, we can show the prompt + // and let the services start authenticating. The cookie should be non-zero. + final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1; + Slog.d(TAG, "Creating auth session. Modality: " + modality + + ", cookie: " + cookie); + final HashMap<Integer, Integer> authenticators = new HashMap<>(); + authenticators.put(modality, cookie); + mPendingAuthSession = new AuthSession(authenticators, token, sessionId, userId, + receiver, opPackageName, bundle, callingUid, callingPid, callingUserId, + modality); + mPendingAuthSession.mState = STATE_AUTH_CALLED; + // No polymorphism :( + if ((modality & TYPE_FINGERPRINT) != 0) { + mFingerprintService.prepareForAuthentication(token, sessionId, userId, + mInternalReceiver, opPackageName, cookie, + callingUid, callingPid, callingUserId); + } + if ((modality & TYPE_IRIS) != 0) { + Slog.w(TAG, "Iris unsupported"); + } + if ((modality & TYPE_FACE) != 0) { + mFaceService.prepareForAuthentication(true /* requireConfirmation */, + token, sessionId, userId, mInternalReceiver, opPackageName, + cookie, callingUid, callingPid, callingUserId); + } + } catch (RemoteException e) { + Slog.e(TAG, "Unable to start authentication", e); + } + } + @Override // Binder call public void cancelAuthentication(IBinder token, String opPackageName) throws RemoteException { checkPermission(); - if (token == null || opPackageName == null) { Slog.e(TAG, "Unable to cancel, one or more null arguments"); return; } - final int callingUid = Binder.getCallingUid(); - final int callingPid = Binder.getCallingPid(); - final int callingUserId = UserHandle.getCallingUserId(); - - mHandler.post(() -> { - try { - if (mCurrentModality == BIOMETRIC_FINGERPRINT) { - mFingerprintService.cancelAuthenticationFromService(token, opPackageName, - callingUid, callingPid, callingUserId); - } else if (mCurrentModality == BIOMETRIC_IRIS) { - Slog.w(TAG, "Unsupported modality"); - } else if (mCurrentModality == BIOMETRIC_FACE) { - mFaceService.cancelAuthenticationFromService(token, opPackageName, - callingUid, callingPid, callingUserId); - } else { - Slog.w(TAG, "Unsupported modality"); + // We need to check the current authenticators state. If we're pending confirm + // or idle, we need to dismiss the dialog and send an ERROR_CANCELED to the client, + // since we won't be getting an onError from the driver. + if (mCurrentAuthSession != null && mCurrentAuthSession.mState != STATE_AUTH_STARTED) { + mHandler.post(() -> { + try { + // Send error to client + mCurrentAuthSession.mClientReceiver.onError( + BiometricConstants.BIOMETRIC_ERROR_CANCELED, + getContext().getString( + com.android.internal.R.string.biometric_error_user_canceled) + ); + + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + mStatusBarService.hideBiometricDialog(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to cancel authentication"); - } - }); + }); + } else { + cancelInternal(token, opPackageName, true /* fromClient */); + } } @Override // Binder call @@ -402,6 +812,31 @@ public class BiometricService extends SystemService { Binder.restoreCallingIdentity(ident); } } + + void cancelInternal(IBinder token, String opPackageName, boolean fromClient) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int callingUserId = UserHandle.getCallingUserId(); + mHandler.post(() -> { + try { + // TODO: For multiple modalities, send a single ERROR_CANCELED only when all + // drivers have canceled authentication. + if ((mCurrentModality & TYPE_FINGERPRINT) != 0) { + mFingerprintService.cancelAuthenticationFromService(token, opPackageName, + callingUid, callingPid, callingUserId, fromClient); + } + if ((mCurrentModality & TYPE_IRIS) != 0) { + Slog.w(TAG, "Iris unsupported"); + } + if ((mCurrentModality & TYPE_FACE) != 0) { + mFaceService.cancelAuthenticationFromService(token, opPackageName, + callingUid, callingPid, callingUserId, fromClient); + } + } catch (RemoteException e) { + Slog.e(TAG, "Unable to cancel authentication"); + } + }); + } } private void checkAppOp(String opPackageName, int callingUid) { @@ -413,7 +848,7 @@ public class BiometricService extends SystemService { } private void checkInternalPermission() { - getContext().enforceCallingPermission(USE_BIOMETRIC_INTERNAL, + getContext().enforceCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL, "Must have USE_BIOMETRIC_INTERNAL permission"); } @@ -490,16 +925,19 @@ public class BiometricService extends SystemService { * returns errors through the callback (no biometric feature, hardware not detected, no * templates enrolled, etc). This service must not start authentication if errors are sent. * - * @Returns A pair [Modality, Error] with Modality being one of {@link #BIOMETRIC_NONE}, - * {@link #BIOMETRIC_FINGERPRINT}, {@link #BIOMETRIC_IRIS}, {@link #BIOMETRIC_FACE} + * @Returns A pair [Modality, Error] with Modality being one of + * {@link BiometricAuthenticator#TYPE_NONE}, + * {@link BiometricAuthenticator#TYPE_FINGERPRINT}, + * {@link BiometricAuthenticator#TYPE_IRIS}, + * {@link BiometricAuthenticator#TYPE_FACE} * and the error containing one of the {@link BiometricConstants} errors. */ private Pair<Integer, Integer> checkAndGetBiometricModality(int callingUid) { - int modality = BIOMETRIC_NONE; + int modality = TYPE_NONE; // No biometric features, send error if (mAuthenticators.isEmpty()) { - return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT); + return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT); } // Assuming that authenticators are listed in priority-order, the rest of this function @@ -512,13 +950,13 @@ public class BiometricService extends SystemService { boolean hasTemplatesEnrolled = false; boolean enabledForApps = false; - int firstHwAvailable = BIOMETRIC_NONE; + int firstHwAvailable = TYPE_NONE; for (int i = 0; i < mAuthenticators.size(); i++) { modality = mAuthenticators.get(i).getType(); BiometricAuthenticator authenticator = mAuthenticators.get(i).getAuthenticator(); if (authenticator.isHardwareDetected()) { isHardwareDetected = true; - if (firstHwAvailable == BIOMETRIC_NONE) { + if (firstHwAvailable == TYPE_NONE) { // Store the first one since we want to return the error in correct priority // order. firstHwAvailable = modality; @@ -538,13 +976,13 @@ public class BiometricService extends SystemService { // Check error conditions if (!isHardwareDetected) { - return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); + return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); } else if (!hasTemplatesEnrolled) { // Return the modality here so the correct error string can be sent. This error is // preferred over !enabledForApps return new Pair<>(firstHwAvailable, BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS); } else if (!enabledForApps) { - return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); + return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); } return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS); @@ -552,11 +990,11 @@ public class BiometricService extends SystemService { private boolean isEnabledForApp(int modality) { switch(modality) { - case BIOMETRIC_FINGERPRINT: + case TYPE_FINGERPRINT: return true; - case BIOMETRIC_IRIS: + case TYPE_IRIS: return true; - case BIOMETRIC_FACE: + case TYPE_FACE: return mSettingObserver.getFaceEnabledForApps(); default: Slog.w(TAG, "Unsupported modality: " + modality); @@ -566,12 +1004,12 @@ public class BiometricService extends SystemService { private String getErrorString(int type, int error, int vendorCode) { switch (type) { - case BIOMETRIC_FINGERPRINT: + case TYPE_FINGERPRINT: return FingerprintManager.getErrorString(getContext(), error, vendorCode); - case BIOMETRIC_IRIS: + case TYPE_IRIS: Slog.w(TAG, "Modality not supported"); return null; // not supported - case BIOMETRIC_FACE: + case TYPE_FACE: return FaceManager.getErrorString(getContext(), error, vendorCode); default: Slog.w(TAG, "Unable to get error string for modality: " + type); @@ -581,12 +1019,12 @@ public class BiometricService extends SystemService { private BiometricAuthenticator getAuthenticator(int type) { switch (type) { - case BIOMETRIC_FINGERPRINT: + case TYPE_FINGERPRINT: return (FingerprintManager) getContext().getSystemService(Context.FINGERPRINT_SERVICE); - case BIOMETRIC_IRIS: + case TYPE_IRIS: return null; - case BIOMETRIC_FACE: + case TYPE_FACE: return (FaceManager) getContext().getSystemService(Context.FACE_SERVICE); default: @@ -596,11 +1034,11 @@ public class BiometricService extends SystemService { private boolean hasFeature(int type) { switch (type) { - case BIOMETRIC_FINGERPRINT: + case TYPE_FINGERPRINT: return mHasFeatureFingerprint; - case BIOMETRIC_IRIS: + case TYPE_IRIS: return mHasFeatureIris; - case BIOMETRIC_FACE: + case TYPE_FACE: return mHasFeatureFace; default: return false; diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java index 74d742af5b84..9649ccd3c750 100644 --- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java +++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java @@ -16,7 +16,6 @@ package com.android.server.biometrics; -import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; import android.app.ActivityManager; @@ -36,8 +35,9 @@ import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; -import android.hardware.biometrics.IBiometricPromptReceiver; +import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.fingerprint.Fingerprint; import android.os.Binder; import android.os.Bundle; @@ -56,7 +56,6 @@ import android.util.Slog; import android.util.SparseBooleanArray; import android.util.SparseIntArray; -import com.android.internal.R; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.server.SystemService; @@ -106,6 +105,7 @@ public abstract class BiometricServiceBase extends SystemService protected final AppOpsManager mAppOps; protected final H mHandler = new H(); + private IBiometricService mBiometricService; private ClientMonitor mCurrentClient; private ClientMonitor mPendingClient; private PerformanceStats mPerformanceStats; @@ -223,12 +223,9 @@ public abstract class BiometricServiceBase extends SystemService public AuthenticationClientImpl(Context context, DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId, - boolean restricted, String owner, Bundle bundle, - IBiometricPromptReceiver dialogReceiver, - IStatusBarService statusBarService, boolean requireConfirmation) { - super(context, getMetrics(), daemon, halDeviceId, token, listener, - targetUserId, groupId, opId, restricted, owner, bundle, dialogReceiver, - statusBarService, requireConfirmation); + boolean restricted, String owner, int cookie, boolean requireConfirmation) { + super(context, getMetrics(), daemon, halDeviceId, token, listener, targetUserId, + groupId, opId, restricted, owner, cookie, requireConfirmation); } @Override @@ -279,11 +276,6 @@ public abstract class BiometricServiceBase extends SystemService } return AuthenticationClient.LOCKOUT_NONE; } - - @Override - public void onAuthenticationConfirmed() { - removeClient(mCurrentClient); - } } protected class EnrollClientImpl extends EnrollClient { @@ -345,18 +337,28 @@ public abstract class BiometricServiceBase extends SystemService default void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) throws RemoteException {}; - void onAcquired(long deviceId, int acquiredInfo, int vendorCode) - throws RemoteException; + void onAcquired(long deviceId, int acquiredInfo, int vendorCode) throws RemoteException; - void onAuthenticationSucceeded(long deviceId, - BiometricAuthenticator.Identifier biometric, int userId) - throws RemoteException; + default void onAuthenticationSucceeded(long deviceId, + BiometricAuthenticator.Identifier biometric, int userId) throws RemoteException { + throw new UnsupportedOperationException("Stub!"); + } - void onAuthenticationFailed(long deviceId) - throws RemoteException; + default void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] token) + throws RemoteException { + throw new UnsupportedOperationException("Stub!"); + } - void onError(long deviceId, int error, int vendorCode) - throws RemoteException; + default void onAuthenticationFailed(long deviceId) throws RemoteException { + throw new UnsupportedOperationException("Stub!"); + } + + default void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation) + throws RemoteException { + throw new UnsupportedOperationException("Stub!"); + } + + void onError(long deviceId, int error, int vendorCode, int cookie) throws RemoteException; default void onRemoved(BiometricAuthenticator.Identifier identifier, int remaining) throws RemoteException {}; @@ -366,6 +368,37 @@ public abstract class BiometricServiceBase extends SystemService } /** + * Wraps the callback interface from Service -> BiometricPrompt + */ + protected abstract class BiometricServiceListener implements ServiceListener { + private IBiometricServiceReceiverInternal mWrapperReceiver; + + public BiometricServiceListener(IBiometricServiceReceiverInternal wrapperReceiver) { + mWrapperReceiver = wrapperReceiver; + } + + public IBiometricServiceReceiverInternal getWrapperReceiver() { + return mWrapperReceiver; + } + + @Override + public void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] token) + throws RemoteException { + if (getWrapperReceiver() != null) { + getWrapperReceiver().onAuthenticationSucceeded(requireConfirmation, token); + } + } + + @Override + public void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation) + throws RemoteException { + if (getWrapperReceiver() != null) { + getWrapperReceiver().onAuthenticationFailed(cookie, requireConfirmation); + } + } + } + + /** * Wraps a portion of the interface from Service -> Daemon that is used by the ClientMonitor * subclasses. */ @@ -706,30 +739,6 @@ public abstract class BiometricServiceBase extends SystemService } mHandler.post(() -> { - if (client.isBiometricPrompt()) { - try { - final List<ActivityManager.RunningAppProcessInfo> procs = - ActivityManager.getService().getRunningAppProcesses(); - for (int i = 0; i < procs.size(); i++) { - final ActivityManager.RunningAppProcessInfo info = procs.get(i); - if (info.uid == callingUid && info.importance == IMPORTANCE_FOREGROUND) { - PackageManager pm = getContext().getPackageManager(); - final CharSequence label = pm.getApplicationLabel( - pm.getApplicationInfo(info.processName, - PackageManager.GET_META_DATA)); - final String title = getContext() - .getString(R.string.biometric_dialog_default_title, label); - client.setTitleIfEmpty(title); - break; - } - } - } catch (RemoteException e) { - Slog.e(getTag(), "Unable to get application name", e); - } catch (PackageManager.NameNotFoundException e) { - Slog.e(getTag(), "Unable to get application name", e); - } - } - mMetricsLogger.histogram(getMetrics().tagAuthToken(), opId != 0L ? 1 : 0); // Get performance stats object for this user. @@ -751,29 +760,37 @@ public abstract class BiometricServiceBase extends SystemService final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final int callingUserId = UserHandle.getCallingUserId(); - cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid, callingUserId); + cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid, callingUserId, + true /* fromClient */); } protected void cancelAuthenticationInternal(final IBinder token, final String opPackageName, - int callingUid, int callingPid, int callingUserId) { - if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid, - callingUserId)) { - if (DEBUG) Slog.v(getTag(), "cancelAuthentication(): reject " + opPackageName); - return; + int callingUid, int callingPid, int callingUserId, boolean fromClient) { + if (fromClient) { + // Only check this if cancel was called from the client (app). If cancel was called + // from BiometricService, it means the dialog was dismissed due to user interaction. + if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid, + callingUserId)) { + if (DEBUG) Slog.v(getTag(), "cancelAuthentication(): reject " + opPackageName); + return; + } } mHandler.post(() -> { ClientMonitor client = mCurrentClient; if (client instanceof AuthenticationClient) { - if (client.getToken() == token) { - if (DEBUG) Slog.v(getTag(), "stop client " + client.getOwnerString()); + if (client.getToken() == token || !fromClient) { + if (DEBUG) Slog.v(getTag(), "Stopping client " + client.getOwnerString() + + ", fromClient: " + fromClient); + // If cancel was from BiometricService, it means the dialog was dismissed + // and authentication should be canceled. client.stop(client.getToken() == token); } else { - if (DEBUG) Slog.v(getTag(), "can't stop client " - + client.getOwnerString() + " since tokens don't match"); + if (DEBUG) Slog.v(getTag(), "Can't stop client " + client.getOwnerString() + + " since tokens don't match. fromClient: " + fromClient); } } else if (client != null) { - if (DEBUG) Slog.v(getTag(), "can't cancel non-authenticating client " + if (DEBUG) Slog.v(getTag(), "Can't cancel non-authenticating client " + client.getOwnerString()); } }); @@ -805,8 +822,7 @@ public abstract class BiometricServiceBase extends SystemService int lockoutMode = getLockoutMode(); if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) { - Slog.v(getTag(), "In lockout mode(" + lockoutMode + - ") ; disallowing authentication"); + Slog.v(getTag(), "In lockout mode(" + lockoutMode + ") ; disallowing authentication"); int errorCode = lockoutMode == AuthenticationClient.LOCKOUT_TIMED ? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT : BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; @@ -919,7 +935,6 @@ public abstract class BiometricServiceBase extends SystemService if (currentClient != null) { if (DEBUG) Slog.v(getTag(), "request stop current client " + currentClient.getOwnerString()); - // This check only matters for FingerprintService, since enumerate may call back // multiple times. if (currentClient instanceof FingerprintService.EnumerateClientImpl || @@ -940,15 +955,49 @@ public abstract class BiometricServiceBase extends SystemService mHandler.removeCallbacks(mResetClientState); mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT); } else if (newClient != null) { + // For BiometricPrompt clients, do not start until + // <Biometric>Service#startPreparedClient is called. BiometricService waits until all + // modalities are ready before initiating authentication. + if (newClient instanceof AuthenticationClient) { + AuthenticationClient client = (AuthenticationClient) newClient; + if (client.isBiometricPrompt()) { + if (DEBUG) Slog.v(getTag(), "Returning cookie: " + client.getCookie()); + mCurrentClient = newClient; + if (mBiometricService == null) { + mBiometricService = IBiometricService.Stub.asInterface( + ServiceManager.getService(Context.BIOMETRIC_SERVICE)); + } + try { + mBiometricService.onReadyForAuthentication(client.getCookie(), + client.getRequireConfirmation(), client.getTargetUserId()); + } catch (RemoteException e) { + Slog.e(getTag(), "Remote exception", e); + } + return; + } + } + + // We are not a BiometricPrompt client, start the client immediately mCurrentClient = newClient; - if (DEBUG) Slog.v(getTag(), "starting client " - + newClient.getClass().getSuperclass().getSimpleName() - + "(" + newClient.getOwnerString() + ")" - + ", initiatedByClient = " + initiatedByClient); - notifyClientActiveCallbacks(true); + startCurrentClient(mCurrentClient.getCookie()); + } + } - newClient.start(); + protected void startCurrentClient(int cookie) { + if (mCurrentClient == null) { + Slog.e(getTag(), "Trying to start null client!"); + return; + } + if (DEBUG) Slog.v(getTag(), "starting client " + + mCurrentClient.getClass().getSuperclass().getSimpleName() + + "(" + mCurrentClient.getOwnerString() + ")" + + " cookie: " + cookie + "/" + mCurrentClient.getCookie()); + if (cookie != mCurrentClient.getCookie()) { + Slog.e(getTag(), "Mismatched cookie"); + return; } + notifyClientActiveCallbacks(true); + mCurrentClient.start(); } protected void removeClient(ClientMonitor client) { diff --git a/services/core/java/com/android/server/biometrics/ClientMonitor.java b/services/core/java/com/android/server/biometrics/ClientMonitor.java index a7ada2f6556c..d19aff69b832 100644 --- a/services/core/java/com/android/server/biometrics/ClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/ClientMonitor.java @@ -58,6 +58,9 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient { private IBinder mToken; private BiometricServiceBase.ServiceListener mListener; + // Currently only used for authentication client. The cookie generated by BiometricService + // is never 0. + private final int mCookie; protected final MetricsLogger mMetricsLogger; protected final Metrics mMetrics; @@ -80,7 +83,7 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient { public ClientMonitor(Context context, Metrics metrics, BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token, BiometricServiceBase.ServiceListener listener, int userId, int groupId, - boolean restricted, String owner) { + boolean restricted, String owner, int cookie) { mContext = context; mMetrics = metrics; mDaemon = daemon; @@ -91,6 +94,7 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient { mGroupId = groupId; mIsRestricted = restricted; mOwner = owner; + mCookie = cookie; mSuccessVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); mErrorVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK); mMetricsLogger = new MetricsLogger(); @@ -107,6 +111,10 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient { return mMetrics.logTag(); } + public int getCookie() { + return mCookie; + } + /** * Contacts the biometric's HAL to start the client. * @return 0 on success, errno from driver on failure @@ -174,7 +182,7 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient { public boolean onError(long deviceId, int error, int vendorCode) { try { if (mListener != null) { - mListener.onError(deviceId, error, vendorCode); + mListener.onError(deviceId, error, vendorCode, getCookie()); } } catch (RemoteException e) { Slog.w(getLogTag(), "Failed to invoke sendError", e); diff --git a/services/core/java/com/android/server/biometrics/EnrollClient.java b/services/core/java/com/android/server/biometrics/EnrollClient.java index 76dc5a91bdbe..f858ef5ec6f8 100644 --- a/services/core/java/com/android/server/biometrics/EnrollClient.java +++ b/services/core/java/com/android/server/biometrics/EnrollClient.java @@ -40,7 +40,7 @@ public abstract class EnrollClient extends ClientMonitor { BiometricServiceBase.ServiceListener listener, int userId, int groupId, byte[] cryptoToken, boolean restricted, String owner, BiometricUtils utils) { super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted, - owner); + owner, 0 /* cookie */); mBiometricUtils = utils; mCryptoToken = Arrays.copyOf(cryptoToken, cryptoToken.length); } diff --git a/services/core/java/com/android/server/biometrics/EnumerateClient.java b/services/core/java/com/android/server/biometrics/EnumerateClient.java index 47dc7ffda456..df6220cfd94b 100644 --- a/services/core/java/com/android/server/biometrics/EnumerateClient.java +++ b/services/core/java/com/android/server/biometrics/EnumerateClient.java @@ -34,7 +34,7 @@ public abstract class EnumerateClient extends ClientMonitor { BiometricServiceBase.ServiceListener listener, int groupId, int userId, boolean restricted, String owner) { super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted, - owner); + owner, 0 /* cookie */); } @Override diff --git a/services/core/java/com/android/server/biometrics/RemovalClient.java b/services/core/java/com/android/server/biometrics/RemovalClient.java index 15b3773314bf..be233ec89342 100644 --- a/services/core/java/com/android/server/biometrics/RemovalClient.java +++ b/services/core/java/com/android/server/biometrics/RemovalClient.java @@ -37,7 +37,7 @@ public abstract class RemovalClient extends ClientMonitor { BiometricServiceBase.ServiceListener listener, int biometricId, int groupId, int userId, boolean restricted, String owner, BiometricUtils utils) { super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted, - owner); + owner, 0 /* cookie */); mBiometricId = biometricId; mBiometricUtils = utils; } diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index 7aa2e47300dd..557af0478b87 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -27,9 +27,8 @@ import android.content.Context; import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; -import android.hardware.biometrics.IBiometricPromptReceiver; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; -import android.hardware.biometrics.IBiometricServiceReceiver; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback; import android.hardware.biometrics.face.V1_0.Status; @@ -38,7 +37,6 @@ import android.hardware.face.FaceManager; import android.hardware.face.IFaceService; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; -import android.os.Bundle; import android.os.Environment; import android.os.IBinder; import android.os.RemoteException; @@ -50,7 +48,6 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; -import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.DumpUtils; import com.android.server.SystemServerInitThreadPool; import com.android.server.biometrics.BiometricServiceBase; @@ -89,27 +86,9 @@ public class FaceService extends BiometricServiceBase { public FaceAuthClient(Context context, DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId, - boolean restricted, String owner, Bundle bundle, - IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService, - boolean requireConfirmation) { + boolean restricted, String owner, int cookie, boolean requireConfirmation) { super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId, - restricted, owner, bundle, dialogReceiver, statusBarService, - requireConfirmation); - } - - @Override - public String getErrorString(int error, int vendorCode) { - return FaceManager.getErrorString(getContext(), error, vendorCode); - } - - @Override - public String getAcquiredString(int acquireInfo, int vendorCode) { - return FaceManager.getAcquiredString(getContext(), acquireInfo, vendorCode); - } - - @Override - public int getBiometricType() { - return BiometricAuthenticator.TYPE_FACE; + restricted, owner, cookie, requireConfirmation); } } @@ -162,28 +141,33 @@ public class FaceService extends BiometricServiceBase { final AuthenticationClientImpl client = new FaceAuthClient(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, - null /* bundle */, null /* dialogReceiver */, mStatusBarService, - false /* requireConfirmation */); + 0 /* cookie */, false /* requireConfirmation */); authenticateInternal(client, opId, opPackageName); } @Override // Binder call - public void authenticateFromService(boolean requireConfirmation, IBinder token, long opId, - int groupId, IBiometricServiceReceiver receiver, int flags, - String opPackageName, Bundle bundle, IBiometricPromptReceiver dialogReceiver, - int callingUid, int callingPid, int callingUserId) { + public void prepareForAuthentication(boolean requireConfirmation, IBinder token, long opId, + int groupId, IBiometricServiceReceiverInternal wrapperReceiver, + String opPackageName, int cookie, int callingUid, int callingPid, + int callingUserId) { checkPermission(USE_BIOMETRIC_INTERNAL); final boolean restricted = true; // BiometricPrompt is always restricted final AuthenticationClientImpl client = new FaceAuthClient(getContext(), mDaemonWrapper, mHalDeviceId, token, - new BiometricPromptServiceListenerImpl(receiver), - mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, - bundle, dialogReceiver, mStatusBarService, true /* requireConfirmation */); + new BiometricPromptServiceListenerImpl(wrapperReceiver), + mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, cookie, + true /* requireConfirmation */); authenticateInternal(client, opId, opPackageName, callingUid, callingPid, callingUserId); } @Override // Binder call + public void startPreparedClient(int cookie) { + checkPermission(MANAGE_BIOMETRIC); + startCurrentClient(cookie); + } + + @Override // Binder call public void cancelAuthentication(final IBinder token, final String opPackageName) { checkPermission(USE_BIOMETRIC_INTERNAL); cancelAuthenticationInternal(token, opPackageName); @@ -191,10 +175,10 @@ public class FaceService extends BiometricServiceBase { @Override // Binder call public void cancelAuthenticationFromService(final IBinder token, final String opPackageName, - int callingUid, int callingPid, int callingUserId) { + int callingUid, int callingPid, int callingUserId, boolean fromClient) { checkPermission(USE_BIOMETRIC_INTERNAL); - cancelAuthenticationInternal(token, opPackageName, - callingUid, callingPid, callingUserId); + cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid, + callingUserId, fromClient); } @Override // Binder call @@ -405,12 +389,9 @@ public class FaceService extends BiometricServiceBase { * Receives callbacks from the ClientMonitor implementations. The results are forwarded to * BiometricPrompt. */ - private class BiometricPromptServiceListenerImpl implements ServiceListener { - - private IBiometricServiceReceiver mBiometricServiceReceiver; - - public BiometricPromptServiceListenerImpl(IBiometricServiceReceiver receiver) { - mBiometricServiceReceiver = receiver; + private class BiometricPromptServiceListenerImpl extends BiometricServiceListener { + BiometricPromptServiceListenerImpl(IBiometricServiceReceiverInternal wrapperReceiver) { + super(wrapperReceiver); } @Override @@ -419,32 +400,18 @@ public class FaceService extends BiometricServiceBase { /** * Map the acquired codes onto existing {@link BiometricConstants} acquired codes. */ - if (mBiometricServiceReceiver != null) { - mBiometricServiceReceiver.onAcquired(deviceId, + if (getWrapperReceiver() != null) { + getWrapperReceiver().onAcquired( FaceManager.getMappedAcquiredInfo(acquiredInfo, vendorCode), FaceManager.getAcquiredString(getContext(), acquiredInfo, vendorCode)); } } @Override - public void onAuthenticationSucceeded(long deviceId, - BiometricAuthenticator.Identifier biometric, int userId) throws RemoteException { - if (mBiometricServiceReceiver != null) { - mBiometricServiceReceiver.onAuthenticationSucceeded(deviceId); - } - } - - @Override - public void onAuthenticationFailed(long deviceId) throws RemoteException { - if (mBiometricServiceReceiver != null) { - mBiometricServiceReceiver.onAuthenticationFailed(deviceId); - } - } - - @Override - public void onError(long deviceId, int error, int vendorCode) throws RemoteException { - if (mBiometricServiceReceiver != null) { - mBiometricServiceReceiver.onError(deviceId, error, + public void onError(long deviceId, int error, int vendorCode, int cookie) + throws RemoteException { + if (getWrapperReceiver() != null) { + getWrapperReceiver().onError(cookie, error, FaceManager.getErrorString(getContext(), error, vendorCode)); } } @@ -455,7 +422,6 @@ public class FaceService extends BiometricServiceBase { * the FaceManager. */ private class ServiceListenerImpl implements ServiceListener { - private IFaceServiceReceiver mFaceServiceReceiver; public ServiceListenerImpl(IFaceServiceReceiver receiver) { @@ -501,7 +467,8 @@ public class FaceService extends BiometricServiceBase { } @Override - public void onError(long deviceId, int error, int vendorCode) throws RemoteException { + public void onError(long deviceId, int error, int vendorCode, int cookie) + throws RemoteException { if (mFaceServiceReceiver != null) { mFaceServiceReceiver.onError(deviceId, error, vendorCode); } diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java index b0b788fbe589..6a5bc61f2eb6 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java @@ -30,9 +30,8 @@ import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; -import android.hardware.biometrics.IBiometricPromptReceiver; -import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback; import android.hardware.fingerprint.Fingerprint; @@ -42,7 +41,6 @@ import android.hardware.fingerprint.IFingerprintService; import android.hardware.fingerprint.IFingerprintServiceReceiver; import android.os.Binder; import android.os.Build; -import android.os.Bundle; import android.os.Environment; import android.os.IBinder; import android.os.RemoteException; @@ -55,7 +53,6 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; -import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.DumpUtils; import com.android.server.SystemServerInitThreadPool; import com.android.server.biometrics.AuthenticationClient; @@ -109,27 +106,10 @@ public class FingerprintService extends BiometricServiceBase { public FingerprintAuthClient(Context context, DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId, - boolean restricted, String owner, Bundle bundle, - IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService, + boolean restricted, String owner, int cookie, boolean requireConfirmation) { super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId, - restricted, owner, bundle, dialogReceiver, statusBarService, - requireConfirmation); - } - - @Override - public String getErrorString(int error, int vendorCode) { - return FingerprintManager.getErrorString(getContext(), error, vendorCode); - } - - @Override - public String getAcquiredString(int acquireInfo, int vendorCode) { - return FingerprintManager.getAcquiredString(getContext(), acquireInfo, vendorCode); - } - - @Override - public int getBiometricType() { - return BiometricAuthenticator.TYPE_FINGERPRINT; + restricted, owner, cookie, requireConfirmation); } } @@ -182,38 +162,44 @@ public class FingerprintService extends BiometricServiceBase { final boolean restricted = isRestricted(); final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), - mCurrentUserId, groupId, opId, restricted, opPackageName, null /* bundle */, - null /* dialogReceiver */, mStatusBarService, false /* requireConfirmation */); + mCurrentUserId, groupId, opId, restricted, opPackageName, + 0 /* cookie */, false /* requireConfirmation */); authenticateInternal(client, opId, opPackageName); } @Override // Binder call - public void authenticateFromService(IBinder token, long opId, int groupId, - IBiometricServiceReceiver receiver, int flags, String opPackageName, - Bundle bundle, IBiometricPromptReceiver dialogReceiver, - int callingUid, int callingPid, int callingUserId) { + public void prepareForAuthentication(IBinder token, long opId, int groupId, + IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName, + int cookie, int callingUid, int callingPid, int callingUserId) { checkPermission(MANAGE_BIOMETRIC); final boolean restricted = true; // BiometricPrompt is always restricted final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(), mDaemonWrapper, mHalDeviceId, token, - new BiometricPromptServiceListenerImpl(receiver), - mCurrentUserId, groupId, opId, restricted, opPackageName, bundle, - dialogReceiver, mStatusBarService, false /* requireConfirmation */); + new BiometricPromptServiceListenerImpl(wrapperReceiver), + mCurrentUserId, groupId, opId, restricted, opPackageName, cookie, + false /* requireConfirmation */); authenticateInternal(client, opId, opPackageName, callingUid, callingPid, callingUserId); } @Override // Binder call + public void startPreparedClient(int cookie) { + checkPermission(MANAGE_BIOMETRIC); + startCurrentClient(cookie); + } + + + @Override // Binder call public void cancelAuthentication(final IBinder token, final String opPackageName) { cancelAuthenticationInternal(token, opPackageName); } @Override // Binder call public void cancelAuthenticationFromService(final IBinder token, final String opPackageName, - int callingUid, int callingPid, int callingUserId) { + int callingUid, int callingPid, int callingUserId, boolean fromClient) { checkPermission(MANAGE_BIOMETRIC); - cancelAuthenticationInternal(token, opPackageName, - callingUid, callingPid, callingUserId); + cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid, + callingUserId, fromClient); } @Override // Binder call @@ -388,43 +374,25 @@ public class FingerprintService extends BiometricServiceBase { * Receives callbacks from the ClientMonitor implementations. The results are forwarded to * BiometricPrompt. */ - private class BiometricPromptServiceListenerImpl implements ServiceListener { - - private IBiometricServiceReceiver mBiometricServiceReceiver; - - public BiometricPromptServiceListenerImpl(IBiometricServiceReceiver receiver) { - mBiometricServiceReceiver = receiver; + private class BiometricPromptServiceListenerImpl extends BiometricServiceListener { + BiometricPromptServiceListenerImpl(IBiometricServiceReceiverInternal wrapperReceiver) { + super(wrapperReceiver); } @Override public void onAcquired(long deviceId, int acquiredInfo, int vendorCode) throws RemoteException { - if (mBiometricServiceReceiver != null) { - mBiometricServiceReceiver.onAcquired(deviceId, acquiredInfo, - FingerprintManager.getAcquiredString( + if (getWrapperReceiver() != null) { + getWrapperReceiver().onAcquired(acquiredInfo, FingerprintManager.getAcquiredString( getContext(), acquiredInfo, vendorCode)); } } @Override - public void onAuthenticationSucceeded(long deviceId, - BiometricAuthenticator.Identifier biometric, int userId) throws RemoteException { - if (mBiometricServiceReceiver != null) { - mBiometricServiceReceiver.onAuthenticationSucceeded(deviceId); - } - } - - @Override - public void onAuthenticationFailed(long deviceId) throws RemoteException { - if (mBiometricServiceReceiver != null) { - mBiometricServiceReceiver.onAuthenticationFailed(deviceId); - } - } - - @Override - public void onError(long deviceId, int error, int vendorCode) throws RemoteException { - if (mBiometricServiceReceiver != null) { - mBiometricServiceReceiver.onError(deviceId, error, + public void onError(long deviceId, int error, int vendorCode, int cookie) + throws RemoteException { + if (getWrapperReceiver() != null) { + getWrapperReceiver().onError(cookie, error, FingerprintManager.getErrorString(getContext(), error, vendorCode)); } } @@ -435,7 +403,6 @@ public class FingerprintService extends BiometricServiceBase { * the FingerprintManager. */ private class ServiceListenerImpl implements ServiceListener { - private IFingerprintServiceReceiver mFingerprintServiceReceiver; public ServiceListenerImpl(IFingerprintServiceReceiver receiver) { @@ -483,7 +450,8 @@ public class FingerprintService extends BiometricServiceBase { } @Override - public void onError(long deviceId, int error, int vendorCode) throws RemoteException { + public void onError(long deviceId, int error, int vendorCode, int cookie) + throws RemoteException { if (mFingerprintServiceReceiver != null) { mFingerprintServiceReceiver.onError(deviceId, error, vendorCode); } diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java index c2f4406c615d..bf95210195b7 100644 --- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -313,6 +313,7 @@ public class NetworkMonitor extends StateMachine { private final State mCaptivePortalState = new CaptivePortalState(); private final State mEvaluatingPrivateDnsState = new EvaluatingPrivateDnsState(); private final State mProbingState = new ProbingState(); + private final State mWaitingForNextProbeState = new WaitingForNextProbeState(); private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null; @@ -368,6 +369,7 @@ public class NetworkMonitor extends StateMachine { addState(mMaybeNotifyState, mDefaultState); addState(mEvaluatingState, mMaybeNotifyState); addState(mProbingState, mEvaluatingState); + addState(mWaitingForNextProbeState, mEvaluatingState); addState(mCaptivePortalState, mMaybeNotifyState); addState(mEvaluatingPrivateDnsState, mDefaultState); addState(mValidatedState, mDefaultState); @@ -877,6 +879,11 @@ public class NetworkMonitor extends StateMachine { @Override public void enter() { + if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) { + //Don't continue to blame UID forever. + TrafficStats.clearThreadStatsUid(); + } + final int token = ++mProbeToken; mThread = new Thread(() -> sendMessage(obtainMessage(CMD_PROBE_COMPLETE, token, 0, isCaptivePortal()))); @@ -904,29 +911,16 @@ public class NetworkMonitor extends StateMachine { mLastPortalProbeResult = probeResult; transitionTo(mCaptivePortalState); } else { - final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); - sendMessageDelayed(msg, mReevaluateDelayMs); logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED); notifyNetworkTestResultInvalid(probeResult.redirectUrl); - if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) { - // Don't continue to blame UID forever. - TrafficStats.clearThreadStatsUid(); - } - mReevaluateDelayMs *= 2; - if (mReevaluateDelayMs > MAX_REEVALUATE_DELAY_MS) { - mReevaluateDelayMs = MAX_REEVALUATE_DELAY_MS; - } + transitionTo(mWaitingForNextProbeState); } return HANDLED; - case CMD_REEVALUATE: - // Leave the event to EvaluatingState. Defer this message will result in reset - // of mReevaluateDelayMs and mEvaluateAttempts. - case CMD_NETWORK_DISCONNECTED: case EVENT_DNS_NOTIFICATION: + // Leave the event to DefaultState to record correct dns timestamp. return NOT_HANDLED; default: - // TODO: Some events may able to handle in this state, instead of deferring to - // next state. + // Wait for probe result and defer events to next state by default. deferMessage(message); return HANDLED; } @@ -941,6 +935,29 @@ public class NetworkMonitor extends StateMachine { } } + // Being in the WaitingForNextProbeState indicates that evaluating probes failed and state is + // transited from ProbingState. This ensures that the state machine is only in ProbingState + // while a probe is in progress, not while waiting to perform the next probe. That allows + // ProbingState to defer most messages until the probe is complete, which keeps the code simple + // and matches the pre-Q behaviour where probes were a blocking operation performed on the state + // machine thread. + private class WaitingForNextProbeState extends State { + @Override + public void enter() { + final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); + sendMessageDelayed(msg, mReevaluateDelayMs); + mReevaluateDelayMs *= 2; + if (mReevaluateDelayMs > MAX_REEVALUATE_DELAY_MS) { + mReevaluateDelayMs = MAX_REEVALUATE_DELAY_MS; + } + } + + @Override + public boolean processMessage(Message message) { + return NOT_HANDLED; + } + } + // Limits the list of IP addresses returned by getAllByName or tried by openConnection to at // most one per address family. This ensures we only wait up to 20 seconds for TCP connections // to complete, regardless of how many IP addresses a host has. diff --git a/services/core/java/com/android/server/intelligence/IntelligenceManagerInternal.java b/services/core/java/com/android/server/intelligence/IntelligenceManagerInternal.java index 6fe632459eaa..d5be26ac3389 100644 --- a/services/core/java/com/android/server/intelligence/IntelligenceManagerInternal.java +++ b/services/core/java/com/android/server/intelligence/IntelligenceManagerInternal.java @@ -27,12 +27,13 @@ import android.view.autofill.IAutoFillManagerClient; * * @hide Only for use within the system server. */ +//TODO(b/111276913): rename once the final name is defined public abstract class IntelligenceManagerInternal { /** * Checks whether the given {@code uid} owns the - * {@link android.service.intelligence.IntelligenceService} implementation associated with the - * given {@code userId}. + * {@link android.service.intelligence.SmartSuggestionsService} implementation associated with + * the given {@code userId}. */ public abstract boolean isIntelligenceServiceForUser(int uid, @UserIdInt int userId); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index ae27d0c07ea8..6c2549e9b04d 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -198,6 +198,7 @@ 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.os.SomeArgs; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; @@ -206,6 +207,7 @@ import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.server.DeviceIdleController; import com.android.server.EventLogTags; +import com.android.server.IoThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.lights.Light; @@ -264,11 +266,12 @@ public class NotificationManagerService extends SystemService { // message codes static final int MESSAGE_DURATION_REACHED = 2; - static final int MESSAGE_SAVE_POLICY_FILE = 3; + // 3: removed to a different handler static final int MESSAGE_SEND_RANKING_UPDATE = 4; static final int MESSAGE_LISTENER_HINTS_CHANGED = 5; static final int MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED = 6; static final int MESSAGE_FINISH_TOKEN_TIMEOUT = 7; + static final int MESSAGE_ON_PACKAGE_CHANGED = 8; // ranking thread messages private static final int MESSAGE_RECONSIDER_RANKING = 1000; @@ -570,7 +573,7 @@ public class NotificationManagerService extends SystemService { mListeners.migrateToXml(); mAssistants.migrateToXml(); mConditionProviders.migrateToXml(); - savePolicyFile(); + handleSavePolicyFile(); } mAssistants.ensureAssistant(); @@ -600,31 +603,28 @@ public class NotificationManagerService extends SystemService { } } - public void savePolicyFile() { - mHandler.removeMessages(MESSAGE_SAVE_POLICY_FILE); - mHandler.sendEmptyMessage(MESSAGE_SAVE_POLICY_FILE); - } - private void handleSavePolicyFile() { - if (DBG) Slog.d(TAG, "handleSavePolicyFile"); - synchronized (mPolicyFile) { - final FileOutputStream stream; - try { - stream = mPolicyFile.startWrite(); - } catch (IOException e) { - Slog.w(TAG, "Failed to save policy file", e); - return; - } + IoThread.getHandler().post(() -> { + if (DBG) Slog.d(TAG, "handleSavePolicyFile"); + synchronized (mPolicyFile) { + final FileOutputStream stream; + try { + stream = mPolicyFile.startWrite(); + } catch (IOException e) { + Slog.w(TAG, "Failed to save policy file", e); + return; + } - try { - writePolicyXml(stream, false /*forBackup*/); - mPolicyFile.finishWrite(stream); - } catch (IOException e) { - Slog.w(TAG, "Failed to save policy file, restoring backup", e); - mPolicyFile.failWrite(stream); + try { + writePolicyXml(stream, false /*forBackup*/); + mPolicyFile.finishWrite(stream); + } catch (IOException e) { + Slog.w(TAG, "Failed to save policy file, restoring backup", e); + mPolicyFile.failWrite(stream); + } } - } - BackupManager.dataChanged(getContext().getPackageName()); + BackupManager.dataChanged(getContext().getPackageName()); + }); } private void writePolicyXml(OutputStream stream, boolean forBackup) throws IOException { @@ -1133,12 +1133,7 @@ public class NotificationManagerService extends SystemService { } } - mListeners.onPackagesChanged(removingPackage, pkgList, uidList); - mAssistants.onPackagesChanged(removingPackage, pkgList, uidList); - mConditionProviders.onPackagesChanged(removingPackage, pkgList, uidList); - mPreferencesHelper.onPackagesChanged( - removingPackage, changeUserId, pkgList, uidList); - savePolicyFile(); + mHandler.scheduleOnPackageChanged(removingPackage, changeUserId, pkgList, uidList); } } }; @@ -1205,7 +1200,7 @@ public class NotificationManagerService extends SystemService { mListeners.onUserRemoved(userId); mConditionProviders.onUserRemoved(userId); mAssistants.onUserRemoved(userId); - savePolicyFile(); + handleSavePolicyFile(); } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); mUserProfiles.updateCache(context); @@ -1456,7 +1451,7 @@ public class NotificationManagerService extends SystemService { mZenModeHelper.addCallback(new ZenModeHelper.Callback() { @Override public void onConfigChanged() { - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -1755,7 +1750,7 @@ public class NotificationManagerService extends SystemService { modifiedChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); } - savePolicyFile(); + handleSavePolicyFile(); } private void maybeNotifyChannelOwner(String pkg, int uid, NotificationChannel preUpdate, @@ -2223,7 +2218,7 @@ public class NotificationManagerService extends SystemService { Slog.w(TAG, "Can't notify app about app block change", e); } - savePolicyFile(); + handleSavePolicyFile(); } /** @@ -2280,7 +2275,7 @@ public class NotificationManagerService extends SystemService { public void setShowBadge(String pkg, int uid, boolean showBadge) { checkCallerIsSystem(); mPreferencesHelper.setShowBadge(pkg, uid, showBadge); - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -2296,7 +2291,7 @@ public class NotificationManagerService extends SystemService { if (info != null) { mPreferencesHelper.setNotificationDelegate( callingPkg, callingUid, delegate, info.uid); - savePolicyFile(); + handleSavePolicyFile(); } } catch (RemoteException e) { // :( @@ -2307,7 +2302,7 @@ public class NotificationManagerService extends SystemService { public void revokeNotificationDelegate(String callingPkg) { checkCallerIsSameApp(callingPkg); mPreferencesHelper.revokeNotificationDelegate(callingPkg, Binder.getCallingUid()); - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -2342,7 +2337,7 @@ public class NotificationManagerService extends SystemService { NotificationChannelGroup group) throws RemoteException { enforceSystemOrSystemUI("Caller not system or systemui"); createNotificationChannelGroup(pkg, uid, group, false, false); - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -2355,7 +2350,7 @@ public class NotificationManagerService extends SystemService { final NotificationChannelGroup group = groups.get(i); createNotificationChannelGroup(pkg, Binder.getCallingUid(), group, true, false); } - savePolicyFile(); + handleSavePolicyFile(); } private void createNotificationChannelsImpl(String pkg, int uid, @@ -2373,7 +2368,7 @@ public class NotificationManagerService extends SystemService { mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false), NOTIFICATION_CHANNEL_OR_GROUP_ADDED); } - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -2418,7 +2413,7 @@ public class NotificationManagerService extends SystemService { UserHandle.getUserHandleForUid(callingUid), mPreferencesHelper.getNotificationChannel(pkg, callingUid, channelId, true), NOTIFICATION_CHANNEL_OR_GROUP_DELETED); - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -2460,7 +2455,7 @@ public class NotificationManagerService extends SystemService { mListeners.notifyNotificationChannelGroupChanged( pkg, UserHandle.getUserHandleForUid(callingUid), groupToDelete, NOTIFICATION_CHANNEL_OR_GROUP_DELETED); - savePolicyFile(); + handleSavePolicyFile(); } } @@ -2593,7 +2588,7 @@ public class NotificationManagerService extends SystemService { true, UserHandle.getCallingUserId(), packages, uids); } - savePolicyFile(); + handleSavePolicyFile(); } @@ -3381,7 +3376,7 @@ public class NotificationManagerService extends SystemService { final ByteArrayInputStream bais = new ByteArrayInputStream(payload); try { readPolicyXml(bais, true /*forRestore*/); - savePolicyFile(); + handleSavePolicyFile(); } catch (NumberFormatException | XmlPullParserException | IOException e) { Slog.w(TAG, "applyRestore: error reading payload", e); } @@ -3422,7 +3417,7 @@ public class NotificationManagerService extends SystemService { .setPackage(pkg) .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.of(userId), null); - savePolicyFile(); + handleSavePolicyFile(); } } finally { Binder.restoreCallingIdentity(identity); @@ -3566,7 +3561,7 @@ public class NotificationManagerService extends SystemService { .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.of(userId), null); - savePolicyFile(); + handleSavePolicyFile(); } } finally { Binder.restoreCallingIdentity(identity); @@ -3592,7 +3587,7 @@ public class NotificationManagerService extends SystemService { .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.of(userId), null); - savePolicyFile(); + handleSavePolicyFile(); } } finally { Binder.restoreCallingIdentity(identity); @@ -3675,7 +3670,7 @@ public class NotificationManagerService extends SystemService { verifyPrivilegedListener(token, user, false); createNotificationChannelGroup( pkg, getUidForPackageAndUser(pkg, user), group, false, true); - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -3724,7 +3719,7 @@ public class NotificationManagerService extends SystemService { } if (allow != mLockScreenAllowSecureNotifications) { mLockScreenAllowSecureNotifications = allow; - savePolicyFile(); + handleSavePolicyFile(); } } @@ -4247,18 +4242,7 @@ public class NotificationManagerService extends SystemService { // Fix the notification as best we can. try { - final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser( - pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING, - (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId); - Notification.addFieldsFromContext(ai, notification); - - int canColorize = mPackageManagerClient.checkPermission( - android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, pkg); - if (canColorize == PERMISSION_GRANTED) { - notification.flags |= Notification.FLAG_CAN_COLORIZE; - } else { - notification.flags &= ~Notification.FLAG_CAN_COLORIZE; - } + fixNotification(notification, pkg, userId); } catch (NameNotFoundException e) { Slog.e(TAG, "Cannot create a context for sending app", e); @@ -4359,6 +4343,33 @@ public class NotificationManagerService extends SystemService { mHandler.post(new EnqueueNotificationRunnable(userId, r)); } + @VisibleForTesting + protected void fixNotification(Notification notification, String pkg, int userId) + throws NameNotFoundException { + final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser( + pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING, + (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId); + Notification.addFieldsFromContext(ai, notification); + + int canColorize = mPackageManagerClient.checkPermission( + android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, pkg); + if (canColorize == PERMISSION_GRANTED) { + notification.flags |= Notification.FLAG_CAN_COLORIZE; + } else { + notification.flags &= ~Notification.FLAG_CAN_COLORIZE; + } + + if (ai.targetSdkVersion >= Build.VERSION_CODES.Q) { + int fullscreenIntentPermission = mPackageManagerClient.checkPermission( + android.Manifest.permission.USE_FULL_SCREEN_INTENT, pkg); + if (fullscreenIntentPermission != PERMISSION_GRANTED) { + notification.fullScreenIntent = null; + Log.w(TAG, "Package " + pkg + + ": Use of fullScreenIntent requires the USE_FULL_SCREEN_INTENT permission"); + } + } + } + private void doChannelWarningToast(CharSequence toastText) { Binder.withCleanCallingIdentity(() -> { final int defaultWarningEnabled = Build.IS_DEBUGGABLE ? 1 : 0; @@ -4467,7 +4478,7 @@ public class NotificationManagerService extends SystemService { Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey()); } mSnoozeHelper.update(userId, r); - savePolicyFile(); + handleSavePolicyFile(); return false; } @@ -4598,7 +4609,7 @@ public class NotificationManagerService extends SystemService { mSnoozeHelper.snooze(r, mDuration); } r.recordSnoozed(); - savePolicyFile(); + handleSavePolicyFile(); } } @@ -4676,7 +4687,7 @@ public class NotificationManagerService extends SystemService { if (mReason != REASON_SNOOZED) { final boolean wasSnoozed = mSnoozeHelper.cancel(mUserId, mPkg, mTag, mId); if (wasSnoozed) { - savePolicyFile(); + handleSavePolicyFile(); } } } @@ -5691,6 +5702,16 @@ public class NotificationManagerService extends SystemService { } } + private void handleOnPackageChanged(boolean removingPackage, int changeUserId, + String[] pkgList, int[] uidList) { + mListeners.onPackagesChanged(removingPackage, pkgList, uidList); + mAssistants.onPackagesChanged(removingPackage, pkgList, uidList); + mConditionProviders.onPackagesChanged(removingPackage, pkgList, uidList); + mPreferencesHelper.onPackagesChanged( + removingPackage, changeUserId, pkgList, uidList); + handleSavePolicyFile(); + } + protected class WorkerHandler extends Handler { public WorkerHandler(Looper looper) { @@ -5703,13 +5724,10 @@ public class NotificationManagerService extends SystemService { switch (msg.what) { case MESSAGE_DURATION_REACHED: - handleDurationReached((ToastRecord)msg.obj); + handleDurationReached((ToastRecord) msg.obj); break; case MESSAGE_FINISH_TOKEN_TIMEOUT: - handleKillTokenTimeout((ToastRecord)msg.obj); - break; - case MESSAGE_SAVE_POLICY_FILE: - handleSavePolicyFile(); + handleKillTokenTimeout((ToastRecord) msg.obj); break; case MESSAGE_SEND_RANKING_UPDATE: handleSendRankingUpdate(); @@ -5720,6 +5738,12 @@ public class NotificationManagerService extends SystemService { case MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED: handleListenerInterruptionFilterChanged(msg.arg1); break; + case MESSAGE_ON_PACKAGE_CHANGED: + SomeArgs args = (SomeArgs) msg.obj; + handleOnPackageChanged((boolean) args.arg1, args.argi1, (String[]) args.arg2, + (int[]) args.arg3); + args.recycle(); + break; } } @@ -5735,6 +5759,16 @@ public class NotificationManagerService extends SystemService { sendMessage(Message.obtain(this, cancelRunnable)); } } + + protected void scheduleOnPackageChanged(boolean removingPackage, int changeUserId, + String[] pkgList, int[] uidList) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = removingPackage; + args.argi1 = changeUserId; + args.arg2 = pkgList; + args.arg3 = uidList; + sendMessage(Message.obtain(this, MESSAGE_ON_PACKAGE_CHANGED, args)); + } } private final class RankingHandlerWorker extends Handler implements RankingHandler @@ -6212,7 +6246,7 @@ public class NotificationManagerService extends SystemService { Slog.d(TAG, String.format("unsnooze event(%s, %s)", key, listenerName)); } mSnoozeHelper.repost(key); - savePolicyFile(); + handleSavePolicyFile(); } @GuardedBy("mNotificationLock") diff --git a/services/core/java/com/android/server/oemlock/OemLock.java b/services/core/java/com/android/server/oemlock/OemLock.java index ee70c29b7da4..352884b606a0 100644 --- a/services/core/java/com/android/server/oemlock/OemLock.java +++ b/services/core/java/com/android/server/oemlock/OemLock.java @@ -19,6 +19,9 @@ package com.android.server.oemlock; import android.annotation.Nullable; abstract class OemLock { + @Nullable + abstract String getLockName(); + abstract void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature); abstract boolean isOemUnlockAllowedByCarrier(); diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java index a6200bf433e4..6e82c24a6e9b 100644 --- a/services/core/java/com/android/server/oemlock/OemLockService.java +++ b/services/core/java/com/android/server/oemlock/OemLockService.java @@ -113,6 +113,19 @@ public class OemLockService extends SystemService { */ private final IBinder mService = new IOemLockService.Stub() { @Override + @Nullable + public String getLockName() { + enforceManageCarrierOemUnlockPermission(); + + final long token = Binder.clearCallingIdentity(); + try { + return mOemLock.getLockName(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature) { enforceManageCarrierOemUnlockPermission(); enforceUserIsAdmin(); diff --git a/services/core/java/com/android/server/oemlock/PersistentDataBlockLock.java b/services/core/java/com/android/server/oemlock/PersistentDataBlockLock.java index d9362d4b0f28..a1c27d6432f1 100644 --- a/services/core/java/com/android/server/oemlock/PersistentDataBlockLock.java +++ b/services/core/java/com/android/server/oemlock/PersistentDataBlockLock.java @@ -40,6 +40,12 @@ class PersistentDataBlockLock extends OemLock { } @Override + @Nullable + String getLockName() { + return ""; + } + + @Override void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature) { // Note: this implementation does not require a signature if (signature != null) { diff --git a/services/core/java/com/android/server/oemlock/VendorLock.java b/services/core/java/com/android/server/oemlock/VendorLock.java index 1b9de3612367..37540d039b9e 100644 --- a/services/core/java/com/android/server/oemlock/VendorLock.java +++ b/services/core/java/com/android/server/oemlock/VendorLock.java @@ -22,8 +22,6 @@ import android.hardware.oemlock.V1_0.IOemLock; import android.hardware.oemlock.V1_0.OemLockSecureStatus; import android.hardware.oemlock.V1_0.OemLockStatus; import android.os.RemoteException; -import android.os.UserHandle; -import android.os.UserManager; import android.util.Slog; import java.util.ArrayList; @@ -55,14 +53,49 @@ class VendorLock extends OemLock { } @Override + @Nullable + String getLockName() { + final Integer[] requestStatus = new Integer[1]; + final String[] lockName = new String[1]; + + try { + mOemLock.getName((status, name) -> { + requestStatus[0] = status; + lockName[0] = name; + }); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to get name from HAL", e); + throw e.rethrowFromSystemServer(); + } + + switch (requestStatus[0]) { + case OemLockStatus.OK: + // Success + return lockName[0]; + + case OemLockStatus.FAILED: + Slog.e(TAG, "Failed to get OEM lock name."); + return null; + + default: + Slog.e(TAG, "Unknown return value indicates code is out of sync with HAL"); + return null; + } + } + + @Override void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature) { try { - switch (mOemLock.setOemUnlockAllowedByCarrier(allowed, toByteArrayList(signature))) { + ArrayList<Byte> signatureBytes = toByteArrayList(signature); + switch (mOemLock.setOemUnlockAllowedByCarrier(allowed, signatureBytes)) { case OemLockSecureStatus.OK: Slog.i(TAG, "Updated carrier allows OEM lock state to: " + allowed); return; case OemLockSecureStatus.INVALID_SIGNATURE: + if (signatureBytes.isEmpty()) { + throw new IllegalArgumentException("Signature required for carrier unlock"); + } throw new SecurityException( "Invalid signature used in attempt to carrier unlock"); @@ -154,9 +187,9 @@ class VendorLock extends OemLock { } } - private ArrayList toByteArrayList(byte[] data) { + private ArrayList<Byte> toByteArrayList(byte[] data) { if (data == null) { - return null; + return new ArrayList<Byte>(); } ArrayList<Byte> result = new ArrayList<Byte>(data.length); for (final byte b : data) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b76eaaffe078..8abb5000d544 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -20135,7 +20135,7 @@ public class PackageManagerService extends IPackageManager.Stub if (Process.isIsolated(uid)) { return Zygote.MOUNT_EXTERNAL_NONE; } - if (SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + if (StorageManager.hasIsolatedStorage()) { return checkUidPermission(WRITE_MEDIA_STORAGE, uid) == PERMISSION_GRANTED ? Zygote.MOUNT_EXTERNAL_FULL : Zygote.MOUNT_EXTERNAL_WRITE; diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index e9b9930600a0..68fe1d8a05f8 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -189,7 +189,7 @@ public final class DefaultPermissionGrantPolicy { private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>(); static { // STOPSHIP(b/112545973): remove once feature enabled by default - if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + if (!StorageManager.hasIsolatedStorage()) { STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE); STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); } @@ -198,7 +198,7 @@ public final class DefaultPermissionGrantPolicy { private static final Set<String> MEDIA_AURAL_PERMISSIONS = new ArraySet<>(); static { // STOPSHIP(b/112545973): remove once feature enabled by default - if (SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + if (StorageManager.hasIsolatedStorage()) { MEDIA_AURAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_AUDIO); } } @@ -206,7 +206,7 @@ public final class DefaultPermissionGrantPolicy { private static final Set<String> MEDIA_VISUAL_PERMISSIONS = new ArraySet<>(); static { // STOPSHIP(b/112545973): remove once feature enabled by default - if (SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + if (StorageManager.hasIsolatedStorage()) { MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_VIDEO); MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_IMAGES); } diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index 21adc47b1e30..f0ebb7512015 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -195,10 +195,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { "/system/bin/traced", // Perfetto. "/system/bin/traced_probes", // Perfetto. "webview_zygote", - // Temporarily excluded zygote to investigate its forking consequences in - // NativeProcessMemoryState. - // "zygote", - // "zygote64", + "zygote", + "zygote64", }; private static final int CPU_TIME_PER_THREAD_FREQ_NUM_FREQUENCIES = 8; @@ -1090,6 +1088,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private void pullNativeProcessMemoryState( int tagId, long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) { + final List<String> processNames = Arrays.asList(MEMORY_INTERESTING_NATIVE_PROCESSES); int[] pids = getPidsForCommands(MEMORY_INTERESTING_NATIVE_PROCESSES); for (int i = 0; i < pids.length; i++) { int pid = pids[i]; @@ -1099,6 +1098,12 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } int uid = getUidForPid(pid); String processName = readCmdlineFromProcfs(pid); + // Sometimes we get here processName that is not included in the whitelist. It comes + // from forking the zygote for an app. We can ignore that sample because this process + // is collected by ProcessMemoryState. + if (!processNames.contains(processName)) { + continue; + } StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); e.writeInt(uid); e.writeString(processName); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 0d66a2c8b442..e645b84e83a0 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -24,7 +24,7 @@ import android.app.StatusBarManager; import android.content.ComponentName; import android.content.Context; import android.graphics.Rect; -import android.hardware.biometrics.IBiometricPromptReceiver; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -598,8 +598,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } @Override - public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type, - boolean requireConfirmation, int userId) { + public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, + int type, boolean requireConfirmation, int userId) { enforceBiometricDialog(); if (mBar != null) { try { @@ -654,6 +654,17 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } @Override + public void showBiometricTryAgain() { + enforceBiometricDialog(); + if (mBar != null) { + try { + mBar.showBiometricTryAgain(); + } catch (RemoteException ex) { + } + } + } + + @Override public void disable(int what, IBinder token, String pkg) { disableForUser(what, token, pkg, mCurrentUserId); } diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index d5e59c8dfd6a..1163d3916cb1 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -626,7 +626,7 @@ public final class TvInputManagerService extends SystemService { updateServiceConnectionLocked(serviceState.component, userId); } - private void createSessionInternalLocked(ITvInputService service, IBinder sessionToken, + private boolean createSessionInternalLocked(ITvInputService service, IBinder sessionToken, int userId) { UserState userState = getOrCreateUserStateLocked(userId); SessionState sessionState = userState.sessionStateMap.get(sessionToken); @@ -638,6 +638,7 @@ public final class TvInputManagerService extends SystemService { // Set up a callback to send the session token. ITvInputSessionCallback callback = new SessionCallback(sessionState, channels); + boolean created = true; // Create a session. When failed, send a null token immediately. try { if (sessionState.isRecordingSession) { @@ -647,11 +648,12 @@ public final class TvInputManagerService extends SystemService { } } catch (RemoteException e) { Slog.e(TAG, "error in createSession", e); - removeSessionStateLocked(sessionToken, userId); sendSessionTokenToClientLocked(sessionState.client, sessionState.inputId, null, null, sessionState.seq); + created = false; } channels[1].dispose(); + return created; } private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId, @@ -1193,8 +1195,10 @@ public final class TvInputManagerService extends SystemService { serviceState.sessionTokens.add(sessionToken); if (serviceState.service != null) { - createSessionInternalLocked(serviceState.service, sessionToken, - resolvedUserId); + if (!createSessionInternalLocked(serviceState.service, sessionToken, + resolvedUserId)) { + removeSessionStateLocked(sessionToken, resolvedUserId); + } } else { updateServiceConnectionLocked(info.getComponent(), resolvedUserId); } @@ -2282,9 +2286,17 @@ public final class TvInputManagerService extends SystemService { } } + List<IBinder> tokensToBeRemoved = new ArrayList<>(); + // And create sessions, if any. for (IBinder sessionToken : serviceState.sessionTokens) { - createSessionInternalLocked(serviceState.service, sessionToken, mUserId); + if (!createSessionInternalLocked(serviceState.service, sessionToken, mUserId)) { + tokensToBeRemoved.add(sessionToken); + } + } + + for (IBinder sessionToken : tokensToBeRemoved) { + removeSessionStateLocked(sessionToken, mUserId); } for (TvInputState inputState : userState.inputMap.values()) { diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 8045fd50adee..f3c5630b53f0 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -662,31 +662,29 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { ResolveInfo resolveIntent(Intent intent, String resolvedType, int userId, int flags, int filterCallingUid) { - synchronized (mService.mGlobalLock) { - try { - Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "resolveIntent"); - int modifiedFlags = flags - | PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS; - if (intent.isWebIntent() - || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) { - modifiedFlags |= PackageManager.MATCH_INSTANT; - } + try { + Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "resolveIntent"); + int modifiedFlags = flags + | PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS; + if (intent.isWebIntent() + || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) { + modifiedFlags |= PackageManager.MATCH_INSTANT; + } - // In order to allow cross-profile lookup, we clear the calling identity here. - // Note the binder identity won't affect the result, but filterCallingUid will. + // In order to allow cross-profile lookup, we clear the calling identity here. + // Note the binder identity won't affect the result, but filterCallingUid will. - // Cross-user/profile call check are done at the entry points - // (e.g. AMS.startActivityAsUser). - final long token = Binder.clearCallingIdentity(); - try { - return mService.getPackageManagerInternalLocked().resolveIntent( - intent, resolvedType, modifiedFlags, userId, true, filterCallingUid); - } finally { - Binder.restoreCallingIdentity(token); - } + // Cross-user/profile call check are done at the entry points + // (e.g. AMS.startActivityAsUser). + final long token = Binder.clearCallingIdentity(); + try { + return mService.getPackageManagerInternalLocked().resolveIntent( + intent, resolvedType, modifiedFlags, userId, true, filterCallingUid); } finally { - Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + Binder.restoreCallingIdentity(token); } + } finally { + Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 4ad97bb99b7e..0967afda6d2d 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -190,6 +190,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.IUserManager; import android.os.LocaleList; +import android.os.Looper; import android.os.Message; import android.os.PersistableBundle; import android.os.PowerManager; @@ -246,7 +247,6 @@ import com.android.internal.util.Preconditions; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.AppOpsService; import com.android.server.AttributeCache; -import com.android.server.DisplayThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.SystemServiceManager; @@ -753,9 +753,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return mGlobalLock; } - public void setActivityManagerService(IntentFirewall intentFirewall, - PendingIntentController intentController) { - mH = new H(); + public void initialize(IntentFirewall intentFirewall, PendingIntentController intentController, + Looper looper) { + mH = new H(looper); mUiHandler = new UiHandler(); mIntentFirewall = intentFirewall; final File systemDir = SystemServiceManager.ensureSystemDir(); @@ -5593,8 +5593,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { static final int FIRST_ACTIVITY_STACK_MSG = 100; static final int FIRST_SUPERVISOR_STACK_MSG = 200; - public H() { - super(DisplayThread.get().getLooper()); + H(Looper looper) { + super(looper); } @Override diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 871ceaf4d231..05e82676a40a 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -29,12 +29,12 @@ import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_PRIVATE; +import static android.view.InsetsState.TYPE_IME; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static android.view.View.GONE; -import static android.view.InsetsState.TYPE_IME; import static android.view.WindowManager.DOCKED_BOTTOM; import static android.view.WindowManager.DOCKED_INVALID; import static android.view.WindowManager.DOCKED_TOP; @@ -4736,4 +4736,16 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo || mDisplayId == mWmService.mVr2dDisplayId || mWmService.mForceDesktopModeOnExternalDisplays; } + + /** + * Re-parent the DisplayContent's top surfaces, {@link #mWindowingLayer} and + * {@link #mOverlayLayer} to the specified surfaceControl. + * + * @param surfaceControlHandle The handle for the new SurfaceControl, where the DisplayContent's + * surfaces will be re-parented to. + */ + void reparentDisplayContent(IBinder surfaceControlHandle) { + mPendingTransaction.reparent(mWindowingLayer, surfaceControlHandle) + .reparent(mOverlayLayer, surfaceControlHandle); + } } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index f1d77b9961cf..581cec928334 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -2834,6 +2834,9 @@ public class DisplayPolicy { return 0; } + mDisplayContent.getInsetsStateController().onBarControllingWindowChanged( + mTopFullscreenOpaqueWindowState); + int tmpVisibility = PolicyControl.getSystemUiVisibility(win, null) & ~mResettingSystemUiFlags & ~mForceClearedSystemUiFlags; diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java index e3ddadc7f70e..a667d679ee94 100644 --- a/services/core/java/com/android/server/wm/DragDropController.java +++ b/services/core/java/com/android/server/wm/DragDropController.java @@ -31,13 +31,12 @@ import android.util.Slog; import android.view.Display; import android.view.IWindow; import android.view.SurfaceControl; -import android.view.SurfaceControl.Transaction; import android.view.SurfaceSession; import android.view.View; import com.android.internal.util.Preconditions; -import android.view.InputWindowHandle; import com.android.server.wm.WindowManagerInternal.IDragDropCallback; + import java.util.concurrent.atomic.AtomicReference; /** @@ -71,7 +70,7 @@ class DragDropController { new IDragDropCallback() {}); boolean dragDropActiveLocked() { - return mDragState != null; + return mDragState != null && !mDragState.isClosing(); } void showInputSurface(SurfaceControl.Transaction t, int displayId) { diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index 8f6ed85d122d..607ee767869f 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -31,25 +31,24 @@ import android.animation.ValueAnimator; import android.annotation.Nullable; import android.content.ClipData; import android.content.ClipDescription; -import android.content.Context; -import android.graphics.Rect; import android.graphics.Point; +import android.graphics.Rect; import android.hardware.input.InputManager; -import android.os.Build; import android.os.Binder; +import android.os.Build; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; -import android.os.IUserManager; import android.os.UserManagerInternal; import android.util.Slog; import android.view.Display; import android.view.DragEvent; +import android.view.InputApplicationHandle; import android.view.InputChannel; import android.view.InputDevice; +import android.view.InputWindowHandle; import android.view.PointerIcon; import android.view.SurfaceControl; import android.view.View; @@ -59,8 +58,6 @@ import android.view.animation.Interpolator; import com.android.internal.view.IDragAndDropPermissions; import com.android.server.LocalServices; -import android.view.InputApplicationHandle; -import android.view.InputWindowHandle; import java.util.ArrayList; @@ -125,6 +122,12 @@ class DragState { private final Rect mTmpClipRect = new Rect(); + /** + * Whether we are finishing this drag and drop. This starts with {@code false}, and is set to + * {@code true} when {@link #closeLocked()} is called. + */ + private boolean mIsClosing; + DragState(WindowManagerService service, DragDropController controller, IBinder token, SurfaceControl surface, int flags, IBinder localWin) { mService = service; @@ -137,6 +140,10 @@ class DragState { } + boolean isClosing() { + return mIsClosing; + } + void hideInputSurface(SurfaceControl.Transaction t, int displayId) { if (displayId != mDisplayContent.getDisplayId()) { return; @@ -177,6 +184,7 @@ class DragState { * DragDropController#mDragState becomes null. */ void closeLocked() { + mIsClosing = true; // Unregister the input interceptor. if (mInputInterceptor != null) { if (DEBUG_DRAG) diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index e96f0b1c4416..282838f7d58b 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -18,11 +18,18 @@ package com.android.server.wm; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.Point; import android.graphics.Rect; +import android.util.proto.ProtoOutputStream; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; import android.view.InsetsSource; +import android.view.InsetsSourceControl; import com.android.internal.util.function.TriConsumer; -import com.android.server.policy.WindowManagerPolicy; +import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; + +import java.io.PrintWriter; /** * Controller for a specific inset source on the server. It's called provider as it provides the @@ -32,11 +39,19 @@ class InsetsSourceProvider { private final Rect mTmpRect = new Rect(); private final @NonNull InsetsSource mSource; + private final DisplayContent mDisplayContent; + private final InsetsStateController mStateController; + private @Nullable InsetsSourceControl mControl; + private @Nullable WindowState mControllingWin; + private @Nullable ControlAdapter mAdapter; private WindowState mWin; private TriConsumer<DisplayFrames, WindowState, Rect> mFrameProvider; - InsetsSourceProvider(InsetsSource source) { + InsetsSourceProvider(InsetsSource source, InsetsStateController stateController, + DisplayContent displayContent) { mSource = source; + mDisplayContent = displayContent; + mStateController = stateController; } InsetsSource getSource() { @@ -84,4 +99,81 @@ class InsetsSourceProvider { mSource.setVisible(mWin.isVisible() && !mWin.mGivenInsetsPending); } + + void updateControlForTarget(@Nullable WindowState target) { + if (target == mControllingWin) { + return; + } + if (target == null) { + revokeControl(); + return; + } + mAdapter = new ControlAdapter(); + mWin.startAnimation(mDisplayContent.getPendingTransaction(), mAdapter, + false /* TODO hidden */); + mControllingWin = target; + mControl = new InsetsSourceControl(mSource.getType(), mAdapter.mCapturedLeash); + } + + InsetsSourceControl getControl() { + return mControl; + } + + void revokeControl() { + if (mControllingWin != null) { + + // Cancelling the animation will invoke onAnimationCancelled, resetting all the fields. + mWin.cancelAnimation(); + } + } + + private class ControlAdapter implements AnimationAdapter { + + private SurfaceControl mCapturedLeash; + + @Override + public boolean getShowWallpaper() { + return false; + } + + @Override + public int getBackgroundColor() { + return 0; + } + + @Override + public void startAnimation(SurfaceControl animationLeash, Transaction t, + OnAnimationFinishedCallback finishCallback) { + mCapturedLeash = animationLeash; + t.setPosition(mCapturedLeash, mSource.getFrame().left, mSource.getFrame().top); + } + + @Override + public void onAnimationCancelled(SurfaceControl animationLeash) { + if (mAdapter == this) { + mStateController.notifyControlRevoked(mControllingWin, InsetsSourceProvider.this); + mControl = null; + mControllingWin = null; + mAdapter = null; + } + } + + @Override + public long getDurationHint() { + return 0; + } + + @Override + public long getStatusBarTransitionsStartTime() { + return 0; + } + + @Override + public void dump(PrintWriter pw, String prefix) { + } + + @Override + public void writeToProto(ProtoOutputStream proto) { + } + }; } diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 1189ee660605..592b7fba4bfd 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -20,10 +20,17 @@ import static android.view.InsetsState.TYPE_IME; import static android.view.InsetsState.TYPE_NAVIGATION_BAR; import static android.view.InsetsState.TYPE_TOP_BAR; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.SparseArray; +import android.view.InsetsSourceControl; import android.view.InsetsState; +import android.view.ViewRootImpl; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.function.Consumer; /** @@ -34,7 +41,11 @@ class InsetsStateController { private final InsetsState mLastState = new InsetsState(); private final InsetsState mState = new InsetsState(); private final DisplayContent mDisplayContent; - private ArrayMap<Integer, InsetsSourceProvider> mControllers = new ArrayMap<>(); + + private final ArrayMap<Integer, InsetsSourceProvider> mControllers = new ArrayMap<>(); + private final ArrayMap<WindowState, ArrayList<Integer>> mWinControlTypeMap = new ArrayMap<>(); + private final SparseArray<WindowState> mTypeWinControlMap = new SparseArray<>(); + private final ArraySet<WindowState> mPendingControlChanged = new ArraySet<>(); private final Consumer<WindowState> mDispatchInsetsChanged = w -> { if (w.isVisible()) { @@ -72,12 +83,25 @@ class InsetsStateController { return state; } + @Nullable InsetsSourceControl[] getControlsForDispatch(WindowState target) { + ArrayList<Integer> controlled = mWinControlTypeMap.get(target); + if (controlled == null) { + return null; + } + final int size = controlled.size(); + final InsetsSourceControl[] result = new InsetsSourceControl[size]; + for (int i = 0; i < size; i++) { + result[i] = mControllers.get(controlled.get(i)).getControl(); + } + return result; + } + /** * @return The provider of a specific type. */ InsetsSourceProvider getSourceProvider(int type) { return mControllers.computeIfAbsent(type, - key -> new InsetsSourceProvider(mState.getSource(key))); + key -> new InsetsSourceProvider(mState.getSource(key), this, mDisplayContent)); } /** @@ -93,6 +117,84 @@ class InsetsStateController { } } + void onImeTargetChanged(@Nullable WindowState imeTarget) { + onControlChanged(TYPE_IME, imeTarget); + notifyPendingInsetsControlChanged(); + } + + /** + * Called when the top opaque fullscreen window that is able to control the system bars changes. + * + * @param controllingWindow The window that is now able to control the system bars appearance + * and visibility. + */ + void onBarControllingWindowChanged(@Nullable WindowState controllingWindow) { + // TODO: Apply policy that determines whether controllingWindow is able to control system + // bars + + // TODO: Depending on the form factor, mapping is different + onControlChanged(TYPE_TOP_BAR, controllingWindow); + onControlChanged(TYPE_NAVIGATION_BAR, controllingWindow); + notifyPendingInsetsControlChanged(); + } + + void notifyControlRevoked(@NonNull WindowState previousControllingWin, + InsetsSourceProvider provider) { + removeFromControlMaps(previousControllingWin, provider.getSource().getType()); + } + + private void onControlChanged(int type, @Nullable WindowState win) { + if (!ViewRootImpl.USE_NEW_INSETS) { + return; + } + final WindowState previous = mTypeWinControlMap.get(type); + if (win == previous) { + return; + } + final InsetsSourceProvider controller = mControllers.get(type); + if (controller == null) { + return; + } + controller.updateControlForTarget(win); + if (previous != null) { + removeFromControlMaps(previous, type); + mPendingControlChanged.add(previous); + } + if (win != null) { + addToControlMaps(win, type); + mPendingControlChanged.add(win); + } + } + + private void removeFromControlMaps(@NonNull WindowState win, int type) { + final ArrayList<Integer> array = mWinControlTypeMap.get(win); + if (array == null) { + return; + } + array.remove((Integer) type); + if (array.isEmpty()) { + mWinControlTypeMap.remove(win); + } + mTypeWinControlMap.remove(type); + } + + private void addToControlMaps(@NonNull WindowState win, int type) { + final ArrayList<Integer> array = mWinControlTypeMap.computeIfAbsent(win, + key -> new ArrayList<>()); + array.add(type); + mTypeWinControlMap.put(type, win); + } + + private void notifyPendingInsetsControlChanged() { + mDisplayContent.mWmService.mAnimator.addAfterPrepareSurfacesRunnable(() -> { + for (int i = mPendingControlChanged.size() - 1; i >= 0; i--) { + final WindowState controllingWin = mPendingControlChanged.valueAt(i); + controllingWin.notifyInsetsControlChanged(); + } + mPendingControlChanged.clear(); + }); + } + private void notifyInsetsChanged() { mDisplayContent.forAllWindows(mDispatchInsetsChanged, true /* traverseTopToBottom */); } diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java index 5a70325fbd87..e15bf5b9a6f0 100644 --- a/services/core/java/com/android/server/wm/TaskPositioningController.java +++ b/services/core/java/com/android/server/wm/TaskPositioningController.java @@ -28,12 +28,12 @@ import android.os.Looper; import android.os.RemoteException; import android.util.Slog; import android.view.Display; -import android.view.SurfaceControl; import android.view.IWindow; +import android.view.InputWindowHandle; +import android.view.SurfaceControl; import com.android.internal.annotations.GuardedBy; import com.android.server.input.InputManagerService; -import android.view.InputWindowHandle; /** * Controller for task positioning by drag. @@ -184,9 +184,7 @@ class TaskPositioningController { if (!mInputManager.transferTouchFocus( transferFocusFromWin.mInputChannel, mTaskPositioner.mServerChannel)) { Slog.e(TAG_WM, "startPositioningLocked: Unable to transfer touch focus"); - mTaskPositioner.unregister(); - mTaskPositioner = null; - displayContent.getInputMonitor().updateInputWindowsLw(true /*force*/); + cleanUpTaskPositioner(); return false; } @@ -199,12 +197,21 @@ class TaskPositioningController { if (DEBUG_TASK_POSITIONING) Slog.d(TAG_WM, "finishPositioning"); synchronized (mService.mGlobalLock) { - if (mTaskPositioner != null) { - mTaskPositioner.unregister(); - mTaskPositioner = null; - } + cleanUpTaskPositioner(); mPositioningDisplay = null; } }); } + + private void cleanUpTaskPositioner() { + final TaskPositioner positioner = mTaskPositioner; + if (positioner == null) { + return; + } + + // We need to assign task positioner to null first to indicate that we're finishing task + // positioning. + mTaskPositioner = null; + positioner.unregister(); + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 4085f3d8062a..52b24b3e7307 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -66,6 +66,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED; import static android.view.WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID; import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN; import static com.android.server.LockGuard.INDEX_WINDOW; @@ -208,6 +209,7 @@ import android.view.IWindowSessionCallback; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEventReceiver; +import android.view.InsetsState; import android.view.KeyEvent; import android.view.MagnificationSpec; import android.view.MotionEvent; @@ -218,7 +220,6 @@ import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.View; import android.view.WindowContentFrameStats; -import android.view.InsetsState; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.view.WindowManager.RemoveContentMode; @@ -5640,22 +5641,12 @@ public class WindowManagerService extends IWindowManager.Stub } } - // TODO: Make the callers use getNavBarPosition(int) only. - /** - * Used by SystemUI and shared SystemUI libraries. - * @see DisplayPolicy#getNavBarPosition() - */ - @Override - @WindowManagerPolicy.NavigationBarPosition - public int getNavBarPosition() { - return getNavBarPosition(Display.DEFAULT_DISPLAY); - } - /** * Used by ActivityManager to determine where to position an app with aspect ratio shorter then * the screen is. * @see DisplayPolicy#getNavBarPosition() */ + @Override @WindowManagerPolicy.NavigationBarPosition public int getNavBarPosition(int displayId) { synchronized (mGlobalLock) { @@ -5665,7 +5656,7 @@ public class WindowManagerService extends IWindowManager.Stub if (displayContent == null) { Slog.w(TAG, "getNavBarPosition with invalid displayId=" + displayId + " callers=" + Debug.getCallers(3)); - return -1; + return NAV_BAR_INVALID; } displayContent.performLayout(false /* initial */, false /* updateInputWindows */); @@ -7457,4 +7448,29 @@ public class WindowManagerService extends IWindowManager.Stub } } } + + @Override + public void reparentDisplayContent(int displayId, IBinder surfaceControlHandle) { + final Display display = mDisplayManager.getDisplay(displayId); + if (display == null) { + throw new IllegalArgumentException( + "Can't reparent display for non-existent displayId: " + displayId); + } + + final int callingUid = Binder.getCallingUid(); + final int displayOwnerUid = display.getOwnerUid(); + if (callingUid != displayOwnerUid) { + throw new SecurityException("Only owner of the display can reparent surfaces to it."); + } + + synchronized (mGlobalLock) { + long token = Binder.clearCallingIdentity(); + try { + DisplayContent displayContent = getDisplayContentOrCreate(displayId, null); + displayContent.reparentDisplayContent(surfaceControlHandle); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index cdf911881395..ef22bb84decc 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2972,6 +2972,17 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } + void notifyInsetsControlChanged() { + final InsetsStateController stateController = + getDisplayContent().getInsetsStateController(); + try { + mClient.insetsControlChanged(stateController.getInsetsForDispatch(this), + stateController.getControlsForDispatch(this)); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to deliver inset state change", e); + } + } + Rect getBackdropFrame(Rect frame) { // When the task is docked, we send fullscreen sized backDropFrame as soon as resizing // start even if we haven't received the relayout window, so that the client requests diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index b36a8a7cdf19..43d2dcf7e0d1 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -1750,7 +1750,7 @@ int register_android_server_InputManager(JNIEnv* env) { jclass clazz; FIND_CLASS(clazz, "com/android/server/input/InputManagerService"); - gServiceClassInfo.clazz = clazz; + gServiceClassInfo.clazz = reinterpret_cast<jclass>(env->NewGlobalRef(clazz)); GET_METHOD_ID(gServiceClassInfo.notifyConfigurationChanged, clazz, "notifyConfigurationChanged", "(J)V"); diff --git a/services/intelligence/java/com/android/server/intelligence/ContentCaptureSession.java b/services/intelligence/java/com/android/server/intelligence/ContentCaptureSession.java index 08fbf5549a87..108f91c7fe4c 100644 --- a/services/intelligence/java/com/android/server/intelligence/ContentCaptureSession.java +++ b/services/intelligence/java/com/android/server/intelligence/ContentCaptureSession.java @@ -19,9 +19,9 @@ import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; import android.os.IBinder; -import android.service.intelligence.IntelligenceService; import android.service.intelligence.InteractionContext; import android.service.intelligence.InteractionSessionId; +import android.service.intelligence.SmartSuggestionsService; import android.service.intelligence.SnapshotData; import android.util.Slog; import android.view.autofill.AutofillId; @@ -59,7 +59,7 @@ final class ContentCaptureSession implements RemoteIntelligenceServiceCallbacks mService = service; mId = Preconditions.checkNotNull(sessionId); mRemoteService = new RemoteIntelligenceService(context, - IntelligenceService.SERVICE_INTERFACE, serviceComponentName, userId, this, + SmartSuggestionsService.SERVICE_INTERFACE, serviceComponentName, userId, this, bindInstantServiceAllowed, verbose); mInterationContext = new InteractionContext(appComponentName, taskId, displayId, flags); } @@ -72,7 +72,7 @@ final class ContentCaptureSession implements RemoteIntelligenceServiceCallbacks } /** - * Notifies the {@link IntelligenceService} that the service started. + * Notifies the {@link SmartSuggestionsService} that the service started. */ @GuardedBy("mLock") public void notifySessionStartedLocked() { @@ -80,14 +80,14 @@ final class ContentCaptureSession implements RemoteIntelligenceServiceCallbacks } /** - * Notifies the {@link IntelligenceService} of a batch of events. + * Notifies the {@link SmartSuggestionsService} of a batch of events. */ public void sendEventsLocked(@NonNull List<ContentCaptureEvent> events) { mRemoteService.onContentCaptureEventsRequest(mId, events); } /** - * Notifies the {@link IntelligenceService} of a snapshot of an activity. + * Notifies the {@link SmartSuggestionsService} of a snapshot of an activity. */ @GuardedBy("mLock") public void sendActivitySnapshotLocked(@NonNull SnapshotData snapshotData) { @@ -110,7 +110,7 @@ final class ContentCaptureSession implements RemoteIntelligenceServiceCallbacks * Cleans up the session and removes it from the service. * * @param notifyRemoteService whether it should trigger a {@link - * IntelligenceService#onDestroyInteractionSession(InteractionSessionId)} + * SmartSuggestionsService#onDestroyInteractionSession(InteractionSessionId)} * request. */ @GuardedBy("mLock") @@ -126,7 +126,7 @@ final class ContentCaptureSession implements RemoteIntelligenceServiceCallbacks * Cleans up the session, but not removes it from the service. * * @param notifyRemoteService whether it should trigger a {@link - * IntelligenceService#onDestroyInteractionSession(InteractionSessionId)} + * SmartSuggestionsService#onDestroyInteractionSession(InteractionSessionId)} * request. */ @GuardedBy("mLock") diff --git a/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java b/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java index 9fd797d1e549..e0d47d2c8802 100644 --- a/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java +++ b/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java @@ -16,7 +16,7 @@ package com.android.server.intelligence; -import static android.content.Context.INTELLIGENCE_MANAGER_SERVICE; +import static android.content.Context.CONTENT_CAPTURE_MANAGER_SERVICE; import android.annotation.NonNull; import android.annotation.Nullable; @@ -50,6 +50,7 @@ import java.util.List; * <p>The data collected by this service can be analyzed and combined with other sources to provide * contextual data in other areas of the system such as Autofill. */ +//TODO(b/111276913): rename once the final name is defined public final class IntelligenceManagerService extends AbstractMasterSystemService<IntelligenceManagerService, IntelligencePerUserService> { @@ -67,7 +68,7 @@ public final class IntelligenceManagerService extends @Override // from AbstractMasterSystemService protected String getServiceSettingsProperty() { // TODO(b/111276913): STOPSHIP temporary settings, until it's set by resourcs + cmd - return "intel_service"; + return "smart_suggestions_service"; } @Override // from AbstractMasterSystemService @@ -78,7 +79,7 @@ public final class IntelligenceManagerService extends @Override // from SystemService public void onStart() { - publishBinderService(INTELLIGENCE_MANAGER_SERVICE, + publishBinderService(CONTENT_CAPTURE_MANAGER_SERVICE, new IntelligenceManagerServiceStub()); publishLocalService(IntelligenceManagerInternal.class, mLocalService); } diff --git a/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java b/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java index 9ab7e58feb9b..e3b09c630499 100644 --- a/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java +++ b/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java @@ -41,7 +41,7 @@ import android.util.Slog; import android.view.autofill.AutofillId; import android.view.autofill.IAutoFillManagerClient; import android.view.intelligence.ContentCaptureEvent; -import android.view.intelligence.IntelligenceManager; +import android.view.intelligence.ContentCaptureManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.IResultReceiver; @@ -54,6 +54,7 @@ import java.util.List; /** * Per-user instance of {@link IntelligenceManagerService}. */ +//TODO(b/111276913): rename once the final name is defined final class IntelligencePerUserService extends AbstractPerUserSystemService<IntelligencePerUserService, IntelligenceManagerService> { @@ -86,16 +87,23 @@ final class IntelligencePerUserService Slog.w(TAG, "Could not get service for " + serviceComponent + ": " + e); return null; } - if (!Manifest.permission.BIND_INTELLIGENCE_SERVICE.equals(si.permission)) { - Slog.w(TAG, "IntelligenceService from '" + si.packageName + if (!Manifest.permission.BIND_SMART_SUGGESTIONS_SERVICE.equals(si.permission)) { + Slog.w(TAG, "SmartSuggestionsService from '" + si.packageName + "' does not require permission " - + Manifest.permission.BIND_INTELLIGENCE_SERVICE); + + Manifest.permission.BIND_SMART_SUGGESTIONS_SERVICE); throw new SecurityException("Service does not require permission " - + Manifest.permission.BIND_INTELLIGENCE_SERVICE); + + Manifest.permission.BIND_SMART_SUGGESTIONS_SERVICE); } return si; } + @Override // from PerUserSystemService + @GuardedBy("mLock") + protected boolean updateLocked(boolean disabled) { + destroyLocked(); + return super.updateLocked(disabled); + } + // TODO(b/111276913): log metrics @GuardedBy("mLock") public void startSessionLocked(@NonNull IBinder activityToken, @@ -103,7 +111,7 @@ final class IntelligencePerUserService @NonNull InteractionSessionId sessionId, int flags, @NonNull IResultReceiver resultReceiver) { if (!isEnabledLocked()) { - sendToClient(resultReceiver, IntelligenceManager.STATE_DISABLED); + sendToClient(resultReceiver, ContentCaptureManager.STATE_DISABLED); return; } final ComponentName serviceComponentName = getServiceComponentName(); @@ -126,7 +134,7 @@ final class IntelligencePerUserService // TODO(b/111276913): check if local ids match and decide what to do if they don't // TODO(b/111276913): should we call session.notifySessionStartedLocked() again?? // if not, move notifySessionStartedLocked() into session constructor - sendToClient(resultReceiver, IntelligenceManager.STATE_ACTIVE); + sendToClient(resultReceiver, ContentCaptureManager.STATE_ACTIVE); return; } @@ -142,7 +150,7 @@ final class IntelligencePerUserService } mSessions.put(sessionId, session); session.notifySessionStartedLocked(); - sendToClient(resultReceiver, IntelligenceManager.STATE_ACTIVE); + sendToClient(resultReceiver, ContentCaptureManager.STATE_ACTIVE); } // TODO(b/111276913): log metrics @@ -192,7 +200,7 @@ final class IntelligencePerUserService return; } if (mMaster.verbose) { - Slog.v(TAG, "sendEvents(): id=" + sessionId + "; events =" + events.size()); + Slog.v(TAG, "sendEvents(): id=" + sessionId + ", events=" + events.size()); } session.sendEventsLocked(events); } diff --git a/services/intelligence/java/com/android/server/intelligence/RemoteIntelligenceService.java b/services/intelligence/java/com/android/server/intelligence/RemoteIntelligenceService.java index 00c5b6a1d67b..d9f4f20dc971 100644 --- a/services/intelligence/java/com/android/server/intelligence/RemoteIntelligenceService.java +++ b/services/intelligence/java/com/android/server/intelligence/RemoteIntelligenceService.java @@ -23,6 +23,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.IInterface; import android.os.RemoteException; +import android.service.intelligence.ContentCaptureEventsRequest; import android.service.intelligence.IIntelligenceService; import android.service.intelligence.InteractionContext; import android.service.intelligence.InteractionSessionId; @@ -35,11 +36,13 @@ import android.view.autofill.IAutoFillManagerClient; import android.view.intelligence.ContentCaptureEvent; import com.android.internal.os.IResultReceiver; -import com.android.server.AbstractRemoteService; +import com.android.server.AbstractMultiplePendingRequestsRemoteService; import java.util.List; -final class RemoteIntelligenceService extends AbstractRemoteService { +//TODO(b/111276913): rename once the final name is defined +final class RemoteIntelligenceService + extends AbstractMultiplePendingRequestsRemoteService<RemoteIntelligenceService> { private static final String TAG = "RemoteIntelligenceService"; @@ -54,7 +57,7 @@ final class RemoteIntelligenceService extends AbstractRemoteService { RemoteIntelligenceServiceCallbacks callbacks, boolean bindInstantServiceAllowed, boolean verbose) { super(context, serviceInterface, componentName, userId, callbacks, - bindInstantServiceAllowed, verbose); + bindInstantServiceAllowed, verbose, /* initialCapacity= */ 2); mCallbacks = callbacks; } @@ -194,7 +197,8 @@ final class RemoteIntelligenceService extends AbstractRemoteService { @Override // from MyPendingRequest public void myRun(@NonNull RemoteIntelligenceService remoteService) throws RemoteException { - remoteService.mService.onContentCaptureEvents(mSessionId, mEvents); + remoteService.mService.onContentCaptureEventsRequest(mSessionId, + new ContentCaptureEventsRequest(mEvents)); } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 56f7cff565af..f3704d818b30 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1503,6 +1503,14 @@ public final class SystemServer { } traceEnd(); + traceBeginAndSlog("RuntimeService"); + try { + ServiceManager.addService("runtime", new RuntimeService(context)); + } catch (Throwable e) { + reportWtf("starting RuntimeService", e); + } + traceEnd(); + // timezone.RulesManagerService will prevent a device starting up if the chain of trust // required for safe time zone updates might be broken. RuleManagerService cannot do // this check when mOnlyCore == true, so we don't enable the service in this case. diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index ead9731782e8..3a56419f67ae 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -34,7 +34,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING; import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; @@ -403,11 +402,10 @@ class ActivityTestsBase { doReturn(true).when(this).isBackgroundActivityStartsEnabled(); } - void setActivityManagerService(IntentFirewall intentFirewall, - PendingIntentController intentController, ActivityManagerInternal amInternal, - WindowManagerService wm) { + void setup(IntentFirewall intentFirewall, PendingIntentController intentController, + ActivityManagerInternal amInternal, WindowManagerService wm, Looper looper) { mAmInternal = amInternal; - setActivityManagerService(intentFirewall, intentController); + initialize(intentFirewall, intentController, looper); initRootActivityContainerMocks(wm); setWindowManager(wm); } @@ -517,8 +515,8 @@ class ActivityTestsBase { mWindowManager = prepareMockWindowManager(); mUgmInternal = mock(UriGrantsManagerInternal.class); - atm.setActivityManagerService(mIntentFirewall, mPendingIntentController, - new LocalService(), mWindowManager); + atm.setup(mIntentFirewall, mPendingIntentController, new LocalService(), mWindowManager, + testInjector.mHandlerThread.getLooper()); mActivityTaskManager = atm; mAtmInternal = atm.mInternal; diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java index b94f472965ab..845a09f44b82 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -95,308 +95,340 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { } @Test - public void layoutWindowLw_appDrawsBars() { + public void addingWindow_doesNotTamperWithSysuiFlags() { mWindow.mAttrs.flags |= FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; addWindow(mWindow); - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - - assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0); - assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); - assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, 0, 0); + assertEquals(0, mWindow.mAttrs.systemUiVisibility); + assertEquals(0, mWindow.mAttrs.subtreeSystemUiVisibility); } @Test - public void layoutWindowLw_appWontDrawBars() { - mWindow.mAttrs.flags &= ~FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; - addWindow(mWindow); - - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - - assertInsetByTopBottom(mWindow.getParentFrame(), 0, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDecorFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, NAV_BAR_HEIGHT); + public void layoutWindowLw_appDrawsBars() { + synchronized (mWm.mGlobalLock) { + mWindow.mAttrs.flags |= FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + addWindow(mWindow); + + mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); + + assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0); + assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); + assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, 0, 0); + } } @Test - public void layoutWindowLw_appWontDrawBars_forceStatus() throws Exception { - mWindow.mAttrs.flags &= ~FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; - mWindow.mAttrs.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND; - addWindow(mWindow); - - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - - assertInsetByTopBottom(mWindow.getParentFrame(), 0, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDecorFrame(), 0, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, NAV_BAR_HEIGHT); + public void layoutWindowLw_appWontDrawBars() { + synchronized (mWm.mGlobalLock) { + mWindow.mAttrs.flags &= ~FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + addWindow(mWindow); + + mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); + + assertInsetByTopBottom(mWindow.getParentFrame(), 0, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getDecorFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, NAV_BAR_HEIGHT); + } } @Test - public void addingWindow_doesNotTamperWithSysuiFlags() { - mWindow.mAttrs.flags |= FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; - addWindow(mWindow); - - assertEquals(0, mWindow.mAttrs.systemUiVisibility); - assertEquals(0, mWindow.mAttrs.subtreeSystemUiVisibility); + public void layoutWindowLw_appWontDrawBars_forceStatus() throws Exception { + synchronized (mWm.mGlobalLock) { + mWindow.mAttrs.flags &= ~FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND; + addWindow(mWindow); + + mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); + + assertInsetByTopBottom(mWindow.getParentFrame(), 0, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getDecorFrame(), 0, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, NAV_BAR_HEIGHT); + } } @Test public void layoutWindowLw_withDisplayCutout() { - addDisplayCutout(); + synchronized (mWm.mGlobalLock) { + addDisplayCutout(); - addWindow(mWindow); + addWindow(mWindow); - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); + mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0); - assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); - assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0); + assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0); + assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); + assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0); + } } @Test public void layoutWindowLw_withDisplayCutout_never() { - addDisplayCutout(); + synchronized (mWm.mGlobalLock) { + addDisplayCutout(); - mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; - addWindow(mWindow); + mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; + addWindow(mWindow); - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); + mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, 0); - assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); - assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, 0); + assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, 0); + assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); + assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, 0); + } } @Test public void layoutWindowLw_withDisplayCutout_layoutFullscreen() { - addDisplayCutout(); + synchronized (mWm.mGlobalLock) { + addDisplayCutout(); - mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; - addWindow(mWindow); + mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + addWindow(mWindow); - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); + mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0); - assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); - assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, 0, 0); + assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0); + assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); + assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, 0, 0); + } } @Test public void layoutWindowLw_withDisplayCutout_fullscreen() { - addDisplayCutout(); + synchronized (mWm.mGlobalLock) { + addDisplayCutout(); - mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; - addWindow(mWindow); + mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; + addWindow(mWindow); - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); + mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, 0); - assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); - assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, 0); + assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, 0); + assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); + assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, 0); + } } @Test public void layoutWindowLw_withDisplayCutout_fullscreenInCutout() { - addDisplayCutout(); - - mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; - mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - addWindow(mWindow); - - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - - assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0); - assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); - assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0); + synchronized (mWm.mGlobalLock) { + addDisplayCutout(); + + mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; + mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + addWindow(mWindow); + + mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); + + assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0); + assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); + assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0); + } } @Test public void layoutWindowLw_withDisplayCutout_landscape() { - addDisplayCutout(); - setRotation(ROTATION_90); - addWindow(mWindow); - - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - - assertInsetBy(mWindow.getParentFrame(), DISPLAY_CUTOUT_HEIGHT, 0, 0, 0); - assertInsetBy(mWindow.getStableFrameLw(), 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); - assertInsetBy(mWindow.getContentFrameLw(), - DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); - assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0); - assertInsetBy(mWindow.getDisplayFrameLw(), DISPLAY_CUTOUT_HEIGHT, 0, 0, 0); + synchronized (mWm.mGlobalLock) { + addDisplayCutout(); + setRotation(ROTATION_90); + addWindow(mWindow); + + mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); + + assertInsetBy(mWindow.getParentFrame(), DISPLAY_CUTOUT_HEIGHT, 0, 0, 0); + assertInsetBy(mWindow.getStableFrameLw(), 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); + assertInsetBy(mWindow.getContentFrameLw(), + DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); + assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0); + assertInsetBy(mWindow.getDisplayFrameLw(), DISPLAY_CUTOUT_HEIGHT, 0, 0, 0); + } } @Test public void layoutWindowLw_withDisplayCutout_seascape() { - addDisplayCutout(); - setRotation(ROTATION_270); - addWindow(mWindow); - - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - - assertInsetBy(mWindow.getParentFrame(), 0, 0, DISPLAY_CUTOUT_HEIGHT, 0); - assertInsetBy(mWindow.getStableFrameLw(), NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, 0, 0); - assertInsetBy(mWindow.getContentFrameLw(), - NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, DISPLAY_CUTOUT_HEIGHT, 0); - assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0); - assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, DISPLAY_CUTOUT_HEIGHT, 0); + synchronized (mWm.mGlobalLock) { + addDisplayCutout(); + setRotation(ROTATION_270); + addWindow(mWindow); + + mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); + + assertInsetBy(mWindow.getParentFrame(), 0, 0, DISPLAY_CUTOUT_HEIGHT, 0); + assertInsetBy(mWindow.getStableFrameLw(), NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, 0, 0); + assertInsetBy(mWindow.getContentFrameLw(), + NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, DISPLAY_CUTOUT_HEIGHT, 0); + assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0); + assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, DISPLAY_CUTOUT_HEIGHT, 0); + } } @Test public void layoutWindowLw_withDisplayCutout_fullscreen_landscape() { - addDisplayCutout(); - setRotation(ROTATION_90); - - mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; - addWindow(mWindow); - - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - - assertInsetBy(mWindow.getParentFrame(), DISPLAY_CUTOUT_HEIGHT, 0, 0, 0); - assertInsetBy(mWindow.getStableFrameLw(), 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); - assertInsetBy(mWindow.getContentFrameLw(), - DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); - assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0); + synchronized (mWm.mGlobalLock) { + addDisplayCutout(); + setRotation(ROTATION_90); + + mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + addWindow(mWindow); + + mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); + + assertInsetBy(mWindow.getParentFrame(), DISPLAY_CUTOUT_HEIGHT, 0, 0, 0); + assertInsetBy(mWindow.getStableFrameLw(), 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); + assertInsetBy(mWindow.getContentFrameLw(), + DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); + assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0); + } } @Test public void layoutWindowLw_withDisplayCutout_floatingInScreen() { - addDisplayCutout(); + synchronized (mWm.mGlobalLock) { + addDisplayCutout(); - mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN; - mWindow.mAttrs.type = TYPE_APPLICATION_OVERLAY; - mWindow.mAttrs.width = DISPLAY_WIDTH; - mWindow.mAttrs.height = DISPLAY_HEIGHT; - addWindow(mWindow); + mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN; + mWindow.mAttrs.type = TYPE_APPLICATION_OVERLAY; + mWindow.mAttrs.width = DISPLAY_WIDTH; + mWindow.mAttrs.height = DISPLAY_HEIGHT; + addWindow(mWindow); - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); + mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - assertInsetByTopBottom(mWindow.getParentFrame(), 0, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getParentFrame(), 0, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + } } @Test public void layoutWindowLw_withDisplayCutout_fullscreenInCutout_landscape() { - addDisplayCutout(); - setRotation(ROTATION_90); - - mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; - mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - addWindow(mWindow); - - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - - assertInsetBy(mWindow.getParentFrame(), 0, 0, 0, 0); - assertInsetBy(mWindow.getStableFrameLw(), 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); - assertInsetBy(mWindow.getContentFrameLw(), - DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); - assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0); + synchronized (mWm.mGlobalLock) { + addDisplayCutout(); + setRotation(ROTATION_90); + + mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + addWindow(mWindow); + + mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); + + assertInsetBy(mWindow.getParentFrame(), 0, 0, 0, 0); + assertInsetBy(mWindow.getStableFrameLw(), 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); + assertInsetBy(mWindow.getContentFrameLw(), + DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); + assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0); + } } @Test public void layoutHint_appWindow() { - // Initialize DisplayFrames - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - - final Rect outFrame = new Rect(); - final Rect outContentInsets = new Rect(); - final Rect outStableInsets = new Rect(); - final Rect outOutsets = new Rect(); - final DisplayCutout.ParcelableWrapper outDisplayCutout = - new DisplayCutout.ParcelableWrapper(); - - mDisplayPolicy.getLayoutHintLw(mWindow.mAttrs, null, mFrames, - false /* floatingStack */, outFrame, outContentInsets, outStableInsets, outOutsets, - outDisplayCutout); - - assertThat(outFrame, is(mFrames.mUnrestricted)); - assertThat(outContentInsets, is(new Rect(0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT))); - assertThat(outStableInsets, is(new Rect(0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT))); - assertThat(outOutsets, is(new Rect())); - assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper())); + synchronized (mWm.mGlobalLock) { + // Initialize DisplayFrames + mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + + final Rect outFrame = new Rect(); + final Rect outContentInsets = new Rect(); + final Rect outStableInsets = new Rect(); + final Rect outOutsets = new Rect(); + final DisplayCutout.ParcelableWrapper outDisplayCutout = + new DisplayCutout.ParcelableWrapper(); + + mDisplayPolicy.getLayoutHintLw(mWindow.mAttrs, null, mFrames, + false /* floatingStack */, outFrame, outContentInsets, outStableInsets, + outOutsets, outDisplayCutout); + + assertThat(outFrame, is(mFrames.mUnrestricted)); + assertThat(outContentInsets, is(new Rect(0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT))); + assertThat(outStableInsets, is(new Rect(0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT))); + assertThat(outOutsets, is(new Rect())); + assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper())); + } } @Test public void layoutHint_appWindowInTask() { - // Initialize DisplayFrames - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - - final Rect taskBounds = new Rect(100, 100, 200, 200); - - final Rect outFrame = new Rect(); - final Rect outContentInsets = new Rect(); - final Rect outStableInsets = new Rect(); - final Rect outOutsets = new Rect(); - final DisplayCutout.ParcelableWrapper outDisplayCutout = - new DisplayCutout.ParcelableWrapper(); - - mDisplayPolicy.getLayoutHintLw(mWindow.mAttrs, taskBounds, mFrames, - false /* floatingStack */, outFrame, outContentInsets, outStableInsets, outOutsets, - outDisplayCutout); - - assertThat(outFrame, is(taskBounds)); - assertThat(outContentInsets, is(new Rect())); - assertThat(outStableInsets, is(new Rect())); - assertThat(outOutsets, is(new Rect())); - assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper())); + synchronized (mWm.mGlobalLock) { + // Initialize DisplayFrames + mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + + final Rect taskBounds = new Rect(100, 100, 200, 200); + + final Rect outFrame = new Rect(); + final Rect outContentInsets = new Rect(); + final Rect outStableInsets = new Rect(); + final Rect outOutsets = new Rect(); + final DisplayCutout.ParcelableWrapper outDisplayCutout = + new DisplayCutout.ParcelableWrapper(); + + mDisplayPolicy.getLayoutHintLw(mWindow.mAttrs, taskBounds, mFrames, + false /* floatingStack */, outFrame, outContentInsets, outStableInsets, + outOutsets, outDisplayCutout); + + assertThat(outFrame, is(taskBounds)); + assertThat(outContentInsets, is(new Rect())); + assertThat(outStableInsets, is(new Rect())); + assertThat(outOutsets, is(new Rect())); + assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper())); + } } @Test public void layoutHint_appWindowInTask_outsideContentFrame() { - // Initialize DisplayFrames - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - - // Task is in the nav bar area (usually does not happen, but this is similar enough to the - // possible overlap with the IME) - final Rect taskBounds = new Rect(100, mFrames.mContent.bottom + 1, - 200, mFrames.mContent.bottom + 10); - - final Rect outFrame = new Rect(); - final Rect outContentInsets = new Rect(); - final Rect outStableInsets = new Rect(); - final Rect outOutsets = new Rect(); - final DisplayCutout.ParcelableWrapper outDisplayCutout = - new DisplayCutout.ParcelableWrapper(); - - mDisplayPolicy.getLayoutHintLw(mWindow.mAttrs, taskBounds, mFrames, - true /* floatingStack */, outFrame, outContentInsets, outStableInsets, outOutsets, - outDisplayCutout); - - assertThat(outFrame, is(taskBounds)); - assertThat(outContentInsets, is(new Rect())); - assertThat(outStableInsets, is(new Rect())); - assertThat(outOutsets, is(new Rect())); - assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper())); + synchronized (mWm.mGlobalLock) { + // Initialize DisplayFrames + mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + + // Task is in the nav bar area (usually does not happen, but this is similar enough to + // the possible overlap with the IME) + final Rect taskBounds = new Rect(100, mFrames.mContent.bottom + 1, + 200, mFrames.mContent.bottom + 10); + + final Rect outFrame = new Rect(); + final Rect outContentInsets = new Rect(); + final Rect outStableInsets = new Rect(); + final Rect outOutsets = new Rect(); + final DisplayCutout.ParcelableWrapper outDisplayCutout = + new DisplayCutout.ParcelableWrapper(); + + mDisplayPolicy.getLayoutHintLw(mWindow.mAttrs, taskBounds, mFrames, + true /* floatingStack */, outFrame, outContentInsets, outStableInsets, + outOutsets, outDisplayCutout); + + assertThat(outFrame, is(taskBounds)); + assertThat(outContentInsets, is(new Rect())); + assertThat(outStableInsets, is(new Rect())); + assertThat(outOutsets, is(new Rect())); + assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper())); + } } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java index c11e606386e6..8821544903c1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java @@ -29,6 +29,7 @@ import android.view.InsetsSource; import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; +import org.junit.Before; import org.junit.Test; @SmallTest @@ -36,8 +37,15 @@ import org.junit.Test; @Presubmit public class InsetsSourceProviderTest extends WindowTestsBase { - private InsetsSourceProvider mProvider = new InsetsSourceProvider( - new InsetsSource(TYPE_TOP_BAR)); + private InsetsSource mSource = new InsetsSource(TYPE_TOP_BAR); + private InsetsSourceProvider mProvider; + + @Before + public void setUp() throws Exception { + mSource.setVisible(true); + mProvider = new InsetsSourceProvider(mSource, + mDisplayContent.getInsetsStateController(), mDisplayContent); + } @Test public void testPostLayout() { diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index 331622ce22a5..11526a85aafb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -23,8 +23,10 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import android.platform.test.annotations.Presubmit; +import android.view.InsetsSourceControl; import android.view.InsetsState; import androidx.test.filters.FlakyTest; @@ -41,12 +43,9 @@ public class InsetsStateControllerTest extends WindowTestsBase { public void testStripForDispatch_notOwn() { final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow"); final WindowState app = createWindow(null, TYPE_APPLICATION, "parentWindow"); - mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_TOP_BAR) - .setWindow(topBar, null); - topBar.setInsetProvider( - mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_TOP_BAR)); - assertNotNull(mDisplayContent.getInsetsStateController().getInsetsForDispatch(app) - .getSource(TYPE_TOP_BAR)); + getController().getSourceProvider(TYPE_TOP_BAR).setWindow(topBar, null); + topBar.setInsetProvider(getController().getSourceProvider(TYPE_TOP_BAR)); + assertNotNull(getController().getInsetsForDispatch(app).getSource(TYPE_TOP_BAR)); } @Test @@ -54,10 +53,8 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow"); mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_TOP_BAR) .setWindow(topBar, null); - topBar.setInsetProvider( - mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_TOP_BAR)); - assertEquals(new InsetsState(), - mDisplayContent.getInsetsStateController().getInsetsForDispatch(topBar)); + topBar.setInsetProvider(getController().getSourceProvider(TYPE_TOP_BAR)); + assertEquals(new InsetsState(), getController().getInsetsForDispatch(topBar)); } @Test @@ -65,13 +62,47 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState navBar = createWindow(null, TYPE_APPLICATION, "parentWindow"); final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow"); final WindowState ime = createWindow(null, TYPE_APPLICATION, "parentWindow"); - mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_TOP_BAR) - .setWindow(topBar, null); - mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_NAVIGATION_BAR) - .setWindow(navBar, null); - mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_IME) - .setWindow(ime, null); - assertEquals(new InsetsState(), - mDisplayContent.getInsetsStateController().getInsetsForDispatch(navBar)); + getController().getSourceProvider(TYPE_TOP_BAR).setWindow(topBar, null); + getController().getSourceProvider(TYPE_NAVIGATION_BAR).setWindow(navBar, null); + getController().getSourceProvider(TYPE_IME).setWindow(ime, null); + assertEquals(new InsetsState(), getController().getInsetsForDispatch(navBar)); + } + + @Test + public void testBarControllingWinChanged() { + final WindowState navBar = createWindow(null, TYPE_APPLICATION, "parentWindow"); + final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow"); + final WindowState app = createWindow(null, TYPE_APPLICATION, "parentWindow"); + getController().getSourceProvider(TYPE_TOP_BAR).setWindow(topBar, null); + getController().getSourceProvider(TYPE_NAVIGATION_BAR).setWindow(navBar, null); + getController().onBarControllingWindowChanged(app); + InsetsSourceControl[] controls = getController().getControlsForDispatch(app); + assertEquals(2, controls.length); + } + + @Test + public void testControlRevoked() { + final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow"); + final WindowState app = createWindow(null, TYPE_APPLICATION, "parentWindow"); + getController().getSourceProvider(TYPE_TOP_BAR).setWindow(topBar, null); + getController().onBarControllingWindowChanged(app); + assertNotNull(getController().getControlsForDispatch(app)); + getController().onBarControllingWindowChanged(null); + assertNull(getController().getControlsForDispatch(app)); + } + + @Test + public void testControlRevoked_animation() { + final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow"); + final WindowState app = createWindow(null, TYPE_APPLICATION, "parentWindow"); + getController().getSourceProvider(TYPE_TOP_BAR).setWindow(topBar, null); + getController().onBarControllingWindowChanged(app); + assertNotNull(getController().getControlsForDispatch(app)); + topBar.cancelAnimation(); + assertNull(getController().getControlsForDispatch(app)); + } + + private InsetsStateController getController() { + return mDisplayContent.getInsetsStateController(); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java index 432af0d7a469..29738ff2a63d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java @@ -24,6 +24,7 @@ import android.util.MergedConfiguration; import android.view.DisplayCutout; import android.view.DragEvent; import android.view.IWindow; +import android.view.InsetsSourceControl; import android.view.InsetsState; import com.android.internal.os.IResultReceiver; @@ -40,11 +41,17 @@ public class TestIWindow extends IWindow.Stub { Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar, int displayId, DisplayCutout.ParcelableWrapper displayCutout) throws RemoteException { } + @Override public void insetsChanged(InsetsState insetsState) throws RemoteException { } @Override + public void insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls) + throws RemoteException { + } + + @Override public void moved(int newX, int newY) throws RemoteException { } diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp index 0c40a6b5fa84..de40e0df48e7 100644 --- a/startop/view_compiler/Android.bp +++ b/startop/view_compiler/Android.bp @@ -24,7 +24,6 @@ cc_defaults { "libdexfile", "slicer", ], - cppflags: ["-std=c++17"], } cc_library_host_static { diff --git a/telephony/java/android/telephony/AvailableNetworkInfo.aidl b/telephony/java/android/telephony/AvailableNetworkInfo.aidl new file mode 100644 index 000000000000..1d4378c502a3 --- /dev/null +++ b/telephony/java/android/telephony/AvailableNetworkInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +parcelable AvailableNetworkInfo; diff --git a/telephony/java/android/telephony/AvailableNetworkInfo.java b/telephony/java/android/telephony/AvailableNetworkInfo.java new file mode 100644 index 000000000000..fe07370394ad --- /dev/null +++ b/telephony/java/android/telephony/AvailableNetworkInfo.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * Defines available network information which includes corresponding subscription id, + * network plmns and corresponding priority to be used for network selection by Alternative Network + * Service. + */ +public final class AvailableNetworkInfo implements Parcelable { + + /* + * Defines number of priority level high. + */ + public static final int PRIORITY_HIGH = 1; + + /* + * Defines number of priority level medium. + */ + public static final int PRIORITY_MED = 2; + + /* + * Defines number of priority level low. + */ + public static final int PRIORITY_LOW = 3; + + /** + * subscription Id of the available network. This value must be one of the entry retrieved from + * {@link SubscriptionManager#getOpportunisticSubscriptions} + */ + private int mSubId; + + /** + * Priority for the subscription id. + * Priorities are in the range of 1 to 3 where 1 + * has the highest priority. + */ + private int mPriority; + + /** + * Describes the List of PLMN ids (MCC-MNC) associated with mSubId. + * If this entry is left empty, then the platform software will not scan the network + * to revalidate the input. + */ + private ArrayList<String> mMccMncs; + + /** + * Return subscription Id of the available network. + * This value must be one of the entry retrieved from + * {@link SubscriptionManager#getOpportunisticSubscriptions} + * @return subscription id + */ + public int getSubId() { + return mSubId; + } + + /** + * Return priority for the subscription id. Valid value will be within + * [{@link AvailableNetworkInfo#PRIORITY_HIGH}, {@link AvailableNetworkInfo#PRIORITY_LOW}] + * @return priority level + */ + public int getPriority() { + return mPriority; + } + + /** + * Return List of PLMN ids (MCC-MNC) associated with the sub ID. + * If this entry is left empty, then the platform software will not scan the network + * to revalidate the input. + * @return list of PLMN ids + */ + public List<String> getMccMncs() { + return (List<String>) mMccMncs.clone(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mSubId); + dest.writeInt(mPriority); + dest.writeStringList(mMccMncs); + } + + private AvailableNetworkInfo(Parcel in) { + mSubId = in.readInt(); + mPriority = in.readInt(); + in.readStringList(mMccMncs); + } + + public AvailableNetworkInfo(int subId, int priority, ArrayList<String> mccMncs) { + mSubId = subId; + mPriority = priority; + mMccMncs = new ArrayList<String>(mccMncs); + } + + @Override + public boolean equals(Object o) { + AvailableNetworkInfo ani; + + try { + ani = (AvailableNetworkInfo) o; + } catch (ClassCastException ex) { + return false; + } + + if (o == null) { + return false; + } + + return (mSubId == ani.mSubId + && mPriority == ani.mPriority + && (((mMccMncs != null) + && mMccMncs.equals(ani.mMccMncs)))); + } + + @Override + public int hashCode() { + return Objects.hash(mSubId, mPriority, mMccMncs); + } + + public static final Parcelable.Creator<AvailableNetworkInfo> CREATOR = + new Creator<AvailableNetworkInfo>() { + @Override + public AvailableNetworkInfo createFromParcel(Parcel in) { + return new AvailableNetworkInfo(in); + } + + @Override + public AvailableNetworkInfo[] newArray(int size) { + return new AvailableNetworkInfo[size]; + } + }; + + @Override + public String toString() { + return ("AvailableNetworkInfo:" + + " mSubId: " + mSubId + + " mPriority: " + mPriority + + " mMccMncs: " + Arrays.toString(mMccMncs.toArray())); + } +} + diff --git a/telephony/java/android/telephony/NumberVerificationCallback.java b/telephony/java/android/telephony/NumberVerificationCallback.java new file mode 100644 index 000000000000..b00c57351589 --- /dev/null +++ b/telephony/java/android/telephony/NumberVerificationCallback.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; + +/** + * A callback for number verification. After a request for number verification is received, + * the system will call {@link #onCallReceived(String)} if a phone call was received from a number + * matching the provided {@link PhoneNumberRange} or it will call {@link #onVerificationFailed(int)} + * if an error occurs. + * @hide + */ +@SystemApi +public interface NumberVerificationCallback { + /** @hide */ + @IntDef(value = {REASON_UNSPECIFIED, REASON_TIMED_OUT, REASON_NETWORK_NOT_AVAILABLE, + REASON_TOO_MANY_CALLS, REASON_CONCURRENT_REQUESTS, REASON_IN_ECBM, + REASON_IN_EMERGENCY_CALL}, + prefix = {"REASON_"}) + @interface NumberVerificationFailureReason {} + + /** + * Verification failed for an unspecified reason. + */ + int REASON_UNSPECIFIED = 0; + + /** + * Verification failed because no phone call was received from a matching number within the + * provided timeout. + */ + int REASON_TIMED_OUT = 1; + + /** + * Verification failed because no cellular voice network is available. + */ + int REASON_NETWORK_NOT_AVAILABLE = 2; + + /** + * Verification failed because there are currently too many ongoing phone calls for a new + * incoming phone call to be received. + */ + int REASON_TOO_MANY_CALLS = 3; + + /** + * Verification failed because a previous request for verification has not yet completed. + */ + int REASON_CONCURRENT_REQUESTS = 4; + + /** + * Verification failed because the phone is in emergency callback mode. + */ + int REASON_IN_ECBM = 5; + + /** + * Verification failed because the phone is currently in an emergency call. + */ + int REASON_IN_EMERGENCY_CALL = 6; + + /** + * Called when the device receives a phone call from the provided {@link PhoneNumberRange}. + * @param phoneNumber The phone number within the range that called. May or may not contain the + * country code, but will be entirely numeric. + */ + default void onCallReceived(@NonNull String phoneNumber) { } + + /** + * Called when verification fails for some reason. + * @param reason The reason for failure. + */ + default void onVerificationFailed(@NumberVerificationFailureReason int reason) { } +} diff --git a/telephony/java/android/telephony/PhoneNumberRange.aidl b/telephony/java/android/telephony/PhoneNumberRange.aidl new file mode 100644 index 000000000000..b0727be50e0b --- /dev/null +++ b/telephony/java/android/telephony/PhoneNumberRange.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +parcelable PhoneNumberRange; diff --git a/telephony/java/android/telephony/PhoneNumberRange.java b/telephony/java/android/telephony/PhoneNumberRange.java new file mode 100644 index 000000000000..d65156fd3ca2 --- /dev/null +++ b/telephony/java/android/telephony/PhoneNumberRange.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * This class is used to represent a range of phone numbers. Each range corresponds to a contiguous + * block of phone numbers. + * + * Example: + * {@code + * { + * mCountryCode = "1" + * mPrefix = "650555" + * mLowerBound = "0055" + * mUpperBound = "0899" + * } + * } + * would match 16505550089 and 6505550472, but not 63827593759 or 16505550900 + * @hide + */ +@SystemApi +public final class PhoneNumberRange implements Parcelable { + public static final Creator<PhoneNumberRange> CREATOR = new Creator<PhoneNumberRange>() { + @Override + public PhoneNumberRange createFromParcel(Parcel in) { + return new PhoneNumberRange(in); + } + + @Override + public PhoneNumberRange[] newArray(int size) { + return new PhoneNumberRange[size]; + } + }; + + private final String mCountryCode; + private final String mPrefix; + private final String mLowerBound; + private final String mUpperBound; + + /** + * @param countryCode The country code, omitting the leading "+" + * @param prefix A prefix that all numbers matching the range must have. + * @param lowerBound When concatenated with the prefix, represents the lower bound of phone + * numbers that match this range. + * @param upperBound When concatenated with the prefix, represents the upper bound of phone + * numbers that match this range. + */ + public PhoneNumberRange(@NonNull String countryCode, @NonNull String prefix, + @NonNull String lowerBound, @NonNull String upperBound) { + validateLowerAndUpperBounds(lowerBound, upperBound); + if (!Pattern.matches("[0-9]+", countryCode)) { + throw new IllegalArgumentException("Country code must be all numeric"); + } + if (!Pattern.matches("[0-9]+", prefix)) { + throw new IllegalArgumentException("Prefix must be all numeric"); + } + mCountryCode = countryCode; + mPrefix = prefix; + mLowerBound = lowerBound; + mUpperBound = upperBound; + } + + private PhoneNumberRange(Parcel in) { + mCountryCode = in.readStringNoHelper(); + mPrefix = in.readStringNoHelper(); + mLowerBound = in.readStringNoHelper(); + mUpperBound = in.readStringNoHelper(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeStringNoHelper(mCountryCode); + dest.writeStringNoHelper(mPrefix); + dest.writeStringNoHelper(mLowerBound); + dest.writeStringNoHelper(mUpperBound); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PhoneNumberRange that = (PhoneNumberRange) o; + return Objects.equals(mCountryCode, that.mCountryCode) + && Objects.equals(mPrefix, that.mPrefix) + && Objects.equals(mLowerBound, that.mLowerBound) + && Objects.equals(mUpperBound, that.mUpperBound); + } + + @Override + public int hashCode() { + return Objects.hash(mCountryCode, mPrefix, mLowerBound, mUpperBound); + } + + @Override + public String toString() { + return "PhoneNumberRange{" + + "mCountryCode='" + mCountryCode + '\'' + + ", mPrefix='" + mPrefix + '\'' + + ", mLowerBound='" + mLowerBound + '\'' + + ", mUpperBound='" + mUpperBound + '\'' + + '}'; + } + + private void validateLowerAndUpperBounds(String lowerBound, String upperBound) { + if (lowerBound.length() != upperBound.length()) { + throw new IllegalArgumentException("Lower and upper bounds must have the same length"); + } + if (!Pattern.matches("[0-9]+", lowerBound)) { + throw new IllegalArgumentException("Lower bound must be all numeric"); + } + if (!Pattern.matches("[0-9]+", upperBound)) { + throw new IllegalArgumentException("Upper bound must be all numeric"); + } + if (Integer.parseInt(lowerBound) > Integer.parseInt(upperBound)) { + throw new IllegalArgumentException("Lower bound must be lower than upper bound"); + } + } + + /** + * Checks to see if the provided phone number matches this range. + * @param number A phone number, with or without separators or a country code. + * @return {@code true} if the number matches, {@code false} otherwise. + */ + public boolean matches(String number) { + // Check the prefix, make sure it matches either with or without the country code. + String normalizedNumber = number.replaceAll("[^0-9]", ""); + String prefixWithCountryCode = mCountryCode + mPrefix; + String numberPostfix; + if (normalizedNumber.startsWith(prefixWithCountryCode)) { + numberPostfix = normalizedNumber.substring(prefixWithCountryCode.length()); + } else if (normalizedNumber.startsWith(mPrefix)) { + numberPostfix = normalizedNumber.substring(mPrefix.length()); + } else { + return false; + } + + // Next check the postfix to make sure it lies within the bounds. + try { + int lower = Integer.parseInt(mLowerBound); + int upper = Integer.parseInt(mUpperBound); + int numberToCheck = Integer.parseInt(numberPostfix); + return numberToCheck <= upper && numberToCheck >= lower; + } catch (NumberFormatException e) { + Log.e(PhoneNumberRange.class.getSimpleName(), "Invalid bounds or number.", e); + return false; + } + } +} diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index b41e14e09554..bacfe61a1a10 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -150,6 +150,11 @@ public class SubscriptionInfo implements Parcelable { private String mGroupUUID; /** + * A property in opportunistic subscription to indicate whether it is metered or not. + */ + private boolean mIsMetered; + + /** * @hide */ public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, @@ -158,7 +163,7 @@ public class SubscriptionInfo implements Parcelable { @Nullable UiccAccessRule[] accessRules, String cardId) { this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardId, - false, null); + false, null, true); } /** @@ -168,7 +173,7 @@ public class SubscriptionInfo implements Parcelable { CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @Nullable UiccAccessRule[] accessRules, String cardId, boolean isOpportunistic, - @Nullable String groupUUID) { + @Nullable String groupUUID, boolean isMetered) { this.mId = id; this.mIccId = iccId; this.mSimSlotIndex = simSlotIndex; @@ -187,8 +192,10 @@ public class SubscriptionInfo implements Parcelable { this.mCardId = cardId; this.mIsOpportunistic = isOpportunistic; this.mGroupUUID = groupUUID; + this.mIsMetered = isMetered; } + /** * @return the subscription ID. */ @@ -403,6 +410,18 @@ public class SubscriptionInfo implements Parcelable { } /** + * Used in opportunistic subscription ({@link #isOpportunistic()}) to indicate whether it's + * metered or not.This is one of the factors when deciding to switch to the subscription. + * (a non-metered subscription, for example, would likely be preferred over a metered one). + * + * @return whether subscription is metered. + * @hide + */ + public boolean isMetered() { + return mIsMetered; + } + + /** * Checks whether the app with the given context is authorized to manage this subscription * according to its metadata. Only supported for embedded subscriptions (if {@link #isEmbedded} * returns true). @@ -496,10 +515,11 @@ public class SubscriptionInfo implements Parcelable { String cardId = source.readString(); boolean isOpportunistic = source.readBoolean(); String groupUUID = source.readString(); + boolean isMetered = source.readBoolean(); return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso, - isEmbedded, accessRules, cardId, isOpportunistic, groupUUID); + isEmbedded, accessRules, cardId, isOpportunistic, groupUUID, isMetered); } @Override @@ -528,6 +548,7 @@ public class SubscriptionInfo implements Parcelable { dest.writeString(mCardId); dest.writeBoolean(mIsOpportunistic); dest.writeString(mGroupUUID); + dest.writeBoolean(mIsMetered); } @Override @@ -561,14 +582,14 @@ public class SubscriptionInfo implements Parcelable { + " mnc " + mMnc + "mCountryIso=" + mCountryIso + " isEmbedded " + mIsEmbedded + " accessRules " + Arrays.toString(mAccessRules) + " cardId=" + cardIdToPrint + " isOpportunistic " + mIsOpportunistic - + " mGroupUUID=" + mGroupUUID + "}"; + + " mGroupUUID=" + mGroupUUID + " isMetered=" + mIsMetered + "}"; } @Override public int hashCode() { return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded, - mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc, mCountryIso, - mCardId, mDisplayName, mCarrierName, mAccessRules); + mIsOpportunistic, mGroupUUID, mIsMetered, mIccId, mNumber, mMcc, mMnc, + mCountryIso, mCardId, mDisplayName, mCarrierName, mAccessRules); } @Override @@ -591,6 +612,7 @@ public class SubscriptionInfo implements Parcelable { && mIsEmbedded == toCompare.mIsEmbedded && mIsOpportunistic == toCompare.mIsOpportunistic && Objects.equals(mGroupUUID, toCompare.mGroupUUID) + && mIsMetered == toCompare.mIsMetered && Objects.equals(mIccId, toCompare.mIccId) && Objects.equals(mNumber, toCompare.mNumber) && Objects.equals(mMcc, toCompare.mMcc) diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 45cfe1e303ec..4b24646df422 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -576,7 +576,12 @@ public class SubscriptionManager { * @hide */ public static final String GROUP_UUID = "group_uuid"; - + /** + * TelephonyProvider column name for whether a subscription is metered or not, that is, whether + * the network it connects to charges for subscription or not. For example, paid CBRS or unpaid. + * @hide + */ + public static final String IS_METERED = "is_metered"; /** * Broadcast Action: The user has changed one of the default subs related to * data, phone calls, or sms</p> @@ -2403,6 +2408,21 @@ public class SubscriptionManager { return groupUUID; } + /** + * Set metered by simInfo index + * + * @param isMetered whether it’s a metered subscription. + * @param subId the unique SubscriptionInfo index in database + * @return the number of records updated + * @hide + */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public int setMetered(boolean isMetered, int subId) { + if (VDBG) logd("[setIsMetered]+ isMetered:" + isMetered + " subId:" + subId); + return setSubscriptionPropertyHelper(subId, "setIsMetered", + (iSub)-> iSub.setMetered(isMetered, subId)); + } + private interface CallISubMethodHelper { int callMethod(ISub iSub) throws RemoteException; } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index ef290d23fcdb..45d914e0dfdd 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -77,6 +77,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telecom.ITelecomService; import com.android.internal.telephony.CellNetworkScanResult; import com.android.internal.telephony.IAns; +import com.android.internal.telephony.INumberVerificationCallback; import com.android.internal.telephony.IPhoneSubInfo; import com.android.internal.telephony.ITelephony; import com.android.internal.telephony.ITelephonyRegistry; @@ -1341,6 +1342,13 @@ public class TelephonyManager { */ public static final String EXTRA_RECOVERY_ACTION = "recoveryAction"; + /** + * The max value for the timeout passed in {@link #requestNumberVerification}. + * @hide + */ + @SystemApi + public static final long MAX_NUMBER_VERIFICATION_TIMEOUT_MILLIS = 60000; + // // // Device Info @@ -4217,7 +4225,8 @@ public class TelephonyManager { } /** - * Returns the voice mail count for a subscription. Return 0 if unavailable. + * Returns the voice mail count for a subscription. Return 0 if unavailable or the caller does + * not have the READ_PHONE_STATE permission. * @param subId whose voice message count is returned * @hide */ @@ -4228,7 +4237,7 @@ public class TelephonyManager { ITelephony telephony = getITelephony(); if (telephony == null) return 0; - return telephony.getVoiceMessageCountForSubscriber(subId); + return telephony.getVoiceMessageCountForSubscriber(subId, getOpPackageName()); } catch (RemoteException ex) { return 0; } catch (NullPointerException ex) { @@ -5371,7 +5380,7 @@ public class TelephonyManager { /** * Rollback modem configurations to factory default except some config which are in whitelist. - * Used for device configuration by some CDMA operators. + * Used for device configuration by some carriers. * * <p>Requires Permission: * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling @@ -5398,7 +5407,7 @@ public class TelephonyManager { } /** - * Generate a radio modem reset. Used for device configuration by some CDMA operators. + * Generate a radio modem reset. Used for device configuration by some carriers. * * <p>Requires Permission: * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling @@ -5502,6 +5511,52 @@ public class TelephonyManager { } /** + * Request that the next incoming call from a number matching {@code range} be intercepted. + * + * This API is intended for OEMs to provide a service for apps to verify the device's phone + * number. When called, the Telephony stack will store the provided {@link PhoneNumberRange} and + * intercept the next incoming call from a number that lies within the range, within a timeout + * specified by {@code timeoutMillis}. + * + * If such a phone call is received, the caller will be notified via + * {@link NumberVerificationCallback#onCallReceived(String)} on the provided {@link Executor}. + * If verification fails for any reason, the caller will be notified via + * {@link NumberVerificationCallback#onVerificationFailed(int)} + * on the provided {@link Executor}. + * + * In addition to the {@link Manifest.permission#MODIFY_PHONE_STATE} permission, callers of this + * API must also be listed in the device configuration as an authorized app in + * {@code packages/services/Telephony/res/values/config.xml} under the + * {@code config_number_verification_package_name} key. + * + * @hide + * @param range The range of phone numbers the caller expects a phone call from. + * @param timeoutMillis The amount of time to wait for such a call, or + * {@link #MAX_NUMBER_VERIFICATION_TIMEOUT_MILLIS}, whichever is lesser. + * @param executor The {@link Executor} that callbacks should be executed on. + * @param callback The callback to use for delivering results. + */ + @SystemApi + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void requestNumberVerification(@NonNull PhoneNumberRange range, long timeoutMillis, + @NonNull @CallbackExecutor Executor executor, + @NonNull NumberVerificationCallback callback) { + INumberVerificationCallback internalCallback = new INumberVerificationCallback.Stub() { + @Override + public void onCallReceived(String phoneNumber) throws RemoteException { + Binder.withCleanCallingIdentity(() -> callback.onCallReceived(phoneNumber)); + } + + @Override + public void onVerificationFailed(int reason) throws RemoteException { + Binder.withCleanCallingIdentity(() -> callback.onVerificationFailed(reason)); + } + }; + + // TODO -- call the aidl method + } + + /** * Sets a per-phone telephony property with the value specified. * * @hide @@ -8582,7 +8637,8 @@ public class TelephonyManager { /** * Return the application ID for the uicc application type like {@link #APPTYPE_CSIM}. - * All uicc applications are uniquely identified by application ID. See ETSI 102.221 and 101.220 + * All uicc applications are uniquely identified by application ID, represented by the hex + * string. e.g, A00000015141434C00. See ETSI 102.221 and 101.220 * <p>Requires Permission: * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE} * @@ -9531,4 +9587,34 @@ public class TelephonyManager { } return subId; } + + /** + * Update availability of a list of networks in the current location. + * + * This api should be called to inform AlternativeNetwork Service about the availability + * of a network at the current location. This information will be used by AlternativeNetwork + * service to decide to attach to the network opportunistically. If an empty list is passed, + * it is assumed that no network is available. + * Requires that the calling app has carrier privileges on both primary and + * secondary subscriptions (see {@link #hasCarrierPrivileges}), or has permission + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}. + * @param availableNetworks is a list of available network information. + * @return true if request is accepted + * + */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges + public boolean updateAvailableNetworks(List<AvailableNetworkInfo> availableNetworks) { + String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>"; + boolean ret = false; + try { + IAns iAlternativeNetworkService = getIAns(); + if (iAlternativeNetworkService != null) { + ret = iAlternativeNetworkService.updateAvailableNetworks(availableNetworks, + pkgForDebug); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "updateAvailableNetworks RemoteException", ex); + } + return ret; + } } diff --git a/telephony/java/com/android/internal/telephony/IAns.aidl b/telephony/java/com/android/internal/telephony/IAns.aidl index e9a46491522e..98bcd415a1ca 100755 --- a/telephony/java/com/android/internal/telephony/IAns.aidl +++ b/telephony/java/com/android/internal/telephony/IAns.aidl @@ -16,6 +16,7 @@ package com.android.internal.telephony; +import android.telephony.AvailableNetworkInfo; interface IAns { @@ -78,4 +79,23 @@ interface IAns { * */ int getPreferredData(String callingPackage); + + /** + * Update availability of a list of networks in the current location. + * + * This api should be called if the caller is aware of the availability of a network + * at the current location. This information will be used by AlternativeNetwork service + * to decide to attach to the network. If an empty list is passed, + * it is assumed that no network is available. + * Requires that the calling app has carrier privileges on both primary and + * secondary subscriptions (see + * {@link #hasCarrierPrivileges}), or has permission + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}. + * @param availableNetworks is a list of available network information. + * @param callingPackage caller's package name + * @return true if request is accepted + * + */ + boolean updateAvailableNetworks(in List<AvailableNetworkInfo> availableNetworks, + String callingPackage); } diff --git a/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl b/telephony/java/com/android/internal/telephony/INumberVerificationCallback.aidl index 27d25b86b859..76918afb564a 100644 --- a/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl +++ b/telephony/java/com/android/internal/telephony/INumberVerificationCallback.aidl @@ -13,12 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.hardware.biometrics; -/** - * Communication channel from the BiometricPrompt (SysUI) back to AuthenticationClient. - * @hide - */ -oneway interface IBiometricPromptReceiver { - void onDialogDismissed(int reason); +package com.android.internal.telephony; + +oneway interface INumberVerificationCallback { + void onCallReceived(String phoneNumber); + void onVerificationFailed(int reason); } diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl index bc4451977d92..f9db4b0afd12 100755 --- a/telephony/java/com/android/internal/telephony/ISub.aidl +++ b/telephony/java/com/android/internal/telephony/ISub.aidl @@ -184,6 +184,15 @@ interface ISub { String setSubscriptionGroup(in int[] subIdList, String callingPackage); /** + * Set whether a subscription is metered + * + * @param isMetered whether it’s a metered subscription. + * @param subId the unique SubscriptionInfo index in database + * @return the number of records updated + */ + int setMetered(boolean isMetered, int subId); + + /** * Set which subscription is preferred for cellular data. It's * designed to overwrite default data subscription temporarily. * diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index fc42de58319e..32e939a0c925 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -393,16 +393,11 @@ interface ITelephony { int getDataActivationState(int subId, String callingPackage); /** - * Returns the unread count of voicemails - */ - int getVoiceMessageCount(); - - /** * Returns the unread count of voicemails for a subId. * @param subId user preferred subId. * Returns the unread count of voicemails */ - int getVoiceMessageCountForSubscriber(int subId); + int getVoiceMessageCountForSubscriber(int subId, String callingPackage); /** * Returns true if current state supports both voice and data diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl index 923ab066ef0f..76e7509c1094 100644 --- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -25,6 +25,7 @@ import android.telephony.PhoneCapability; import android.telephony.PhysicalChannelConfig; import android.telephony.ServiceState; import android.telephony.SignalStrength; +import android.telephony.emergency.EmergencyNumber; import com.android.internal.telephony.IPhoneStateListener; import com.android.internal.telephony.IOnSubscriptionsChangedListener; @@ -80,4 +81,5 @@ interface ITelephonyRegistry { void notifyPhoneCapabilityChanged(in PhoneCapability capability); void notifyPreferredDataSubIdChanged(int preferredSubId); void notifyRadioPowerStateChanged(in int state); + void notifyEmergencyNumberList(in List<EmergencyNumber> emergencyNumberList); } diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp index d1a70a75a44e..31d205e1b9c9 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -298,19 +298,20 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res "<colgroup align=\"left\" />\n" "<tr><th>Attribute</th><th>Description</th></tr>\n"; - // Build the table of attributes with their links and names. - for (const StyleableAttr& entry : sorted_attributes) { - if (SkipSymbol(entry.symbol)) { - continue; - } - + // Removed and hidden attributes are public but hidden from the documentation, so don't emit + // them as part of the class documentation. + std::vector<StyleableAttr> documentation_attrs = sorted_attributes; + auto documentation_remove_iter = std::remove_if(documentation_attrs.begin(), + documentation_attrs.end(), + [&](StyleableAttr entry) -> bool { StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment(); - if (attr_comment_line.contains("@removed")) { - // Removed attributes are public but hidden from the documentation, so - // don't emit them as part of the class documentation. - continue; - } + return SkipSymbol(entry.symbol) || attr_comment_line.contains("@removed") + || attr_comment_line.contains("@hide"); + }); + documentation_attrs.erase(documentation_remove_iter, documentation_attrs.end()); + // Build the table of attributes with their links and names. + for (const StyleableAttr& entry : documentation_attrs) { const ResourceName& attr_name = entry.attr_ref->name.value(); styleable_comment << "<tr><td><code>{@link #" << entry.field_name << " " << (!attr_name.package.empty() ? attr_name.package @@ -320,16 +321,14 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res // Only use the comment up until the first '.'. This is to stay compatible with // the way old AAPT did it (presumably to keep it short and to avoid including // annotations like @hide which would affect this Styleable). + StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment(); styleable_comment << "<td>" << AnnotationProcessor::ExtractFirstSentence(attr_comment_line) << "</td></tr>\n"; } styleable_comment << "</table>\n"; // Generate the @see lines for each attribute. - for (const StyleableAttr& entry : sorted_attributes) { - if (SkipSymbol(entry.symbol)) { - continue; - } + for (const StyleableAttr& entry : documentation_attrs) { styleable_comment << "@see #" << entry.field_name << "\n"; } diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp index fa208be120ed..4f51fc48c80e 100644 --- a/tools/aapt2/java/JavaClassGenerator_test.cpp +++ b/tools/aapt2/java/JavaClassGenerator_test.cpp @@ -366,6 +366,46 @@ TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) ASSERT_TRUE(generator.Generate("android", &out)); out.Flush(); + EXPECT_THAT(output, HasSubstr("#Container_one android:one")); + EXPECT_THAT(output, HasSubstr("@see #Container_one")); + EXPECT_THAT(output, HasSubstr("attr name android:one")); + EXPECT_THAT(output, HasSubstr("attr description")); + EXPECT_THAT(output, HasSubstr(attr.GetComment())); + EXPECT_THAT(output, HasSubstr(styleable.GetComment())); +} + +TEST(JavaClassGeneratorTest, CommentsForStyleableHiddenAttributesAreNotPresent) { + Attribute attr; + attr.SetComment(StringPiece("This is an attribute @hide")); + + Styleable styleable; + styleable.entries.push_back(Reference(test::ParseNameOrDie("android:attr/one"))); + styleable.SetComment(StringPiece("This is a styleable")); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("android", 0x01) + .AddValue("android:attr/one", util::make_unique<Attribute>(attr)) + .AddValue("android:styleable/Container", + std::unique_ptr<Styleable>(styleable.Clone(nullptr))) + .Build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) + .SetNameManglerPolicy(NameManglerPolicy{"android"}) + .Build(); + JavaClassGeneratorOptions options; + options.use_final = false; + JavaClassGenerator generator(context.get(), table.get(), options); + + std::string output; + StringOutputStream out(&output); + ASSERT_TRUE(generator.Generate("android", &out)); + out.Flush(); + + EXPECT_THAT(output, Not(HasSubstr("#Container_one android:one"))); + EXPECT_THAT(output, Not(HasSubstr("@see #Container_one"))); EXPECT_THAT(output, HasSubstr("attr name android:one")); EXPECT_THAT(output, HasSubstr("attr description")); EXPECT_THAT(output, HasSubstr(attr.GetComment())); diff --git a/tools/aosp/aosp_sha.sh b/tools/aosp/aosp_sha.sh index e50c70d0656a..f25fcdcb7479 100755 --- a/tools/aosp/aosp_sha.sh +++ b/tools/aosp/aosp_sha.sh @@ -19,6 +19,6 @@ else echo "If your change contains no confidential details (such as security fixes), please" echo "upload and merge this change at https://android-review.googlesource.com/." echo - exit 77 + exit 1 fi fi diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py index fdc800bcc177..01728fa1a0db 100755 --- a/tools/hiddenapi/generate_hiddenapi_lists.py +++ b/tools/hiddenapi/generate_hiddenapi_lists.py @@ -15,23 +15,56 @@ # limitations under the License. """ Generate API lists for non-SDK API enforcement. - -usage: generate-hiddenapi-lists.py [-h] - --input-public INPUT_PUBLIC - --input-private INPUT_PRIVATE - [--input-whitelists [INPUT_WHITELISTS [INPUT_WHITELISTS ...]]] - [--input-greylists [INPUT_GREYLISTS [INPUT_GREYLISTS ...]]] - [--input-blacklists [INPUT_BLACKLISTS [INPUT_BLACKLISTS ...]]] - --output-whitelist OUTPUT_WHITELIST - --output-light-greylist OUTPUT_LIGHT_GREYLIST - --output-dark-greylist OUTPUT_DARK_GREYLIST - --output-blacklist OUTPUT_BLACKLIST """ import argparse import os import sys import re +# Names of flags recognized by the `hiddenapi` tool. +FLAG_WHITELIST = "whitelist" +FLAG_GREYLIST = "greylist" +FLAG_BLACKLIST = "blacklist" +FLAG_GREYLIST_MAX_O = "greylist-max-o" +FLAG_GREYLIST_MAX_P = "greylist-max-p" + +# List of all known flags. +FLAGS = [ + FLAG_WHITELIST, + FLAG_GREYLIST, + FLAG_BLACKLIST, + FLAG_GREYLIST_MAX_O, + FLAG_GREYLIST_MAX_P, +] +FLAGS_SET = set(FLAGS) + +# Suffix used in command line args to express that only known and +# otherwise unassigned entries should be assign the given flag. +# For example, the P dark greylist is checked in as it was in P, +# but signatures have changes since then. The flag instructs this +# script to skip any entries which do not exist any more. +FLAG_IGNORE_CONFLICTS_SUFFIX = "-ignore-conflicts" + +# Regex patterns of fields/methods used in serialization. These are +# considered public API despite being hidden. +SERIALIZATION_PATTERNS = [ + r'readObject\(Ljava/io/ObjectInputStream;\)V', + r'readObjectNoData\(\)V', + r'readResolve\(\)Ljava/lang/Object;', + r'serialVersionUID:J', + r'serialPersistentFields:\[Ljava/io/ObjectStreamField;', + r'writeObject\(Ljava/io/ObjectOutputStream;\)V', + r'writeReplace\(\)Ljava/lang/Object;', +] + +# Single regex used to match serialization API. It combines all the +# SERIALIZATION_PATTERNS into a single regular expression. +SERIALIZATION_REGEX = re.compile(r'.*->(' + '|'.join(SERIALIZATION_PATTERNS) + r')$') + +# Predicates to be used with filter_apis. +IS_UNASSIGNED = lambda api, flags: not flags +IS_SERIALIZATION = lambda api, flags: SERIALIZATION_REGEX.match(api) + def get_args(): """Parses command line arguments. @@ -39,21 +72,21 @@ def get_args(): Namespace: dictionary of parsed arguments """ parser = argparse.ArgumentParser() - parser.add_argument('--input-public', required=True, help='List of all public members') - parser.add_argument('--input-private', required=True, help='List of all private members') - parser.add_argument( - '--input-whitelists', nargs='*', - help='Lists of members to force on whitelist') - parser.add_argument( - '--input-greylists', nargs='*', - help='Lists of members to force on light greylist') - parser.add_argument( - '--input-blacklists', nargs='*', - help='Lists of members to force on blacklist') - parser.add_argument('--output-whitelist', required=True) - parser.add_argument('--output-light-greylist', required=True) - parser.add_argument('--output-dark-greylist', required=True) - parser.add_argument('--output-blacklist', required=True) + parser.add_argument('--output', required=True) + parser.add_argument('--public', required=True, help='list of all public entries') + parser.add_argument('--private', required=True, help='list of all private entries') + parser.add_argument('--csv', nargs='*', default=[], metavar='CSV_FILE', + help='CSV files to be merged into output') + + for flag in FLAGS: + ignore_conflicts_flag = flag + FLAG_IGNORE_CONFLICTS_SUFFIX + parser.add_argument('--' + flag, dest=flag, nargs='*', default=[], metavar='TXT_FILE', + help='lists of entries with flag "' + flag + '"') + parser.add_argument('--' + ignore_conflicts_flag, dest=ignore_conflicts_flag, nargs='*', + default=[], metavar='TXT_FILE', + help='lists of entries with flag "' + flag + + '". skip entry if missing or flag conflict.') + return parser.parse_args() def read_lines(filename): @@ -65,10 +98,13 @@ def read_lines(filename): filename (string): Path to the file to read from. Returns: - list: Lines of the loaded file as a list of strings. + Lines of the file as a list of string. """ with open(filename, 'r') as f: - return filter(lambda line: not line.startswith('#'), f.readlines()) + lines = f.readlines(); + lines = filter(lambda line: not line.startswith('#'), lines) + lines = map(lambda line: line.strip(), lines) + return set(lines) def write_lines(filename, lines): """Writes list of lines into a file, overwriting the file it it exists. @@ -77,167 +113,168 @@ def write_lines(filename, lines): filename (string): Path to the file to be writting into. lines (list): List of strings to write into the file. """ + lines = map(lambda line: line + '\n', lines) with open(filename, 'w') as f: f.writelines(lines) -def move_between_sets(subset, src, dst, source = "<unknown>"): - """Removes a subset of elements from one set and add it to another. - - Args: - subset (set): The subset of `src` to be moved from `src` to `dst`. - src (set): Source set. Must be a superset of `subset`. - dst (set): Destination set. Must be disjoint with `subset`. - """ - assert src.issuperset(subset), ( - "Error processing: {}\n" - "The following entries were not found:\n" - "{}" - "Please visit go/hiddenapi for more information.").format( - source, "".join(map(lambda x: " " + str(x), subset.difference(src)))) - assert dst.isdisjoint(subset) - # Order matters if `src` and `subset` are the same object. - dst.update(subset) - src.difference_update(subset) - -def get_package_name(signature): - """Returns the package name prefix of a class member signature. - - Example: "Ljava/lang/String;->hashCode()J" --> "Ljava/lang/" - - Args: - signature (string): Member signature - - Returns - string: Package name of the given member - """ - class_name_end = signature.find("->") - assert class_name_end != -1, "Invalid signature: {}".format(signature) - package_name_end = signature.rfind("/", 0, class_name_end) - assert package_name_end != -1, "Invalid signature: {}".format(signature) - return signature[:package_name_end + 1] - -def all_package_names(*args): - """Returns a set of packages names in given lists of member signatures. - - Example: args = [ set([ "Lpkg1/ClassA;->foo()V", "Lpkg2/ClassB;->bar()J" ]), - set([ "Lpkg1/ClassC;->baz()Z" ]) ] - return value = set([ "Lpkg1/", "Lpkg2" ]) - - Args: - *args (list): List of sets to iterate over and extract the package names - of its elements (member signatures) - - Returns: - set: All package names extracted from the given lists of signatures. - """ - packages = set() - for arg in args: - packages = packages.union(map(get_package_name, arg)) - return packages - -def move_all(src, dst): - """Moves all elements of one set to another. - - Args: - src (set): Source set. Will become empty. - dst (set): Destination set. Will contain all elements of `src`. - """ - move_between_sets(src, src, dst) - -def move_from_files(filenames, src, dst): - """Loads member signatures from a list of files and moves them to a given set. - - Opens files in `filenames`, reads all their lines and moves those from `src` - set to `dst` set. - - Args: - filenames (list): List of paths to files to be loaded. - src (set): Set that loaded lines should be moved from. - dst (set): Set that loaded lines should be moved to. - """ - if filenames: - for filename in filenames: - move_between_sets(set(read_lines(filename)), src, dst, filename) - -def move_serialization(src, dst): - """Moves all members matching serialization API signatures between given sets. - - Args: - src (set): Set that will be searched for serialization API and that API - will be removed from it. - dst (set): Set that serialization API will be moved to. - """ - serialization_patterns = [ - r'readObject\(Ljava/io/ObjectInputStream;\)V', - r'readObjectNoData\(\)V', - r'readResolve\(\)Ljava/lang/Object;', - r'serialVersionUID:J', - r'serialPersistentFields:\[Ljava/io/ObjectStreamField;', - r'writeObject\(Ljava/io/ObjectOutputStream;\)V', - r'writeReplace\(\)Ljava/lang/Object;', - ] - regex = re.compile(r'.*->(' + '|'.join(serialization_patterns) + r')$') - move_between_sets(filter(lambda api: regex.match(api), src), src, dst) - -def move_from_packages(packages, src, dst): - """Moves all members of given package names from one set to another. - - Args: - packages (list): List of string package names. - src (set): Set that will be searched for API matching one of the given - package names. Surch API will be removed from the set. - dst (set): Set that matching API will be moved to. - """ - move_between_sets(filter(lambda api: get_package_name(api) in packages, src), src, dst) +class FlagsDict: + def __init__(self, public_api, private_api): + # Bootstrap the entries dictionary. + + # Check that the two sets do not overlap. + public_api_set = set(public_api) + private_api_set = set(private_api) + assert public_api_set.isdisjoint(private_api_set), ( + "Lists of public and private API overlap. " + + "This suggests an issue with the `hiddenapi` build tool.") + + # Compute the whole key set + self._dict_keyset = public_api_set.union(private_api_set) + + # Create a dict that creates entries for both public and private API, + # and assigns public API to the whitelist. + self._dict = {} + for api in public_api: + self._dict[api] = set([ FLAG_WHITELIST ]) + for api in private_api: + self._dict[api] = set() + + def _check_entries_set(self, keys_subset, source): + assert isinstance(keys_subset, set) + assert keys_subset.issubset(self._dict_keyset), ( + "Error processing: {}\n" + "The following entries were unexpected:\n" + "{}" + "Please visit go/hiddenapi for more information.").format( + source, "".join(map(lambda x: " " + str(x), keys_subset - self._dict_keyset))) + + def _check_flags_set(self, flags_subset, source): + assert isinstance(flags_subset, set) + assert flags_subset.issubset(FLAGS_SET), ( + "Error processing: {}\n" + "The following flags were not recognized: \n" + "{}\n" + "Please visit go/hiddenapi for more information.").format( + source, "\n".join(flags_subset - FLAGS_SET)) + + def filter_apis(self, filter_fn): + """Returns APIs which match a given predicate. + + This is a helper function which allows to filter on both signatures (keys) and + flags (values). The built-in filter() invokes the lambda only with dict's keys. + + Args: + filter_fn : Function which takes two arguments (signature/flags) and returns a boolean. + + Returns: + A set of APIs which match the predicate. + """ + return set(filter(lambda x: filter_fn(x, self._dict[x]), self._dict_keyset)) + + def get_valid_subset_of_unassigned_apis(self, api_subset): + """Sanitizes a key set input to only include keys which exist in the dictionary + and have not been assigned any flags. + + Args: + entries_subset (set/list): Key set to be sanitized. + + Returns: + Sanitized key set. + """ + assert isinstance(api_subset, set) + return api_subset.intersection(self.filter_apis(IS_UNASSIGNED)) + + def generate_csv(self): + """Constructs CSV entries from a dictionary. + + Returns: + List of lines comprising a CSV file. See "parse_and_merge_csv" for format description. + """ + return sorted(map(lambda api: ",".join([api] + sorted(self._dict[api])), self._dict)) + + def parse_and_merge_csv(self, csv_lines, source = "<unknown>"): + """Parses CSV entries and merges them into a given dictionary. + + The expected CSV format is: + <api signature>,<flag1>,<flag2>,...,<flagN> + + Args: + csv_lines (list of strings): Lines read from a CSV file. + source (string): Origin of `csv_lines`. Will be printed in error messages. + + Throws: + AssertionError if parsed API signatures of flags are invalid. + """ + # Split CSV lines into arrays of values. + csv_values = [ line.split(',') for line in csv_lines ] + + # Check that all entries exist in the dict. + csv_keys = set([ csv[0] for csv in csv_values ]) + self._check_entries_set(csv_keys, source) + + # Check that all flags are known. + csv_flags = set(reduce(lambda x, y: set(x).union(y), [ csv[1:] for csv in csv_values ], [])) + self._check_flags_set(csv_flags, source) + + # Iterate over all CSV lines, find entry in dict and append flags to it. + for csv in csv_values: + self._dict[csv[0]].update(csv[1:]) + + def assign_flag(self, flag, apis, source="<unknown>"): + """Assigns a flag to given subset of entries. + + Args: + flag (string): One of FLAGS. + apis (set): Subset of APIs to recieve the flag. + source (string): Origin of `entries_subset`. Will be printed in error messages. + + Throws: + AssertionError if parsed API signatures of flags are invalid. + """ + # Check that all APIs exist in the dict. + self._check_entries_set(apis, source) + + # Check that the flag is known. + self._check_flags_set(set([ flag ]), source) + + # Iterate over the API subset, find each entry in dict and assign the flag to it. + for api in apis: + self._dict[api].add(flag) def main(argv): - args = get_args() - - # Initialize API sets by loading lists of public and private API. Public API - # are all members resolvable from SDK API stubs, other members are private. - # As an optimization, skip the step of moving public API from a full set of - # members and start with a populated whitelist. - whitelist = set(read_lines(args.input_public)) - uncategorized = set(read_lines(args.input_private)) - light_greylist = set() - dark_greylist = set() - blacklist = set() - - # Assert that there is no overlap between public and private API. - assert whitelist.isdisjoint(uncategorized) - num_all_api = len(whitelist) + len(uncategorized) - - # Read all files which manually assign members to specific lists. - move_from_files(args.input_whitelists, uncategorized, whitelist) - move_from_files(args.input_greylists, uncategorized, light_greylist) - move_from_files(args.input_blacklists, uncategorized, blacklist) - - # Iterate over all uncategorized members and move serialization API to whitelist. - move_serialization(uncategorized, whitelist) - - # Extract package names of members from whitelist and light greylist, which - # are assumed to have been finalized at this point. Assign all uncategorized - # members from the same packages to the dark greylist. - dark_greylist_packages = all_package_names(whitelist, light_greylist) - move_from_packages(dark_greylist_packages, uncategorized, dark_greylist) - - # Assign all uncategorized members to the blacklist. - move_all(uncategorized, blacklist) - - # Assert we have not missed anything. - assert whitelist.isdisjoint(light_greylist) - assert whitelist.isdisjoint(dark_greylist) - assert whitelist.isdisjoint(blacklist) - assert light_greylist.isdisjoint(dark_greylist) - assert light_greylist.isdisjoint(blacklist) - assert dark_greylist.isdisjoint(blacklist) - assert num_all_api == len(whitelist) + len(light_greylist) + len(dark_greylist) + len(blacklist) - - # Write final lists to disk. - write_lines(args.output_whitelist, whitelist) - write_lines(args.output_light_greylist, light_greylist) - write_lines(args.output_dark_greylist, dark_greylist) - write_lines(args.output_blacklist, blacklist) + # Parse arguments. + args = vars(get_args()) + + flags = FlagsDict(read_lines(args["public"]), read_lines(args["private"])) + + # Combine inputs which do not require any particular order. + # (1) Assign serialization API to whitelist. + flags.assign_flag(FLAG_WHITELIST, flags.filter_apis(IS_SERIALIZATION)) + + # (2) Merge input CSV files into the dictionary. + for filename in args["csv"]: + flags.parse_and_merge_csv(read_lines(filename), filename) + + # (3) Merge text files with a known flag into the dictionary. + for flag in FLAGS: + for filename in args[flag]: + flags.assign_flag(flag, read_lines(filename), filename) + + # Merge text files where conflicts should be ignored. + # This will only assign the given flag if: + # (a) the entry exists, and + # (b) it has not been assigned any other flag. + # Because of (b), this must run after all strict assignments have been performed. + for flag in FLAGS: + for filename in args[flag + FLAG_IGNORE_CONFLICTS_SUFFIX]: + valid_entries = flags.get_valid_subset_of_unassigned_apis(read_lines(filename)) + flags.assign_flag(flag, valid_entries, filename) + + # Assign all remaining entries to the blacklist. + flags.assign_flag(FLAG_BLACKLIST, flags.filter_apis(IS_UNASSIGNED)) + + # Write output. + write_lines(args["output"], flags.generate_csv()) if __name__ == "__main__": main(sys.argv) diff --git a/tools/hiddenapi/generate_hiddenapi_lists_test.py b/tools/hiddenapi/generate_hiddenapi_lists_test.py index 4716241940b5..249f37db5a82 100755 --- a/tools/hiddenapi/generate_hiddenapi_lists_test.py +++ b/tools/hiddenapi/generate_hiddenapi_lists_test.py @@ -2,14 +2,14 @@ # # Copyright (C) 2018 The Android Open Source Project # -# Licensed under the Apache License, Version 2.0 (the "License"); +# 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, +# 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. @@ -18,90 +18,90 @@ import unittest from generate_hiddenapi_lists import * class TestHiddenapiListGeneration(unittest.TestCase): + def test_init(self): + # Check empty lists + flags = FlagsDict([], []) + self.assertEquals(flags.generate_csv(), []) - def test_move_between_sets(self): - A = set([1, 2, 3, 4]) - B = set([5, 6, 7, 8]) - move_between_sets(set([2, 4]), A, B) - self.assertEqual(A, set([1, 3])) - self.assertEqual(B, set([2, 4, 5, 6, 7, 8])) - - def test_move_between_sets_fail_not_superset(self): - A = set([1, 2, 3, 4]) - B = set([5, 6, 7, 8]) - with self.assertRaises(AssertionError) as ar: - move_between_sets(set([0, 2]), A, B) - - def test_move_between_sets_fail_not_disjoint(self): - A = set([1, 2, 3, 4]) - B = set([4, 5, 6, 7, 8]) - with self.assertRaises(AssertionError) as ar: - move_between_sets(set([1, 4]), A, B) - - def test_get_package_name(self): - self.assertEqual(get_package_name("Ljava/lang/String;->clone()V"), "Ljava/lang/") - - def test_get_package_name_fail_no_arrow(self): - with self.assertRaises(AssertionError) as ar: - get_package_name("Ljava/lang/String;-clone()V") - with self.assertRaises(AssertionError) as ar: - get_package_name("Ljava/lang/String;>clone()V") - with self.assertRaises(AssertionError) as ar: - get_package_name("Ljava/lang/String;__clone()V") - - def test_get_package_name_fail_no_package(self): - with self.assertRaises(AssertionError) as ar: - get_package_name("LString;->clone()V") - - def test_all_package_names(self): - self.assertEqual(all_package_names(), set()) - self.assertEqual(all_package_names(set(["Lfoo/Bar;->baz()V"])), set(["Lfoo/"])) - self.assertEqual( - all_package_names(set(["Lfoo/Bar;->baz()V", "Lfoo/BarX;->bazx()I"])), - set(["Lfoo/"])) - self.assertEqual( - all_package_names( - set(["Lfoo/Bar;->baz()V"]), - set(["Lfoo/BarX;->bazx()I", "Labc/xyz/Mno;->ijk()J"])), - set(["Lfoo/", "Labc/xyz/"])) - - def test_move_all(self): - src = set([ "abc", "xyz" ]) - dst = set([ "def" ]) - move_all(src, dst) - self.assertEqual(src, set()) - self.assertEqual(dst, set([ "abc", "def", "xyz" ])) - - def test_move_from_packages(self): - src = set([ "Lfoo/bar/ClassA;->abc()J", # will be moved - "Lfoo/bar/ClassA;->def()J", # will be moved - "Lcom/pkg/example/ClassD;->ijk:J", # not moved: different package - "Lfoo/bar/xyz/ClassC;->xyz()Z" ]) # not moved: subpackage - dst = set() - packages = set([ "Lfoo/bar/" ]) - move_from_packages(packages, src, dst) - self.assertEqual( - src, set([ "Lfoo/bar/xyz/ClassC;->xyz()Z", "Lcom/pkg/example/ClassD;->ijk:J" ])) + # Check valid input - two public and two private API signatures. + flags = FlagsDict(['A', 'B'], ['C', 'D']) + self.assertEquals(flags.generate_csv(), + [ 'A,' + FLAG_WHITELIST, 'B,' + FLAG_WHITELIST, 'C', 'D' ]) + + # Check invalid input - overlapping public/private API signatures. + with self.assertRaises(AssertionError): + flags = FlagsDict(['A', 'B'], ['B', 'C', 'D']) + + def test_filter_apis(self): + # Initialize flags so that A and B are put on the whitelist and + # C, D, E are left unassigned. Try filtering for the unassigned ones. + flags = FlagsDict(['A', 'B'], ['C', 'D', 'E']) + filter_set = flags.filter_apis(lambda api, flags: not flags) + self.assertTrue(isinstance(filter_set, set)) + self.assertEqual(filter_set, set([ 'C', 'D', 'E' ])) + + def test_get_valid_subset_of_unassigned_keys(self): + # Create flags where only A is unassigned. + flags = FlagsDict(['A'], ['B', 'C']) + flags.assign_flag(FLAG_GREYLIST, set(['C'])) + self.assertEquals(flags.generate_csv(), + [ 'A,' + FLAG_WHITELIST, 'B', 'C,' + FLAG_GREYLIST ]) + + # Check three things: + # (1) B is selected as valid unassigned + # (2) A is not selected because it is assigned 'whitelist' + # (3) D is not selected because it is not a valid key self.assertEqual( - dst, set([ "Lfoo/bar/ClassA;->abc()J", "Lfoo/bar/ClassA;->def()J" ])) - - def test_move_serialization(self): - # All the entries should be moved apart from the last one - src = set([ "Lfoo/bar/ClassA;->readObject(Ljava/io/ObjectInputStream;)V", - "Lfoo/bar/ClassA;->readObjectNoData()V", - "Lfoo/bar/ClassA;->readResolve()Ljava/lang/Object;", - "Lfoo/bar/ClassA;->serialVersionUID:J", - "Lfoo/bar/ClassA;->serialPersistentFields:[Ljava/io/ObjectStreamField;", - "Lfoo/bar/ClassA;->writeObject(Ljava/io/ObjectOutputStream;)V", - "Lfoo/bar/ClassA;->writeReplace()Ljava/lang/Object;", - # Should not be moved as signature does not match - "Lfoo/bar/ClassA;->readObject(Ljava/io/ObjectInputStream;)I"]) - expectedToMove = len(src) - 1 - dst = set() - packages = set([ "Lfoo/bar/" ]) - move_serialization(src, dst) - self.assertEqual(len(src), 1) - self.assertEqual(len(dst), expectedToMove) + flags.get_valid_subset_of_unassigned_apis(set(['A', 'B', 'D'])), set([ 'B' ])) + + def test_parse_and_merge_csv(self): + flags = FlagsDict(['A'], ['B']) + self.assertEquals(flags.generate_csv(), [ 'A,' + FLAG_WHITELIST, 'B' ]) + + # Test empty CSV entry. + flags.parse_and_merge_csv(['B']) + self.assertEquals(flags.generate_csv(), [ 'A,' + FLAG_WHITELIST, 'B' ]) + + # Test assigning an already assigned flag. + flags.parse_and_merge_csv(['A,' + FLAG_WHITELIST]) + self.assertEquals(flags.generate_csv(), [ 'A,' + FLAG_WHITELIST, 'B' ]) + + # Test new additions. + flags.parse_and_merge_csv([ + 'A,' + FLAG_GREYLIST, + 'B,' + FLAG_BLACKLIST + ',' + FLAG_GREYLIST_MAX_O ]) + self.assertEqual(flags.generate_csv(), + [ 'A,' + FLAG_GREYLIST + "," + FLAG_WHITELIST, + 'B,' + FLAG_BLACKLIST + "," + FLAG_GREYLIST_MAX_O ]) + + # Test unknown API signature. + with self.assertRaises(AssertionError): + flags.parse_and_merge_csv([ 'C' ]) + + # Test unknown flag. + with self.assertRaises(AssertionError): + flags.parse_and_merge_csv([ 'A,foo' ]) + + def test_assign_flag(self): + flags = FlagsDict(['A'], ['B']) + self.assertEquals(flags.generate_csv(), [ 'A,' + FLAG_WHITELIST, 'B' ]) + + # Test assigning an already assigned flag. + flags.assign_flag(FLAG_WHITELIST, set([ 'A' ])) + self.assertEquals(flags.generate_csv(), [ 'A,' + FLAG_WHITELIST, 'B' ]) + + # Test new additions. + flags.assign_flag(FLAG_GREYLIST, set([ 'A', 'B' ])) + self.assertEquals(flags.generate_csv(), + [ 'A,' + FLAG_GREYLIST + "," + FLAG_WHITELIST, 'B,' + FLAG_GREYLIST ]) + + # Test invalid API signature. + with self.assertRaises(AssertionError): + flags.assign_flag(FLAG_WHITELIST, set([ 'C' ])) + + # Test invalid flag. + with self.assertRaises(AssertionError): + flags.assign_flag('foo', set([ 'A' ])) if __name__ == '__main__': unittest.main() diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 364d5084fbc9..c6acd026bd39 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -190,8 +190,8 @@ interface IWifiManager void unregisterNetworkRequestMatchCallback(int callbackIdentifier); - boolean addNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName); + int addNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName); - boolean removeNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName); + int removeNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName); } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 8dd6c771a924..8e0d4acf089d 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -25,6 +25,7 @@ import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.UnsupportedAppUsage; +import android.app.ActivityManager; import android.content.Context; import android.content.pm.ParceledListSlice; import android.net.ConnectivityManager; @@ -137,6 +138,55 @@ public class WifiManager { public static final int ERROR_AUTH_FAILURE_EAP_FAILURE = 3; /** + * Maximum number of active network suggestions allowed per app. + * @hide + */ + public static final int NETWORK_SUGGESTIONS_MAX_PER_APP = + ActivityManager.isLowRamDeviceStatic() ? 256 : 1024; + + /** + * Reason code if all of the network suggestions were successfully added or removed. + */ + public static final int STATUS_NETWORK_SUGGESTIONS_SUCCESS = 0; + + /** + * Reason code if there was an internal error in the platform while processing the addition or + * removal of suggestions. + */ + public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL = 1; + + /** + * Reason code if one or more of the network suggestions added already exists in platform's + * database. + * @see WifiNetworkSuggestion#equals(Object) + */ + public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE = 2; + + /** + * Reason code if the number of network suggestions provided by the app crosses the max + * threshold set per app. + * @see #getMaxNumberOfNetworkSuggestionsPerApp() + */ + public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP = 3; + + /** + * Reason code if one or more of the network suggestions removed does not exist in platform's + * database. + */ + public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID = 4; + + @IntDef(prefix = { "STATUS_NETWORK_SUGGESTIONS_" }, value = { + STATUS_NETWORK_SUGGESTIONS_SUCCESS, + STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL, + STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE, + STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP, + STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID, + }) + + @Retention(RetentionPolicy.SOURCE) + public @interface NetworkSuggestionsStatusCode {} + + /** * Broadcast intent action indicating whether Wi-Fi scanning is allowed currently * @hide */ @@ -903,8 +953,12 @@ public class WifiManager { * establish a connection to a remembered access point that is * within range, and will do periodic scans if there are remembered * access points but none are in range. + * + * @deprecated This API is non-functional and will have no impact. */ + @Deprecated public static final int WIFI_MODE_FULL = 1; + /** * In this Wi-Fi lock mode, Wi-Fi will be kept active, * but the only operation that will be supported is initiation of @@ -913,28 +967,62 @@ public class WifiManager { * nor will periodic scans be automatically performed looking for * remembered access points. Scans must be explicitly requested by * an application in this mode. + * + * @deprecated This API is non-functional and will have no impact. */ + @Deprecated public static final int WIFI_MODE_SCAN_ONLY = 2; + /** - * In this Wi-Fi lock mode, Wi-Fi will be kept active as in mode - * {@link #WIFI_MODE_FULL} but it operates at high performance - * with minimum packet loss and low packet latency even when - * the device screen is off. This mode will consume more power - * and hence should be used only when there is a need for such - * an active connection. + * In this Wi-Fi lock mode, Wi-Fi will not go to power save. + * This results in operating with low packet latency. + * The lock is active even when the device screen is off or + * the acquiring application is running in the background. + * This mode will consume more power and hence should be used only + * when there is a need for this tradeoff. * <p> * An example use case is when a voice connection needs to be - * kept active even after the device screen goes off. Holding the - * regular {@link #WIFI_MODE_FULL} lock will keep the wifi - * connection active, but the connection can be lossy. + * kept active even after the device screen goes off. * Holding a {@link #WIFI_MODE_FULL_HIGH_PERF} lock for the - * duration of the voice call will improve the call quality. + * duration of the voice call may improve the call quality. * <p> - * When there is no support from the hardware, this lock mode - * will have the same behavior as {@link #WIFI_MODE_FULL} + * When there is no support from the hardware, the {@link #WIFI_MODE_FULL_HIGH_PERF} + * lock will have no impact. */ public static final int WIFI_MODE_FULL_HIGH_PERF = 3; + /** + * In this Wi-Fi lock mode, Wi-Fi will operate with a priority to achieve low latency. + * {@link #WIFI_MODE_FULL_LOW_LATENCY} lock has the following limitations: + * <ol> + * <li>The lock is only active when the screen is on.</li> + * <li>The lock is only active when the acquiring app is running in the foreground.</li> + * </ol> + * Low latency mode optimizes for reduced packet latency, + * and as a result other performance measures may suffer when there are trade-offs to make: + * <ol> + * <li>Battery life may be reduced.</li> + * <li>Throughput may be reduced.</li> + * <li>Frequency of Wi-Fi scanning may be reduced. This may result in: </li> + * <ul> + * <li>The device may not roam or switch to the AP with highest signal quality.</li> + * <li>Location accuracy may be reduced.</li> + * </ul> + * </ol> + * <p> + * Example use cases are real time gaming or virtual reality applications where + * low latency is a key factor for user experience. + * <p> + * When there is no support from the hardware, the {@link #WIFI_MODE_FULL_LOW_LATENCY} + * lock will cause the device not to go power save. + * <p> + * Note: For an app which acquires both {@link #WIFI_MODE_FULL_LOW_LATENCY} and + * {@link #WIFI_MODE_FULL_HIGH_PERF} locks, {@link #WIFI_MODE_FULL_LOW_LATENCY} + * lock will be effective when app is running in foreground and screen is on, + * while the {@link #WIFI_MODE_FULL_HIGH_PERF} lock will take effect otherwise. + */ + public static final int WIFI_MODE_FULL_LOW_LATENCY = 4; + /** Anything worse than or equal to this will show 0 bars. */ @UnsupportedAppUsage private static final int MIN_RSSI = -100; @@ -1126,7 +1214,6 @@ public class WifiManager { * @throws UnsupportedOperationException if Passpoint is not enabled on the device. * @hide */ - @SystemApi @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public List<OsuProvider> getMatchingOsuProviders(List<ScanResult> scanResults) { try { @@ -1497,12 +1584,13 @@ public class WifiManager { * suggestion back using this API.</li> * * @param networkSuggestions List of network suggestions provided by the app. - * @return true on success, false if any of the suggestions match (See + * @return Status code corresponding to the values in {@link NetworkSuggestionsStatusCode}. * {@link WifiNetworkSuggestion#equals(Object)} any previously provided suggestions by the app. * @throws {@link SecurityException} if the caller is missing required permissions. */ @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) - public boolean addNetworkSuggestions(@NonNull List<WifiNetworkSuggestion> networkSuggestions) { + public @NetworkSuggestionsStatusCode int addNetworkSuggestions( + @NonNull List<WifiNetworkSuggestion> networkSuggestions) { try { return mService.addNetworkSuggestions(networkSuggestions, mContext.getOpPackageName()); } catch (RemoteException e) { @@ -1510,21 +1598,20 @@ public class WifiManager { } } - /** - * Remove a subset of or all of networks from previously provided suggestions by the app to the - * device. + * Remove some or all of the network suggestions that were previously provided by the app. * See {@link WifiNetworkSuggestion} for a detailed explanation of the parameters. * See {@link WifiNetworkSuggestion#equals(Object)} for the equivalence evaluation used. * * @param networkSuggestions List of network suggestions to be removed. Pass an empty list * to remove all the previous suggestions provided by the app. - * @return true on success, false if any of the suggestions do not match any suggestions - * previously provided by the app. Any matching suggestions are removed from the device and - * will not be considered for any further connection attempts. + * @return Status code corresponding to the values in + * {@link NetworkSuggestionsStatusCode}. + * Any matching suggestions are removed from the device and will not be considered for any + * further connection attempts. */ @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) - public boolean removeNetworkSuggestions( + public @NetworkSuggestionsStatusCode int removeNetworkSuggestions( @NonNull List<WifiNetworkSuggestion> networkSuggestions) { try { return mService.removeNetworkSuggestions( @@ -1535,6 +1622,15 @@ public class WifiManager { } /** + * Returns the max number of network suggestions that are allowed per app on the device. + * @see #addNetworkSuggestions(List) + * @see #removeNetworkSuggestions(List) + */ + public int getMaxNumberOfNetworkSuggestionsPerApp() { + return NETWORK_SUGGESTIONS_MAX_PER_APP; + } + + /** * Add or update a Passpoint configuration. The configuration provides a credential * for connecting to Passpoint networks that are operated by the Passpoint * service provider specified in the configuration. @@ -3772,9 +3868,8 @@ public class WifiManager { /** * Creates a new WifiLock. * - * @param lockType the type of lock to create. See {@link #WIFI_MODE_FULL}, - * {@link #WIFI_MODE_FULL_HIGH_PERF} and {@link #WIFI_MODE_SCAN_ONLY} for - * descriptions of the types of Wi-Fi locks. + * @param lockType the type of lock to create. See {@link #WIFI_MODE_FULL_HIGH_PERF} + * and {@link #WIFI_MODE_FULL_LOW_LATENCY} for descriptions of the types of Wi-Fi locks. * @param tag a tag for the WifiLock to identify it in debugging messages. This string is * never shown to the user under normal conditions, but should be descriptive * enough to identify your application and the specific WifiLock within it, if it @@ -3799,12 +3894,14 @@ public class WifiManager { * @return a new, unacquired WifiLock with the given tag. * * @see WifiLock + * + * @deprecated This API is non-functional. */ + @Deprecated public WifiLock createWifiLock(String tag) { return new WifiLock(WIFI_MODE_FULL, tag); } - /** * Create a new MulticastLock * diff --git a/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java b/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java index 87706b936f03..f73b9e5e2a00 100644 --- a/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java +++ b/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java @@ -42,8 +42,10 @@ import java.util.List; public class WifiNetworkConfigBuilder { private static final String MATCH_ALL_SSID_PATTERN_PATH = ".*"; private static final String MATCH_EMPTY_SSID_PATTERN_PATH = ""; - private static final Pair<MacAddress, MacAddress> MATCH_NO_BSSID_PATTERN = + private static final Pair<MacAddress, MacAddress> MATCH_NO_BSSID_PATTERN1 = new Pair(MacAddress.BROADCAST_ADDRESS, MacAddress.BROADCAST_ADDRESS); + private static final Pair<MacAddress, MacAddress> MATCH_NO_BSSID_PATTERN2 = + new Pair(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.BROADCAST_ADDRESS); private static final Pair<MacAddress, MacAddress> MATCH_ALL_BSSID_PATTERN = new Pair(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); private static final MacAddress MATCH_EXACT_BSSID_PATTERN_MASK = @@ -189,7 +191,13 @@ public class WifiNetworkConfigBuilder { * Set the BSSID to use for filtering networks from scan results. Will only match network whose * BSSID is identical to the specified value. * <p> - * <li>Only allowed for creating network specifier, i.e {@link #buildNetworkSpecifier()}. </li> + * <li>For network requests ({@link NetworkSpecifier}), built using + * {@link #buildNetworkSpecifier}, sets the BSSID to use for filtering networks from scan + * results. Will only match networks whose BSSID is identical to specified value.</li> + * <li>For network suggestions ({@link WifiNetworkSuggestion}), built using + * {@link #buildNetworkSuggestion()}, sets a specific BSSID for the network suggestion. + * If set, only the specified BSSID with the specified SSID will be considered for connection. + * If not set, all BSSIDs with the specified SSID will be considered for connection.</li> * <li>Overrides any previous value set using {@link #setBssid(MacAddress)} or * {@link #setBssidPattern(MacAddress, MacAddress)}.</li> * @@ -432,6 +440,9 @@ public class WifiNetworkConfigBuilder { if (mSsidPatternMatcher.getType() == PatternMatcher.PATTERN_LITERAL) { wifiConfiguration.SSID = "\"" + mSsidPatternMatcher.getPath() + "\""; } + if (mBssidPatternMatcher.second == MATCH_EXACT_BSSID_PATTERN_MASK) { + wifiConfiguration.BSSID = mBssidPatternMatcher.first.toString(); + } setSecurityParamsInWifiConfiguration(wifiConfiguration); wifiConfiguration.hiddenSSID = mIsHiddenSSID; wifiConfiguration.priority = mPriority; @@ -460,7 +471,10 @@ public class WifiNetworkConfigBuilder { && mSsidPatternMatcher.getPath().equals(MATCH_EMPTY_SSID_PATTERN_PATH)) { return true; } - if (mBssidPatternMatcher.equals(MATCH_NO_BSSID_PATTERN)) { + if (mBssidPatternMatcher.equals(MATCH_NO_BSSID_PATTERN1)) { + return true; + } + if (mBssidPatternMatcher.equals(MATCH_NO_BSSID_PATTERN2)) { return true; } return false; @@ -474,6 +488,16 @@ public class WifiNetworkConfigBuilder { return false; } + private boolean hasSetMatchExactPattern() { + // exact ssid match with either match-all bssid or match-exact bssid. + if (mSsidPatternMatcher.getType() == PatternMatcher.PATTERN_LITERAL + && (mBssidPatternMatcher.equals(MATCH_ALL_BSSID_PATTERN) + || mBssidPatternMatcher.second.equals(MATCH_EXACT_BSSID_PATTERN_MASK))) { + return true; + } + return false; + } + private void validateSecurityParams() { int numSecurityTypes = 0; numSecurityTypes += mIsEnhancedOpen ? 1 : 0; @@ -566,9 +590,42 @@ public class WifiNetworkConfigBuilder { } /** - * Create a network suggestion object use in - * {@link WifiManager#addNetworkSuggestions(List)}. + * Create a network suggestion object use in {@link WifiManager#addNetworkSuggestions(List)}. * See {@link WifiNetworkSuggestion}. + *<p> + * Note: Apps can set a combination of SSID using {@link #setSsid(String)} and BSSID + * using {@link #setBssid(MacAddress)} to provide more fine grained network suggestions to the + * platform. + * </p> + * + * For example: + * To provide credentials for one open, one WPA2 and one WPA3 network with their + * corresponding SSID's: + * {@code + * final WifiNetworkSuggestion suggestion1 = + * new WifiNetworkConfigBuilder() + * .setSsid("test111111") + * .buildNetworkSuggestion() + * final WifiNetworkSuggestion suggestion2 = + * new WifiNetworkConfigBuilder() + * .setSsid("test222222") + * .setWpa2Passphrase("test123456") + * .buildNetworkSuggestion() + * final WifiNetworkSuggestion suggestion3 = + * new WifiNetworkConfigBuilder() + * .setSsid("test333333") + * .setWpa3Passphrase("test6789") + * .buildNetworkSuggestion() + * final List<WifiNetworkSuggestion> suggestionsList = new ArrayList<WifiNetworkSuggestion> {{ + * add(suggestion1); + * add(suggestion2); + * add(suggestion3); + * }}; + * final WifiManager wifiManager = + * context.getSystemService(Context.WIFI_SERVICE); + * wifiManager.addNetworkSuggestions(suggestionsList); + * ... + * } * * @return Instance of {@link WifiNetworkSuggestion}. * @throws IllegalStateException on invalid params set. @@ -577,11 +634,14 @@ public class WifiNetworkConfigBuilder { if (mSsidPatternMatcher == null) { throw new IllegalStateException("setSsid should be invoked for suggestion"); } - if (mSsidPatternMatcher.getType() != PatternMatcher.PATTERN_LITERAL - || mBssidPatternMatcher != null) { - throw new IllegalStateException("none of setSsidPattern/setBssidPattern/setBssid are" + setMatchAnyPatternIfUnset(); + if (!hasSetMatchExactPattern()) { + throw new IllegalStateException("none of setSsidPattern/setBssidPattern are" + " allowed for suggestion"); } + if (hasSetMatchNonePattern()) { + throw new IllegalStateException("cannot set match-none for suggestion"); + } validateSecurityParams(); return new WifiNetworkSuggestion( diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java index 25121e2dc8c7..760f1e6bc5e2 100644 --- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java +++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java @@ -104,8 +104,8 @@ public final class WifiNetworkSuggestion implements Parcelable { @Override public int hashCode() { - return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.allowedKeyManagement, - suggestorUid); + return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID, + wifiConfiguration.allowedKeyManagement, suggestorUid); } /** @@ -121,6 +121,7 @@ public final class WifiNetworkSuggestion implements Parcelable { } WifiNetworkSuggestion lhs = (WifiNetworkSuggestion) obj; return Objects.equals(this.wifiConfiguration.SSID, lhs.wifiConfiguration.SSID) + && Objects.equals(this.wifiConfiguration.BSSID, lhs.wifiConfiguration.BSSID) && Objects.equals(this.wifiConfiguration.allowedKeyManagement, lhs.wifiConfiguration.allowedKeyManagement) && suggestorUid == lhs.suggestorUid; diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java index 6622a2571870..fc5caf0a47d7 100644 --- a/wifi/java/android/net/wifi/WifiScanner.java +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -253,6 +253,14 @@ public class WifiScanner { */ @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public int type = TYPE_LOW_LATENCY; + /** + * This scan request may ignore location settings while receiving scans. This should only + * be used in emergency situations. + * {@hide} + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) + public boolean ignoreLocationSettings; /** Implement the Parcelable interface {@hide} */ public int describeContents() { @@ -270,6 +278,7 @@ public class WifiScanner { dest.writeInt(stepCount); dest.writeInt(isPnoScan ? 1 : 0); dest.writeInt(type); + dest.writeInt(ignoreLocationSettings ? 1 : 0); if (channels != null) { dest.writeInt(channels.length); for (int i = 0; i < channels.length; i++) { @@ -304,6 +313,7 @@ public class WifiScanner { settings.stepCount = in.readInt(); settings.isPnoScan = in.readInt() == 1; settings.type = in.readInt(); + settings.ignoreLocationSettings = in.readInt() == 1; int num_channels = in.readInt(); settings.channels = new ChannelSpec[num_channels]; for (int i = 0; i < num_channels; i++) { diff --git a/wifi/java/com/android/server/wifi/AbstractWifiService.java b/wifi/java/com/android/server/wifi/AbstractWifiService.java index aa526d248d14..0f4e3a8ba20f 100644 --- a/wifi/java/com/android/server/wifi/AbstractWifiService.java +++ b/wifi/java/com/android/server/wifi/AbstractWifiService.java @@ -442,13 +442,13 @@ public abstract class AbstractWifiService extends IWifiManager.Stub { } @Override - public boolean addNetworkSuggestions( + public int addNetworkSuggestions( List<WifiNetworkSuggestion> networkSuggestions, String callingPackageName) { throw new UnsupportedOperationException(); } @Override - public boolean removeNetworkSuggestions( + public int removeNetworkSuggestions( List<WifiNetworkSuggestion> networkSuggestions, String callingPackageName) { throw new UnsupportedOperationException(); } diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 8fe5af998dcf..13c8c9ea7ead 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -1298,13 +1298,26 @@ i * Verify that a call to cancel WPS immediately returns a failure. */ @Test public void addRemoveNetworkSuggestions() throws Exception { - when(mWifiService.addNetworkSuggestions(any(List.class), anyString())).thenReturn(true); - when(mWifiService.removeNetworkSuggestions(any(List.class), anyString())).thenReturn(true); + when(mWifiService.addNetworkSuggestions(any(List.class), anyString())) + .thenReturn(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS); + when(mWifiService.removeNetworkSuggestions(any(List.class), anyString())) + .thenReturn(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS); - assertTrue(mWifiManager.addNetworkSuggestions(new ArrayList<>())); + assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS, + mWifiManager.addNetworkSuggestions(new ArrayList<>())); verify(mWifiService).addNetworkSuggestions(anyList(), eq(TEST_PACKAGE_NAME)); - assertTrue(mWifiManager.removeNetworkSuggestions(new ArrayList<>())); + assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS, + mWifiManager.removeNetworkSuggestions(new ArrayList<>())); verify(mWifiService).removeNetworkSuggestions(anyList(), eq(TEST_PACKAGE_NAME)); } + + /** + * Verify call to {@link WifiManager#getMaxNumberOfNetworkSuggestionsPerApp()}. + */ + @Test + public void getMaxNumberOfNetworkSuggestionsPerApp() { + assertEquals(WifiManager.NETWORK_SUGGESTIONS_MAX_PER_APP, + mWifiManager.getMaxNumberOfNetworkSuggestionsPerApp()); + } } diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkConfigBuilderTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkConfigBuilderTest.java index c455c6f0836d..2505499c85d7 100644 --- a/wifi/tests/src/android/net/wifi/WifiNetworkConfigBuilderTest.java +++ b/wifi/tests/src/android/net/wifi/WifiNetworkConfigBuilderTest.java @@ -244,7 +244,7 @@ public class WifiNetworkConfigBuilderTest { * when match-none SSID pattern is set. */ @Test(expected = IllegalStateException.class) - public void testWifiNetworkSpecifierBuilderWithMatchNoneSsidPattern() { + public void testWifiNetworkSpecifierBuilderWithMatchNoneSsidPattern1() { new WifiNetworkConfigBuilder() .setSsidPattern(new PatternMatcher("", PatternMatcher.PATTERN_LITERAL)) .buildNetworkSpecifier(); @@ -252,10 +252,21 @@ public class WifiNetworkConfigBuilderTest { /** * Ensure {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} throws an exception + * when match-none SSID pattern is set. + */ + @Test(expected = IllegalStateException.class) + public void testWifiNetworkSpecifierBuilderWithMatchNoneSsidPattern2() { + new WifiNetworkConfigBuilder() + .setSsid("") + .buildNetworkSpecifier(); + } + + /** + * Ensure {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} throws an exception * when match-none BSSID pattern is set. */ @Test(expected = IllegalStateException.class) - public void testWifiNetworkSpecifierBuilderWithMatchNoneBssidPattern() { + public void testWifiNetworkSpecifierBuilderWithMatchNoneBssidPattern1() { new WifiNetworkConfigBuilder() .setBssidPattern(MacAddress.BROADCAST_ADDRESS, MacAddress.BROADCAST_ADDRESS) .buildNetworkSpecifier(); @@ -263,6 +274,28 @@ public class WifiNetworkConfigBuilderTest { /** * Ensure {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} throws an exception + * when match-none BSSID pattern is set. + */ + @Test(expected = IllegalStateException.class) + public void testWifiNetworkSpecifierBuilderWithMatchNoneBssidPattern2() { + new WifiNetworkConfigBuilder() + .setBssid(MacAddress.BROADCAST_ADDRESS) + .buildNetworkSpecifier(); + } + + /** + * Ensure {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} throws an exception + * when match-none BSSID pattern is set. + */ + @Test(expected = IllegalStateException.class) + public void testWifiNetworkSpecifierBuilderWithMatchNoneBssidPattern3() { + new WifiNetworkConfigBuilder() + .setBssid(MacAddress.ALL_ZEROS_ADDRESS) + .buildNetworkSpecifier(); + } + + /** + * Ensure {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} throws an exception * when SSID pattern is set for hidden network. */ @Test(expected = IllegalStateException.class) @@ -429,13 +462,15 @@ public class WifiNetworkConfigBuilderTest { * {@link WifiNetworkConfigBuilder#buildNetworkSuggestion()} for OWE network. */ @Test - public void testWifiNetworkSuggestionBuilderForEnhancedOpenNetwork() { + public void testWifiNetworkSuggestionBuilderForEnhancedOpenNetworkWithBssid() { WifiNetworkSuggestion suggestion = new WifiNetworkConfigBuilder() .setSsid(TEST_SSID) + .setBssid(MacAddress.fromString(TEST_BSSID)) .setIsEnhancedOpen() .buildNetworkSuggestion(); assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID); + assertEquals(TEST_BSSID, suggestion.wifiConfiguration.BSSID); assertTrue(suggestion.wifiConfiguration.allowedKeyManagement .get(WifiConfiguration.KeyMgmt.OWE)); assertNull(suggestion.wifiConfiguration.preSharedKey); @@ -505,7 +540,7 @@ public class WifiNetworkConfigBuilderTest { /** * Ensure {@link WifiNetworkConfigBuilder#buildNetworkSuggestion()} throws an exception - * when {@link WifiNetworkConfigBuilder#setBssid(MacAddress)} is set. + * when {@link WifiNetworkConfigBuilder#setBssidPattern(MacAddress, MacAddress)} is set. */ @Test(expected = IllegalStateException.class) public void testWifiNetworkSuggestionBuilderWithBssidPattern() { @@ -518,23 +553,46 @@ public class WifiNetworkConfigBuilderTest { /** * Ensure {@link WifiNetworkConfigBuilder#buildNetworkSuggestion()} throws an exception - * when {@link WifiNetworkConfigBuilder#setBssidPattern(MacAddress, MacAddress)} is set. + * when {@link WifiNetworkConfigBuilder#setSsid(String)} is not set. + */ + @Test(expected = IllegalStateException.class) + public void testWifiNetworkSuggestionBuilderWithNoSsid() { + new WifiNetworkConfigBuilder() + .buildNetworkSuggestion(); + } + + /** + * Ensure {@link WifiNetworkConfigBuilder#buildNetworkSuggestion()} throws an exception + * when {@link WifiNetworkConfigBuilder#setSsid(String)} is invoked with an invalid value. */ @Test(expected = IllegalStateException.class) - public void testWifiNetworkSuggestionBuilderWithBssid() { + public void testWifiNetworkSuggestionBuilderWithInvalidSsid() { + new WifiNetworkConfigBuilder() + .setSsid("") + .buildNetworkSuggestion(); + } + + /** + * Ensure {@link WifiNetworkConfigBuilder#buildNetworkSuggestion()} throws an exception + * when {@link WifiNetworkConfigBuilder#setBssid(MacAddress)} is invoked with an invalid value. + */ + @Test(expected = IllegalStateException.class) + public void testWifiNetworkSuggestionBuilderWithInvalidBroadcastBssid() { new WifiNetworkConfigBuilder() .setSsid(TEST_SSID) - .setBssid(MacAddress.fromString(TEST_BSSID)) + .setBssid(MacAddress.BROADCAST_ADDRESS) .buildNetworkSuggestion(); } /** * Ensure {@link WifiNetworkConfigBuilder#buildNetworkSuggestion()} throws an exception - * when {@link WifiNetworkConfigBuilder#setSsid(String)} is not set. + * when {@link WifiNetworkConfigBuilder#setBssid(MacAddress)} is invoked with an invalid value. */ @Test(expected = IllegalStateException.class) - public void testWifiNetworkSuggestionBuilderWithNoSsid() { + public void testWifiNetworkSuggestionBuilderWithInvalidAllZeroBssid() { new WifiNetworkConfigBuilder() + .setSsid(TEST_SSID) + .setBssid(MacAddress.ALL_ZEROS_ADDRESS) .buildNetworkSuggestion(); } diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java index 6bab60dd480b..5cc821717462 100644 --- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java +++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java @@ -29,6 +29,7 @@ import org.junit.Test; @SmallTest public class WifiNetworkSuggestionTest { private static final String TEST_SSID = "\"Test123\""; + private static final String TEST_BSSID = "12:12:12:12:12:12"; private static final String TEST_SSID_1 = "\"Test1234\""; /** @@ -38,6 +39,7 @@ public class WifiNetworkSuggestionTest { public void testWifiNetworkSuggestionParcel() { WifiConfiguration configuration = new WifiConfiguration(); configuration.SSID = TEST_SSID; + configuration.BSSID = TEST_BSSID; configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion(configuration, false, true, 0); @@ -65,18 +67,20 @@ public class WifiNetworkSuggestionTest { /** * Check NetworkSuggestion equals returns {@code true} for 2 network suggestions with the same - * SSID, key mgmt and UID. + * SSID, BSSID, key mgmt and UID. */ @Test public void testWifiNetworkSuggestionEqualsSame() { WifiConfiguration configuration = new WifiConfiguration(); configuration.SSID = TEST_SSID; + configuration.BSSID = TEST_BSSID; configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion(configuration, true, false, 0); WifiConfiguration configuration1 = new WifiConfiguration(); configuration1.SSID = TEST_SSID; + configuration1.BSSID = TEST_BSSID; configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); WifiNetworkSuggestion suggestion1 = new WifiNetworkSuggestion(configuration1, false, true, 0); @@ -86,7 +90,7 @@ public class WifiNetworkSuggestionTest { /** * Check NetworkSuggestion equals returns {@code false} for 2 network suggestions with the same - * key mgmt and UID, but different SSID. + * BSSID, key mgmt and UID, but different SSID. */ @Test public void testWifiNetworkSuggestionEqualsFailsWhenSsidIsDifferent() { @@ -107,7 +111,29 @@ public class WifiNetworkSuggestionTest { /** * Check NetworkSuggestion equals returns {@code false} for 2 network suggestions with the same - * SSID and UID, but different key mgmt. + * SSID, key mgmt and UID, but different BSSID. + */ + @Test + public void testWifiNetworkSuggestionEqualsFailsWhenBssidIsDifferent() { + WifiConfiguration configuration = new WifiConfiguration(); + configuration.SSID = TEST_SSID; + configuration.BSSID = TEST_BSSID; + configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); + WifiNetworkSuggestion suggestion = + new WifiNetworkSuggestion(configuration, false, false, 0); + + WifiConfiguration configuration1 = new WifiConfiguration(); + configuration1.SSID = TEST_SSID; + configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); + WifiNetworkSuggestion suggestion1 = + new WifiNetworkSuggestion(configuration1, false, false, 0); + + assertNotEquals(suggestion, suggestion1); + } + + /** + * Check NetworkSuggestion equals returns {@code false} for 2 network suggestions with the same + * SSID, BSSID and UID, but different key mgmt. */ @Test public void testWifiNetworkSuggestionEqualsFailsWhenKeyMgmtIsDifferent() { @@ -128,7 +154,7 @@ public class WifiNetworkSuggestionTest { /** * Check NetworkSuggestion equals returns {@code false} for 2 network suggestions with the same - * SSID and key mgmt, but different UID. + * SSID, BSSID and key mgmt, but different UID. */ @Test public void testWifiNetworkSuggestionEqualsFailsWhenUidIsDifferent() { |