diff options
206 files changed, 9701 insertions, 1614 deletions
diff --git a/Android.bp b/Android.bp index e096c9df5662..1b81306b3699 100644 --- a/Android.bp +++ b/Android.bp @@ -207,6 +207,8 @@ java_defaults { "core/java/android/hardware/usb/IUsbSerialReader.aidl", "core/java/android/net/ICaptivePortal.aidl", "core/java/android/net/IConnectivityManager.aidl", + "core/java/android/hardware/ISensorPrivacyListener.aidl", + "core/java/android/hardware/ISensorPrivacyManager.aidl", "core/java/android/net/IIpConnectivityMetrics.aidl", "core/java/android/net/IEthernetManager.aidl", "core/java/android/net/IEthernetServiceListener.aidl", @@ -1190,6 +1192,7 @@ framework_docs_only_libs = [ metalava_framework_docs_args = "--manifest $(location core/res/AndroidManifest.xml) " + "--hide-package com.android.okhttp " + "--hide-package com.android.org.conscrypt --hide-package com.android.server " + + "--error UnhiddenSystemApi " + "--hide RequiresPermission " + "--hide MissingPermission --hide BroadcastBehavior " + "--hide HiddenSuperclass --hide DeprecationMismatch --hide UnavailableSymbol " + @@ -1606,6 +1609,7 @@ droidstubs { dex_mapping_filename: "dex-mapping.txt", args: metalava_framework_docs_args + " --hide ReferencesHidden " + + " --hide UnhiddenSystemApi " + " --show-unannotated " + " --show-annotation android.annotation.SystemApi " + " --show-annotation android.annotation.TestApi " diff --git a/api/current.txt b/api/current.txt index 92526d4f8bc4..c8283d04b273 100644 --- a/api/current.txt +++ b/api/current.txt @@ -417,7 +417,7 @@ package android { field public static final int clipOrientation = 16843274; // 0x101020a field public static final int clipToPadding = 16842987; // 0x10100eb field public static final int closeIcon = 16843905; // 0x1010481 - field public static final int codes = 16843330; // 0x1010242 + field public static final deprecated int codes = 16843330; // 0x1010242 field public static final int collapseColumns = 16843083; // 0x101014b field public static final int collapseContentDescription = 16843984; // 0x10104d0 field public static final int collapseIcon = 16844031; // 0x10104ff @@ -718,7 +718,7 @@ package android { field public static final int homeAsUpIndicator = 16843531; // 0x101030b field public static final int homeLayout = 16843549; // 0x101031d field public static final int horizontalDivider = 16843053; // 0x101012d - field public static final int horizontalGap = 16843327; // 0x101023f + field public static final deprecated int horizontalGap = 16843327; // 0x101023f field public static final int horizontalScrollViewStyle = 16843603; // 0x1010353 field public static final int horizontalSpacing = 16843028; // 0x1010114 field public static final int host = 16842792; // 0x1010028 @@ -726,7 +726,7 @@ package android { field public static final int hotSpotY = 16844056; // 0x1010518 field public static final int hyphenationFrequency = 16843998; // 0x10104de field public static final int icon = 16842754; // 0x1010002 - field public static final int iconPreview = 16843337; // 0x1010249 + field public static final deprecated int iconPreview = 16843337; // 0x1010249 field public static final int iconSpaceReserved = 16844129; // 0x1010561 field public static final int iconTint = 16844126; // 0x101055e field public static final int iconTintMode = 16844127; // 0x101055f @@ -787,12 +787,12 @@ package android { field public static final int isGame = 16843764; // 0x10103f4 field public static final int isIndicator = 16843079; // 0x1010147 field public static final int isLightTheme = 16844176; // 0x1010590 - field public static final int isModifier = 16843334; // 0x1010246 - field public static final int isRepeatable = 16843336; // 0x1010248 + field public static final deprecated int isModifier = 16843334; // 0x1010246 + field public static final deprecated int isRepeatable = 16843336; // 0x1010248 field public static final int isScrollContainer = 16843342; // 0x101024e field public static final int isSplitRequired = 16844177; // 0x1010591 field public static final int isStatic = 16844122; // 0x101055a - field public static final int isSticky = 16843335; // 0x1010247 + field public static final deprecated int isSticky = 16843335; // 0x1010247 field public static final int isolatedProcess = 16843689; // 0x10103a9 field public static final int isolatedSplits = 16844107; // 0x101054b field public static final int itemBackground = 16843056; // 0x1010130 @@ -802,27 +802,27 @@ package android { field public static final int justificationMode = 16844135; // 0x1010567 field public static final int keepScreenOn = 16843286; // 0x1010216 field public static final int key = 16843240; // 0x10101e8 - field public static final int keyBackground = 16843315; // 0x1010233 - field public static final int keyEdgeFlags = 16843333; // 0x1010245 - field public static final int keyHeight = 16843326; // 0x101023e - field public static final int keyIcon = 16843340; // 0x101024c - field public static final int keyLabel = 16843339; // 0x101024b - field public static final int keyOutputText = 16843338; // 0x101024a - field public static final int keyPreviewHeight = 16843321; // 0x1010239 - field public static final int keyPreviewLayout = 16843319; // 0x1010237 - field public static final int keyPreviewOffset = 16843320; // 0x1010238 + field public static final deprecated int keyBackground = 16843315; // 0x1010233 + field public static final deprecated int keyEdgeFlags = 16843333; // 0x1010245 + field public static final deprecated int keyHeight = 16843326; // 0x101023e + field public static final deprecated int keyIcon = 16843340; // 0x101024c + field public static final deprecated int keyLabel = 16843339; // 0x101024b + field public static final deprecated int keyOutputText = 16843338; // 0x101024a + field public static final deprecated int keyPreviewHeight = 16843321; // 0x1010239 + field public static final deprecated int keyPreviewLayout = 16843319; // 0x1010237 + field public static final deprecated int keyPreviewOffset = 16843320; // 0x1010238 field public static final int keySet = 16843739; // 0x10103db - field public static final int keyTextColor = 16843318; // 0x1010236 - field public static final int keyTextSize = 16843316; // 0x1010234 - field public static final int keyWidth = 16843325; // 0x101023d + field public static final deprecated int keyTextColor = 16843318; // 0x1010236 + field public static final deprecated int keyTextSize = 16843316; // 0x1010234 + field public static final deprecated int keyWidth = 16843325; // 0x101023d field public static final int keyboardLayout = 16843691; // 0x10103ab - field public static final int keyboardMode = 16843341; // 0x101024d + field public static final deprecated int keyboardMode = 16843341; // 0x101024d field public static final int keyboardNavigationCluster = 16844096; // 0x1010540 field public static final int keycode = 16842949; // 0x10100c5 field public static final int killAfterRestore = 16843420; // 0x101029c field public static final int label = 16842753; // 0x1010001 field public static final int labelFor = 16843718; // 0x10103c6 - field public static final int labelTextSize = 16843317; // 0x1010235 + field public static final deprecated int labelTextSize = 16843317; // 0x1010235 field public static final int languageTag = 16844040; // 0x1010508 field public static final int largeHeap = 16843610; // 0x101035a field public static final int largeScreens = 16843398; // 0x1010286 @@ -940,6 +940,7 @@ package android { field public static final int menuCategory = 16843230; // 0x10101de field public static final int mimeType = 16842790; // 0x1010026 field public static final int min = 16844089; // 0x1010539 + field public static final int minAspectRatio = 16844193; // 0x10105a1 field public static final int minDate = 16843583; // 0x101033f field public static final int minEms = 16843098; // 0x101015a field public static final int minHeight = 16843072; // 0x1010140 @@ -1046,12 +1047,12 @@ package android { field public static final int pointerIcon = 16844041; // 0x1010509 field public static final int popupAnimationStyle = 16843465; // 0x10102c9 field public static final int popupBackground = 16843126; // 0x1010176 - field public static final int popupCharacters = 16843332; // 0x1010244 + field public static final deprecated int popupCharacters = 16843332; // 0x1010244 field public static final int popupElevation = 16843916; // 0x101048c field public static final int popupEnterTransition = 16844063; // 0x101051f field public static final int popupExitTransition = 16844064; // 0x1010520 - field public static final int popupKeyboard = 16843331; // 0x1010243 - field public static final int popupLayout = 16843323; // 0x101023b + field public static final deprecated int popupKeyboard = 16843331; // 0x1010243 + field public static final deprecated int popupLayout = 16843323; // 0x101023b field public static final int popupMenuStyle = 16843520; // 0x1010300 field public static final int popupTheme = 16843945; // 0x10104a9 field public static final int popupWindowStyle = 16842870; // 0x1010076 @@ -1150,7 +1151,7 @@ package android { field public static final int roundIcon = 16844076; // 0x101052c field public static final int rowCount = 16843637; // 0x1010375 field public static final int rowDelay = 16843216; // 0x10101d0 - field public static final int rowEdgeFlags = 16843329; // 0x1010241 + field public static final deprecated int rowEdgeFlags = 16843329; // 0x1010241 field public static final int rowHeight = 16843058; // 0x1010132 field public static final int rowOrderPreserved = 16843638; // 0x1010376 field public static final int saveEnabled = 16842983; // 0x10100e7 @@ -1286,7 +1287,7 @@ package android { field public static final int state_focused = 16842908; // 0x101009c field public static final int state_hovered = 16843623; // 0x1010367 field public static final int state_last = 16842918; // 0x10100a6 - field public static final int state_long_pressable = 16843324; // 0x101023c + field public static final deprecated int state_long_pressable = 16843324; // 0x101023c field public static final int state_middle = 16842917; // 0x10100a5 field public static final int state_multiline = 16843597; // 0x101034d field public static final int state_pressed = 16842919; // 0x10100a7 @@ -1524,9 +1525,9 @@ package android { field public static final int versionCodeMajor = 16844150; // 0x1010576 field public static final int versionMajor = 16844151; // 0x1010577 field public static final int versionName = 16843292; // 0x101021c - field public static final int verticalCorrection = 16843322; // 0x101023a + field public static final deprecated int verticalCorrection = 16843322; // 0x101023a field public static final int verticalDivider = 16843054; // 0x101012e - field public static final int verticalGap = 16843328; // 0x1010240 + field public static final deprecated int verticalGap = 16843328; // 0x1010240 field public static final int verticalScrollbarPosition = 16843572; // 0x1010334 field public static final int verticalSpacing = 16843029; // 0x1010115 field public static final int viewportHeight = 16843779; // 0x1010403 @@ -1894,7 +1895,7 @@ package android { field public static final int input = 16908297; // 0x1020009 field public static final int inputArea = 16908318; // 0x102001e field public static final int inputExtractEditText = 16908325; // 0x1020025 - field public static final int keyboardView = 16908326; // 0x1020026 + field public static final deprecated int keyboardView = 16908326; // 0x1020026 field public static final int list = 16908298; // 0x102000a field public static final int list_container = 16908351; // 0x102003f field public static final int mask = 16908334; // 0x102002e @@ -2605,7 +2606,7 @@ package android { field public static final int Widget_Holo_WebView = 16973993; // 0x10300a9 field public static final int Widget_ImageButton = 16973862; // 0x1030026 field public static final int Widget_ImageWell = 16973861; // 0x1030025 - field public static final int Widget_KeyboardView = 16973911; // 0x1030057 + field public static final deprecated int Widget_KeyboardView = 16973911; // 0x1030057 field public static final int Widget_ListPopupWindow = 16973957; // 0x1030085 field public static final int Widget_ListView = 16973870; // 0x103002e field public static final int Widget_ListView_DropDown = 16973872; // 0x1030030 @@ -16583,6 +16584,7 @@ package android.hardware.camera2 { field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Byte> REQUEST_PIPELINE_MAX_DEPTH; field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Float> SCALER_AVAILABLE_MAX_DIGITAL_ZOOM; field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SCALER_CROPPING_TYPE; + field public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_STREAM_COMBINATIONS; field public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.StreamConfigurationMap> SCALER_STREAM_CONFIGURATION_MAP; field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> SENSOR_AVAILABLE_TEST_PATTERN_MODES; field public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_BLACK_LEVEL_PATTERN; @@ -17153,6 +17155,18 @@ package android.hardware.camera2.params { field public static final float MINIMUM_GAIN_FACTOR = 1.0f; } + public final class MandatoryStreamCombination { + method public java.lang.String getDescription(); + method public java.util.List<android.hardware.camera2.params.MandatoryStreamCombination.MandatoryStreamInformation> getStreamsInformation(); + method public boolean isReprocessable(); + } + + public static final class MandatoryStreamCombination.MandatoryStreamInformation { + method public java.util.List<android.util.Size> getAvailableSizes(); + method public int getFormat(); + method public boolean isInput(); + } + public final class MeteringRectangle { ctor public MeteringRectangle(int, int, int, int, int); ctor public MeteringRectangle(android.graphics.Point, android.util.Size, int); @@ -22368,7 +22382,7 @@ package android.inputmethodservice { field public int visibleTopInsets; } - public class Keyboard { + public deprecated class Keyboard { ctor public Keyboard(android.content.Context, int); ctor public Keyboard(android.content.Context, int, int, int, int); ctor public Keyboard(android.content.Context, int, int); @@ -22442,7 +22456,7 @@ package android.inputmethodservice { field public int verticalGap; } - public class KeyboardView extends android.view.View implements android.view.View.OnClickListener { + public deprecated class KeyboardView extends android.view.View implements android.view.View.OnClickListener { ctor public KeyboardView(android.content.Context, android.util.AttributeSet); ctor public KeyboardView(android.content.Context, android.util.AttributeSet, int); ctor public KeyboardView(android.content.Context, android.util.AttributeSet, int, int); @@ -29663,8 +29677,8 @@ package android.net.wifi.aware { public class DiscoverySession implements java.lang.AutoCloseable { method public void close(); - method public android.net.NetworkSpecifier createNetworkSpecifierOpen(android.net.wifi.aware.PeerHandle); - method public android.net.NetworkSpecifier createNetworkSpecifierPassphrase(android.net.wifi.aware.PeerHandle, java.lang.String); + method public deprecated android.net.NetworkSpecifier createNetworkSpecifierOpen(android.net.wifi.aware.PeerHandle); + method public deprecated android.net.NetworkSpecifier createNetworkSpecifierPassphrase(android.net.wifi.aware.PeerHandle, java.lang.String); method public void sendMessage(android.net.wifi.aware.PeerHandle, int, byte[]); } @@ -29749,6 +29763,21 @@ package android.net.wifi.aware { field public static final int WIFI_AWARE_DATA_PATH_ROLE_RESPONDER = 1; // 0x1 } + public static class WifiAwareManager.NetworkSpecifierBuilder { + ctor public WifiAwareManager.NetworkSpecifierBuilder(); + method public android.net.NetworkSpecifier build(); + method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setDiscoverySession(android.net.wifi.aware.DiscoverySession); + method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setPeerHandle(android.net.wifi.aware.PeerHandle); + method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setPskPassphrase(java.lang.String); + } + + public final class WifiAwareNetworkInfo implements android.os.Parcelable android.net.TransportInfo { + method public int describeContents(); + method public java.net.Inet6Address getPeerIpv6Addr(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.net.wifi.aware.WifiAwareNetworkInfo> CREATOR; + } + public class WifiAwareSession implements java.lang.AutoCloseable { method public void close(); method public android.net.NetworkSpecifier createNetworkSpecifierOpen(int, byte[]); @@ -36355,6 +36384,12 @@ package android.provider { field public static final java.lang.String CACHED_NUMBER_TYPE = "numbertype"; field public static final java.lang.String CACHED_PHOTO_ID = "photo_id"; field public static final java.lang.String CACHED_PHOTO_URI = "photo_uri"; + field public static final java.lang.String CALL_ID_APP_NAME = "call_id_app_name"; + field public static final java.lang.String CALL_ID_DESCRIPTION = "call_id_description"; + field public static final java.lang.String CALL_ID_DETAILS = "call_id_details"; + field public static final java.lang.String CALL_ID_NAME = "call_id_name"; + field public static final java.lang.String CALL_ID_NUISANCE_CONFIDENCE = "call_id_nuisance_confidence"; + field public static final java.lang.String CALL_ID_PACKAGE_NAME = "call_id_package_name"; field public static final java.lang.String CALL_SCREENING_APP_NAME = "call_screening_app_name"; field public static final java.lang.String CALL_SCREENING_COMPONENT_NAME = "call_screening_component_name"; field public static final android.net.Uri CONTENT_FILTER_URI; @@ -41064,12 +41099,12 @@ package android.service.notification { public static class NotificationListenerService.Ranking { ctor public NotificationListenerService.Ranking(); - method public boolean audiblyAlerted(); method public boolean canShowBadge(); method public android.app.NotificationChannel getChannel(); method public int getImportance(); method public java.lang.CharSequence getImportanceExplanation(); method public java.lang.String getKey(); + method public long getLastAudiblyAlertedMillis(); method public java.lang.String getOverrideGroupKey(); method public int getRank(); method public int getSuppressedVisualEffects(); @@ -42205,7 +42240,9 @@ package android.system { field public static final int SIOCGIFBRDADDR; field public static final int SIOCGIFDSTADDR; field public static final int SIOCGIFNETMASK; + field public static final int SOCK_CLOEXEC; field public static final int SOCK_DGRAM; + field public static final int SOCK_NONBLOCK; field public static final int SOCK_RAW; field public static final int SOCK_SEQPACKET; field public static final int SOCK_STREAM; @@ -42512,6 +42549,7 @@ package android.telecom { method public static java.lang.String capabilitiesToString(int); method public android.telecom.PhoneAccountHandle getAccountHandle(); method public int getCallCapabilities(); + method public android.telecom.CallIdentification getCallIdentification(); method public int getCallProperties(); method public java.lang.String getCallerDisplayName(); method public int getCallerDisplayNamePresentation(); @@ -42590,6 +42628,34 @@ package android.telecom { field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5 } + public final class CallIdentification implements android.os.Parcelable { + method public int describeContents(); + method public java.lang.String getCallScreeningAppName(); + method public java.lang.String getCallScreeningPackageName(); + method public java.lang.String getDescription(); + method public java.lang.String getDetails(); + method public java.lang.String getName(); + method public int getNuisanceConfidence(); + method public android.graphics.drawable.Icon getPhoto(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int CONFIDENCE_LIKELY_NOT_NUISANCE = -1; // 0xffffffff + field public static final int CONFIDENCE_LIKELY_NUISANCE = 1; // 0x1 + field public static final int CONFIDENCE_NOT_NUISANCE = -2; // 0xfffffffe + field public static final int CONFIDENCE_NUISANCE = 2; // 0x2 + field public static final int CONFIDENCE_UNKNOWN = 0; // 0x0 + field public static final android.os.Parcelable.Creator<android.telecom.CallIdentification> CREATOR; + } + + public static class CallIdentification.Builder { + ctor public CallIdentification.Builder(); + method public android.telecom.CallIdentification build(); + method public android.telecom.CallIdentification.Builder setDescription(java.lang.String); + method public android.telecom.CallIdentification.Builder setDetails(java.lang.String); + method public android.telecom.CallIdentification.Builder setName(java.lang.String); + method public android.telecom.CallIdentification.Builder setNuisanceConfidence(int); + method public android.telecom.CallIdentification.Builder setPhoto(android.graphics.drawable.Icon); + } + public abstract class CallRedirectionService extends android.app.Service { ctor public CallRedirectionService(); method public final void cancelCall(); @@ -42605,6 +42671,7 @@ package android.telecom { ctor public CallScreeningService(); method public android.os.IBinder onBind(android.content.Intent); method public abstract void onScreenCall(android.telecom.Call.Details); + method public final void provideCallIdentification(android.telecom.Call.Details, android.telecom.CallIdentification); method public final void respondToCall(android.telecom.Call.Details, android.telecom.CallScreeningService.CallResponse); field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.CallScreeningService"; } @@ -43199,7 +43266,6 @@ package android.telecom { method public java.lang.String getVoiceMailNumber(android.telecom.PhoneAccountHandle); method public boolean handleMmi(java.lang.String); method public boolean handleMmi(java.lang.String, android.telecom.PhoneAccountHandle); - method public boolean isDefaultCallScreeningApp(android.content.ComponentName); method public boolean isInCall(); method public boolean isInManagedCall(); method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle); @@ -43208,7 +43274,6 @@ package android.telecom { method public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, java.lang.String); method public void placeCall(android.net.Uri, android.os.Bundle); method public void registerPhoneAccount(android.telecom.PhoneAccount); - method public void requestChangeDefaultCallScreeningApp(android.content.ComponentName); method public void showInCallScreen(boolean); method public void silenceRinger(); method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle); @@ -44245,6 +44310,8 @@ package android.telephony { method public void removeOnOpportunisticSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener); method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener); method public boolean removeSubscriptionsFromGroup(int[]); + method public boolean setMetered(boolean, int); + method public boolean setOpportunistic(boolean, int); method public java.lang.String setSubscriptionGroup(int[]); method public void setSubscriptionOverrideCongested(int, boolean, long); method public void setSubscriptionOverrideUnmetered(int, boolean, long); @@ -52593,9 +52660,10 @@ package android.view.inspector { package android.view.textclassifier { public final class ConversationActions implements android.os.Parcelable { - ctor public ConversationActions(java.util.List<android.view.textclassifier.ConversationActions.ConversationAction>); + ctor public ConversationActions(java.util.List<android.view.textclassifier.ConversationActions.ConversationAction>, java.lang.String); method public int describeContents(); method public java.util.List<android.view.textclassifier.ConversationActions.ConversationAction> getConversationActions(); + method public java.lang.String getId(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions> CREATOR; field public static final java.lang.String HINT_FOR_IN_APP = "in_app"; @@ -52656,6 +52724,7 @@ package android.view.textclassifier { method public int describeContents(); method public java.lang.String getCallingPackageName(); method public java.util.List<android.view.textclassifier.ConversationActions.Message> getConversation(); + method public java.lang.String getConversationId(); method public java.util.List<java.lang.String> getHints(); method public int getMaxSuggestions(); method public android.view.textclassifier.ConversationActions.TypeConfig getTypeConfig(); @@ -52666,6 +52735,7 @@ package android.view.textclassifier { public static final class ConversationActions.Request.Builder { ctor public ConversationActions.Request.Builder(java.util.List<android.view.textclassifier.ConversationActions.Message>); method public android.view.textclassifier.ConversationActions.Request build(); + method public android.view.textclassifier.ConversationActions.Request.Builder setConversationId(java.lang.String); method public android.view.textclassifier.ConversationActions.Request.Builder setHints(java.util.List<java.lang.String>); method public android.view.textclassifier.ConversationActions.Request.Builder setMaxSuggestions(int); method public android.view.textclassifier.ConversationActions.Request.Builder setTypeConfig(android.view.textclassifier.ConversationActions.TypeConfig); @@ -52828,6 +52898,7 @@ package android.view.textclassifier { method public default int getMaxGenerateLinksTextLength(); method public default boolean isDestroyed(); method public default void onSelectionEvent(android.view.textclassifier.SelectionEvent); + method public default void onTextClassifierEvent(android.view.textclassifier.TextClassifierEvent); method public default android.view.textclassifier.ConversationActions suggestConversationActions(android.view.textclassifier.ConversationActions.Request); method public default android.view.textclassifier.TextSelection suggestSelection(android.view.textclassifier.TextSelection.Request); method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList); @@ -52849,6 +52920,7 @@ package android.view.textclassifier { field public static final java.lang.String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview"; field public static final java.lang.String WIDGET_TYPE_EDITTEXT = "edittext"; field public static final java.lang.String WIDGET_TYPE_EDIT_WEBVIEW = "edit-webview"; + field public static final java.lang.String WIDGET_TYPE_NOTIFICATION = "notification"; field public static final java.lang.String WIDGET_TYPE_TEXTVIEW = "textview"; field public static final java.lang.String WIDGET_TYPE_UNKNOWN = "unknown"; field public static final java.lang.String WIDGET_TYPE_UNSELECTABLE_TEXTVIEW = "nosel-textview"; @@ -52866,6 +52938,68 @@ package android.view.textclassifier { field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassifier.EntityConfig> CREATOR; } + public final class TextClassifierEvent implements android.os.Parcelable { + method public int describeContents(); + method public int[] getActionIndices(); + method public java.lang.String getEntityType(); + method public int getEventCategory(); + method public android.view.textclassifier.TextClassificationContext getEventContext(); + method public int getEventIndex(); + method public long getEventTime(); + method public int getEventType(); + method public android.os.Bundle getExtras(); + method public java.lang.String getLanguage(); + method public int getRelativeSuggestedWordEndIndex(); + method public int getRelativeSuggestedWordStartIndex(); + method public int getRelativeWordEndIndex(); + method public int getRelativeWordStartIndex(); + method public java.lang.String getResultId(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int CATEGORY_CONVERSATION_ACTIONS = 3; // 0x3 + field public static final int CATEGORY_LANGUAGE_DETECTION = 4; // 0x4 + field public static final int CATEGORY_LINKIFY = 2; // 0x2 + field public static final int CATEGORY_SELECTION = 1; // 0x1 + field public static final int CATEGORY_UNDEFINED = 0; // 0x0 + field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassifierEvent> CREATOR; + field public static final int TYPE_ACTIONS_SHOWN = 6; // 0x6 + field public static final int TYPE_AUTO_SELECTION = 5; // 0x5 + field public static final int TYPE_COPY_ACTION = 9; // 0x9 + field public static final int TYPE_CUT_ACTION = 11; // 0xb + field public static final int TYPE_LINK_CLICKED = 7; // 0x7 + field public static final int TYPE_MANUAL_REPLY = 19; // 0x13 + field public static final int TYPE_OTHER_ACTION = 16; // 0x10 + field public static final int TYPE_OVERTYPE = 8; // 0x8 + field public static final int TYPE_PASTE_ACTION = 10; // 0xa + field public static final int TYPE_SELECTION_DESTROYED = 15; // 0xf + field public static final int TYPE_SELECTION_DRAG = 14; // 0xe + field public static final int TYPE_SELECTION_MODIFIED = 2; // 0x2 + field public static final int TYPE_SELECTION_RESET = 18; // 0x12 + field public static final int TYPE_SELECTION_STARTED = 1; // 0x1 + field public static final int TYPE_SELECT_ALL = 17; // 0x11 + field public static final int TYPE_SHARE_ACTION = 12; // 0xc + field public static final int TYPE_SMART_ACTION = 13; // 0xd + field public static final int TYPE_SMART_SELECTION_MULTI = 4; // 0x4 + field public static final int TYPE_SMART_SELECTION_SINGLE = 3; // 0x3 + field public static final int TYPE_UNDEFINED = 0; // 0x0 + } + + public static final class TextClassifierEvent.Builder { + ctor public TextClassifierEvent.Builder(int, int); + method public android.view.textclassifier.TextClassifierEvent build(); + method public android.view.textclassifier.TextClassifierEvent.Builder setActionIndices(int...); + method public android.view.textclassifier.TextClassifierEvent.Builder setEntityType(java.lang.String); + method public android.view.textclassifier.TextClassifierEvent.Builder setEventContext(android.view.textclassifier.TextClassificationContext); + method public android.view.textclassifier.TextClassifierEvent.Builder setEventIndex(int); + method public android.view.textclassifier.TextClassifierEvent.Builder setEventTime(long); + method public android.view.textclassifier.TextClassifierEvent.Builder setExtras(android.os.Bundle); + method public android.view.textclassifier.TextClassifierEvent.Builder setLanguage(java.lang.String); + method public android.view.textclassifier.TextClassifierEvent.Builder setRelativeSuggestedWordEndIndex(int); + method public android.view.textclassifier.TextClassifierEvent.Builder setRelativeSuggestedWordStartIndex(int); + method public android.view.textclassifier.TextClassifierEvent.Builder setRelativeWordEndIndex(int); + method public android.view.textclassifier.TextClassifierEvent.Builder setRelativeWordStartIndex(int); + method public android.view.textclassifier.TextClassifierEvent.Builder setResultId(java.lang.String); + } + public final class TextLanguage implements android.os.Parcelable { method public int describeContents(); method public float getConfidenceScore(android.icu.util.ULocale); @@ -56241,6 +56375,7 @@ package android.widget { method public boolean isCursorVisible(); method public boolean isElegantTextHeight(); method public boolean isFallbackLineSpacing(); + method public final boolean isHorizontallyScrolling(); method public boolean isInputMethodTarget(); method public boolean isSingleLine(); method public boolean isSuggestionsEnabled(); diff --git a/api/system-current.txt b/api/system-current.txt index 5a49f0bf3326..d15e8264a151 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -93,6 +93,7 @@ package android { field public static final java.lang.String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS"; field public static final java.lang.String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS"; field public static final java.lang.String MANAGE_ROLE_HOLDERS = "android.permission.MANAGE_ROLE_HOLDERS"; + field public static final java.lang.String MANAGE_SENSOR_PRIVACY = "android.permission.MANAGE_SENSOR_PRIVACY"; field public static final java.lang.String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER"; field public static final java.lang.String MANAGE_SUBSCRIPTION_PLANS = "android.permission.MANAGE_SUBSCRIPTION_PLANS"; field public static final java.lang.String MANAGE_USB = "android.permission.MANAGE_USB"; @@ -2762,7 +2763,9 @@ package android.location { method public deprecated boolean addGpsNavigationMessageListener(android.location.GpsNavigationMessageEvent.Listener); method public void flushGnssBatch(); method public int getGnssBatchSize(); + method public java.lang.String getLocationControllerExtraPackage(); method public java.lang.String getNetworkProviderPackage(); + method public boolean isLocationControllerExtraPackageEnabled(); method public boolean isLocationEnabledForUser(android.os.UserHandle); method public boolean isProviderEnabledForUser(java.lang.String, android.os.UserHandle); method public boolean registerGnssBatchedLocationCallback(long, boolean, android.location.BatchedLocationCallback, android.os.Handler); @@ -2770,6 +2773,8 @@ package android.location { method public deprecated void removeGpsNavigationMessageListener(android.location.GpsNavigationMessageEvent.Listener); method public void requestLocationUpdates(android.location.LocationRequest, android.location.LocationListener, android.os.Looper); method public void requestLocationUpdates(android.location.LocationRequest, android.app.PendingIntent); + method public void setLocationControllerExtraPackage(java.lang.String); + method public void setLocationControllerExtraPackageEnabled(boolean); method public void setLocationEnabledForUser(boolean, android.os.UserHandle); method public boolean setProviderEnabledForUser(java.lang.String, boolean, android.os.UserHandle); method public boolean unregisterGnssBatchedLocationCallback(android.location.BatchedLocationCallback); @@ -3884,7 +3889,11 @@ package android.net.wifi { package android.net.wifi.aware { public class DiscoverySession implements java.lang.AutoCloseable { - method public android.net.NetworkSpecifier createNetworkSpecifierPmk(android.net.wifi.aware.PeerHandle, byte[]); + method public deprecated android.net.NetworkSpecifier createNetworkSpecifierPmk(android.net.wifi.aware.PeerHandle, byte[]); + } + + public static class WifiAwareManager.NetworkSpecifierBuilder { + method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setPmk(byte[]); } public class WifiAwareSession implements java.lang.AutoCloseable { @@ -4647,6 +4656,7 @@ package android.provider { public final class Settings { field public static final java.lang.String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS"; + field public static final java.lang.String ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS = "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS"; field public static final java.lang.String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS"; } @@ -5357,9 +5367,10 @@ package android.service.textclassifier { method public void onDestroyTextClassificationSession(android.view.textclassifier.TextClassificationSessionId); method public void onDetectLanguage(android.view.textclassifier.TextClassificationSessionId, android.view.textclassifier.TextLanguage.Request, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextLanguage>); method public abstract void onGenerateLinks(android.view.textclassifier.TextClassificationSessionId, android.view.textclassifier.TextLinks.Request, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextLinks>); - method public void onSelectionEvent(android.view.textclassifier.TextClassificationSessionId, android.view.textclassifier.SelectionEvent); + method public deprecated void onSelectionEvent(android.view.textclassifier.TextClassificationSessionId, android.view.textclassifier.SelectionEvent); method public void onSuggestConversationActions(android.view.textclassifier.TextClassificationSessionId, android.view.textclassifier.ConversationActions.Request, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.ConversationActions>); method public abstract void onSuggestSelection(android.view.textclassifier.TextClassificationSessionId, android.view.textclassifier.TextSelection.Request, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextSelection>); + method public void onTextClassifierEvent(android.view.textclassifier.TextClassificationSessionId, android.view.textclassifier.TextClassifierEvent); field public static final java.lang.String SERVICE_INTERFACE = "android.service.textclassifier.TextClassifierService"; } @@ -5657,7 +5668,7 @@ package android.telecom { method public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(java.lang.String); method public boolean isInEmergencyCall(); method public boolean isRinging(); - method public boolean setDefaultDialer(java.lang.String); + method public deprecated boolean setDefaultDialer(java.lang.String); field public static final java.lang.String EXTRA_CALL_BACK_INTENT = "android.telecom.extra.CALL_BACK_INTENT"; field public static final java.lang.String EXTRA_CLEAR_MISSED_CALLS_INTENT = "android.telecom.extra.CLEAR_MISSED_CALLS_INTENT"; field public static final java.lang.String EXTRA_CONNECTION_SERVICE = "android.telecom.extra.CONNECTION_SERVICE"; @@ -5729,16 +5740,15 @@ package android.telephony { public abstract class NetworkService extends android.app.Service { ctor public NetworkService(); method protected abstract android.telephony.NetworkService.NetworkServiceProvider createNetworkServiceProvider(int); - field public static final java.lang.String NETWORK_SERVICE_EXTRA_SLOT_ID = "android.telephony.extra.SLOT_ID"; field public static final java.lang.String NETWORK_SERVICE_INTERFACE = "android.telephony.NetworkService"; } - public class NetworkService.NetworkServiceProvider { + public abstract class NetworkService.NetworkServiceProvider implements java.lang.AutoCloseable { ctor public NetworkService.NetworkServiceProvider(int); + method public abstract void close(); method public void getNetworkRegistrationState(int, android.telephony.NetworkServiceCallback); method public final int getSlotId(); method public final void notifyNetworkRegistrationStateChanged(); - method protected void onDestroy(); } public class NetworkServiceCallback { @@ -6078,20 +6088,19 @@ package android.telephony.data { public abstract class DataService extends android.app.Service { ctor public DataService(); method public abstract android.telephony.data.DataService.DataServiceProvider createDataServiceProvider(int); - field public static final java.lang.String DATA_SERVICE_EXTRA_SLOT_ID = "android.telephony.data.extra.SLOT_ID"; field public static final java.lang.String DATA_SERVICE_INTERFACE = "android.telephony.data.DataService"; field public static final int REQUEST_REASON_HANDOVER = 3; // 0x3 field public static final int REQUEST_REASON_NORMAL = 1; // 0x1 field public static final int REQUEST_REASON_SHUTDOWN = 2; // 0x2 } - public class DataService.DataServiceProvider { + public abstract class DataService.DataServiceProvider implements java.lang.AutoCloseable { ctor public DataService.DataServiceProvider(int); + method public abstract void close(); method public void deactivateDataCall(int, int, android.telephony.data.DataServiceCallback); method public void getDataCallList(android.telephony.data.DataServiceCallback); method public final int getSlotId(); method public final void notifyDataCallListChanged(java.util.List<android.telephony.data.DataCallResponse>); - method protected void onDestroy(); method public void setDataProfile(java.util.List<android.telephony.data.DataProfile>, boolean, android.telephony.data.DataServiceCallback); method public void setInitialAttachApn(android.telephony.data.DataProfile, boolean, android.telephony.data.DataServiceCallback); method public void setupDataCall(int, android.telephony.data.DataProfile, boolean, boolean, int, android.net.LinkProperties, android.telephony.data.DataServiceCallback); @@ -6613,8 +6622,8 @@ package android.telephony.ims { field public static final int CODE_SIP_SERVER_TIMEOUT = 353; // 0x161 field public static final int CODE_SIP_SERVICE_UNAVAILABLE = 352; // 0x160 field public static final int CODE_SIP_TEMPRARILY_UNAVAILABLE = 336; // 0x150 - field public static final int CODE_SIP_TRANSACTION_DOES_NOT_EXIST = 343; // 0x157 field public static final int CODE_SIP_TOO_MANY_HOPS = 374; // 0x176 + field public static final int CODE_SIP_TRANSACTION_DOES_NOT_EXIST = 343; // 0x157 field public static final int CODE_SIP_UNDECIPHERABLE = 378; // 0x17a field public static final int CODE_SIP_USER_MARKED_UNWANTED = 365; // 0x16d field public static final int CODE_SIP_USER_REJECTED = 361; // 0x169 diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java index 783c8c45d603..be5a5bf6c35a 100644 --- a/cmds/sm/src/com/android/commands/sm/Sm.java +++ b/cmds/sm/src/com/android/commands/sm/Sm.java @@ -282,14 +282,31 @@ public final class Sm { StorageManager.DEBUG_VIRTUAL_DISK); } - public void runIsolatedStorage() throws RemoteException { - final boolean enableIsolatedStorage = Boolean.parseBoolean(nextArg()); + public void runIsolatedStorage() { + final int value; + final int mask = StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_ON + | StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_OFF; + switch (nextArg()) { + case "on": + case "true": + value = StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_ON; + break; + case "off": + value = StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_OFF; + break; + case "default": + case "false": + value = 0; + break; + default: + return; + } + // Toggling isolated-storage state will result in a device reboot. So to avoid this command // from erroring out (DeadSystemException), call setDebugFlags() in a separate thread. new Thread(() -> { try { - mSm.setDebugFlags(enableIsolatedStorage ? StorageManager.DEBUG_ISOLATED_STORAGE : 0, - StorageManager.DEBUG_ISOLATED_STORAGE); + mSm.setDebugFlags(value, mask); } catch (RemoteException e) { Log.e(TAG, "Encountered an error!", e); } @@ -334,7 +351,7 @@ public final class Sm { System.err.println(""); System.err.println(" sm set-emulate-fbe [true|false]"); System.err.println(""); - System.err.println(" sm set-isolated-storage [true|false]"); + System.err.println(" sm set-isolated-storage [on|off|default]"); System.err.println(""); return 1; } diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 410bd198a227..9ee078696196 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -1222,7 +1222,7 @@ message UsbDeviceAttached { STATE_CONNECTED = 1; } optional State state = 6; - optional int64 last_connect_duration_ms = 7; + optional int64 last_connect_duration_millis = 7; } diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt index bf1443867aff..5cd3d5d4fa4d 100644 --- a/config/hiddenapi-greylist.txt +++ b/config/hiddenapi-greylist.txt @@ -2913,7 +2913,6 @@ Lcom/android/internal/telephony/dataconnection/DcTracker;->notifyOffApnsOfAvaila Lcom/android/internal/telephony/dataconnection/DcTracker;->onActionIntentDataStallAlarm(Landroid/content/Intent;)V Lcom/android/internal/telephony/dataconnection/DcTracker;->onActionIntentProvisioningApnAlarm(Landroid/content/Intent;)V Lcom/android/internal/telephony/dataconnection/DcTracker;->onRecordsLoadedOrSubIdChanged()V -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;->registerSettingsObserver()V diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index f8309bcd3267..1ae0f52ac74e 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -270,14 +270,14 @@ public class KeyguardManager { } /** + * Handle returned by {@link KeyguardManager#newKeyguardLock} that allows + * you to disable / reenable the keyguard. + * * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD} * and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} * instead; this allows you to seamlessly hide the keyguard as your application * moves in and out of the foreground and does not require that any special * permissions be requested. - * - * Handle returned by {@link KeyguardManager#newKeyguardLock} that allows - * you to disable / reenable the keyguard. */ @Deprecated public class KeyguardLock { @@ -303,7 +303,7 @@ public class KeyguardManager { @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD) public void disableKeyguard() { try { - mWM.disableKeyguard(mToken, mTag); + mWM.disableKeyguard(mToken, mTag, mContext.getUserId()); } catch (RemoteException ex) { } } @@ -322,16 +322,17 @@ public class KeyguardManager { @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD) public void reenableKeyguard() { try { - mWM.reenableKeyguard(mToken); + mWM.reenableKeyguard(mToken, mContext.getUserId()); } catch (RemoteException ex) { } } } /** - * @deprecated Use {@link KeyguardDismissCallback} * Callback passed to {@link KeyguardManager#exitKeyguardSecurely} to notify * caller of result. + * + * @deprecated Use {@link KeyguardDismissCallback} */ @Deprecated public interface OnKeyguardExitResult { @@ -380,12 +381,6 @@ public class KeyguardManager { } /** - * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD} - * and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} - * instead; this allows you to seamlessly hide the keyguard as your application - * moves in and out of the foreground and does not require that any special - * permissions be requested. - * * Enables you to lock or unlock the keyguard. Get an instance of this class by * calling {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}. * This class is wrapped by {@link android.app.KeyguardManager KeyguardManager}. @@ -394,6 +389,12 @@ public class KeyguardManager { * * @return A {@link KeyguardLock} handle to use to disable and reenable the * keyguard. + * + * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD} + * and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} + * instead; this allows you to seamlessly hide the keyguard as your application + * moves in and out of the foreground and does not require that any special + * permissions be requested. */ @Deprecated public KeyguardLock newKeyguardLock(String tag) { @@ -430,13 +431,12 @@ public class KeyguardManager { } /** - * @deprecated Use {@link #isKeyguardLocked()} instead. - * * If keyguard screen is showing or in restricted key input mode (i.e. in * keyguard password emergency screen). When in such mode, certain keys, * such as the Home key and the right soft keys, don't work. * * @return true if in keyguard restricted input mode. + * @deprecated Use {@link #isKeyguardLocked()} instead. */ public boolean inKeyguardRestrictedInputMode() { return isKeyguardLocked(); @@ -588,12 +588,6 @@ public class KeyguardManager { } /** - * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD} - * and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} - * instead; this allows you to seamlessly hide the keyguard as your application - * moves in and out of the foreground and does not require that any special - * permissions be requested. - * * Exit the keyguard securely. The use case for this api is that, after * disabling the keyguard, your app, which was granted permission to * disable the keyguard and show a limited amount of information deemed @@ -606,6 +600,12 @@ public class KeyguardManager { * @param callback Lets you know whether the operation was successful and * it is safe to launch anything that would normally be considered safe * once the user has gotten past the keyguard. + + * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD} + * and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} + * instead; this allows you to seamlessly hide the keyguard as your application + * moves in and out of the foreground and does not require that any special + * permissions be requested. */ @Deprecated @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD) diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index f4fd5d132069..45e87e045ae2 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -54,6 +54,7 @@ import android.debug.IAdbManager; import android.hardware.ConsumerIrManager; import android.hardware.ISerialManager; import android.hardware.SensorManager; +import android.hardware.SensorPrivacyManager; import android.hardware.SerialManager; import android.hardware.SystemSensorManager; import android.hardware.biometrics.BiometricManager; @@ -503,6 +504,13 @@ final class SystemServiceRegistry { ctx.mMainThread.getHandler().getLooper()); }}); + registerService(Context.SENSOR_PRIVACY_SERVICE, SensorPrivacyManager.class, + new CachedServiceFetcher<SensorPrivacyManager>() { + @Override + public SensorPrivacyManager createService(ContextImpl ctx) { + return SensorPrivacyManager.getInstance(ctx); + }}); + registerService(Context.STATS_MANAGER, StatsManager.class, new CachedServiceFetcher<StatsManager>() { @Override diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java index e84517dc6942..e7fe16123036 100644 --- a/core/java/android/app/job/JobInfo.java +++ b/core/java/android/app/job/JobInfo.java @@ -1545,13 +1545,6 @@ public class JobInfo implements Parcelable { * @return The job object to hand to the JobScheduler. This object is immutable. */ public JobInfo build() { - // Allow jobs with no constraints - What am I, a database? - if (!mHasEarlyConstraint && !mHasLateConstraint && mConstraintFlags == 0 && - mNetworkRequest == null && - mTriggerContentUris == null) { - throw new IllegalArgumentException("You're trying to build a job with no " + - "constraints, this is not allowed."); - } // Check that network estimates require network type if ((mNetworkDownloadBytes > 0 || mNetworkUploadBytes > 0) && mNetworkRequest == null) { throw new IllegalArgumentException( diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index b39010d9d14e..001e328fb0ba 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3080,6 +3080,7 @@ public abstract class Context { //@hide: COUNTRY_DETECTOR, SEARCH_SERVICE, SENSOR_SERVICE, + SENSOR_PRIVACY_SERVICE, STORAGE_SERVICE, STORAGE_STATS_SERVICE, WALLPAPER_SERVICE, @@ -3544,6 +3545,18 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve a {@link + * android.hardware.SensorPrivacyManager} for accessing sensor privacy + * functions. + * + * @see #getSystemService(String) + * @see android.hardware.SensorPrivacyManager + * + * @hide + */ + public static final String SENSOR_PRIVACY_SERVICE = "sensor_privacy"; + + /** + * Use with {@link #getSystemService(String)} to retrieve a {@link * android.os.storage.StorageManager} for accessing system storage * functions. * diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 0a4f4eb8506c..0edb36c5daff 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -236,6 +236,15 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { public float maxAspectRatio; /** + * Value indicating the minimum aspect ratio the activity supports. + * <p> + * 0 means unset. + * @See {@link android.R.attr#minAspectRatio}. + * @hide + */ + public float minAspectRatio; + + /** * Name of the VrListenerService component to run for this activity. * @see android.R.attr#enableVrMode * @hide @@ -979,6 +988,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { rotationAnimation = orig.rotationAnimation; colorMode = orig.colorMode; maxAspectRatio = orig.maxAspectRatio; + minAspectRatio = orig.minAspectRatio; } /** @@ -1142,6 +1152,9 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { if (maxAspectRatio != 0) { pw.println(prefix + "maxAspectRatio=" + maxAspectRatio); } + if (minAspectRatio != 0) { + pw.println(prefix + "minAspectRatio=" + minAspectRatio); + } super.dumpBack(pw, prefix, dumpFlags); } @@ -1190,6 +1203,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { dest.writeInt(rotationAnimation); dest.writeInt(colorMode); dest.writeFloat(maxAspectRatio); + dest.writeFloat(minAspectRatio); } /** @@ -1309,6 +1323,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { rotationAnimation = source.readInt(); colorMode = source.readInt(); maxAspectRatio = source.readFloat(); + minAspectRatio = source.readFloat(); } /** diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index c361ac12667e..0c438afd7a83 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -712,6 +712,15 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public float maxAspectRatio; + /** + * Value indicating the minimum aspect ratio the application supports. + * <p> + * 0 means unset. + * @see {@link android.R.attr#minAspectRatio}. + * @hide + */ + public float minAspectRatio; + /** @removed */ @Deprecated public String volumeUuid; diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 61a74ded02d0..e38b294b4485 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -30,6 +30,7 @@ import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE; +import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; @@ -162,6 +163,8 @@ public class PackageParser { SystemProperties.getBoolean(PROPERTY_CHILD_PACKAGES_ENABLED, false); private static final float DEFAULT_PRE_O_MAX_ASPECT_RATIO = 1.86f; + private static final float DEFAULT_PRE_Q_MIN_ASPECT_RATIO = 1.333f; + private static final float DEFAULT_PRE_Q_MIN_ASPECT_RATIO_WATCH = 1f; // TODO: switch outError users to PackageParserException // TODO: refactor "codePath" to "apkPath" @@ -3673,6 +3676,7 @@ public class PackageParser { } ai.maxAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, 0); + ai.minAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_minAspectRatio, 0); ai.networkSecurityConfigRes = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestApplication_networkSecurityConfig, @@ -3995,6 +3999,7 @@ public class PackageParser { // Must be ran after the entire {@link ApplicationInfo} has been fully processed and after // every activity info has had a chance to set it from its attributes. setMaxAspectRatio(owner); + setMinAspectRatio(owner); PackageBackwardCompatibility.modifySharedLibraries(owner); @@ -4492,6 +4497,13 @@ public class PackageParser { 0 /*default*/)); } + if (sa.hasValue(R.styleable.AndroidManifestActivity_minAspectRatio) + && sa.getType(R.styleable.AndroidManifestActivity_minAspectRatio) + == TypedValue.TYPE_FLOAT) { + a.setMinAspectRatio(sa.getFloat(R.styleable.AndroidManifestActivity_minAspectRatio, + 0 /*default*/)); + } + a.info.lockTaskLaunchMode = sa.getInt(R.styleable.AndroidManifestActivity_lockTaskMode, 0); @@ -4751,6 +4763,34 @@ public class PackageParser { } /** + * Sets every the max aspect ratio of every child activity that doesn't already have an aspect + * ratio set. + */ + private void setMinAspectRatio(Package owner) { + final float minAspectRatio; + if (owner.applicationInfo.minAspectRatio != 0) { + // Use the application max aspect ration as default if set. + minAspectRatio = owner.applicationInfo.minAspectRatio; + } else { + // Default to (1.33) 4:3 aspect ratio for pre-Q apps and unset for Q and greater. + // NOTE: 4:3 was the min aspect ratio Android devices can support pre-Q per the CDD, + // except for watches which always supported 1:1. + minAspectRatio = owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q + ? 0 + : mCallback.hasFeature(FEATURE_WATCH) + ? DEFAULT_PRE_Q_MIN_ASPECT_RATIO_WATCH + : DEFAULT_PRE_Q_MIN_ASPECT_RATIO; + } + + for (Activity activity : owner.activities) { + if (activity.hasMinAspectRatio()) { + continue; + } + activity.setMinAspectRatio(minAspectRatio); + } + } + + /** * @param configChanges The bit mask of configChanges fetched from AndroidManifest.xml. * @param recreateOnConfigChanges The bit mask recreateOnConfigChanges fetched from * AndroidManifest.xml. @@ -4888,6 +4928,7 @@ public class PackageParser { info.windowLayout = target.info.windowLayout; info.resizeMode = target.info.resizeMode; info.maxAspectRatio = target.info.maxAspectRatio; + info.minAspectRatio = target.info.minAspectRatio; info.requestedVrComponent = target.info.requestedVrComponent; info.encryptionAware = info.directBootAware = target.info.directBootAware; @@ -7773,11 +7814,16 @@ public class PackageParser { @UnsupportedAppUsage public final ActivityInfo info; private boolean mHasMaxAspectRatio; + private boolean mHasMinAspectRatio; private boolean hasMaxAspectRatio() { return mHasMaxAspectRatio; } + private boolean hasMinAspectRatio() { + return mHasMinAspectRatio; + } + // To construct custom activity which does not exist in manifest Activity(final Package owner, final String className, final ActivityInfo info) { super(owner, new ArrayList<>(0), className); @@ -7813,6 +7859,22 @@ public class PackageParser { mHasMaxAspectRatio = true; } + private void setMinAspectRatio(float minAspectRatio) { + if (info.resizeMode == RESIZE_MODE_RESIZEABLE + || info.resizeMode == RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) { + // Resizeable activities can be put in any aspect ratio. + return; + } + + if (minAspectRatio < 1.0f && minAspectRatio != 0) { + // Ignore any value lesser than 1.0. + return; + } + + info.minAspectRatio = minAspectRatio; + mHasMinAspectRatio = true; + } + public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("Activity{"); @@ -7833,12 +7895,14 @@ public class PackageParser { super.writeToParcel(dest, flags); dest.writeParcelable(info, flags | Parcelable.PARCELABLE_ELIDE_DUPLICATES); dest.writeBoolean(mHasMaxAspectRatio); + dest.writeBoolean(mHasMinAspectRatio); } private Activity(Parcel in) { super(in); info = in.readParcelable(Object.class.getClassLoader()); mHasMaxAspectRatio = in.readBoolean(); + mHasMinAspectRatio = in.readBoolean(); for (ActivityIntentInfo aii : intents) { aii.activity = this; diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 740cdae0dd5b..f1a4db25cd14 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -203,11 +203,13 @@ public final class AssetManager implements AutoCloseable { if (FEATURE_FLAG_IDMAP2) { final String[] systemIdmapPaths = nativeCreateIdmapsForStaticOverlaysTargetingAndroid(); - if (systemIdmapPaths == null) { - throw new IOException("idmap2 scan failed"); - } - for (String idmapPath : systemIdmapPaths) { - apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/)); + if (systemIdmapPaths != null) { + for (String idmapPath : systemIdmapPaths) { + apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/)); + } + } else { + Log.w(TAG, "'idmap2 --scan' failed: no static=\"true\" overlays targeting " + + "\"android\" will be loaded"); } } else { nativeVerifySystemIdmaps(); diff --git a/core/java/android/hardware/ISensorPrivacyListener.aidl b/core/java/android/hardware/ISensorPrivacyListener.aidl new file mode 100644 index 000000000000..5d4026541c4c --- /dev/null +++ b/core/java/android/hardware/ISensorPrivacyListener.aidl @@ -0,0 +1,29 @@ +/** + * 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; + +/** + * @hide + */ +oneway interface ISensorPrivacyListener { + // Since these transactions are also called from native code, these must be kept in sync with + // the ones in + // frameworks/native/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyListener.aidl + // =============== Beginning of transactions used on native side as well ====================== + void onSensorPrivacyChanged(boolean enabled); + // =============== End of transactions used on native side as well ============================ +} diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl new file mode 100644 index 000000000000..1ba7b989a764 --- /dev/null +++ b/core/java/android/hardware/ISensorPrivacyManager.aidl @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware; + +import android.hardware.ISensorPrivacyListener; + +/** @hide */ +interface ISensorPrivacyManager { + // Since these transactions are also called from native code, these must be kept in sync with + // the ones in + // frameworks/native/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl + // =============== Beginning of transactions used on native side as well ====================== + void addSensorPrivacyListener(in ISensorPrivacyListener listener); + + void removeSensorPrivacyListener(in ISensorPrivacyListener listener); + + boolean isSensorPrivacyEnabled(); + + void setSensorPrivacy(boolean enable); + // =============== End of transactions used on native side as well ============================ +}
\ No newline at end of file diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java new file mode 100644 index 000000000000..274202f7c190 --- /dev/null +++ b/core/java/android/hardware/SensorPrivacyManager.java @@ -0,0 +1,171 @@ +/* + * 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; + +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemService; +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.ArrayMap; + +import com.android.internal.annotations.GuardedBy; + +/** + * This class provides access to the sensor privacy services; sensor privacy allows the + * user to disable access to all sensors on the device. This class provides methods to query the + * current state of sensor privacy as well as to register / unregister for notification when + * the sensor privacy state changes. + * + * @hide + */ +@SystemService(Context.SENSOR_PRIVACY_SERVICE) +public final class SensorPrivacyManager { + + /** + * A class implementing this interface can register with the {@link + * android.hardware.SensorPrivacyManager} to receive notification when the sensor privacy + * state changes. + */ + public interface OnSensorPrivacyChangedListener { + /** + * Callback invoked when the sensor privacy state changes. + * + * @param enabled true if sensor privacy is enabled, false otherwise. + */ + void onSensorPrivacyChanged(boolean enabled); + } + + private static final Object sInstanceLock = new Object(); + + @GuardedBy("sInstanceLock") + private static SensorPrivacyManager sInstance; + + @NonNull + private final Context mContext; + + @NonNull + private final ISensorPrivacyManager mService; + + @NonNull + private final ArrayMap<OnSensorPrivacyChangedListener, ISensorPrivacyListener> mListeners; + + /** + * Private constructor to ensure only a single instance is created. + */ + private SensorPrivacyManager(Context context, ISensorPrivacyManager service) { + mContext = context; + mService = service; + mListeners = new ArrayMap<>(); + } + + /** + * Returns the single instance of the SensorPrivacyManager. + */ + public static SensorPrivacyManager getInstance(Context context) { + synchronized (sInstanceLock) { + if (sInstance == null) { + try { + IBinder b = ServiceManager.getServiceOrThrow(Context.SENSOR_PRIVACY_SERVICE); + ISensorPrivacyManager service = ISensorPrivacyManager.Stub.asInterface(b); + sInstance = new SensorPrivacyManager(context, service); + } catch (ServiceManager.ServiceNotFoundException e) { + throw new IllegalStateException(e); + } + } + return sInstance; + } + } + + /** + * Sets sensor privacy to the specified state. + * + * @param enable the state to which sensor privacy should be set. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) + public void setSensorPrivacy(boolean enable) { + try { + mService.setSensorPrivacy(enable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Registers a new listener to receive notification when the state of sensor privacy + * changes. + * + * @param listener the OnSensorPrivacyChangedListener to be notified when the state of sensor + * privacy changes. + */ + public void addSensorPrivacyListener(final OnSensorPrivacyChangedListener listener) { + synchronized (mListeners) { + ISensorPrivacyListener iListener = mListeners.get(listener); + if (iListener == null) { + iListener = new ISensorPrivacyListener.Stub() { + @Override + public void onSensorPrivacyChanged(boolean enabled) { + listener.onSensorPrivacyChanged(enabled); + } + }; + mListeners.put(listener, iListener); + } + + try { + mService.addSensorPrivacyListener(iListener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Unregisters the specified listener from receiving notifications when the state of sensor + * privacy changes. + * + * @param listener the OnSensorPrivacyChangedListener to be unregistered from notifications when + * sensor privacy changes. + */ + public void removeSensorPrivacyListener(OnSensorPrivacyChangedListener listener) { + synchronized (mListeners) { + ISensorPrivacyListener iListener = mListeners.get(listener); + if (iListener != null) { + mListeners.remove(iListener); + try { + mService.removeSensorPrivacyListener(iListener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + + /** + * Returns whether sensor privacy is currently enabled. + * + * @return true if sensor privacy is currently enabled, false otherwise. + */ + public boolean isSensorPrivacyEnabled() { + try { + return mService.isSensorPrivacyEnabled(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 5e402c7edc40..105ae6815589 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -24,6 +24,8 @@ import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.impl.SyntheticKey; import android.hardware.camera2.params.RecommendedStreamConfigurationMap; import android.hardware.camera2.params.SessionConfiguration; +import android.hardware.camera2.params.MandatoryStreamCombination; +import android.hardware.camera2.params.MandatoryStreamCombination.MandatoryStreamInformation; import android.hardware.camera2.utils.ArrayUtils; import android.hardware.camera2.utils.TypeReference; import android.util.Rational; @@ -2609,6 +2611,39 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<android.hardware.camera2.params.ReprocessFormatsMap>("android.scaler.availableRecommendedInputOutputFormatsMap", android.hardware.camera2.params.ReprocessFormatsMap.class); /** + * <p>An array of mandatory stream combinations generated according to the camera device + * {@link android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL } + * and {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES }. + * This is an app-readable conversion of the mandatory stream combination + * {@link android.hardware.camera2.CameraDevice#createCaptureSession tables}.</p> + * <p>The array of + * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is + * generated according to the documented + * {@link android.hardware.camera2.CameraDevice#createCaptureSession guideline} based on + * specific device level and capabilities. + * Clients can use the array as a quick reference to find an appropriate camera stream + * combination. + * As per documentation, the stream combinations with given PREVIEW, RECORD and + * MAXIMUM resolutions and anything smaller from the list given by + * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes } are + * guaranteed to work. + * The mandatory stream combination array will be {@code null} in case the device is a + * physical camera not independently exposed in + * {@link android.hardware.camera2.CameraManager#getCameraIdList } or is not backward + * compatible.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * <p><b>Limited capability</b> - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + */ + @PublicKey + @SyntheticKey + public static final Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_STREAM_COMBINATIONS = + new Key<android.hardware.camera2.params.MandatoryStreamCombination[]>("android.scaler.mandatoryStreamCombinations", android.hardware.camera2.params.MandatoryStreamCombination[].class); + + /** * <p>The area of the image sensor which corresponds to active pixels after any geometric * distortion correction has been applied.</p> * <p>This is the rectangle representing the size of the active region of the sensor (i.e. diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 448591f2c52a..9c213f2f27a5 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -435,9 +435,13 @@ public abstract class CameraDevice implements AutoCloseable { * </table><br> * </p> * + * <p>Clients can access the above mandatory stream combination tables via + * {@link android.hardware.camera2.params.MandatoryStreamCombination}.</p> + * * <p>Since the capabilities of camera devices vary greatly, a given camera device may support * target combinations with sizes outside of these guarantees, but this can only be tested for - * by attempting to create a session with such targets.</p> + * by calling {@link #isSessionConfigurationSupported} or attempting to create a session with + * such targets.</p> * * @param outputs The new set of Surfaces that should be made available as * targets for captured image data. @@ -619,6 +623,9 @@ public abstract class CameraDevice implements AutoCloseable { * </table><br> * </p> * + * <p>Clients can access the above mandatory stream combination tables via + * {@link android.hardware.camera2.params.MandatoryStreamCombination}.</p> + * * @param inputConfig The configuration for the input {@link Surface} * @param outputs The new set of Surfaces that should be made available as * targets for captured image data. @@ -980,6 +987,12 @@ public abstract class CameraDevice implements AutoCloseable { * It must not impact normal camera behavior in any way and must complete significantly * faster than creating a regular or constrained capture session.</p> * + * <p>Although this method is faster than creating a new capture session, it is not intended + * to be used for exploring the entire space of supported stream combinations. The available + * mandatory stream combinations + * {@link android.hardware.camera2.params.MandatoryStreamCombination} are better suited for this + * purpose.</p> + * * <p>Note that session parameters will be ignored and calls to * {@link SessionConfiguration#setSessionParameters} are not required.</p> * diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 44d7364ce43c..536c2e13389b 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -40,6 +40,9 @@ import android.os.ServiceSpecificException; import android.os.SystemProperties; import android.util.ArrayMap; import android.util.Log; +import android.util.Size; +import android.view.Display; +import android.view.WindowManager; import java.util.ArrayList; import java.util.Arrays; @@ -231,6 +234,30 @@ public final class CameraManager { CameraManagerGlobal.get().unregisterTorchCallback(callback); } + private Size getDisplaySize() { + Size ret = new Size(0, 0); + + try { + WindowManager windowManager = + (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + Display display = windowManager.getDefaultDisplay(); + + int width = display.getWidth(); + int height = display.getHeight(); + + if (height > width) { + height = width; + width = display.getHeight(); + } + + ret = new Size(width, height); + } catch (Exception e) { + Log.e(TAG, "getDisplaySize Failed. " + e.toString()); + } + + return ret; + } + /** * <p>Query the capabilities of a camera device. These capabilities are * immutable for a given camera.</p> @@ -269,6 +296,8 @@ public final class CameraManager { "Camera service is currently unavailable"); } try { + Size displaySize = getDisplaySize(); + // First check isHiddenPhysicalCamera to avoid supportsCamera2ApiLocked throwing // exception in case cameraId is a hidden physical camera. if (!isHiddenPhysicalCamera(cameraId) && !supportsCamera2ApiLocked(cameraId)) { @@ -280,10 +309,19 @@ public final class CameraManager { CameraInfo info = cameraService.getCameraInfo(id); - characteristics = LegacyMetadataMapper.createCharacteristics(parameters, info); + characteristics = LegacyMetadataMapper.createCharacteristics(parameters, info, + id, displaySize); } else { // Normal path: Get the camera characteristics directly from the camera service CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId); + if (!isHiddenPhysicalCamera(cameraId)) { + try { + info.setCameraId(Integer.parseInt(cameraId)); + } catch (NumberFormatException e) { + Log.e(TAG, "Failed to parse camera Id " + cameraId + " to integer"); + } + } + info.setDisplaySize(displaySize); characteristics = new CameraCharacteristics(info); } @@ -363,7 +401,8 @@ public final class CameraManager { } Log.i(TAG, "Using legacy camera HAL."); - cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id); + cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id, + getDisplaySize()); } } catch (ServiceSpecificException e) { if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) { diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index f81bd1377a7a..c527ab4c6730 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -51,6 +51,8 @@ import android.hardware.camera2.marshal.impl.MarshalQueryableString; import android.hardware.camera2.params.Face; import android.hardware.camera2.params.HighSpeedVideoConfiguration; import android.hardware.camera2.params.LensShadingMap; +import android.hardware.camera2.params.MandatoryStreamCombination; +import android.hardware.camera2.params.MandatoryStreamCombination.MandatoryStreamInformation; import android.hardware.camera2.params.OisSample; import android.hardware.camera2.params.RecommendedStreamConfiguration; import android.hardware.camera2.params.RecommendedStreamConfigurationMap; @@ -75,6 +77,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; /** * Implementation of camera metadata marshal/unmarshal across Binder to @@ -577,6 +580,15 @@ public class CameraMetadataNative implements Parcelable { } }); sGetCommandMap.put( + CameraCharacteristics.SCALER_MANDATORY_STREAM_COMBINATIONS.getNativeKey(), + new GetCommand() { + @Override + @SuppressWarnings("unchecked") + public <T> T getValue(CameraMetadataNative metadata, Key<T> key) { + return (T) metadata.getMandatoryStreamCombinations(); + } + }); + sGetCommandMap.put( CameraCharacteristics.CONTROL_MAX_REGIONS_AE.getNativeKey(), new GetCommand() { @Override @SuppressWarnings("unchecked") @@ -1161,6 +1173,26 @@ public class CameraMetadataNative implements Parcelable { return ret; } + private MandatoryStreamCombination[] getMandatoryStreamCombinations() { + int[] capabilities = getBase(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES); + ArrayList<Integer> caps = new ArrayList<Integer>(); + caps.ensureCapacity(capabilities.length); + for (int c : capabilities) { + caps.add(new Integer(c)); + } + int hwLevel = getBase(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); + MandatoryStreamCombination.Builder build = new MandatoryStreamCombination.Builder( + mCameraId, hwLevel, mDisplaySize, caps, getStreamConfigurationMap()); + List<MandatoryStreamCombination> combs = build.getAvailableMandatoryStreamCombinations(); + if ((combs != null) && (!combs.isEmpty())) { + MandatoryStreamCombination[] combArray = new MandatoryStreamCombination[combs.size()]; + combArray = combs.toArray(combArray); + return combArray; + } + + return null; + } + private StreamConfigurationMap getStreamConfigurationMap() { StreamConfiguration[] configurations = getBase( CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS); @@ -1433,6 +1465,31 @@ public class CameraMetadataNative implements Parcelable { return true; } + private int mCameraId = -1; + private Size mDisplaySize = new Size(0, 0); + + /** + * Set the current camera Id. + * + * @param cameraId Current camera id. + * + * @hide + */ + public void setCameraId(int cameraId) { + mCameraId = cameraId; + } + + /** + * Set the current display size. + * + * @param displaySize The current display size. + * + * @hide + */ + public void setDisplaySize(Size displaySize) { + mDisplaySize = displaySize; + } + @UnsupportedAppUsage private long mMetadataPtr; // native CameraMetadata* @@ -1476,6 +1533,8 @@ public class CameraMetadataNative implements Parcelable { */ public void swap(CameraMetadataNative other) { nativeSwap(other); + mCameraId = other.mCameraId; + mDisplaySize = other.mDisplaySize; } /** diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java index 123eb8ee1431..3e130c530cad 100644 --- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java +++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java @@ -39,6 +39,7 @@ import android.os.Message; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.util.Log; +import android.util.Size; import android.util.SparseArray; import android.view.Surface; @@ -356,7 +357,7 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { } public static CameraDeviceUserShim connectBinderShim(ICameraDeviceCallbacks callbacks, - int cameraId) { + int cameraId, Size displaySize) { if (DEBUG) { Log.d(TAG, "Opening shim Camera device"); } @@ -393,7 +394,8 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { } CameraCharacteristics characteristics = - LegacyMetadataMapper.createCharacteristics(legacyParameters, info); + LegacyMetadataMapper.createCharacteristics(legacyParameters, info, cameraId, + displaySize); LegacyCameraDevice device = new LegacyCameraDevice( cameraId, legacyCamera, characteristics, threadCallbacks); return new CameraDeviceUserShim(cameraId, device, characteristics, init, threadCallbacks); @@ -482,8 +484,38 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { @Override public boolean isSessionConfigurationSupported(SessionConfiguration sessionConfig) { - // TODO: Add support for this in legacy mode - throw new UnsupportedOperationException("Session configuration query not supported!"); + if (sessionConfig.getSessionType() != SessionConfiguration.SESSION_REGULAR) { + Log.e(TAG, "Session type: " + sessionConfig.getSessionType() + " is different from " + + " regular. Legacy devices support only regular session types!"); + return false; + } + + if (sessionConfig.getInputConfiguration() != null) { + Log.e(TAG, "Input configuration present, legacy devices do not support this feature!"); + return false; + } + + List<OutputConfiguration> outputConfigs = sessionConfig.getOutputConfigurations(); + if (outputConfigs.isEmpty()) { + Log.e(TAG, "Empty output configuration list!"); + return false; + } + + SparseArray<Surface> surfaces = new SparseArray<Surface>(outputConfigs.size()); + int idx = 0; + for (OutputConfiguration outputConfig : outputConfigs) { + List<Surface> surfaceList = outputConfig.getSurfaces(); + if (surfaceList.isEmpty() || (surfaceList.size() > 1)) { + Log.e(TAG, "Legacy devices do not support deferred or shared surfaces!"); + return false; + } + + surfaces.put(idx++, outputConfig.getSurface()); + } + + int ret = mLegacyDevice.configureOutputs(surfaces, /*validateSurfacesOnly*/true); + + return ret == LegacyExceptionUtils.NO_ERROR; } @Override diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java index 71a361bd00c6..aff09f2a45f2 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java +++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java @@ -346,6 +346,25 @@ public class LegacyCameraDevice implements AutoCloseable { * on success. */ public int configureOutputs(SparseArray<Surface> outputs) { + return configureOutputs(outputs, /*validateSurfacesOnly*/false); + } + + /** + * Configure the device with a set of output surfaces. + * + * <p>Using empty or {@code null} {@code outputs} is the same as unconfiguring.</p> + * + * <p>Every surface in {@code outputs} must be non-{@code null}.</p> + * + * @param outputs a list of surfaces to set. LegacyCameraDevice will take ownership of this + * list; it must not be modified by the caller once it's passed in. + * @param validateSurfacesOnly If set it will only check whether the outputs are supported + * and avoid any device configuration. + * @return an error code for this binder operation, or {@link NO_ERROR} + * on success. + * @hide + */ + public int configureOutputs(SparseArray<Surface> outputs, boolean validateSurfacesOnly) { List<Pair<Surface, Size>> sizedSurfaces = new ArrayList<>(); if (outputs != null) { int count = outputs.size(); @@ -397,7 +416,9 @@ public class LegacyCameraDevice implements AutoCloseable { sizedSurfaces.add(new Pair<>(output, s)); } // Lock down the size before configuration - setSurfaceDimens(output, s.getWidth(), s.getHeight()); + if (!validateSurfacesOnly) { + setSurfaceDimens(output, s.getWidth(), s.getHeight()); + } } catch (BufferQueueAbandonedException e) { Log.e(TAG, "Surface bufferqueue is abandoned, cannot configure as output: ", e); return BAD_VALUE; @@ -406,6 +427,10 @@ public class LegacyCameraDevice implements AutoCloseable { } } + if (validateSurfacesOnly) { + return LegacyExceptionUtils.NO_ERROR; + } + boolean success = false; if (mDeviceState.setConfiguring()) { mRequestThreadManager.configure(sizedSurfaces); diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java index 8822f713257d..6953a5b793c3 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java +++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java @@ -111,13 +111,15 @@ public class LegacyMetadataMapper { * * @param parameters A non-{@code null} parameters set * @param info Camera info with camera facing direction and angle of orientation + * @param cameraId Current camera Id + * @param displaySize Device display size * * @return static camera characteristics for a camera device * * @throws NullPointerException if any of the args were {@code null} */ public static CameraCharacteristics createCharacteristics(Camera.Parameters parameters, - CameraInfo info) { + CameraInfo info, int cameraId, Size displaySize) { checkNotNull(parameters, "parameters must not be null"); checkNotNull(info, "info must not be null"); @@ -125,7 +127,7 @@ public class LegacyMetadataMapper { android.hardware.CameraInfo outerInfo = new android.hardware.CameraInfo(); outerInfo.info = info; - return createCharacteristics(paramStr, outerInfo); + return createCharacteristics(paramStr, outerInfo, cameraId, displaySize); } /** @@ -134,12 +136,14 @@ public class LegacyMetadataMapper { * * @param parameters A string parseable by {@link Camera.Parameters#unflatten} * @param info Camera info with camera facing direction and angle of orientation + * @param cameraId Current camera id + * @param displaySize Device display size * @return static camera characteristics for a camera device * * @throws NullPointerException if any of the args were {@code null} */ public static CameraCharacteristics createCharacteristics(String parameters, - android.hardware.CameraInfo info) { + android.hardware.CameraInfo info, int cameraId, Size displaySize) { checkNotNull(parameters, "parameters must not be null"); checkNotNull(info, "info must not be null"); checkNotNull(info.info, "info.info must not be null"); @@ -159,6 +163,9 @@ public class LegacyMetadataMapper { Log.v(TAG, "--------------------------------------------------- (end)"); } + m.setCameraId(cameraId); + m.setDisplaySize(displaySize); + return new CameraCharacteristics(m); } diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java new file mode 100644 index 000000000000..b8e2d7adcf82 --- /dev/null +++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java @@ -0,0 +1,1219 @@ +/* + * 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.camera2.params; + +import static com.android.internal.util.Preconditions.*; +import static android.hardware.camera2.params.StreamConfigurationMap.checkArgumentFormat; + +import android.annotation.NonNull; +import android.content.Context; +import android.graphics.ImageFormat; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraCharacteristics.Key; +import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CameraMetadata; +import android.hardware.camera2.params.StreamConfigurationMap; +import android.hardware.camera2.utils.HashCodeHelpers; +import android.graphics.PixelFormat; +import android.media.CamcorderProfile; +import android.util.Size; +import android.util.Log; +import android.util.Pair; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; + +/** + * Immutable class to store the available mandatory stream combination. + * + * <p>The individual stream combinations are generated according to the guidelines + * at {@link CameraDevice#createCaptureSession}.</p> + * + * <p>The list of stream combinations is available by invoking + * {@link CameraCharacteristics#get} and passing key + * {@link android.hardware.camera2.CameraCharacteristics#SCALER_MANDATORY_STREAM_COMBINATIONS}.</p> + */ +public final class MandatoryStreamCombination { + private static final String TAG = "MandatoryStreamCombination"; + /** + * Immutable class to store available mandatory stream information. + */ + public static final class MandatoryStreamInformation { + private final int mFormat; + private final ArrayList<Size> mAvailableSizes = new ArrayList<Size> (); + private final boolean mIsInput; + + /** + * Create a new {@link MandatoryStreamInformation}. + * + @param availableSizes List of possible stream sizes. + * @param format Image format. + * + * @throws IllegalArgumentException + * if sizes is empty or if the format was not user-defined in + * ImageFormat/PixelFormat. + * @hide + */ + public MandatoryStreamInformation(@NonNull List<Size> availableSizes, int format) { + this(availableSizes, format, /*isInput*/false); + } + + /** + * Create a new {@link MandatoryStreamInformation}. + * + @param availableSizes List of possible stream sizes. + * @param format Image format. + * @param isInput Flag indicating whether this stream is input. + * + * @throws IllegalArgumentException + * if sizes is empty or if the format was not user-defined in + * ImageFormat/PixelFormat. + * @hide + */ + public MandatoryStreamInformation(@NonNull List<Size> availableSizes, int format, + boolean isInput) { + if (availableSizes.isEmpty()) { + throw new IllegalArgumentException("No available sizes"); + } + mAvailableSizes.addAll(availableSizes); + mFormat = checkArgumentFormat(format); + mIsInput = isInput; + } + + /** + * Confirms whether or not this is an input stream. + * @return true in case the stream is input, false otherwise. + */ + public boolean isInput() { + return mIsInput; + } + + /** + * Return the list of available sizes for this mandatory stream. + * + * <p>Per documented {@link CameraDevice#createCaptureSession guideline} the largest + * resolution in the result will be tested and guaranteed to work. If clients want to use + * smaller sizes, then the resulting + * {@link android.hardware.camera2.params.SessionConfiguration session configuration} can + * be tested either by calling {@link CameraDevice#createCaptureSession} or + * {@link CameraDevice#isSessionConfigurationSupported}. + * + * @return non-modifiable ascending list of available sizes. + */ + public List<Size> getAvailableSizes() { + return Collections.unmodifiableList(mAvailableSizes); + } + + /** + * Retrieve the mandatory stream {@code format}. + * + * @return integer format. + */ + public int getFormat() { + return mFormat; + } + + /** + * Check if this {@link MandatoryStreamInformation} is equal to another + * {@link MandatoryStreamInformation}. + * + * <p>Two vectors are only equal if and only if each of the respective elements is + * equal.</p> + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof MandatoryStreamInformation) { + final MandatoryStreamInformation other = (MandatoryStreamInformation) obj; + if ((mFormat != other.mFormat) || (mIsInput != other.mIsInput) || + (mAvailableSizes.size() != other.mAvailableSizes.size())) { + return false; + } + + return mAvailableSizes.equals(other.mAvailableSizes); + } + + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return HashCodeHelpers.hashCode(mFormat, Boolean.hashCode(mIsInput), + mAvailableSizes.hashCode()); + } + } + + private final String mDescription; + private final boolean mIsReprocessable; + private final ArrayList<MandatoryStreamInformation> mStreamsInformation = + new ArrayList<MandatoryStreamInformation>(); + /** + * Create a new {@link MandatoryStreamCombination}. + * + * @param streamsInformation list of available streams in the stream combination. + * @param description Summary of the stream combination use case. + * @param isReprocessable Flag whether the mandatory stream combination is reprocessable. + * + * @throws IllegalArgumentException + * if stream information is empty + * @hide + */ + public MandatoryStreamCombination(@NonNull List<MandatoryStreamInformation> streamsInformation, + String description, boolean isReprocessable) { + if (streamsInformation.isEmpty()) { + throw new IllegalArgumentException("Empty stream information"); + } + mStreamsInformation.addAll(streamsInformation); + mDescription = description; + mIsReprocessable = isReprocessable; + } + + /** + * Get the mandatory stream combination description. + * + * @return String with the mandatory combination description. + */ + public String getDescription() { + return mDescription; + } + + /** + * Indicates whether the mandatory stream combination is reprocessable. + * + * @return {@code true} in case the mandatory stream combination contains an input, + * {@code false} otherwise. + */ + public boolean isReprocessable() { + return mIsReprocessable; + } + + /** + * Get information about each stream in the mandatory combination. + * + * @return Non-modifiable list of stream information. + * + */ + public List<MandatoryStreamInformation> getStreamsInformation() { + return Collections.unmodifiableList(mStreamsInformation); + } + + /** + * Check if this {@link MandatoryStreamCombination} is equal to another + * {@link MandatoryStreamCombination}. + * + * <p>Two vectors are only equal if and only if each of the respective elements is equal.</p> + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof MandatoryStreamCombination) { + final MandatoryStreamCombination other = (MandatoryStreamCombination) obj; + if ((mDescription != other.mDescription) || + (mIsReprocessable != other.mIsReprocessable) || + (mStreamsInformation.size() != other.mStreamsInformation.size())) { + return false; + } + + return mStreamsInformation.equals(other.mStreamsInformation); + } + + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return HashCodeHelpers.hashCode(Boolean.hashCode(mIsReprocessable), mDescription.hashCode(), + mStreamsInformation.hashCode()); + } + + private static enum SizeThreshold { VGA, PREVIEW, RECORD, MAXIMUM } + private static enum ReprocessType { NONE, PRIVATE, YUV } + private static final class StreamTemplate { + public int mFormat; + public SizeThreshold mSizeThreshold; + public boolean mIsInput; + public StreamTemplate(int format, SizeThreshold sizeThreshold) { + this(format, sizeThreshold, /*isInput*/false); + } + + public StreamTemplate(int format, SizeThreshold sizeThreshold, boolean isInput) { + mFormat = format; + mSizeThreshold = sizeThreshold; + mIsInput = isInput; + } + } + + private static final class StreamCombinationTemplate { + public StreamTemplate[] mStreamTemplates; + public String mDescription; + public ReprocessType mReprocessType; + + public StreamCombinationTemplate(StreamTemplate[] streamTemplates, String description) { + this(streamTemplates, description, /*reprocessType*/ReprocessType.NONE); + } + + public StreamCombinationTemplate(StreamTemplate[] streamTemplates, String description, + ReprocessType reprocessType) { + mStreamTemplates = streamTemplates; + mReprocessType = reprocessType; + mDescription = description; + } + } + + private static StreamCombinationTemplate sLegacyCombinations[] = { + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM) }, + "Simple preview, GPU video processing, or no-preview video recording"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) }, + "No-viewfinder still image capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) }, + "In-application video/image processing"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) }, + "Standard still imaging"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) }, + "In-app processing plus still capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW) }, + "Standard recording"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW) }, + "Preview plus in-app processing"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) }, + "Still capture plus in-app processing") + }; + + private static StreamCombinationTemplate sLimitedCombinations[] = { + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD)}, + "High-resolution video recording with preview"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD)}, + "High-resolution in-app video processing with preview"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD) }, + "Two-input in-app video processing"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.RECORD) }, + "High-resolution recording with video snapshot"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.RECORD) }, + "High-resolution in-app processing with video snapshot"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) }, + "Two-input in-app processing with still capture") + }; + + private static StreamCombinationTemplate sBurstCombinations[] = { + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM) }, + "Maximum-resolution GPU processing with preview"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) }, + "Maximum-resolution in-app processing with preview"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) }, + "Maximum-resolution two-input in-app processsing") + }; + + private static StreamCombinationTemplate sFullCombinations[] = { + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM) }, + "Maximum-resolution GPU processing with preview"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) }, + "Maximum-resolution in-app processing with preview"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) }, + "Maximum-resolution two-input in-app processsing"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) }, + "Video recording with maximum-size video snapshot"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.VGA), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) }, + "Standard video recording plus maximum-resolution in-app processing"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.VGA), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) }, + "Preview plus two-input maximum-resolution in-app processing") + }; + + private static StreamCombinationTemplate sRawCombinations[] = { + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "No-preview DNG capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "Standard DNG capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "In-app processing plus DNG capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "Video recording with DNG capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "Preview with in-app processing and DNG capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "Two-input in-app processing plus DNG capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "Still capture with simultaneous JPEG and DNG"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "In-app processing with simultaneous JPEG and DNG") + }; + + private static StreamCombinationTemplate sLevel3Combinations[] = { + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "In-app viewfinder analysis with dynamic selection of output format"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "In-app viewfinder analysis with dynamic selection of output format") + }; + + private static StreamCombinationTemplate sLimitedPrivateReprocCombinations[] = { + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) }, + "No-viewfinder still image reprocessing", + /*reprocessType*/ ReprocessType.PRIVATE), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) }, + "ZSL(Zero-Shutter-Lag) still imaging", + /*reprocessType*/ ReprocessType.PRIVATE), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) }, + "ZSL still and in-app processing imaging", + /*reprocessType*/ ReprocessType.PRIVATE), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) }, + "ZSL in-app processing with still capture", + /*reprocessType*/ ReprocessType.PRIVATE), + }; + + private static StreamCombinationTemplate sLimitedYUVReprocCombinations[] = { + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) }, + "No-viewfinder still image reprocessing", + /*reprocessType*/ ReprocessType.YUV), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) }, + "ZSL(Zero-Shutter-Lag) still imaging", + /*reprocessType*/ ReprocessType.YUV), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) }, + "ZSL still and in-app processing imaging", + /*reprocessType*/ ReprocessType.YUV), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) }, + "ZSL in-app processing with still capture", + /*reprocessType*/ ReprocessType.YUV), + }; + + private static StreamCombinationTemplate sFullPrivateReprocCombinations[] = { + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD) }, + "High-resolution ZSL in-app video processing with regular preview", + /*reprocessType*/ ReprocessType.PRIVATE), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) }, + "Maximum-resolution ZSL in-app processing with regular preview", + /*reprocessType*/ ReprocessType.PRIVATE), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) }, + "Maximum-resolution two-input ZSL in-app processing", + /*reprocessType*/ ReprocessType.PRIVATE), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) }, + "ZSL still capture and in-app processing", + /*reprocessType*/ ReprocessType.PRIVATE), + }; + + private static StreamCombinationTemplate sFullYUVReprocCombinations[] = { + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW) }, + "Maximum-resolution multi-frame image fusion in-app processing with regular " + + "preview", + /*reprocessType*/ ReprocessType.YUV), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW) }, + "Maximum-resolution multi-frame image fusion two-input in-app processing", + /*reprocessType*/ ReprocessType.YUV), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD) }, + "High-resolution ZSL in-app video processing with regular preview", + /*reprocessType*/ ReprocessType.YUV), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) }, + "ZSL still capture and in-app processing", + /*reprocessType*/ ReprocessType.YUV), + }; + + private static StreamCombinationTemplate sRAWPrivateReprocCombinations[] = { + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "Mutually exclusive ZSL in-app processing and DNG capture", + /*reprocessType*/ ReprocessType.PRIVATE), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "Mutually exclusive ZSL in-app processing and preview with DNG capture", + /*reprocessType*/ ReprocessType.PRIVATE), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "Mutually exclusive ZSL two-input in-app processing and DNG capture", + /*reprocessType*/ ReprocessType.PRIVATE), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "Mutually exclusive ZSL still capture and preview with DNG capture", + /*reprocessType*/ ReprocessType.PRIVATE), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "Mutually exclusive ZSL in-app processing with still capture and DNG capture", + /*reprocessType*/ ReprocessType.PRIVATE), + }; + + private static StreamCombinationTemplate sRAWYUVReprocCombinations[] = { + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "Mutually exclusive ZSL in-app processing and DNG capture", + /*reprocessType*/ ReprocessType.YUV), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "Mutually exclusive ZSL in-app processing and preview with DNG capture", + /*reprocessType*/ ReprocessType.YUV), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "Mutually exclusive ZSL two-input in-app processing and DNG capture", + /*reprocessType*/ ReprocessType.YUV), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "Mutually exclusive ZSL still capture and preview with DNG capture", + /*reprocessType*/ ReprocessType.YUV), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "Mutually exclusive ZSL in-app processing with still capture and DNG capture", + /*reprocessType*/ ReprocessType.YUV), + }; + + private static StreamCombinationTemplate sLevel3PrivateReprocCombinations[] = { + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) }, + "In-app viewfinder analysis with ZSL, RAW, and JPEG reprocessing output", + /*reprocessType*/ ReprocessType.PRIVATE), + }; + + private static StreamCombinationTemplate sLevel3YUVReprocCombinations[] = { + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) }, + "In-app viewfinder analysis with ZSL and RAW", + /*reprocessType*/ ReprocessType.YUV), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA), + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) }, + "In-app viewfinder analysis with ZSL, RAW, and JPEG reprocessing output", + /*reprocessType*/ ReprocessType.YUV), + }; + + /** + * Helper builder class to generate a list of available mandatory stream combinations. + * @hide + */ + public static final class Builder { + private Size mDisplaySize; + private List<Integer> mCapabilities; + private int mHwLevel, mCameraId; + private StreamConfigurationMap mStreamConfigMap; + + private final Size kPreviewSizeBound = new Size(1920, 1088); + + /** + * Helper class to be used to generate the available mandatory stream combinations. + * + * @param cameraId Current camera id. + * @param hwLevel The camera HW level as reported by android.info.supportedHardwareLevel. + * @param displaySize The device display size. + * @param capabilities The camera device capabilities. + * @param sm The camera device stream configuration map. + */ + public Builder(int cameraId, int hwLevel, @NonNull Size displaySize, + @NonNull List<Integer> capabilities, @NonNull StreamConfigurationMap sm) { + mCameraId = cameraId; + mDisplaySize = displaySize; + mCapabilities = capabilities; + mStreamConfigMap = sm; + mHwLevel = hwLevel; + } + + /** + * Retrieve a list of all available mandatory stream combinations. + * + * @return a non-modifiable list of supported mandatory stream combinations or + * null in case device is not backward compatible or the method encounters + * an error. + */ + public List<MandatoryStreamCombination> getAvailableMandatoryStreamCombinations() { + if (!isColorOutputSupported()) { + Log.v(TAG, "Device is not backward compatible!"); + return null; + } + + if ((mCameraId < 0) && !isExternalCamera()) { + Log.i(TAG, "Invalid camera id"); + return null; + } + + ArrayList<StreamCombinationTemplate> availableTemplates = + new ArrayList<StreamCombinationTemplate> (); + if (isHardwareLevelAtLeastLegacy()) { + availableTemplates.addAll(Arrays.asList(sLegacyCombinations)); + } + + // External devices are identical to limited devices w.r.t. stream combinations. + if (isHardwareLevelAtLeastLimited() || isExternalCamera()) { + availableTemplates.addAll(Arrays.asList(sLimitedCombinations)); + + if (isPrivateReprocessingSupported()) { + availableTemplates.addAll(Arrays.asList(sLimitedPrivateReprocCombinations)); + } + + if (isYUVReprocessingSupported()) { + availableTemplates.addAll(Arrays.asList(sLimitedYUVReprocCombinations)); + } + + } + + if (isCapabilitySupported( + CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE)) { + availableTemplates.addAll(Arrays.asList(sBurstCombinations)); + } + + if (isHardwareLevelAtLeastFull()) { + availableTemplates.addAll(Arrays.asList(sFullCombinations)); + + if (isPrivateReprocessingSupported()) { + availableTemplates.addAll(Arrays.asList(sFullPrivateReprocCombinations)); + } + + if (isYUVReprocessingSupported()) { + availableTemplates.addAll(Arrays.asList(sFullYUVReprocCombinations)); + } + + } + + if (isCapabilitySupported( + CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) { + availableTemplates.addAll(Arrays.asList(sRawCombinations)); + + if (isPrivateReprocessingSupported()) { + availableTemplates.addAll(Arrays.asList(sRAWPrivateReprocCombinations)); + } + + if (isYUVReprocessingSupported()) { + availableTemplates.addAll(Arrays.asList(sRAWYUVReprocCombinations)); + } + + } + + if (isHardwareLevelAtLeastLevel3()) { + availableTemplates.addAll(Arrays.asList(sLevel3Combinations)); + + if (isPrivateReprocessingSupported()) { + availableTemplates.addAll(Arrays.asList(sLevel3PrivateReprocCombinations)); + } + + if (isYUVReprocessingSupported()) { + availableTemplates.addAll(Arrays.asList(sLevel3YUVReprocCombinations)); + } + + } + + return generateAvailableCombinations(availableTemplates); + } + + /** + * Helper method to generate the available stream combinations given the + * list of available combination templates. + * + * @param availableTemplates a list of templates supported by the camera device. + * @return a non-modifiable list of supported mandatory stream combinations or + * null in case of errors. + */ + private List<MandatoryStreamCombination> generateAvailableCombinations( + ArrayList<StreamCombinationTemplate> availableTemplates) { + if (availableTemplates.isEmpty()) { + Log.e(TAG, "No available stream templates!"); + return null; + } + + HashMap<Pair<SizeThreshold, Integer>, List<Size>> availableSizes = + enumerateAvailableSizes(); + if (availableSizes == null) { + Log.e(TAG, "Available size enumeration failed!"); + return null; + } + + // RAW only uses MAXIMUM size threshold + Size[] rawSizes = mStreamConfigMap.getOutputSizes(ImageFormat.RAW_SENSOR); + ArrayList<Size> availableRawSizes = new ArrayList<Size>(); + if (rawSizes != null) { + availableRawSizes.ensureCapacity(rawSizes.length); + availableRawSizes.addAll(Arrays.asList(rawSizes)); + } + + Size maxPrivateInputSize = new Size(0, 0); + if (isPrivateReprocessingSupported()) { + maxPrivateInputSize = getMaxSize(mStreamConfigMap.getInputSizes( + ImageFormat.PRIVATE)); + } + + Size maxYUVInputSize = new Size(0, 0); + if (isYUVReprocessingSupported()) { + maxYUVInputSize = getMaxSize(mStreamConfigMap.getInputSizes( + ImageFormat.YUV_420_888)); + } + + // Generate the available mandatory stream combinations given the supported templates + // and size ranges. + ArrayList<MandatoryStreamCombination> availableStreamCombinations = + new ArrayList<MandatoryStreamCombination>(); + availableStreamCombinations.ensureCapacity(availableTemplates.size()); + for (StreamCombinationTemplate combTemplate : availableTemplates) { + ArrayList<MandatoryStreamInformation> streamsInfo = + new ArrayList<MandatoryStreamInformation>(); + streamsInfo.ensureCapacity(combTemplate.mStreamTemplates.length); + boolean isReprocessable = combTemplate.mReprocessType != ReprocessType.NONE; + if (isReprocessable) { + // The first and second streams in a reprocessable combination have the + // same size and format. The first is the input and the second is the output + // used for generating the subsequent input buffers. + ArrayList<Size> inputSize = new ArrayList<Size>(); + int format; + if (combTemplate.mReprocessType == ReprocessType.PRIVATE) { + inputSize.add(maxPrivateInputSize); + format = ImageFormat.PRIVATE; + } else { + inputSize.add(maxYUVInputSize); + format = ImageFormat.YUV_420_888; + } + + streamsInfo.add(new MandatoryStreamInformation(inputSize, format, + /*isInput*/true)); + streamsInfo.add(new MandatoryStreamInformation(inputSize, format)); + } + + for (StreamTemplate template : combTemplate.mStreamTemplates) { + List<Size> sizes = null; + if (template.mFormat == ImageFormat.RAW_SENSOR) { + sizes = availableRawSizes; + } else { + Pair<SizeThreshold, Integer> pair; + pair = new Pair<SizeThreshold, Integer>(template.mSizeThreshold, + new Integer(template.mFormat)); + sizes = availableSizes.get(pair); + } + + MandatoryStreamInformation streamInfo; + try { + streamInfo = new MandatoryStreamInformation(sizes, template.mFormat); + } catch (IllegalArgumentException e) { + Log.e(TAG, "No available sizes found for format: " + template.mFormat + + " size threshold: " + template.mSizeThreshold + " combination: " + + combTemplate.mDescription); + return null; + } + + streamsInfo.add(streamInfo); + } + + MandatoryStreamCombination streamCombination; + try { + streamCombination = new MandatoryStreamCombination(streamsInfo, + combTemplate.mDescription, isReprocessable); + } catch (IllegalArgumentException e) { + Log.e(TAG, "No stream information for mandatory combination: " + + combTemplate.mDescription); + return null; + } + + availableStreamCombinations.add(streamCombination); + } + + return Collections.unmodifiableList(availableStreamCombinations); + } + + /** + * Helper method to enumerate all available sizes according to size threshold and format. + */ + private HashMap<Pair<SizeThreshold, Integer>, List<Size>> enumerateAvailableSizes() { + final int[] formats = { + ImageFormat.PRIVATE, + ImageFormat.YUV_420_888, + ImageFormat.JPEG + }; + Size recordingMaxSize = new Size(0, 0); + Size previewMaxSize = new Size(0, 0); + Size vgaSize = new Size(640, 480); + if (isExternalCamera()) { + recordingMaxSize = getMaxExternalRecordingSize(); + } else { + recordingMaxSize = getMaxRecordingSize(); + } + if (recordingMaxSize == null) { + Log.e(TAG, "Failed to find maximum recording size!"); + return null; + } + + HashMap<Integer, Size[]> allSizes = new HashMap<Integer, Size[]>(); + for (int format : formats) { + Integer intFormat = new Integer(format); + allSizes.put(intFormat, mStreamConfigMap.getOutputSizes(format)); + } + + List<Size> previewSizes = getSizesWithinBound( + allSizes.get(new Integer(ImageFormat.PRIVATE)), kPreviewSizeBound); + if ((previewSizes == null) || (previewSizes.isEmpty())) { + Log.e(TAG, "No preview sizes within preview size bound!"); + return null; + } + List<Size> orderedPreviewSizes = getAscendingOrderSizes(previewSizes, + /*ascending*/false); + previewMaxSize = getMaxPreviewSize(orderedPreviewSizes); + + HashMap<Pair<SizeThreshold, Integer>, List<Size>> availableSizes = + new HashMap<Pair<SizeThreshold, Integer>, List<Size>>(); + + for (int format : formats) { + Integer intFormat = new Integer(format); + Size[] sizes = allSizes.get(intFormat); + Pair<SizeThreshold, Integer> pair = new Pair<SizeThreshold, Integer>( + SizeThreshold.VGA, intFormat); + availableSizes.put(pair, getSizesWithinBound(sizes, vgaSize)); + + pair = new Pair<SizeThreshold, Integer>(SizeThreshold.PREVIEW, intFormat); + availableSizes.put(pair, getSizesWithinBound(sizes, previewMaxSize)); + + pair = new Pair<SizeThreshold, Integer>(SizeThreshold.RECORD, intFormat); + availableSizes.put(pair, getSizesWithinBound(sizes, recordingMaxSize)); + + pair = new Pair<SizeThreshold, Integer>(SizeThreshold.MAXIMUM, intFormat); + availableSizes.put(pair, Arrays.asList(sizes)); + } + + return availableSizes; + } + + /** + * Compile a list of sizes smaller than or equal to given bound. + * Return an empty list if there is no size smaller than or equal to the bound. + */ + private static List<Size> getSizesWithinBound(Size[] sizes, Size bound) { + if (sizes == null || sizes.length == 0) { + Log.e(TAG, "Empty or invalid size array!"); + return null; + } + + ArrayList<Size> ret = new ArrayList<Size>(); + for (Size size : sizes) { + if (size.getWidth() <= bound.getWidth() && size.getHeight() <= bound.getHeight()) { + ret.add(size); + } + } + + return ret; + } + + /** + * Get the largest size by area. + * + * @param sizes an array of sizes, must have at least 1 element + * + * @return Largest Size + * + * @throws IllegalArgumentException if sizes was null or had 0 elements + */ + public static Size getMaxSize(Size... sizes) { + if (sizes == null || sizes.length == 0) { + throw new IllegalArgumentException("sizes was empty"); + } + + Size sz = sizes[0]; + for (Size size : sizes) { + if (size.getWidth() * size.getHeight() > sz.getWidth() * sz.getHeight()) { + sz = size; + } + } + + return sz; + } + + /** + * Whether or not the hardware level reported by android.info.supportedHardwareLevel is + * at least the desired one (but could be higher) + */ + private boolean isHardwareLevelAtLeast(int level) { + final int[] sortedHwLevels = { + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL, + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 + }; + if (level == mHwLevel) { + return true; + } + + for (int sortedlevel : sortedHwLevels) { + if (sortedlevel == level) { + return true; + } else if (sortedlevel == mHwLevel) { + return false; + } + } + + return false; + } + + /** + * Whether or not the camera is an external camera. + * + * @return {@code true} if the device is external, {@code false} otherwise. + */ + private boolean isExternalCamera() { + return mHwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL; + } + + /** + * Whether or not the hardware level is at least legacy. + * + * @return {@code true} if the device is {@code LEGACY}, {@code false} otherwise. + */ + private boolean isHardwareLevelAtLeastLegacy() { + return isHardwareLevelAtLeast(CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY); + } + + /** + * Whether or not the hardware level is at least limited. + * + * @return {@code true} if the device is {@code LIMITED} or {@code FULL}, + * {@code false} otherwise (i.e. LEGACY). + */ + private boolean isHardwareLevelAtLeastLimited() { + return isHardwareLevelAtLeast(CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED); + } + + /** + * Whether or not the hardware level is at least full. + * + * @return {@code true} if the device is {@code FULL}, {@code false} otherwise. + */ + private boolean isHardwareLevelAtLeastFull() { + return isHardwareLevelAtLeast(CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL); + } + + /** + * Whether or not the hardware level is at least Level 3. + * + * @return {@code true} if the device is {@code LEVEL3}, {@code false} otherwise. + */ + private boolean isHardwareLevelAtLeastLevel3() { + return isHardwareLevelAtLeast(CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_3); + } + + /** + * Determine whether the current device supports a capability or not. + * + * @param capability (non-negative) + * + * @return {@code true} if the capability is supported, {@code false} otherwise. + * + */ + private boolean isCapabilitySupported(int capability) { + return mCapabilities.contains(capability); + } + + /** + * Check whether the current device is backward compatible. + */ + private boolean isColorOutputSupported() { + return isCapabilitySupported( + CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE); + } + + /** + * Check whether the current device supports private reprocessing. + */ + private boolean isPrivateReprocessingSupported() { + return isCapabilitySupported( + CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING); + } + + /** + * Check whether the current device supports YUV reprocessing. + */ + private boolean isYUVReprocessingSupported() { + return isCapabilitySupported( + CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING); + } + + /** + * Return the maximum supported video size using the camcorder profile information. + * + * @return Maximum supported video size. + */ + private Size getMaxRecordingSize() { + int quality = + CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_2160P) ? + CamcorderProfile.QUALITY_2160P : + CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_1080P) ? + CamcorderProfile.QUALITY_1080P : + CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_720P) ? + CamcorderProfile.QUALITY_720P : + CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_480P) ? + CamcorderProfile.QUALITY_480P : + CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_QVGA) ? + CamcorderProfile.QUALITY_QVGA : + CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_CIF) ? + CamcorderProfile.QUALITY_CIF : + CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_QCIF) ? + CamcorderProfile.QUALITY_QCIF : + -1; + + if (quality < 0) { + return null; + } + + CamcorderProfile maxProfile = CamcorderProfile.get(mCameraId, quality); + return new Size(maxProfile.videoFrameWidth, maxProfile.videoFrameHeight); + } + + /** + * Return the maximum supported video size for external cameras using data from + * the stream configuration map. + * + * @return Maximum supported video size. + */ + private Size getMaxExternalRecordingSize() { + final Size FULLHD = new Size(1920, 1080); + + Size[] videoSizeArr = mStreamConfigMap.getOutputSizes( + android.media.MediaRecorder.class); + List<Size> sizes = new ArrayList<Size>(); + for (Size sz: videoSizeArr) { + if (sz.getWidth() <= FULLHD.getWidth() && sz.getHeight() <= FULLHD.getHeight()) { + sizes.add(sz); + } + } + List<Size> videoSizes = getAscendingOrderSizes(sizes, /*ascending*/false); + for (Size sz : videoSizes) { + long minFrameDuration = mStreamConfigMap.getOutputMinFrameDuration( + android.media.MediaRecorder.class, sz); + // Give some margin for rounding error + if (minFrameDuration > (1e9 / 30.1)) { + Log.i(TAG, "External camera " + mCameraId + " has max video size:" + sz); + return sz; + } + } + Log.w(TAG, "Camera " + mCameraId + " does not support any 30fps video output"); + return FULLHD; // doesn't matter what size is returned here + } + + private Size getMaxPreviewSize(List<Size> orderedPreviewSizes) { + if (orderedPreviewSizes != null) { + for (Size size : orderedPreviewSizes) { + if ((mDisplaySize.getWidth() >= size.getWidth()) && + (mDisplaySize.getWidth() >= size.getHeight())) { + return size; + } + } + } + + Log.w(TAG,"Camera " + mCameraId + " maximum preview size search failed with " + + "display size " + mDisplaySize); + return kPreviewSizeBound; + } + + /** + * Size comparison method used by size comparators. + */ + private static int compareSizes(int widthA, int heightA, int widthB, int heightB) { + long left = widthA * (long) heightA; + long right = widthB * (long) heightB; + if (left == right) { + left = widthA; + right = widthB; + } + return (left < right) ? -1 : (left > right ? 1 : 0); + } + + /** + * Size comparator that compares the number of pixels it covers. + * + * <p>If two the areas of two sizes are same, compare the widths.</p> + */ + public static class SizeComparator implements Comparator<Size> { + @Override + public int compare(Size lhs, Size rhs) { + return compareSizes(lhs.getWidth(), lhs.getHeight(), rhs.getWidth(), + rhs.getHeight()); + } + } + + /** + * Get a sorted list of sizes from a given size list. + * + * <p> + * The size is compare by area it covers, if the areas are same, then + * compare the widths. + * </p> + * + * @param sizeList The input size list to be sorted + * @param ascending True if the order is ascending, otherwise descending order + * @return The ordered list of sizes + */ + private static List<Size> getAscendingOrderSizes(final List<Size> sizeList, + boolean ascending) { + if (sizeList == null) { + return null; + } + + Comparator<Size> comparator = new SizeComparator(); + List<Size> sortedSizes = new ArrayList<Size>(); + sortedSizes.addAll(sizeList); + Collections.sort(sortedSizes, comparator); + if (!ascending) { + Collections.reverse(sortedSizes); + } + + return sortedSizes; + } + } +} diff --git a/core/java/android/inputmethodservice/Keyboard.java b/core/java/android/inputmethodservice/Keyboard.java index 51d33b2f1d4a..3f25113874e8 100644 --- a/core/java/android/inputmethodservice/Keyboard.java +++ b/core/java/android/inputmethodservice/Keyboard.java @@ -16,8 +16,6 @@ package android.inputmethodservice; -import org.xmlpull.v1.XmlPullParserException; - import android.annotation.UnsupportedAppUsage; import android.annotation.XmlRes; import android.content.Context; @@ -27,10 +25,12 @@ import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.os.Build; import android.text.TextUtils; +import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.util.Xml; -import android.util.DisplayMetrics; + +import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; @@ -59,7 +59,12 @@ import java.util.StringTokenizer; * @attr ref android.R.styleable#Keyboard_keyHeight * @attr ref android.R.styleable#Keyboard_horizontalGap * @attr ref android.R.styleable#Keyboard_verticalGap + * @deprecated This class is deprecated because this is just a convenient UI widget class that + * application developers can re-implement on top of existing public APIs. If you have + * already depended on this class, consider copying the implementation from AOSP into + * your project or re-implementing a similar widget by yourselves */ +@Deprecated public class Keyboard { static final String TAG = "Keyboard"; diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java index 9ca804975b7a..45f067b95298 100644 --- a/core/java/android/inputmethodservice/KeyboardView.java +++ b/core/java/android/inputmethodservice/KeyboardView.java @@ -65,7 +65,13 @@ import java.util.Map; * @attr ref android.R.styleable#KeyboardView_keyTextColor * @attr ref android.R.styleable#KeyboardView_verticalCorrection * @attr ref android.R.styleable#KeyboardView_popupLayout + * + * @deprecated This class is deprecated because this is just a convenient UI widget class that + * application developers can re-implement on top of existing public APIs. If you have + * already depended on this class, consider copying the implementation from AOSP into + * your project or re-implementing a similar widget by yourselves */ +@Deprecated public class KeyboardView extends View implements View.OnClickListener { /** diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index 567efa7b7afb..428d9e156e37 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -49,6 +49,7 @@ import android.util.Slog; import android.webkit.MimeTypeMap; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.SizedInputStream; import libcore.io.IoUtils; @@ -110,8 +111,6 @@ public class FileUtils { public static final Pattern SAFE_FILENAME_PATTERN = Pattern.compile("[\\w%+,./=_-]+"); } - private static final File[] EMPTY = new File[0]; - // non-final so it can be toggled by Robolectric's ShadowFileUtils private static boolean sEnableCopyOptimizations = true; @@ -1164,35 +1163,20 @@ public class FileUtils { /** {@hide} */ public static @NonNull String[] listOrEmpty(@Nullable File dir) { - if (dir == null) return EmptyArray.STRING; - final String[] res = dir.list(); - if (res != null) { - return res; - } else { - return EmptyArray.STRING; - } + return (dir != null) ? ArrayUtils.defeatNullable(dir.list()) + : EmptyArray.STRING; } /** {@hide} */ public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) { - if (dir == null) return EMPTY; - final File[] res = dir.listFiles(); - if (res != null) { - return res; - } else { - return EMPTY; - } + return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles()) + : ArrayUtils.EMPTY_FILE; } /** {@hide} */ public static @NonNull File[] listFilesOrEmpty(@Nullable File dir, FilenameFilter filter) { - if (dir == null) return EMPTY; - final File[] res = dir.listFiles(filter); - if (res != null) { - return res; - } else { - return EMPTY; - } + return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles(filter)) + : ArrayUtils.EMPTY_FILE; } /** {@hide} */ diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java index bdc776dd3702..cbcf600f0fb8 100644 --- a/core/java/android/os/SystemProperties.java +++ b/core/java/android/os/SystemProperties.java @@ -25,10 +25,15 @@ import android.util.MutableInt; import com.android.internal.annotations.GuardedBy; +import libcore.util.HexEncoding; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; - /** * Gives access to the system properties store. The system properties * store contains a list of string key-value pairs. @@ -232,6 +237,27 @@ public class SystemProperties { native_report_sysprop_change(); } + /** + * Return a {@code SHA-1} digest of the given keys and their values as a + * hex-encoded string. The ordering of the incoming keys doesn't change the + * digest result. + * + * @hide + */ + public static @NonNull String digestOf(@NonNull String... keys) { + Arrays.sort(keys); + try { + final MessageDigest digest = MessageDigest.getInstance("SHA-1"); + for (String key : keys) { + final String item = key + "=" + get(key) + "\n"; + digest.update(item.getBytes(StandardCharsets.UTF_8)); + } + return HexEncoding.encodeToString(digest.digest()).toLowerCase(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + private SystemProperties() { } } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index d315383a5775..8b1d3c6c839f 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -226,7 +226,9 @@ public class StorageManager { /** {@hide} */ public static final int DEBUG_VIRTUAL_DISK = 1 << 5; /** {@hide} */ - public static final int DEBUG_ISOLATED_STORAGE = 1 << 6; + public static final int DEBUG_ISOLATED_STORAGE_FORCE_ON = 1 << 6; + /** {@hide} */ + public static final int DEBUG_ISOLATED_STORAGE_FORCE_OFF = 1 << 7; /** {@hide} */ public static final int FLAG_STORAGE_DE = IInstalld.FLAG_STORAGE_DE; diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 3d93afdd1423..de54a8aa5548 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -28,12 +28,14 @@ import android.database.Cursor; import android.location.Country; import android.location.CountryDetector; import android.net.Uri; +import android.os.Build; import android.os.UserHandle; import android.os.UserManager; import android.provider.ContactsContract.CommonDataKinds.Callable; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.DataUsageFeedback; +import android.telecom.CallIdentification; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; @@ -603,6 +605,69 @@ public class CallLog { public static final String BLOCK_REASON = "block_reason"; /** + * The package name of the {@link android.telecom.CallScreeningService} which provided + * {@link android.telecom.CallIdentification} for this call. + * <P>Type: TEXT</P> + */ + public static final String CALL_ID_PACKAGE_NAME = "call_id_package_name"; + + /** + * The app name of the {@link android.telecom.CallScreeningService} which provided + * {@link android.telecom.CallIdentification} for this call. + * <P>Type: TEXT</P> + */ + public static final String CALL_ID_APP_NAME = "call_id_app_name"; + + /** + * The {@link CallIdentification#getName() name} of a call, as provided by the + * {@link android.telecom.CallScreeningService}. + * <p> + * The name is provided by the app identified by {@link #CALL_ID_PACKAGE_NAME} and + * {@link #CALL_ID_APP_NAME}. + * <P>Type: TEXT</P> + */ + public static final String CALL_ID_NAME = "call_id_name"; + + /** + * The {@link CallIdentification#getDescription() description} of a call, as provided by the + * {@link android.telecom.CallScreeningService}. + * <p> + * The description is provided by the app identified by {@link #CALL_ID_PACKAGE_NAME} and + * {@link #CALL_ID_APP_NAME}. + * <P>Type: TEXT</P> + */ + public static final String CALL_ID_DESCRIPTION = "call_id_description"; + + /** + * The {@link CallIdentification#getDetails() details} of a call, as provided by the + * {@link android.telecom.CallScreeningService}. + * <p> + * The details field is provided by the app identified by {@link #CALL_ID_PACKAGE_NAME} and + * {@link #CALL_ID_APP_NAME}. + * <P>Type: TEXT</P> + */ + public static final String CALL_ID_DETAILS = "call_id_details"; + + /** + * The {@link CallIdentification#getNuisanceConfidence() nuisance confidence} of a call, as + * provided by the {@link android.telecom.CallScreeningService}. + * <p> + * Valid values are defined in {@link CallIdentification}, and include: + * <ul> + * <li>{@link CallIdentification#CONFIDENCE_NOT_NUISANCE}</li> + * <li>{@link CallIdentification#CONFIDENCE_LIKELY_NOT_NUISANCE}</li> + * <li>{@link CallIdentification#CONFIDENCE_UNKNOWN}</li> + * <li>{@link CallIdentification#CONFIDENCE_LIKELY_NUISANCE}</li> + * <li>{@link CallIdentification#CONFIDENCE_NUISANCE}</li> + * </ul> + * <p> + * The nuisance confidence is provided by the app identified by + * {@link #CALL_ID_PACKAGE_NAME} and {@link #CALL_ID_APP_NAME}. + * <P>Type: INTEGER</P> + */ + public static final String CALL_ID_NUISANCE_CONFIDENCE = "call_id_nuisance_confidence"; + + /** * Adds a call to the call log. * * @param ci the CallerInfo object to get the target contact from. Can be null @@ -631,7 +696,8 @@ public class CallLog { presentation, callType, features, accountHandle, start, duration, dataUsage, false /* addForAllUsers */, null /* userToBeInsertedTo */, false /* isRead */, Calls.BLOCK_REASON_NOT_BLOCKED /* callBlockReason */, - null /* callScreeningAppName */, null /* callScreeningComponentName */); + null /* callScreeningAppName */, null /* callScreeningComponentName */, + null /* callIdentification */); } @@ -671,7 +737,8 @@ public class CallLog { features, accountHandle, start, duration, dataUsage, addForAllUsers, userToBeInsertedTo, false /* isRead */ , Calls.BLOCK_REASON_NOT_BLOCKED /* callBlockReason */, null /* callScreeningAppName */, - null /* callScreeningComponentName */); + null /* callScreeningComponentName */, + null /* callIdentification */); } /** @@ -705,19 +772,32 @@ public class CallLog { * @param callBlockReason The reason why the call is blocked. * @param callScreeningAppName The call screening application name which block the call. * @param callScreeningComponentName The call screening component name which block the call. + * @param callIdPackageName The package name of the + * {@link android.telecom.CallScreeningService} which provided + * {@link CallIdentification}. + * @param callIdAppName The app name of the {@link android.telecom.CallScreeningService} + * which provided {@link CallIdentification}. + * @param callIdName The caller name provided by the + * {@link android.telecom.CallScreeningService}. + * @param callIdDescription The caller description provided by the + * {@link android.telecom.CallScreeningService}. + * @param callIdDetails The caller details provided by the + * {@link android.telecom.CallScreeningService}. + * @param callIdCallType The caller type provided by the + * {@link android.telecom.CallScreeningService}. * * @result The URI of the call log entry belonging to the user that made or received this * call. This could be of the shadow provider. Do not return it to non-system apps, * as they don't have permissions. * {@hide} */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public static Uri addCall(CallerInfo ci, Context context, String number, String postDialDigits, String viaNumber, int presentation, int callType, int features, PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage, boolean addForAllUsers, UserHandle userToBeInsertedTo, boolean isRead, int callBlockReason, String callScreeningAppName, - String callScreeningComponentName) { + String callScreeningComponentName, CallIdentification callIdentification) { if (VERBOSE_LOG) { Log.v(LOG_TAG, String.format("Add call: number=%s, user=%s, for all=%s", number, userToBeInsertedTo, addForAllUsers)); @@ -799,6 +879,22 @@ public class CallLog { values.put(CALL_SCREENING_APP_NAME, callScreeningAppName); values.put(CALL_SCREENING_COMPONENT_NAME, callScreeningComponentName); + if (callIdentification != null) { + values.put(CALL_ID_PACKAGE_NAME, callIdentification.getCallScreeningPackageName()); + values.put(CALL_ID_APP_NAME, callIdentification.getCallScreeningAppName()); + values.put(CALL_ID_NAME, callIdentification.getName()); + values.put(CALL_ID_DESCRIPTION, callIdentification.getDescription()); + values.put(CALL_ID_DETAILS, callIdentification.getDetails()); + values.put(CALL_ID_NUISANCE_CONFIDENCE, callIdentification.getNuisanceConfidence()); + } else { + values.putNull(CALL_ID_PACKAGE_NAME); + values.putNull(CALL_ID_APP_NAME); + values.putNull(CALL_ID_NAME); + values.putNull(CALL_ID_DESCRIPTION); + values.putNull(CALL_ID_DETAILS); + values.putNull(CALL_ID_NUISANCE_CONFIDENCE); + } + if ((ci != null) && (ci.contactIdOrZero > 0)) { // Update usage information for the number associated with the contact ID. // We need to use both the number and the ID for obtaining a data ID since other diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 93a59502ebea..7f135fb8abb4 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -150,6 +150,23 @@ public final class Settings { "android.settings.LOCATION_SOURCE_SETTINGS"; /** + * Activity Action: Show settings to allow configuration of location controller extra package. + * <p> + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + * + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS = + "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS"; + + /** * Activity Action: Show scanning settings to allow configuration of Wi-Fi * and Bluetooth scanning settings. * <p> @@ -7867,6 +7884,24 @@ public final class Settings { public static final String ASSIST_GESTURE_SETUP_COMPLETE = "assist_gesture_setup_complete"; /** + * Control whether Trust Agents are in active unlock or extend unlock mode. + * @hide + */ + public static final String TRUST_AGENTS_EXTEND_UNLOCK = "trust_agents_extend_unlock"; + + private static final Validator TRUST_AGENTS_EXTEND_UNLOCK_VALIDATOR = + BOOLEAN_VALIDATOR; + + /** + * Control whether the screen locks when trust is lost. + * @hide + */ + public static final String LOCK_SCREEN_WHEN_TRUST_LOST = "lock_screen_when_trust_lost"; + + private static final Validator LOCK_SCREEN_WHEN_TRUST_LOST_VALIDATOR = + BOOLEAN_VALIDATOR; + + /** * Control whether Night display is currently activated. * @hide */ @@ -8244,6 +8279,38 @@ public final class Settings { "packages_to_clear_data_before_full_restore"; /** + * Indicates the location state should be maintained after sensor privacy is disabled. + * @hide + */ + public static final String MAINTAIN_LOCATION_AFTER_SP_DISABLED = "0"; + + /** + * Indicates location should be reenabled after sensor privacy is disabled. + * @hide + */ + public static final String REENABLE_LOCATION_AFTER_SP_DISABLED = "1"; + + /** + * Indicates the state of airplane mode should be maintained after sensor privacy is + * disabled. + * @hide + */ + public static final String MAINTAIN_AIRPLANE_MODE_AFTER_SP_DISABLED = "0"; + + /** + * Indicates airplane mode should be disabled after sensor privacy is disabled. + * @hide + */ + public static final String DISABLE_AIRPLANE_MODE_AFTER_SP_DISABLED = "1"; + + /** + * The state of all sensors managed by SensorPrivacyService when sensor privacy is enabled. + * @hide + */ + public static final String SENSOR_PRIVACY_SENSOR_STATE = + "sensor_privacy_sensor_state"; + + /** * Setting to determine whether to use the new notification priority handling features. * @hide */ @@ -8382,6 +8449,8 @@ public final class Settings { ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS, ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS, NOTIFICATION_NEW_INTERRUPTION_MODEL, + TRUST_AGENTS_EXTEND_UNLOCK, + LOCK_SCREEN_WHEN_TRUST_LOST, }; /** @@ -8543,6 +8612,8 @@ public final class Settings { VALIDATORS.put(USER_SETUP_COMPLETE, BOOLEAN_VALIDATOR); VALIDATORS.put(ASSIST_GESTURE_SETUP_COMPLETE, BOOLEAN_VALIDATOR); VALIDATORS.put(NOTIFICATION_NEW_INTERRUPTION_MODEL, BOOLEAN_VALIDATOR); + VALIDATORS.put(TRUST_AGENTS_EXTEND_UNLOCK, TRUST_AGENTS_EXTEND_UNLOCK_VALIDATOR); + VALIDATORS.put(LOCK_SCREEN_WHEN_TRUST_LOST, LOCK_SCREEN_WHEN_TRUST_LOST_VALIDATOR); } /** @@ -12893,6 +12964,11 @@ public final class Settings { public static final String CONTENT_CAPTURE_SERVICE_EXPLICITLY_ENABLED = "content_capture_service_explicitly_enabled"; + /** {@hide} */ + public static final String ISOLATED_STORAGE_LOCAL = "isolated_storage_local"; + /** {@hide} */ + public static final String ISOLATED_STORAGE_REMOTE = "isolated_storage_remote"; + /** * Settings to backup. This is here so that it's in the same place as the settings * keys and easy to update. @@ -13879,7 +13955,6 @@ public final class Settings { */ public static final String LAST_ACTIVE_USER_ID = "last_active_persistent_user_id"; - /** * Whether we've enabled native flags health check on this device. Takes effect on * reboot. The value "1" enables native flags health check; otherwise it's disabled. @@ -13887,7 +13962,6 @@ public final class Settings { */ public static final String NATIVE_FLAGS_HEALTH_CHECK_ENABLED = "native_flags_health_check_enabled"; - } /** diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java index 9aff281b0472..7683b8ae08b2 100644 --- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java +++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java @@ -254,6 +254,15 @@ public abstract class AugmentedAutofillService extends Service { @GuardedBy("mLock") private AutofillValue mFocusedValue; + /** + * Id of the last field that cause the Autofill UI to be shown. + * + * <p>Used to make sure the SmartSuggestionsParams is updated when a new fields is focused. + */ + // TODO(b/111330312): might not be needed when using IME + @GuardedBy("mLock") + private AutofillId mLastShownId; + // Objects used to log metrics private final long mRequestTime; private long mOnSuccessTime; @@ -284,7 +293,7 @@ public abstract class AugmentedAutofillService extends Service { @NonNull public SystemPopupPresentationParams getSmartSuggestionParams() { synchronized (mLock) { - if (mSmartSuggestion != null) { + if (mSmartSuggestion != null && mFocusedId.equals(mLastShownId)) { return mSmartSuggestion; } Rect rect; @@ -299,6 +308,7 @@ public abstract class AugmentedAutofillService extends Service { return null; } mSmartSuggestion = new SystemPopupPresentationParams(this, rect); + mLastShownId = mFocusedId; return mSmartSuggestion; } } @@ -401,6 +411,9 @@ public abstract class AugmentedAutofillService extends Service { if (mFocusedValue != null) { pw.print(prefix); pw.print("focusedValue: "); pw.println(mFocusedValue); } + if (mLastShownId != null) { + pw.print(prefix); pw.print("lastShownId: "); pw.println(mLastShownId); + } pw.print(prefix); pw.print("client: "); pw.println(mClient); final String prefix2 = prefix + " "; if (mFillWindow != null) { diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 1fe97b79fd69..6d2f85679cff 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -1482,7 +1482,7 @@ public abstract class NotificationListenerService extends Service { private boolean mShowBadge; private @UserSentiment int mUserSentiment = USER_SENTIMENT_NEUTRAL; private boolean mHidden; - private boolean mAudiblyAlerted; + private long mLastAudiblyAlertedMs; private boolean mNoisy; private ArrayList<Notification.Action> mSmartActions; private ArrayList<CharSequence> mSmartReplies; @@ -1650,12 +1650,12 @@ public abstract class NotificationListenerService extends Service { } /** - * Returns whether this notification alerted the user via sound or vibration. + * Returns the last time this notification alerted the user via sound or vibration. * - * @return true if the notification alerted the user, false otherwise. + * @return the time of the last alerting behavior, in milliseconds. */ - public boolean audiblyAlerted() { - return mAudiblyAlerted; + public long getLastAudiblyAlertedMillis() { + return mLastAudiblyAlertedMs; } /** @hide */ @@ -1672,7 +1672,7 @@ public abstract class NotificationListenerService extends Service { CharSequence explanation, String overrideGroupKey, NotificationChannel channel, ArrayList<String> overridePeople, ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge, - int userSentiment, boolean hidden, boolean audiblyAlerted, + int userSentiment, boolean hidden, long lastAudiblyAlertedMs, boolean noisy, ArrayList<Notification.Action> smartActions, ArrayList<CharSequence> smartReplies) { mKey = key; @@ -1690,7 +1690,7 @@ public abstract class NotificationListenerService extends Service { mShowBadge = showBadge; mUserSentiment = userSentiment; mHidden = hidden; - mAudiblyAlerted = audiblyAlerted; + mLastAudiblyAlertedMs = lastAudiblyAlertedMs; mNoisy = noisy; mSmartActions = smartActions; mSmartReplies = smartReplies; @@ -1743,7 +1743,7 @@ public abstract class NotificationListenerService extends Service { private ArrayMap<String, Boolean> mShowBadge; private ArrayMap<String, Integer> mUserSentiment; private ArrayMap<String, Boolean> mHidden; - private ArrayMap<String, Boolean> mAudiblyAlerted; + private ArrayMap<String, Long> mLastAudiblyAlerted; private ArrayMap<String, Boolean> mNoisy; private ArrayMap<String, ArrayList<Notification.Action>> mSmartActions; private ArrayMap<String, ArrayList<CharSequence>> mSmartReplies; @@ -1776,7 +1776,7 @@ public abstract class NotificationListenerService extends Service { getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key), getChannel(key), getOverridePeople(key), getSnoozeCriteria(key), getShowBadge(key), getUserSentiment(key), getHidden(key), - getAudiblyAlerted(key), getNoisy(key), getSmartActions(key), + getLastAudiblyAlerted(key), getNoisy(key), getSmartActions(key), getSmartReplies(key)); return rank >= 0; } @@ -1915,14 +1915,14 @@ public abstract class NotificationListenerService extends Service { return hidden == null ? false : hidden.booleanValue(); } - private boolean getAudiblyAlerted(String key) { + private long getLastAudiblyAlerted(String key) { synchronized (this) { - if (mAudiblyAlerted == null) { - buildAudiblyAlertedLocked(); + if (mLastAudiblyAlerted == null) { + buildLastAudiblyAlertedLocked(); } } - Boolean audiblyAlerted = mAudiblyAlerted.get(key); - return audiblyAlerted == null ? false : audiblyAlerted.booleanValue(); + Long lastAudibleAlerted = mLastAudiblyAlerted.get(key); + return lastAudibleAlerted == null ? -1 : lastAudibleAlerted.longValue(); } private boolean getNoisy(String key) { @@ -1994,6 +1994,14 @@ public abstract class NotificationListenerService extends Service { return newMap; } + private ArrayMap<String, Long> buildLongMapFromBundle(Bundle bundle) { + ArrayMap<String, Long> newMap = new ArrayMap<>(bundle.size()); + for (String key : bundle.keySet()) { + newMap.put(key, bundle.getLong(key)); + } + return newMap; + } + // Locked by 'this' private void buildVisibilityOverridesLocked() { mVisibilityOverrides = buildIntMapFromBundle(mRankingUpdate.getVisibilityOverrides()); @@ -2070,8 +2078,8 @@ public abstract class NotificationListenerService extends Service { } // Locked by 'this' - private void buildAudiblyAlertedLocked() { - mAudiblyAlerted = buildBooleanMapFromBundle(mRankingUpdate.getAudiblyAlerted()); + private void buildLastAudiblyAlertedLocked() { + mLastAudiblyAlerted = buildLongMapFromBundle(mRankingUpdate.getLastAudiblyAlerted()); } // Locked by 'this' diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java index f80df93364f4..ebaeff8ff33c 100644 --- a/core/java/android/service/notification/NotificationRankingUpdate.java +++ b/core/java/android/service/notification/NotificationRankingUpdate.java @@ -39,7 +39,7 @@ public class NotificationRankingUpdate implements Parcelable { private final Bundle mHidden; private final Bundle mSmartActions; private final Bundle mSmartReplies; - private final Bundle mAudiblyAlerted; + private final Bundle mLastAudiblyAlerted; private final Bundle mNoisy; public NotificationRankingUpdate(String[] keys, String[] interceptedKeys, @@ -47,7 +47,7 @@ public class NotificationRankingUpdate implements Parcelable { int[] importance, Bundle explanation, Bundle overrideGroupKeys, Bundle channels, Bundle overridePeople, Bundle snoozeCriteria, Bundle showBadge, Bundle userSentiment, Bundle hidden, Bundle smartActions, - Bundle smartReplies, Bundle audiblyAlerted, Bundle noisy) { + Bundle smartReplies, Bundle lastAudiblyAlerted, Bundle noisy) { mKeys = keys; mInterceptedKeys = interceptedKeys; mVisibilityOverrides = visibilityOverrides; @@ -63,7 +63,7 @@ public class NotificationRankingUpdate implements Parcelable { mHidden = hidden; mSmartActions = smartActions; mSmartReplies = smartReplies; - mAudiblyAlerted = audiblyAlerted; + mLastAudiblyAlerted = lastAudiblyAlerted; mNoisy = noisy; } @@ -84,7 +84,7 @@ public class NotificationRankingUpdate implements Parcelable { mHidden = in.readBundle(); mSmartActions = in.readBundle(); mSmartReplies = in.readBundle(); - mAudiblyAlerted = in.readBundle(); + mLastAudiblyAlerted = in.readBundle(); mNoisy = in.readBundle(); } @@ -110,7 +110,7 @@ public class NotificationRankingUpdate implements Parcelable { out.writeBundle(mHidden); out.writeBundle(mSmartActions); out.writeBundle(mSmartReplies); - out.writeBundle(mAudiblyAlerted); + out.writeBundle(mLastAudiblyAlerted); out.writeBundle(mNoisy); } @@ -185,8 +185,8 @@ public class NotificationRankingUpdate implements Parcelable { return mSmartReplies; } - public Bundle getAudiblyAlerted() { - return mAudiblyAlerted; + public Bundle getLastAudiblyAlerted() { + return mLastAudiblyAlerted; } public Bundle getNoisy() { diff --git a/core/java/android/service/textclassifier/ITextClassifierService.aidl b/core/java/android/service/textclassifier/ITextClassifierService.aidl index 254a710a2c40..794179415fc0 100644 --- a/core/java/android/service/textclassifier/ITextClassifierService.aidl +++ b/core/java/android/service/textclassifier/ITextClassifierService.aidl @@ -26,6 +26,7 @@ import android.view.textclassifier.SelectionEvent; import android.view.textclassifier.TextClassification; import android.view.textclassifier.TextClassificationContext; import android.view.textclassifier.TextClassificationSessionId; +import android.view.textclassifier.TextClassifierEvent; import android.view.textclassifier.TextLinks; import android.view.textclassifier.TextLanguage; import android.view.textclassifier.TextSelection; @@ -52,10 +53,15 @@ oneway interface ITextClassifierService { in TextLinks.Request request, in ITextLinksCallback callback); + // TODO: Remove void onSelectionEvent( in TextClassificationSessionId sessionId, in SelectionEvent event); + void onTextClassifierEvent( + in TextClassificationSessionId sessionId, + in TextClassifierEvent event); + void onCreateTextClassificationSession( in TextClassificationContext context, in TextClassificationSessionId sessionId); diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java index 3b813c7df82b..2221d6ec7296 100644 --- a/core/java/android/service/textclassifier/TextClassifierService.java +++ b/core/java/android/service/textclassifier/TextClassifierService.java @@ -39,6 +39,7 @@ import android.view.textclassifier.TextClassificationContext; import android.view.textclassifier.TextClassificationManager; import android.view.textclassifier.TextClassificationSessionId; import android.view.textclassifier.TextClassifier; +import android.view.textclassifier.TextClassifierEvent; import android.view.textclassifier.TextLanguage; import android.view.textclassifier.TextLinks; import android.view.textclassifier.TextSelection; @@ -193,6 +194,15 @@ public abstract class TextClassifierService extends Service { /** {@inheritDoc} */ @Override + public void onTextClassifierEvent( + TextClassificationSessionId sessionId, + TextClassifierEvent event) { + Preconditions.checkNotNull(event); + TextClassifierService.this.onTextClassifierEvent(sessionId, event); + } + + /** {@inheritDoc} */ + @Override public void onDetectLanguage( TextClassificationSessionId sessionId, TextLanguage.Request request, @@ -368,11 +378,28 @@ public abstract class TextClassifierService extends Service { * * @param sessionId the session id * @param event the selection event + * @deprecated + * Use {@link #onTextClassifierEvent(TextClassificationSessionId, TextClassifierEvent)} + * instead */ + @Deprecated public void onSelectionEvent( @Nullable TextClassificationSessionId sessionId, @NonNull SelectionEvent event) {} /** + * Writes the TextClassifier event. + * This is called when a TextClassifier event occurs. e.g. user changed selection, + * smart selection happened, or a link was clicked. + * + * <p>The default implementation ignores the event. + * + * @param sessionId the session id + * @param event the TextClassifier event + */ + public void onTextClassifierEvent( + @Nullable TextClassificationSessionId sessionId, @NonNull TextClassifierEvent event) {} + + /** * Creates a new text classification session for the specified context. * * @param context the text classification context diff --git a/core/java/android/text/style/LineBackgroundSpan.java b/core/java/android/text/style/LineBackgroundSpan.java index 5a55fd749150..e43fd8303515 100644 --- a/core/java/android/text/style/LineBackgroundSpan.java +++ b/core/java/android/text/style/LineBackgroundSpan.java @@ -118,7 +118,7 @@ public interface LineBackgroundSpan extends ParagraphStyle int lineNumber) { final int originColor = paint.getColor(); paint.setColor(mColor); - canvas.drawRect(left, right, top, bottom, paint); + canvas.drawRect(left, top, right, bottom, paint); paint.setColor(originColor); } } diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index 5e6d3d19fa2f..c06a1fe0a257 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -71,41 +71,18 @@ oneway interface IWindow { void dispatchGetNewSurface(); /** - * Tell the window that it is either gaining or losing focus. - * - * @param hasFocus {@code true} if window has focus, {@code false} otherwise. - * @param inTouchMode {@code true} if screen is in touch mode, {@code false} otherwise. - * @param reportToClient {@code true} when need to report to child view with - * {@link View#onWindowFocusChanged(boolean)}, {@code false} otherwise. - * <p> - * Note: In the previous design, there is only one window focus state tracked by - * WindowManagerService. - * For multi-display, the window focus state is tracked by each display independently. - * <p> - * It will introduce a problem if the window was already focused on one display and then - * switched to another display, since the window focus state on each display is independent, - * there is no global window focus state in WindowManagerService, so the window focus state of - * the former display remains unchanged. - * <p> - * When switched back to former display, some flows that rely on the global window focus state - * in view root will be missed due to the window focus state remaining unchanged. - * (i.e: Showing single IME window when switching between displays.) - * <p> - * To solve the problem, WindowManagerService tracks the top focused display change and then - * callbacks to the client via this method to make sure that the client side will request the - * IME on the top focused display, and then set {@param reportToClient} as {@code false} to - * ignore reporting to the application, since its focus remains unchanged on its display. - * + * Tell the window that it is either gaining or losing focus. Keep it up + * to date on the current state showing navigational focus (touch mode) too. */ - void windowFocusChanged(boolean hasFocus, boolean inTouchMode, boolean reportToClient); - + void windowFocusChanged(boolean hasFocus, boolean inTouchMode); + void closeSystemDialogs(String reason); - + /** * Called for wallpaper windows when their offsets change. */ void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep, boolean sync); - + void dispatchWallpaperCommand(String action, int x, int y, int z, in Bundle extras, boolean sync); diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index c4be0e504c5b..9dfd43cde628 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -117,8 +117,10 @@ interface IWindowManager void stopFreezingScreen(); // these require DISABLE_KEYGUARD permission - void disableKeyguard(IBinder token, String tag); - void reenableKeyguard(IBinder token); + /** @deprecated use Activity.setShowWhenLocked instead. */ + void disableKeyguard(IBinder token, String tag, int userId); + /** @deprecated use Activity.setShowWhenLocked instead. */ + void reenableKeyguard(IBinder token, int userId); void exitKeyguardSecurely(IOnKeyguardExitResult callback); boolean isKeyguardLocked(); boolean isKeyguardSecure(); diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index 4a5ccdff0dd3..ada78532d63f 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -340,7 +340,7 @@ public class NotificationHeaderView extends ViewGroup { } /** Updates icon visibility based on the noisiness of the notification. */ - public void setAudiblyAlerted(boolean audiblyAlerted) { + public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) { mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? View.VISIBLE : View.GONE); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 9fe0ddc110c2..3f7a5127339d 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1876,6 +1876,7 @@ public final class ViewRootImpl implements ViewParent, } void dispatchApplyInsets(View host) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "dispatchApplyInsets"); WindowInsets insets = getWindowInsets(true /* forceConstruct */); final boolean dispatchCutout = (mWindowAttributes.layoutInDisplayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS); @@ -1885,6 +1886,7 @@ public final class ViewRootImpl implements ViewParent, insets = insets.consumeDisplayCutout(); } host.dispatchApplyWindowInsets(insets); + Trace.traceEnd(Trace.TRACE_TAG_VIEW); } InsetsController getInsetsController() { @@ -2763,7 +2765,7 @@ public final class ViewRootImpl implements ViewParent, } } - private void handleWindowFocusChanged(boolean reportToClient) { + private void handleWindowFocusChanged() { final boolean hasWindowFocus; final boolean inTouchMode; synchronized (this) { @@ -2798,9 +2800,8 @@ public final class ViewRootImpl implements ViewParent, } catch (RemoteException ex) { } // Retry in a bit. - final Message msg = mHandler.obtainMessage(MSG_WINDOW_FOCUS_CHANGED); - msg.arg1 = reportToClient ? 1 : 0; - mHandler.sendMessageDelayed(msg, 500); + mHandler.sendMessageDelayed(mHandler.obtainMessage( + MSG_WINDOW_FOCUS_CHANGED), 500); return; } } @@ -2817,15 +2818,8 @@ public final class ViewRootImpl implements ViewParent, } if (mView != null) { mAttachInfo.mKeyDispatchState.reset(); - // We dispatch onWindowFocusChanged to child view only when window is gaining / - // losing focus. - // If the focus is updated from top display change but window focus on the display - // remains unchanged, will not callback onWindowFocusChanged again since it may - // be redundant & can affect the state when it callbacks. - if (reportToClient) { - mView.dispatchWindowFocusChanged(hasWindowFocus); - mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus); - } + mView.dispatchWindowFocusChanged(hasWindowFocus); + mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus); if (mAttachInfo.mTooltipHost != null) { mAttachInfo.mTooltipHost.hideTooltip(); } @@ -4426,7 +4420,7 @@ public final class ViewRootImpl implements ViewParent, } break; case MSG_WINDOW_FOCUS_CHANGED: { - handleWindowFocusChanged(msg.arg1 != 0 /* reportToClient */); + handleWindowFocusChanged(); } break; case MSG_DIE: doDie(); @@ -7370,7 +7364,7 @@ public final class ViewRootImpl implements ViewParent, } if (stage != null) { - handleWindowFocusChanged(true /* reportToClient */); + handleWindowFocusChanged(); stage.deliver(q); } else { finishInputEvent(q); @@ -7710,11 +7704,6 @@ public final class ViewRootImpl implements ViewParent, } public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) { - windowFocusChanged(hasFocus, inTouchMode, true /* reportToClient */); - } - - public void windowFocusChanged(boolean hasFocus, boolean inTouchMode, - boolean reportToClient) { synchronized (this) { mWindowFocusChanged = true; mUpcomingWindowFocus = hasFocus; @@ -7722,7 +7711,6 @@ public final class ViewRootImpl implements ViewParent, } Message msg = Message.obtain(); msg.what = MSG_WINDOW_FOCUS_CHANGED; - msg.arg1 = reportToClient ? 1 : 0; mHandler.sendMessage(msg); } @@ -8284,11 +8272,10 @@ public final class ViewRootImpl implements ViewParent, } @Override - public void windowFocusChanged(boolean hasFocus, boolean inTouchMode, - boolean reportToClient) { + public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { - viewAncestor.windowFocusChanged(hasFocus, inTouchMode, reportToClient); + viewAncestor.windowFocusChanged(hasFocus, inTouchMode); } } diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java index 04924c9a59c4..3f690f70d275 100644 --- a/core/java/android/view/textclassifier/ConversationActions.java +++ b/core/java/android/view/textclassifier/ConversationActions.java @@ -139,17 +139,21 @@ public final class ConversationActions implements Parcelable { */ public static final String HINT_FOR_NOTIFICATION = "notification"; - private List<ConversationAction> mConversationActions; + private final List<ConversationAction> mConversationActions; + private final String mId; /** Constructs a {@link ConversationActions} object. */ - public ConversationActions(@NonNull List<ConversationAction> conversationActions) { + public ConversationActions( + @NonNull List<ConversationAction> conversationActions, @Nullable String id) { mConversationActions = Collections.unmodifiableList(Preconditions.checkNotNull(conversationActions)); + mId = id; } private ConversationActions(Parcel in) { mConversationActions = Collections.unmodifiableList(in.createTypedArrayList(ConversationAction.CREATOR)); + mId = in.readString(); } @Override @@ -160,14 +164,26 @@ public final class ConversationActions implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeTypedList(mConversationActions); + parcel.writeString(mId); } - /** Returns an immutable list of {@link ConversationAction} objects. */ + /** + * Returns an immutable list of {@link ConversationAction} objects, which are ordered from high + * confidence to low confidence. + */ @NonNull public List<ConversationAction> getConversationActions() { return mConversationActions; } + /** + * Returns the id, if one exists, for this object. + */ + @Nullable + public String getId() { + return mId; + } + /** Represents the action suggested by a {@link TextClassifier} on a given conversation. */ public static final class ConversationAction implements Parcelable { @@ -678,35 +694,37 @@ public final class ConversationActions implements Parcelable { private final List<String> mHints; @Nullable private String mCallingPackageName; + @Nullable + private final String mConversationId; private Request( @NonNull List<Message> conversation, @NonNull TypeConfig typeConfig, int maxSuggestions, + String conversationId, @Nullable @Hint List<String> hints) { mConversation = Preconditions.checkNotNull(conversation); mTypeConfig = Preconditions.checkNotNull(typeConfig); mMaxSuggestions = maxSuggestions; + mConversationId = conversationId; mHints = hints; } private static Request readFromParcel(Parcel in) { List<Message> conversation = new ArrayList<>(); in.readParcelableList(conversation, null); - TypeConfig typeConfig = in.readParcelable(null); - int maxSuggestions = in.readInt(); - + String conversationId = in.readString(); List<String> hints = new ArrayList<>(); in.readStringList(hints); - String callingPackageName = in.readString(); Request request = new Request( conversation, typeConfig, maxSuggestions, + conversationId, hints); request.setCallingPackageName(callingPackageName); return request; @@ -717,6 +735,7 @@ public final class ConversationActions implements Parcelable { parcel.writeParcelableList(mConversation, flags); parcel.writeParcelable(mTypeConfig, flags); parcel.writeInt(mMaxSuggestions); + parcel.writeString(mConversationId); parcel.writeStringList(mHints); parcel.writeString(mCallingPackageName); } @@ -759,6 +778,16 @@ public final class ConversationActions implements Parcelable { return mMaxSuggestions; } + /** + * Return an unique identifier of the conversation that is generating actions for. This + * identifier is unique within the calling package only, so use it with + * {@link #getCallingPackageName()}. + */ + @Nullable + public String getConversationId() { + return mConversationId; + } + /** Returns an immutable list of hints */ @Nullable @Hint @@ -794,6 +823,8 @@ public final class ConversationActions implements Parcelable { private TypeConfig mTypeConfig; private int mMaxSuggestions; @Nullable + private String mConversationId; + @Nullable @Hint private List<String> mHints; @@ -823,7 +854,8 @@ public final class ConversationActions implements Parcelable { return this; } - /** Sets the maximum number of suggestions you want. + /** + * Sets the maximum number of suggestions you want. * <p> * Value 0 means no restriction. */ @@ -833,6 +865,15 @@ public final class ConversationActions implements Parcelable { return this; } + /** + * Sets an unique identifier of the conversation that is generating actions for. + */ + @NonNull + public Builder setConversationId(@Nullable String conversationId) { + mConversationId = conversationId; + return this; + } + /** Builds the {@link Request} object. */ @NonNull public Request build() { @@ -840,6 +881,7 @@ public final class ConversationActions implements Parcelable { Collections.unmodifiableList(mConversation), mTypeConfig == null ? new TypeConfig.Builder().build() : mTypeConfig, mMaxSuggestions, + mConversationId, mHints == null ? Collections.emptyList() : Collections.unmodifiableList(mHints)); diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java index c24489c8740b..8b370f543ccc 100644 --- a/core/java/android/view/textclassifier/SystemTextClassifier.java +++ b/core/java/android/view/textclassifier/SystemTextClassifier.java @@ -147,6 +147,18 @@ public final class SystemTextClassifier implements TextClassifier { } @Override + public void onTextClassifierEvent(@NonNull TextClassifierEvent event) { + Preconditions.checkNotNull(event); + Utils.checkMainThread(); + + try { + mManagerService.onTextClassifierEvent(mSessionId, event); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Error reporting textclassifier event.", e); + } + } + + @Override public TextLanguage detectLanguage(TextLanguage.Request request) { Preconditions.checkNotNull(request); Utils.checkMainThread(); diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index ea82bf3f3846..8709e09bbf55 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -127,32 +127,34 @@ public interface TextClassifier { @StringDef({WIDGET_TYPE_TEXTVIEW, WIDGET_TYPE_EDITTEXT, WIDGET_TYPE_UNSELECTABLE_TEXTVIEW, WIDGET_TYPE_WEBVIEW, WIDGET_TYPE_EDIT_WEBVIEW, WIDGET_TYPE_CUSTOM_TEXTVIEW, WIDGET_TYPE_CUSTOM_EDITTEXT, WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW, - WIDGET_TYPE_UNKNOWN}) + WIDGET_TYPE_NOTIFICATION, WIDGET_TYPE_UNKNOWN}) @interface WidgetType {} - /** The widget involved in the text classification session is a standard + /** The widget involved in the text classification context is a standard * {@link android.widget.TextView}. */ String WIDGET_TYPE_TEXTVIEW = "textview"; - /** The widget involved in the text classification session is a standard + /** The widget involved in the text classification context is a standard * {@link android.widget.EditText}. */ String WIDGET_TYPE_EDITTEXT = "edittext"; - /** The widget involved in the text classification session is a standard non-selectable + /** The widget involved in the text classification context is a standard non-selectable * {@link android.widget.TextView}. */ String WIDGET_TYPE_UNSELECTABLE_TEXTVIEW = "nosel-textview"; - /** The widget involved in the text classification session is a standard + /** The widget involved in the text classification context is a standard * {@link android.webkit.WebView}. */ String WIDGET_TYPE_WEBVIEW = "webview"; - /** The widget involved in the text classification session is a standard editable + /** The widget involved in the text classification context is a standard editable * {@link android.webkit.WebView}. */ String WIDGET_TYPE_EDIT_WEBVIEW = "edit-webview"; - /** The widget involved in the text classification session is a custom text widget. */ + /** The widget involved in the text classification context is a custom text widget. */ String WIDGET_TYPE_CUSTOM_TEXTVIEW = "customview"; - /** The widget involved in the text classification session is a custom editable text widget. */ + /** The widget involved in the text classification context is a custom editable text widget. */ String WIDGET_TYPE_CUSTOM_EDITTEXT = "customedit"; - /** The widget involved in the text classification session is a custom non-selectable text + /** The widget involved in the text classification context is a custom non-selectable text * widget. */ String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview"; - /** The widget involved in the text classification session is of an unknown/unspecified type. */ + /** The widget involved in the text classification context is a notification */ + String WIDGET_TYPE_NOTIFICATION = "notification"; + /** The widget involved in the text classification context is of an unknown/unspecified type. */ String WIDGET_TYPE_UNKNOWN = "unknown"; /** @@ -349,16 +351,30 @@ public interface TextClassifier { @NonNull ConversationActions.Request request) { Preconditions.checkNotNull(request); Utils.checkMainThread(); - return new ConversationActions(Collections.emptyList()); + return new ConversationActions(Collections.emptyList(), null); } /** + * <strong>NOTE: </strong>Use {@link #onTextClassifierEvent(TextClassifierEvent)} instead. + * <p> * Reports a selection event. * * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. */ - default void onSelectionEvent(@NonNull SelectionEvent event) {} + default void onSelectionEvent(@NonNull SelectionEvent event) { + // TODO: Consider rerouting to onTextClassifierEvent() + } + + /** + * Reports a text classifier event. + * <p> + * <strong>NOTE: </strong>Call on a worker thread. + * + * @throws IllegalStateException if this TextClassifier has been destroyed. + * @see #isDestroyed() + */ + default void onTextClassifierEvent(@NonNull TextClassifierEvent event) {} /** * Destroys this TextClassifier. diff --git a/core/java/android/view/textclassifier/TextClassifierEvent.aidl b/core/java/android/view/textclassifier/TextClassifierEvent.aidl new file mode 100644 index 000000000000..86fa4f887de4 --- /dev/null +++ b/core/java/android/view/textclassifier/TextClassifierEvent.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.textclassifier; + +parcelable TextClassifierEvent; diff --git a/core/java/android/view/textclassifier/TextClassifierEvent.java b/core/java/android/view/textclassifier/TextClassifierEvent.java new file mode 100644 index 000000000000..3bb9ee88dae9 --- /dev/null +++ b/core/java/android/view/textclassifier/TextClassifierEvent.java @@ -0,0 +1,501 @@ +/* + * 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.textclassifier; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A text classifier event. + */ +// TODO: Comprehensive javadoc. +public final class TextClassifierEvent implements Parcelable { + + public static final Creator<TextClassifierEvent> CREATOR = new Creator<TextClassifierEvent>() { + @Override + public TextClassifierEvent createFromParcel(Parcel in) { + return readFromParcel(in); + } + + @Override + public TextClassifierEvent[] newArray(int size) { + return new TextClassifierEvent[size]; + } + }; + + /** @hide **/ + @Retention(RetentionPolicy.SOURCE) + @IntDef({CATEGORY_UNDEFINED, CATEGORY_SELECTION, CATEGORY_LINKIFY, + CATEGORY_CONVERSATION_ACTIONS, CATEGORY_LANGUAGE_DETECTION}) + public @interface Category { + // For custom event categories, use range 1000+. + } + /** Undefined category */ + public static final int CATEGORY_UNDEFINED = 0; + /** Smart selection */ + public static final int CATEGORY_SELECTION = 1; + /** Linkify */ + public static final int CATEGORY_LINKIFY = 2; + /** Conversation actions */ + public static final int CATEGORY_CONVERSATION_ACTIONS = 3; + /** Language detection */ + public static final int CATEGORY_LANGUAGE_DETECTION = 4; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_UNDEFINED, TYPE_SELECTION_STARTED, TYPE_SELECTION_MODIFIED, + TYPE_SMART_SELECTION_SINGLE, TYPE_SMART_SELECTION_MULTI, TYPE_AUTO_SELECTION, + TYPE_ACTIONS_SHOWN, TYPE_LINK_CLICKED, TYPE_OVERTYPE, TYPE_COPY_ACTION, + TYPE_PASTE_ACTION, TYPE_CUT_ACTION, TYPE_SHARE_ACTION, TYPE_SMART_ACTION, + TYPE_SELECTION_DRAG, TYPE_SELECTION_DESTROYED, TYPE_OTHER_ACTION, TYPE_SELECT_ALL, + TYPE_SELECTION_RESET, TYPE_MANUAL_REPLY}) + public @interface Type { + // For custom event types, use range 1,000,000+. + } + /** User started a new selection. */ + public static final int TYPE_UNDEFINED = 0; + /** User started a new selection. */ + public static final int TYPE_SELECTION_STARTED = 1; + /** User modified an existing selection. */ + public static final int TYPE_SELECTION_MODIFIED = 2; + /** Smart selection triggered for a single token (word). */ + public static final int TYPE_SMART_SELECTION_SINGLE = 3; + /** Smart selection triggered spanning multiple tokens (words). */ + public static final int TYPE_SMART_SELECTION_MULTI = 4; + /** Something else other than user or the default TextClassifier triggered a selection. */ + public static final int TYPE_AUTO_SELECTION = 5; + /** Smart actions shown to the user. */ + public static final int TYPE_ACTIONS_SHOWN = 6; + /** User clicked a link. */ + public static final int TYPE_LINK_CLICKED = 7; + /** User typed over the selection. */ + public static final int TYPE_OVERTYPE = 8; + /** User clicked on Copy action. */ + public static final int TYPE_COPY_ACTION = 9; + /** User clicked on Paste action. */ + public static final int TYPE_PASTE_ACTION = 10; + /** User clicked on Cut action. */ + public static final int TYPE_CUT_ACTION = 11; + /** User clicked on Share action. */ + public static final int TYPE_SHARE_ACTION = 12; + /** User clicked on a Smart action. */ + public static final int TYPE_SMART_ACTION = 13; + /** User dragged+dropped the selection. */ + public static final int TYPE_SELECTION_DRAG = 14; + /** Selection is destroyed. */ + public static final int TYPE_SELECTION_DESTROYED = 15; + /** User clicked on a custom action. */ + public static final int TYPE_OTHER_ACTION = 16; + /** User clicked on Select All action */ + public static final int TYPE_SELECT_ALL = 17; + /** User reset the smart selection. */ + public static final int TYPE_SELECTION_RESET = 18; + /** User composed a reply. */ + public static final int TYPE_MANUAL_REPLY = 19; + + @Category private final int mEventCategory; + @Type private final int mEventType; + @Nullable private final String mEntityType; + @Nullable private final TextClassificationContext mEventContext; + @Nullable private final String mResultId; + private final int mEventIndex; + private final long mEventTime; + private final Bundle mExtras; + + // Smart selection. + private final int mRelativeWordStartIndex; + private final int mRelativeWordEndIndex; + private final int mRelativeSuggestedWordStartIndex; + private final int mRelativeSuggestedWordEndIndex; + + // Smart action. + private final int[] mActionIndices; + + // Language detection. + @Nullable private final String mLanguage; + + private TextClassifierEvent( + int eventCategory, + int eventType, + String entityType, + TextClassificationContext eventContext, + String resultId, + int eventIndex, + long eventTime, + Bundle extras, + int relativeWordStartIndex, + int relativeWordEndIndex, + int relativeSuggestedWordStartIndex, + int relativeSuggestedWordEndIndex, + int[] actionIndex, + String language) { + mEventCategory = eventCategory; + mEventType = eventType; + mEntityType = entityType; + mEventContext = eventContext; + mResultId = resultId; + mEventIndex = eventIndex; + mEventTime = eventTime; + mExtras = extras; + mRelativeWordStartIndex = relativeWordStartIndex; + mRelativeWordEndIndex = relativeWordEndIndex; + mRelativeSuggestedWordStartIndex = relativeSuggestedWordStartIndex; + mRelativeSuggestedWordEndIndex = relativeSuggestedWordEndIndex; + mActionIndices = actionIndex; + mLanguage = language; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mEventCategory); + dest.writeInt(mEventType); + dest.writeString(mEntityType); + dest.writeParcelable(mEventContext, flags); + dest.writeString(mResultId); + dest.writeInt(mEventIndex); + dest.writeLong(mEventTime); + dest.writeBundle(mExtras); + dest.writeInt(mRelativeWordStartIndex); + dest.writeInt(mRelativeWordEndIndex); + dest.writeInt(mRelativeSuggestedWordStartIndex); + dest.writeInt(mRelativeSuggestedWordEndIndex); + dest.writeIntArray(mActionIndices); + dest.writeString(mLanguage); + } + + private static TextClassifierEvent readFromParcel(Parcel in) { + return new TextClassifierEvent( + /* eventCategory= */ in.readInt(), + /* eventType= */ in.readInt(), + /* entityType= */ in.readString(), + /* eventContext= */ in.readParcelable(null), + /* resultId= */ in.readString(), + /* eventIndex= */ in.readInt(), + /* eventTime= */ in.readLong(), + /* extras= */ in.readBundle(), + /* relativeWordStartIndex= */ in.readInt(), + /* relativeWordEndIndex= */ in.readInt(), + /* relativeSuggestedWordStartIndex= */ in.readInt(), + /* relativeSuggestedWordEndIndex= */ in.readInt(), + /* actionIndices= */ in.createIntArray(), + /* language= */ in.readString()); + } + + /** + * Returns the event category. e.g. {@link #CATEGORY_SELECTION}. + */ + @Category + public int getEventCategory() { + return mEventCategory; + } + + /** + * Returns the event type. e.g. {@link #TYPE_SELECTION_STARTED}. + */ + @Type + public int getEventType() { + return mEventType; + } + + /** + * Returns the entity type. e.g. {@link TextClassifier#TYPE_ADDRESS}. + */ + @Nullable + public String getEntityType() { + return mEntityType; + } + + /** + * Returns the event context. + */ + @Nullable + public TextClassificationContext getEventContext() { + return mEventContext; + } + + /** + * Returns the id of the text classifier result related to this event. + */ + @Nullable + public String getResultId() { + return mResultId; + } + + /** + * Returns the index of this event in the series of event it belongs to. + */ + public int getEventIndex() { + return mEventIndex; + } + + /** + * Returns the time this event occurred. This is the number of milliseconds since + * January 1, 1970, 00:00:00 GMT. 0 indicates not set. + */ + public long getEventTime() { + return mEventTime; + } + + /** + * Returns a bundle containing non-structured extra information about this event. + * + * <p><b>NOTE: </b>Do not modify this bundle. + */ + @NonNull + public Bundle getExtras() { + return mExtras; + } + + /** + * For smart selection. Returns the relative word index of the start of the selection. + */ + public int getRelativeWordStartIndex() { + return mRelativeWordStartIndex; + } + + /** + * For smart selection. Returns the relative word (exclusive) index of the end of the selection. + */ + public int getRelativeWordEndIndex() { + return mRelativeWordEndIndex; + } + + /** + * For smart selection. Returns the relative word index of the start of the smart selection. + */ + public int getRelativeSuggestedWordStartIndex() { + return mRelativeSuggestedWordStartIndex; + } + + /** + * For smart selection. Returns the relative word (exclusive) index of the end of the + * smart selection. + */ + public int getRelativeSuggestedWordEndIndex() { + return mRelativeSuggestedWordEndIndex; + } + + /** + * Returns the indices of the actions relating to this event. + * Actions are usually returned by the text classifier in priority order with the most + * preferred action at index 0. This list gives an indication of the position of the actions + * that are being reported. + */ + @NonNull + public int[] getActionIndices() { + return mActionIndices; + } + + /** + * For language detection. Returns the language tag for the detected locale. + * @see java.util.Locale#forLanguageTag(String). + */ + @Nullable + public String getLanguage() { + return mLanguage; + } + + /** + * Builder to build a text classifier event. + */ + public static final class Builder { + + private final int mEventCategory; + private final int mEventType; + @Nullable private String mEntityType; + @Nullable private TextClassificationContext mEventContext; + @Nullable private String mResultId; + private int mEventIndex; + private long mEventTime; + @Nullable private Bundle mExtras; + private int mRelativeWordStartIndex; + private int mRelativeWordEndIndex; + private int mRelativeSuggestedWordStartIndex; + private int mRelativeSuggestedWordEndIndex; + private int[] mActionIndices = new int[0]; + @Nullable private String mLanguage; + + /** + * Creates a builder for building {@link TextClassifierEvent}s. + * + * @param eventCategory The event category. e.g. {@link #CATEGORY_SELECTION} + * @param eventType The event type. e.g. {@link #TYPE_SELECTION_STARTED} + */ + public Builder(@Category int eventCategory, @Type int eventType) { + mEventCategory = eventCategory; + mEventType = eventType; + } + + /** + * Sets the entity type. e.g. {@link TextClassifier#TYPE_ADDRESS}. + */ + @NonNull + public Builder setEntityType(@Nullable String entityType) { + mEntityType = entityType; + return this; + } + + /** + * Sets the event context. + */ + @NonNull + public Builder setEventContext(@Nullable TextClassificationContext eventContext) { + mEventContext = eventContext; + return this; + } + + /** + * Sets the id of the text classifier result related to this event. + */ + @NonNull + public Builder setResultId(@Nullable String resultId) { + mResultId = resultId; + return this; + } + + /** + * Sets the index of this events in the series of events it belongs to. + */ + @NonNull + public Builder setEventIndex(int eventIndex) { + mEventIndex = eventIndex; + return this; + } + + /** + * Sets the time this event occurred. This is the number of milliseconds since + * January 1, 1970, 00:00:00 GMT. 0 indicates not set. + */ + @NonNull + public Builder setEventTime(long eventTime) { + mEventTime = eventTime; + return this; + } + + /** + * Sets a bundle containing non-structured extra information about the event. + * + * <p><b>NOTE: </b>Prefer to set only immutable values on the bundle otherwise, avoid + * updating the internals of this bundle as it may have unexpected consequences on the + * clients of the built event object. For similar reasons, avoid depending on mutable + * objects in this bundle. + */ + @NonNull + public Builder setExtras(@NonNull Bundle extras) { + mExtras = Preconditions.checkNotNull(extras); + return this; + } + + /** + * For smart selection. Sets the relative word index of the start of the selection. + */ + @NonNull + public Builder setRelativeWordStartIndex(int relativeWordStartIndex) { + mRelativeWordStartIndex = relativeWordStartIndex; + return this; + } + + /** + * For smart selection. Sets the relative word (exclusive) index of the end of the + * selection. + */ + @NonNull + public Builder setRelativeWordEndIndex(int relativeWordEndIndex) { + mRelativeWordEndIndex = relativeWordEndIndex; + return this; + } + + /** + * For smart selection. Sets the relative word index of the start of the smart selection. + */ + @NonNull + public Builder setRelativeSuggestedWordStartIndex(int relativeSuggestedWordStartIndex) { + mRelativeSuggestedWordStartIndex = relativeSuggestedWordStartIndex; + return this; + } + + /** + * For smart selection. Sets the relative word (exclusive) index of the end of the + * smart selection. + */ + @NonNull + public Builder setRelativeSuggestedWordEndIndex(int relativeSuggestedWordEndIndex) { + mRelativeSuggestedWordEndIndex = relativeSuggestedWordEndIndex; + return this; + } + + /** + * Sets the indices of the actions involved in this event. Actions are usually returned by + * the text classifier in priority order with the most preferred action at index 0. + * This index gives an indication of the position of the action that is being reported. + */ + @NonNull + public Builder setActionIndices(@NonNull int... actionIndices) { + mActionIndices = new int[actionIndices.length]; + System.arraycopy(actionIndices, 0, mActionIndices, 0, actionIndices.length); + return this; + } + + /** + * For language detection. Sets the language tag for the detected locale. + * @see java.util.Locale#forLanguageTag(String). + */ + @NonNull + public Builder setLanguage(@Nullable String language) { + mLanguage = language; + return this; + } + + /** + * Builds and returns a text classifier event. + */ + @NonNull + public TextClassifierEvent build() { + mExtras = mExtras == null ? Bundle.EMPTY : mExtras; + return new TextClassifierEvent( + mEventCategory, + mEventType, + mEntityType, + mEventContext, + mResultId, + mEventIndex, + mEventTime, + mExtras, + mRelativeWordStartIndex, + mRelativeWordEndIndex, + mRelativeSuggestedWordStartIndex, + mRelativeSuggestedWordEndIndex, + mActionIndices, + mLanguage); + } + // TODO: Add build(boolean validate). + } +} diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index deda92646e71..9b0f9c6ee5e8 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -341,6 +341,11 @@ public final class TextClassifierImpl implements TextClassifier { } } + @Override + public void onTextClassifierEvent(@NonNull TextClassifierEvent event) { + // TODO: Implement. + } + /** @inheritDoc */ @Override public TextLanguage detectLanguage(@NonNull TextLanguage.Request request) { @@ -387,7 +392,10 @@ public final class TextClassifierImpl implements TextClassifier { Collection<String> expectedTypes = resolveActionTypesFromRequest(request); List<ConversationActions.ConversationAction> conversationActions = new ArrayList<>(); - int maxSuggestions = Math.min(request.getMaxSuggestions(), nativeSuggestions.length); + int maxSuggestions = nativeSuggestions.length; + if (request.getMaxSuggestions() > 0) { + maxSuggestions = Math.min(request.getMaxSuggestions(), nativeSuggestions.length); + } for (int i = 0; i < maxSuggestions; i++) { ActionsSuggestionsModel.ActionSuggestion nativeSuggestion = nativeSuggestions[i]; String actionType = nativeSuggestion.getActionType(); @@ -400,7 +408,7 @@ public final class TextClassifierImpl implements TextClassifier { .setConfidenceScore(nativeSuggestion.getScore()) .build()); } - return new ConversationActions(conversationActions); + return new ConversationActions(conversationActions, /*id*/ null); } catch (Throwable t) { // Avoid throwing from this method. Log the error. Log.e(LOG_TAG, "Error suggesting conversation actions.", t); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 90da81276ba3..ddf119097027 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -719,7 +719,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @ViewDebug.ExportedProperty(category = "text") @UnsupportedAppUsage private int mGravity = Gravity.TOP | Gravity.START; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) private boolean mHorizontallyScrolling; private int mAutoLinkMask; @@ -5095,13 +5095,24 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Returns whether the text is allowed to be wider than the View is. + * Returns whether the text is allowed to be wider than the View. + * If false, the text will be wrapped to the width of the View. + * + * @attr ref android.R.styleable#TextView_scrollHorizontally + * @see #setHorizontallyScrolling(boolean) + */ + public final boolean isHorizontallyScrolling() { + return mHorizontallyScrolling; + } + + /** + * Returns whether the text is allowed to be wider than the View. * If false, the text will be wrapped to the width of the View. * * @attr ref android.R.styleable#TextView_scrollHorizontally * @hide */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public boolean getHorizontallyScrolling() { return mHorizontallyScrolling; } diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java index 5465485790be..051a96c39f28 100644 --- a/core/java/com/android/internal/os/BinderCallsStats.java +++ b/core/java/com/android/internal/os/BinderCallsStats.java @@ -80,7 +80,8 @@ public class BinderCallsStats implements BinderInternal.Observer { private final Queue<CallSession> mCallSessionsPool = new ConcurrentLinkedQueue<>(); private final Object mLock = new Object(); private final Random mRandom; - private long mStartTime = System.currentTimeMillis(); + private long mStartCurrentTime = System.currentTimeMillis(); + private long mStartElapsedTime = SystemClock.elapsedRealtime(); private long mCallStatsCount = 0; private boolean mAddDebugEntries = false; @@ -329,8 +330,8 @@ public class BinderCallsStats implements BinderInternal.Observer { // Debug entries added to help validate the data. if (mAddDebugEntries && mBatteryStopwatch != null) { - resultCallStats.add(createDebugEntry("start_time_millis", mStartTime)); - resultCallStats.add(createDebugEntry("end_time_millis", System.currentTimeMillis())); + resultCallStats.add(createDebugEntry("start_time_millis", mStartElapsedTime)); + resultCallStats.add(createDebugEntry("end_time_millis", SystemClock.elapsedRealtime())); resultCallStats.add( createDebugEntry("battery_time_millis", mBatteryStopwatch.getMillis())); } @@ -370,7 +371,7 @@ public class BinderCallsStats implements BinderInternal.Observer { long totalRecordedCallsCount = 0; long totalCpuTime = 0; pw.print("Start time: "); - pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStartTime)); + pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStartCurrentTime)); pw.print("On battery time (ms): "); pw.println(mBatteryStopwatch != null ? mBatteryStopwatch.getMillis() : 0); pw.println("Sampling interval period: " + mPeriodicSamplingInterval); @@ -520,7 +521,8 @@ public class BinderCallsStats implements BinderInternal.Observer { mCallStatsCount = 0; mUidEntries.clear(); mExceptionCounts.clear(); - mStartTime = System.currentTimeMillis(); + mStartCurrentTime = System.currentTimeMillis(); + mStartElapsedTime = SystemClock.elapsedRealtime(); if (mBatteryStopwatch != null) { mBatteryStopwatch.reset(); } diff --git a/core/java/com/android/internal/os/LooperStats.java b/core/java/com/android/internal/os/LooperStats.java index 01fd8ba58c74..9a7fb9f2717b 100644 --- a/core/java/com/android/internal/os/LooperStats.java +++ b/core/java/com/android/internal/os/LooperStats.java @@ -50,7 +50,8 @@ public class LooperStats implements Looper.Observer { private int mSamplingInterval; private CachedDeviceState.Readonly mDeviceState; private CachedDeviceState.TimeInStateStopwatch mBatteryStopwatch; - private long mStartTime = System.currentTimeMillis(); + private long mStartCurrentTime = System.currentTimeMillis(); + private long mStartElapsedTime = SystemClock.elapsedRealtime(); private boolean mAddDebugEntries = false; public LooperStats(int samplingInterval, int entriesSizeCap) { @@ -155,8 +156,8 @@ public class LooperStats implements Looper.Observer { maybeAddSpecialEntry(exportedEntries, mHashCollisionEntry); // Debug entries added to help validate the data. if (mAddDebugEntries && mBatteryStopwatch != null) { - exportedEntries.add(createDebugEntry("start_time_millis", mStartTime)); - exportedEntries.add(createDebugEntry("end_time_millis", System.currentTimeMillis())); + exportedEntries.add(createDebugEntry("start_time_millis", mStartElapsedTime)); + exportedEntries.add(createDebugEntry("end_time_millis", SystemClock.elapsedRealtime())); exportedEntries.add( createDebugEntry("battery_time_millis", mBatteryStopwatch.getMillis())); } @@ -173,7 +174,11 @@ public class LooperStats implements Looper.Observer { /** Returns a timestamp indicating when the statistics were last reset. */ public long getStartTimeMillis() { - return mStartTime; + return mStartCurrentTime; + } + + public long getStartElapsedTimeMillis() { + return mStartElapsedTime; } public long getBatteryTimeMillis() { @@ -199,7 +204,8 @@ public class LooperStats implements Looper.Observer { synchronized (mOverflowEntry) { mOverflowEntry.reset(); } - mStartTime = System.currentTimeMillis(); + mStartCurrentTime = System.currentTimeMillis(); + mStartElapsedTime = SystemClock.elapsedRealtime(); if (mBatteryStopwatch != null) { mBatteryStopwatch.reset(); } diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index f669e94c1779..226b8db1540b 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -24,6 +24,7 @@ import dalvik.system.VMRuntime; import libcore.util.EmptyArray; +import java.io.File; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; @@ -42,6 +43,8 @@ public class ArrayUtils { private static final int CACHE_SIZE = 73; private static Object[] sCache = new Object[CACHE_SIZE]; + public static final File[] EMPTY_FILE = new File[0]; + private ArrayUtils() { /* cannot be instantiated */ } public static byte[] newUnpaddedByteArray(int minLen) { @@ -645,6 +648,10 @@ public class ArrayUtils { return (val != null) ? val : EmptyArray.STRING; } + public static @NonNull File[] defeatNullable(@Nullable File[] val) { + return (val != null) ? val : EMPTY_FILE; + } + /** * Throws {@link ArrayIndexOutOfBoundsException} if the index is out of bounds. * diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java index c8834a889333..ae5c67df8f1e 100644 --- a/core/java/com/android/internal/view/BaseIWindow.java +++ b/core/java/com/android/internal/view/BaseIWindow.java @@ -26,9 +26,9 @@ import android.view.DisplayCutout; 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 android.view.PointerIcon; import com.android.internal.os.IResultReceiver; @@ -76,7 +76,7 @@ public class BaseIWindow extends IWindow.Stub { } @Override - public void windowFocusChanged(boolean hasFocus, boolean touchEnabled, boolean reportToClient) { + public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) { } @Override diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index df24bc4d2abc..e97c9bc911dd 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -577,7 +577,7 @@ namespace PaintGlue { } } - static SkScalar getMetricsInternal(jlong paintHandle, Paint::FontMetrics *metrics) { + static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics *metrics) { const int kElegantTop = 2500; const int kElegantBottom = -1000; const int kElegantAscent = 1900; @@ -609,7 +609,7 @@ namespace PaintGlue { } static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) { - Paint::FontMetrics metrics; + SkFontMetrics metrics; SkScalar spacing = getMetricsInternal(paintHandle, &metrics); if (metricsObj) { @@ -624,7 +624,7 @@ namespace PaintGlue { } static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) { - Paint::FontMetrics metrics; + SkFontMetrics metrics; getMetricsInternal(paintHandle, &metrics); int ascent = SkScalarRoundToInt(metrics.fAscent); @@ -970,19 +970,19 @@ namespace PaintGlue { } static jfloat ascent(jlong paintHandle) { - Paint::FontMetrics metrics; + SkFontMetrics metrics; getMetricsInternal(paintHandle, &metrics); return SkScalarToFloat(metrics.fAscent); } static jfloat descent(jlong paintHandle) { - Paint::FontMetrics metrics; + SkFontMetrics metrics; getMetricsInternal(paintHandle, &metrics); return SkScalarToFloat(metrics.fDescent); } static jfloat getUnderlinePosition(jlong paintHandle) { - Paint::FontMetrics metrics; + SkFontMetrics metrics; getMetricsInternal(paintHandle, &metrics); SkScalar position; if (metrics.hasUnderlinePosition(&position)) { @@ -994,7 +994,7 @@ namespace PaintGlue { } static jfloat getUnderlineThickness(jlong paintHandle) { - Paint::FontMetrics metrics; + SkFontMetrics metrics; getMetricsInternal(paintHandle, &metrics); SkScalar thickness; if (metrics.hasUnderlineThickness(&thickness)) { diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index 7fe3be870994..0ec8c1ada47e 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -220,6 +220,13 @@ message ConstantsProto { // will use heartbeats, false will use a rolling window. optional bool use_heartbeats = 23; + message TimeController { + // Whether or not TimeController should skip setting wakeup alarms for jobs that aren't + // ready now. + optional bool skip_not_ready_jobs = 1; + } + optional TimeController time_controller = 25; + message QuotaController { // How much time each app will have to run jobs within their standby bucket window. optional int64 allowed_time_per_period_ms = 1; @@ -242,8 +249,12 @@ message ConstantsProto { // expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past // WINDOW_SIZE_MS. optional int64 rare_window_size_ms = 6; + // The maximum amount of time an app can have its jobs running within a 24 hour window. + optional int64 max_execution_time_ms = 7; } optional QuotaController quota_controller = 24; + + // Next tag: 26 } message StateControllerProto { diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto index c7a6b68fbc00..82460ec4ed8b 100644 --- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto +++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto @@ -141,4 +141,7 @@ enum EventId { PM_UNINSTALL = 113; WIFI_SERVICE_ADD_NETWORK_SUGGESTIONS = 114; WIFI_SERVICE_ADD_OR_UPDATE_NETWORK = 115; + QUERY_SUMMARY_FOR_DEVICE = 116; + REMOVE_CROSS_PROFILE_WIDGET_PROVIDER = 117; + ESTABLISH_VPN = 118; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index dca15bd49ca4..7fa3e66bae25 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4342,6 +4342,10 @@ @hide --> <permission android:name="android.permission.AMBIENT_WALLPAPER" android:protectionLevel="signature|preinstalled" /> + <!-- @SystemApi Allows sensor privacy to be modified. + @hide --> + <permission android:name="android.permission.MANAGE_SENSOR_PRIVACY" + android:protectionLevel="signature" /> <application android:process="system" android:persistent="true" diff --git a/core/res/res/values-night/themes_device_defaults.xml b/core/res/res/values-night/themes_device_defaults.xml index 84c6446e81ce..43b3552ff8d4 100644 --- a/core/res/res/values-night/themes_device_defaults.xml +++ b/core/res/res/values-night/themes_device_defaults.xml @@ -59,4 +59,6 @@ easier. <!-- Theme for the dialog shown when an app crashes or ANRs. --> <style name="Theme.DeviceDefault.Dialog.AppError" parent="Theme.DeviceDefault.Dialog.Alert" /> -</resources> + + <style name="Theme.DeviceDefault.DayNight" parent="Theme.DeviceDefault" /> +</resources>
\ No newline at end of file diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 8dbea3896217..91faa5517860 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -7632,103 +7632,143 @@ <declare-styleable name="VoiceInteractionSession"> </declare-styleable> + <!-- {@deprecated Copy this definition into your own application project.} --> <declare-styleable name="KeyboardView"> - <!-- Default KeyboardView style. --> + <!-- Default KeyboardView style. + {@deprecated Copy this definition into your own application project.} --> <attr name="keyboardViewStyle" format="reference" /> <!-- Image for the key. This image needs to be a StateListDrawable, with the following possible states: normal, pressed, checkable, checkable+pressed, checkable+checked, - checkable+checked+pressed. --> + checkable+checked+pressed. + {@deprecated Copy this definition into your own application project.} --> <attr name="keyBackground" format="reference" /> - <!-- Size of the text for character keys. --> + <!-- Size of the text for character keys. + {@deprecated Copy this definition into your own application project.} --> <attr name="keyTextSize" format="dimension" /> - <!-- Size of the text for custom keys with some text and no icon. --> + <!-- Size of the text for custom keys with some text and no icon. + {@deprecated Copy this definition into your own application project.} --> <attr name="labelTextSize" format="dimension" /> - <!-- Color to use for the label in a key. --> + <!-- Color to use for the label in a key. + {@deprecated Copy this definition into your own application project.} --> <attr name="keyTextColor" format="color" /> - <!-- Layout resource for key press feedback.--> + <!-- Layout resource for key press feedback. + {@deprecated Copy this definition into your own application project.} --> <attr name="keyPreviewLayout" format="reference" /> - <!-- Vertical offset of the key press feedback from the key. --> + <!-- Vertical offset of the key press feedback from the key. + {@deprecated Copy this definition into your own application project.} --> <attr name="keyPreviewOffset" format="dimension" /> - <!-- Height of the key press feedback popup. --> + <!-- Height of the key press feedback popup. + {@deprecated Copy this definition into your own application project.} --> <attr name="keyPreviewHeight" format="dimension" /> - <!-- Amount to offset the touch Y coordinate by, for bias correction. --> + <!-- Amount to offset the touch Y coordinate by, for bias correction. + {@deprecated Copy this definition into your own application project.} --> <attr name="verticalCorrection" format="dimension" /> - <!-- Layout resource for popup keyboards. --> + <!-- Layout resource for popup keyboards. + {@deprecated Copy this definition into your own application project.} --> <attr name="popupLayout" format="reference" /> + <!-- {@deprecated Copy this definition into your own application project.} --> <attr name="shadowColor" /> + <!-- {@deprecated Copy this definition into your own application project.} --> <attr name="shadowRadius" /> </declare-styleable> + <!-- {@deprecated Copy this definition into your own application project.} --> <declare-styleable name="KeyboardViewPreviewState"> <!-- State for {@link android.inputmethodservice.KeyboardView KeyboardView} - key preview background. --> + key preview background. + {@deprecated Copy this definition into your own application project.} --> <attr name="state_long_pressable" format="boolean" /> </declare-styleable> + <!-- {@deprecated Copy this definition into your own application project.} --> <declare-styleable name="Keyboard"> - <!-- Default width of a key, in pixels or percentage of display width. --> + <!-- Default width of a key, in pixels or percentage of display width. + {@deprecated Copy this definition into your own application project.} --> <attr name="keyWidth" format="dimension|fraction" /> - <!-- Default height of a key, in pixels or percentage of display width. --> + <!-- Default height of a key, in pixels or percentage of display width. + {@deprecated Copy this definition into your own application project.} --> <attr name="keyHeight" format="dimension|fraction" /> - <!-- Default horizontal gap between keys. --> + <!-- Default horizontal gap between keys. + {@deprecated Copy this definition into your own application project.} --> <attr name="horizontalGap" format="dimension|fraction" /> - <!-- Default vertical gap between rows of keys. --> + <!-- Default vertical gap between rows of keys. + {@deprecated Copy this definition into your own application project.} --> <attr name="verticalGap" format="dimension|fraction" /> </declare-styleable> + <!-- {@deprecated Copy this definition into your own application project.} --> <declare-styleable name="Keyboard_Row"> - <!-- Row edge flags. --> + <!-- Row edge flags. + {@deprecated Copy this definition into your own application project.} --> <attr name="rowEdgeFlags"> - <!-- Row is anchored to the top of the keyboard. --> + <!-- Row is anchored to the top of the keyboard. + {@deprecated Copy this definition into your own application project.} --> <flag name="top" value="4" /> - <!-- Row is anchored to the bottom of the keyboard. --> + <!-- Row is anchored to the bottom of the keyboard. + {@deprecated Copy this definition into your own application project.} --> <flag name="bottom" value="8" /> </attr> <!-- Mode of the keyboard. If the mode doesn't match the - requested keyboard mode, the row will be skipped. --> + requested keyboard mode, the row will be skipped. + {@deprecated Copy this definition into your own application project.} --> <attr name="keyboardMode" format="reference" /> </declare-styleable> + <!-- {@deprecated Copy this definition into your own application project.} --> <declare-styleable name="Keyboard_Key"> - <!-- The unicode value or comma-separated values that this key outputs. --> + <!-- The unicode value or comma-separated values that this key outputs. + {@deprecated Copy this definition into your own application project.} --> <attr name="codes" format="integer|string" /> - <!-- The XML keyboard layout of any popup keyboard. --> + <!-- The XML keyboard layout of any popup keyboard. + {@deprecated Copy this definition into your own application project.} --> <attr name="popupKeyboard" format="reference" /> - <!-- The characters to display in the popup keyboard. --> + <!-- The characters to display in the popup keyboard. + {@deprecated Copy this definition into your own application project.} --> <attr name="popupCharacters" format="string" /> - <!-- Key edge flags. --> + <!-- Key edge flags. + {@deprecated Copy this definition into your own application project.} --> <attr name="keyEdgeFlags"> - <!-- Key is anchored to the left of the keyboard. --> + <!-- Key is anchored to the left of the keyboard. + {@deprecated Copy this definition into your own application project.} --> <flag name="left" value="1" /> - <!-- Key is anchored to the right of the keyboard. --> + <!-- Key is anchored to the right of the keyboard. + {@deprecated Copy this definition into your own application project.} --> <flag name="right" value="2" /> </attr> - <!-- Whether this is a modifier key such as Alt or Shift. --> + <!-- Whether this is a modifier key such as Alt or Shift. + {@deprecated Copy this definition into your own application project.} --> <attr name="isModifier" format="boolean" /> - <!-- Whether this is a toggle key. --> + <!-- Whether this is a toggle key. + {@deprecated Copy this definition into your own application project.} --> <attr name="isSticky" format="boolean" /> - <!-- Whether long-pressing on this key will make it repeat. --> + <!-- Whether long-pressing on this key will make it repeat. + {@deprecated Copy this definition into your own application project.} --> <attr name="isRepeatable" format="boolean" /> - <!-- The icon to show in the popup preview. --> + <!-- The icon to show in the popup preview. + {@deprecated Copy this definition into your own application project.} --> <attr name="iconPreview" format="reference" /> - <!-- The string of characters to output when this key is pressed. --> + <!-- The string of characters to output when this key is pressed. + {@deprecated Copy this definition into your own application project.} --> <attr name="keyOutputText" format="string" /> - <!-- The label to display on the key. --> + <!-- The label to display on the key. + {@deprecated Copy this definition into your own application project.} --> <attr name="keyLabel" format="string" /> - <!-- The icon to display on the key instead of the label. --> + <!-- The icon to display on the key instead of the label. + {@deprecated Copy this definition into your own application project.} --> <attr name="keyIcon" format="reference" /> <!-- Mode of the keyboard. If the mode doesn't match the - requested keyboard mode, the key will be skipped. --> + requested keyboard mode, the key will be skipped. + {@deprecated Copy this definition into your own application project.} --> <attr name="keyboardMode" /> </declare-styleable> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 35263a3fa891..dd51cb615d8a 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1294,6 +1294,20 @@ supports any size. --> <attr name="maxAspectRatio" format="float" /> + <!-- This value indicates the minimum aspect ratio the activity supports. If the app runs on a + device with a narrower aspect ratio, the system automatically letterboxes the app, leaving + portions of the screen unused so the app can run at its specified minimum aspect ratio. + <p> + Minimum aspect ratio, expressed as (longer dimension / shorter dimension) in decimal + form. For example, if the minimum aspect ratio is 4:3, set value to 1.33. + <p> + Value needs to be greater or equal to 1.0, otherwise it is ignored. + <p> + NOTE: This attribute is ignored if the activity has + {@link android.R.attr#resizeableActivity} set to true, since that means your activity + supports any size. --> + <attr name="minAspectRatio" format="float" /> + <!-- This value indicates how tasks rooted at this activity will behave in lockTask mode. While in lockTask mode the system will not launch non-permitted tasks until lockTask mode is disabled. @@ -1571,6 +1585,7 @@ <attr name="directBootAware" /> <attr name="resizeableActivity" /> <attr name="maxAspectRatio" /> + <attr name="minAspectRatio" /> <attr name="networkSecurityConfig" /> <!-- Declare the category of this app. Categories are used to cluster multiple apps together into meaningful groups, such as when summarizing battery, network, or @@ -2386,6 +2401,7 @@ <attr name="resizeableActivity" /> <attr name="supportsPictureInPicture" /> <attr name="maxAspectRatio" /> + <attr name="minAspectRatio" /> <attr name="lockTaskMode" /> <attr name="showForAllUsers" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 97a21a55f67f..ecff83558215 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2019,6 +2019,10 @@ This is intended to allow packaging drivers or tools for installation on a PC. --> <string translatable="false" name="config_isoImagePath"></string> + <!-- Whether the system enables per-display focus. If the system has the input method for each + display, this value should be true. --> + <bool name="config_perDisplayFocusEnabled">false</bool> + <!-- Whether a software navigation bar should be shown. NOTE: in the future this may be autodetected from the Configuration. --> <bool name="config_showNavigationBar">false</bool> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index eac8b4854994..6f75d90c46c4 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1067,7 +1067,8 @@ <public type="id" name="inputExtractEditText" id="0x01020025" /> <!-- View ID of the {@link android.inputmethodservice.KeyboardView} within - an input method's input area. --> + an input method's input area. + {@deprecated Use Copy this definition into your own application project.} --> <public type="id" name="keyboardView" id="0x01020026" /> <!-- View ID of a {@link android.view.View} to close a popup keyboard --> <public type="id" name="closeButton" id="0x01020027" /> @@ -1082,6 +1083,7 @@ <public type="style" name="Theme.InputMethod" id="0x01030054" /> <public type="style" name="Theme.NoDisplay" id="0x01030055" /> <public type="style" name="Animation.InputMethod" id="0x01030056" /> + <!-- {@deprecated Use Copy this definition into your own application project.} --> <public type="style" name="Widget.KeyboardView" id="0x01030057" /> <public type="style" name="ButtonBar" id="0x01030058" /> <public type="style" name="Theme.Panel" id="0x01030059" /> @@ -2931,6 +2933,7 @@ <public name="selectionDividerHeight" /> <public name="foregroundServiceType" /> <public name="hasFragileUserData" /> + <public name="minAspectRatio" /> </public-group> <public-group type="drawable" first-id="0x010800b4"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index cab01f9c027f..f25427ad86fe 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3200,6 +3200,8 @@ <!-- [CHAR LIMIT=40] Title of dialog that is shown when system is starting. --> <string name="android_start_title" product="default">Phone is starting\u2026</string> <!-- [CHAR LIMIT=40] Title of dialog that is shown when system is starting. --> + <string name="android_start_title" product="automotive">Android is starting\u2026</string> + <!-- [CHAR LIMIT=40] Title of dialog that is shown when system is starting. --> <string name="android_start_title" product="tablet">Tablet is starting\u2026</string> <!-- [CHAR LIMIT=40] Title of dialog that is shown when system is starting. --> <string name="android_start_title" product="device">Device is starting\u2026</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 161e41681486..f44976ec21e9 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1670,6 +1670,7 @@ <java-symbol type="bool" name="config_lockDayNightMode" /> <java-symbol type="bool" name="config_lockUiMode" /> <java-symbol type="bool" name="config_reverseDefaultRotation" /> + <java-symbol type="bool" name="config_perDisplayFocusEnabled" /> <java-symbol type="bool" name="config_showNavigationBar" /> <java-symbol type="bool" name="config_supportAutoRotation" /> <java-symbol type="bool" name="target_honeycomb_needs_options_menu" /> diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index fec101a6fff3..c0c677a0b496 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -1646,8 +1646,10 @@ easier. <style name="Theme.DeviceDefault.Settings.Dialog.NoActionBar" parent="Theme.DeviceDefault.Light.Dialog.NoActionBar" /> + <style name="Theme.DeviceDefault.DayNight" parent="Theme.DeviceDefault.Light" /> + <!-- Theme used for the intent picker activity. --> - <style name="Theme.DeviceDefault.Resolver" parent="Theme.Material.Light"> + <style name="Theme.DeviceDefault.Resolver" parent="Theme.DeviceDefault.DayNight"> <item name="windowEnterTransition">@empty</item> <item name="windowExitTransition">@empty</item> <item name="windowIsTranslucent">true</item> @@ -1659,30 +1661,8 @@ easier. <item name="colorControlActivated">?attr/colorControlHighlight</item> <item name="listPreferredItemPaddingStart">?attr/dialogPreferredPadding</item> <item name="listPreferredItemPaddingEnd">?attr/dialogPreferredPadding</item> - - <!-- Dialog attributes --> - <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> - <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> - - <!-- Button styles --> - <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item> - <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> - - <!-- Color palette --> - <item name="colorPrimary">@color/primary_device_default_light</item> - <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> - <item name="colorAccent">@color/accent_device_default_light</item> - <item name="colorError">@color/error_color_device_default_light</item> - - <!-- Progress bar attributes --> - <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item> - <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item> - - <!-- Toolbar attributes --> - <item name="toolbarStyle">@style/Widget.DeviceDefault.Toolbar</item> </style> - <!-- @hide DeviceDefault themes for the autofill FillUi --> <style name="Theme.DeviceDefault.Autofill" /> <style name="Theme.DeviceDefault.Light.Autofill" /> diff --git a/core/tests/coretests/src/android/os/OsTests.java b/core/tests/coretests/src/android/os/OsTests.java index 985fa4f3cfc8..2b841269e5ae 100644 --- a/core/tests/coretests/src/android/os/OsTests.java +++ b/core/tests/coretests/src/android/os/OsTests.java @@ -33,7 +33,6 @@ public class OsTests { suite.addTestSuite(MessageQueueTest.class); suite.addTestSuite(MessengerTest.class); suite.addTestSuite(PatternMatcherTest.class); - suite.addTestSuite(SystemPropertiesTest.class); return suite; } diff --git a/core/tests/coretests/src/android/os/SystemPropertiesTest.java b/core/tests/coretests/src/android/os/SystemPropertiesTest.java deleted file mode 100644 index 25868ce0b702..000000000000 --- a/core/tests/coretests/src/android/os/SystemPropertiesTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2006 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.os; - -import static junit.framework.Assert.assertEquals; -import junit.framework.TestCase; - -import android.os.SystemProperties; -import android.test.suitebuilder.annotation.SmallTest; - -public class SystemPropertiesTest extends TestCase { - private static final String KEY = "com.android.frameworks.coretests"; - @SmallTest - public void testProperties() throws Exception { - if (false) { - String value; - - SystemProperties.set(KEY, ""); - value = SystemProperties.get(KEY, "default"); - assertEquals("default", value); - - SystemProperties.set(KEY, "AAA"); - value = SystemProperties.get(KEY, "default"); - assertEquals("AAA", value); - - value = SystemProperties.get(KEY); - assertEquals("AAA", value); - - SystemProperties.set(KEY, ""); - value = SystemProperties.get(KEY, "default"); - assertEquals("default", value); - - value = SystemProperties.get(KEY); - assertEquals("", value); - } - } -} diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 3a37fb627b26..6d1aae12d858 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -543,7 +543,9 @@ public class SettingsBackupTest { Settings.Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED, Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS, Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS, - Settings.Global.BACKUP_MULTI_USER_ENABLED); + Settings.Global.BACKUP_MULTI_USER_ENABLED, + Settings.Global.ISOLATED_STORAGE_LOCAL, + Settings.Global.ISOLATED_STORAGE_REMOTE); private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS = newHashSet( Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, @@ -584,6 +586,7 @@ public class SettingsBackupTest { Settings.Secure.DEFAULT_INPUT_METHOD, Settings.Secure.DEVICE_PAIRED, Settings.Secure.DIALER_DEFAULT_APPLICATION, + Settings.Secure.DISABLE_AIRPLANE_MODE_AFTER_SP_DISABLED, Settings.Secure.DISABLED_PRINT_SERVICES, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, Settings.Secure.DISPLAY_DENSITY_FORCED, @@ -605,6 +608,8 @@ public class SettingsBackupTest { Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT, // Candidate? Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, + Settings.Secure.MAINTAIN_AIRPLANE_MODE_AFTER_SP_DISABLED, + Settings.Secure.MAINTAIN_LOCATION_AFTER_SP_DISABLED, Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, Settings.Secure.MULTI_PRESS_TIMEOUT, Settings.Secure.NFC_PAYMENT_FOREGROUND, @@ -616,6 +621,7 @@ public class SettingsBackupTest { Settings.Secure.PARENTAL_CONTROL_LAST_UPDATE, Settings.Secure.PAYMENT_SERVICE_SEARCH_URI, Settings.Secure.PRINT_SERVICE_SEARCH_URI, + Settings.Secure.REENABLE_LOCATION_AFTER_SP_DISABLED, Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT, // Candidate? Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY, Settings.Secure.SEARCH_MAX_RESULTS_PER_SOURCE, @@ -639,6 +645,7 @@ public class SettingsBackupTest { Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, Settings.Secure.SELECTED_SPELL_CHECKER, // Intentionally removed in Q Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, // Intentionally removed in Q + Settings.Secure.SENSOR_PRIVACY_SENSOR_STATE, Settings.Secure.SETTINGS_CLASSNAME, Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, // candidate? Settings.Secure.SHOW_ROTATION_SUGGESTIONS, diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java index 9b5c0347bdb6..81ec85eb2ea5 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java @@ -18,7 +18,6 @@ package android.view.textclassifier; import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -375,7 +374,7 @@ public class TextClassifierTest { ConversationActions.Message message = new ConversationActions.Message.Builder( ConversationActions.Message.PERSON_USER_REMOTE) - .setText("Hello") + .setText("Where are you?") .build(); ConversationActions.TypeConfig typeConfig = new ConversationActions.TypeConfig.Builder().includeTypesFromTextClassifier(false) @@ -384,19 +383,44 @@ public class TextClassifierTest { .build(); ConversationActions.Request request = new ConversationActions.Request.Builder(Collections.singletonList(message)) - .setMaxSuggestions(3) + .setMaxSuggestions(1) .setTypeConfig(typeConfig) .build(); ConversationActions conversationActions = mClassifier.suggestConversationActions(request); assertTrue(conversationActions.getConversationActions().size() > 0); - assertTrue(conversationActions.getConversationActions().size() <= 3); + assertTrue(conversationActions.getConversationActions().size() == 1); for (ConversationActions.ConversationAction conversationAction : conversationActions.getConversationActions()) { - assertEquals(conversationAction.getType(), ConversationActions.TYPE_TEXT_REPLY); - assertNotNull(conversationAction.getTextReply()); - assertTrue(conversationAction.getConfidenceScore() > 0); - assertTrue(conversationAction.getConfidenceScore() <= 1); + assertThat(conversationAction, + isConversationAction(ConversationActions.TYPE_TEXT_REPLY)); + } + } + + @Test + public void testSuggestConversationActions_textReplyOnly_noMax() { + if (isTextClassifierDisabled()) return; + ConversationActions.Message message = + new ConversationActions.Message.Builder( + ConversationActions.Message.PERSON_USER_REMOTE) + .setText("Where are you?") + .build(); + ConversationActions.TypeConfig typeConfig = + new ConversationActions.TypeConfig.Builder().includeTypesFromTextClassifier(false) + .setIncludedTypes( + Collections.singletonList(ConversationActions.TYPE_TEXT_REPLY)) + .build(); + ConversationActions.Request request = + new ConversationActions.Request.Builder(Collections.singletonList(message)) + .setTypeConfig(typeConfig) + .build(); + + ConversationActions conversationActions = mClassifier.suggestConversationActions(request); + assertTrue(conversationActions.getConversationActions().size() > 1); + for (ConversationActions.ConversationAction conversationAction : + conversationActions.getConversationActions()) { + assertThat(conversationAction, + isConversationAction(ConversationActions.TYPE_TEXT_REPLY)); } } @@ -498,4 +522,36 @@ public class TextClassifierTest { } }; } + + private static Matcher<ConversationActions.ConversationAction> isConversationAction( + String actionType) { + return new BaseMatcher<ConversationActions.ConversationAction>() { + @Override + public boolean matches(Object o) { + if (!(o instanceof ConversationActions.ConversationAction)) { + return false; + } + ConversationActions.ConversationAction conversationAction = + (ConversationActions.ConversationAction) o; + if (!actionType.equals(conversationAction.getType())) { + return false; + } + if (ConversationActions.TYPE_TEXT_REPLY.equals(actionType)) { + if (conversationAction.getTextReply() == null) { + return false; + } + } + if (conversationAction.getConfidenceScore() < 0 + || conversationAction.getConfidenceScore() > 1) { + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("actionType=").appendValue(actionType); + } + }; + } } 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 97f02cbc27e9..dc3a12f0bbf5 100644 --- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import android.os.Binder; +import android.os.SystemClock; import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -637,7 +638,7 @@ public class BinderCallsStatsTest { @Test public void testAddsDebugEntries() { - long startTime = System.currentTimeMillis(); + long startTime = SystemClock.elapsedRealtime(); TestBinderCallsStats bcs = new TestBinderCallsStats(); bcs.setAddDebugEntries(true); ArrayList<BinderCallsStats.ExportedCallStat> callStats = bcs.getExportedCallStats(); diff --git a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java index f26dfad0b037..b65c1e6210a7 100644 --- a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java @@ -442,11 +442,13 @@ public final class LooperStatsTest { LooperStats.ExportedEntry debugEntry1 = entries.get(1); assertThat(debugEntry1.handlerClassName).isEqualTo(""); assertThat(debugEntry1.messageName).isEqualTo("__DEBUG_start_time_millis"); - assertThat(debugEntry1.totalLatencyMicros).isEqualTo(looperStats.getStartTimeMillis()); + assertThat(debugEntry1.totalLatencyMicros).isEqualTo( + looperStats.getStartElapsedTimeMillis()); LooperStats.ExportedEntry debugEntry2 = entries.get(2); assertThat(debugEntry2.handlerClassName).isEqualTo(""); assertThat(debugEntry2.messageName).isEqualTo("__DEBUG_end_time_millis"); - assertThat(debugEntry2.totalLatencyMicros).isAtLeast(looperStats.getStartTimeMillis()); + assertThat(debugEntry2.totalLatencyMicros).isAtLeast( + looperStats.getStartElapsedTimeMillis()); LooperStats.ExportedEntry debugEntry3 = entries.get(3); assertThat(debugEntry3.handlerClassName).isEqualTo(""); assertThat(debugEntry3.messageName).isEqualTo("__DEBUG_battery_time_millis"); diff --git a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java index 933e54e840c5..928351e7de8c 100644 --- a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java +++ b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java @@ -16,13 +16,13 @@ package android.os; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; +import android.test.suitebuilder.annotation.SmallTest; import junit.framework.TestCase; -import android.os.SystemProperties; -import android.test.suitebuilder.annotation.SmallTest; +import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; public class SystemPropertiesTest extends TestCase { private static final String KEY = "sys.testkey"; @@ -188,4 +188,25 @@ public class SystemPropertiesTest extends TestCase { fail("InterruptedException"); } } + + @SmallTest + public void testDigestOf() { + final String empty = SystemProperties.digestOf(); + final String finger = SystemProperties.digestOf("ro.build.fingerprint"); + final String fingerBrand = SystemProperties.digestOf( + "ro.build.fingerprint", "ro.product.brand"); + final String brandFinger = SystemProperties.digestOf( + "ro.product.brand", "ro.build.fingerprint"); + + // Shouldn't change over time + assertTrue(Objects.equals(finger, SystemProperties.digestOf("ro.build.fingerprint"))); + + // Different properties means different results + assertFalse(Objects.equals(empty, finger)); + assertFalse(Objects.equals(empty, fingerBrand)); + assertFalse(Objects.equals(finger, fingerBrand)); + + // Same properties means same result + assertTrue(Objects.equals(fingerBrand, brandFinger)); + } } diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index dcf95fdbb438..540f749f857c 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -386,6 +386,7 @@ applications that come with the platform <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.MANAGE_ACTIVITY_STACKS"/> <permission name="android.permission.MANAGE_DEBUGGING"/> + <permission name="android.permission.MANAGE_SENSOR_PRIVACY"/> <permission name="android.permission.MANAGE_USB"/> <permission name="android.permission.MANAGE_USERS"/> <permission name="android.permission.MASTER_CLEAR"/> diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index d22eaf38cef6..0503f36ec074 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -60,6 +60,7 @@ cc_defaults { "libutils", "libEGL", "libGLESv2", + "libGLESv3", "libvulkan", "libui", "libgui", diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp index 0b9d82b105a3..ed167e57158e 100644 --- a/libs/hwui/DeviceInfo.cpp +++ b/libs/hwui/DeviceInfo.cpp @@ -20,6 +20,7 @@ #include <gui/ISurfaceComposer.h> #include <gui/SurfaceComposerClient.h> +#include <ui/GraphicTypes.h> #include <mutex> #include <thread> @@ -61,6 +62,50 @@ DisplayInfo QueryDisplayInfo() { return displayInfo; } +static void queryWideColorGamutPreference(SkColorSpace::Gamut* colorGamut, + sk_sp<SkColorSpace>* colorSpace, SkColorType* colorType) { + if (Properties::isolatedProcess) { + *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut; + *colorSpace = SkColorSpace::MakeSRGB(); + *colorType = SkColorType::kN32_SkColorType; + return; + } + ui::Dataspace defaultDataspace, wcgDataspace; + ui::PixelFormat defaultPixelFormat, wcgPixelFormat; + status_t status = + SurfaceComposerClient::getCompositionPreference(&defaultDataspace, &defaultPixelFormat, + &wcgDataspace, &wcgPixelFormat); + LOG_ALWAYS_FATAL_IF(status, "Failed to get composition preference, error %d", status); + switch (wcgDataspace) { + case ui::Dataspace::DISPLAY_P3: + *colorGamut = SkColorSpace::Gamut::kDCIP3_D65_Gamut; + *colorSpace = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, + SkColorSpace::Gamut::kDCIP3_D65_Gamut); + break; + case ui::Dataspace::V0_SCRGB: + *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut; + *colorSpace = SkColorSpace::MakeSRGB(); + break; + case ui::Dataspace::V0_SRGB: + // when sRGB is returned, it means wide color gamut is not supported. + *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut; + *colorSpace = SkColorSpace::MakeSRGB(); + break; + default: + LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space."); + } + switch (wcgPixelFormat) { + case ui::PixelFormat::RGBA_8888: + *colorType = SkColorType::kN32_SkColorType; + break; + case ui::PixelFormat::RGBA_FP16: + *colorType = SkColorType::kRGBA_F16_SkColorType; + break; + default: + LOG_ALWAYS_FATAL("Unreachable: unsupported pixel format."); + } +} + DeviceInfo::DeviceInfo() { #if HWUI_NULL_GPU mMaxTextureSize = NULL_GPU_MAX_TEXTURE_SIZE; @@ -68,6 +113,7 @@ DeviceInfo::DeviceInfo() { mMaxTextureSize = -1; #endif mDisplayInfo = QueryDisplayInfo(); + queryWideColorGamutPreference(&mWideColorGamut, &mWideColorSpace, &mWideColorType); } int DeviceInfo::maxTextureSize() const { diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h index 595621573e6e..9bcc8e8a3dbe 100644 --- a/libs/hwui/DeviceInfo.h +++ b/libs/hwui/DeviceInfo.h @@ -16,6 +16,7 @@ #ifndef DEVICEINFO_H #define DEVICEINFO_H +#include <SkImageInfo.h> #include <ui/DisplayInfo.h> #include "utils/Macros.h" @@ -37,6 +38,9 @@ public: // context or if you are using the HWUI_NULL_GPU int maxTextureSize() const; const DisplayInfo& displayInfo() const { return mDisplayInfo; } + SkColorSpace::Gamut getWideColorGamut() const { return mWideColorGamut; } + sk_sp<SkColorSpace> getWideColorSpace() const { return mWideColorSpace; } + SkColorType getWideColorType() const { return mWideColorType; } private: friend class renderthread::RenderThread; @@ -46,6 +50,9 @@ private: int mMaxTextureSize; DisplayInfo mDisplayInfo; + SkColorSpace::Gamut mWideColorGamut; + sk_sp<SkColorSpace> mWideColorSpace; + SkColorType mWideColorType; }; } /* namespace uirenderer */ diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index e2ea2bc37375..a09da6bf359e 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -52,7 +52,7 @@ void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& const SkScalar left = x; const SkScalar right = x + length; if (flags & SkPaint::kUnderlineText_ReserveFlag) { - Paint::FontMetrics metrics; + SkFontMetrics metrics; paint.getFontMetrics(&metrics); SkScalar position; if (!metrics.hasUnderlinePosition(&position)) { diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index 07979a22c988..4338b1cc2a21 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -162,22 +162,17 @@ bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh mEglSurface = EGL_NO_SURFACE; } + setSurfaceColorProperties(colorMode); + if (surface) { mRenderThread.requireGlContext(); - auto newSurface = mEglManager.createSurface(surface, colorMode); + auto newSurface = mEglManager.createSurface(surface, colorMode, mSurfaceColorGamut); if (!newSurface) { return false; } mEglSurface = newSurface.unwrap(); } - if (colorMode == ColorMode::SRGB) { - mSurfaceColorType = SkColorType::kN32_SkColorType; - } else if (colorMode == ColorMode::WideColorGamut) { - mSurfaceColorType = SkColorType::kRGBA_F16_SkColorType; - } - mSurfaceColorSpace = SkColorSpace::MakeSRGB(); - if (mEglSurface != EGL_NO_SURFACE) { const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer); mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer); diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 7a255c15bf5f..7f62ab5abb7d 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -17,6 +17,7 @@ #include "SkiaPipeline.h" #include <SkImageEncoder.h> +#include <SkImageInfo.h> #include <SkImagePriv.h> #include <SkOverdrawCanvas.h> #include <SkOverdrawColorFilter.h> @@ -453,6 +454,20 @@ void SkiaPipeline::dumpResourceCacheUsage() const { ALOGD("%s", log.c_str()); } +void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { + if (colorMode == ColorMode::SRGB) { + mSurfaceColorType = SkColorType::kN32_SkColorType; + mSurfaceColorGamut = SkColorSpace::Gamut::kSRGB_Gamut; + mSurfaceColorSpace = SkColorSpace::MakeSRGB(); + } else if (colorMode == ColorMode::WideColorGamut) { + mSurfaceColorType = DeviceInfo::get()->getWideColorType(); + mSurfaceColorGamut = DeviceInfo::get()->getWideColorGamut(); + mSurfaceColorSpace = DeviceInfo::get()->getWideColorSpace(); + } else { + LOG_ALWAYS_FATAL("Unreachable: unsupported color mode."); + } +} + // Overdraw debugging // These colors should be kept in sync with Caches::getOverdrawColor() with a few differences. diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index 42a411a6808c..af58f634ecd0 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -107,9 +107,11 @@ public: protected: void dumpResourceCacheUsage() const; + void setSurfaceColorProperties(renderthread::ColorMode colorMode); renderthread::RenderThread& mRenderThread; SkColorType mSurfaceColorType; + SkColorSpace::Gamut mSurfaceColorGamut; sk_sp<SkColorSpace> mSurfaceColorSpace; private: diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index 437b5dc83f58..65ae0ddeccf3 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -122,15 +122,10 @@ bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh mVkSurface = nullptr; } - mSurfaceColorSpace = SkColorSpace::MakeSRGB(); + setSurfaceColorProperties(colorMode); if (surface) { - mVkSurface = mVkManager.createSurface(surface, colorMode, mSurfaceColorSpace); - } - - if (colorMode == ColorMode::SRGB) { - mSurfaceColorType = SkColorType::kN32_SkColorType; - } else if (colorMode == ColorMode::WideColorGamut) { - mSurfaceColorType = SkColorType::kRGBA_F16_SkColorType; + mVkSurface = mVkManager.createSurface(surface, colorMode, mSurfaceColorSpace, + mSurfaceColorGamut, mSurfaceColorType); } return mVkSurface != nullptr; diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 8230dfd44f9a..56eedff4a6e6 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -89,7 +89,8 @@ EglManager::EglManager() , mEglConfigWideGamut(nullptr) , mEglContext(EGL_NO_CONTEXT) , mPBufferSurface(EGL_NO_SURFACE) - , mCurrentSurface(EGL_NO_SURFACE) {} + , mCurrentSurface(EGL_NO_SURFACE) + , mHasWideColorGamutSupport(false) {} EglManager::~EglManager() { destroy(); @@ -128,6 +129,81 @@ void EglManager::initialize() { createContext(); createPBufferSurface(); makeCurrent(mPBufferSurface, nullptr, /* force */ true); + + SkColorSpace::Gamut wideColorGamut = DeviceInfo::get()->getWideColorGamut(); + bool hasWideColorSpaceExtension = false; + if (wideColorGamut == SkColorSpace::Gamut::kDCIP3_D65_Gamut) { + hasWideColorSpaceExtension = EglExtensions.displayP3; + } else if (wideColorGamut == SkColorSpace::Gamut::kSRGB_Gamut) { + hasWideColorSpaceExtension = EglExtensions.scRGB; + } else { + LOG_ALWAYS_FATAL("Unsupported wide color space."); + } + mHasWideColorGamutSupport = EglExtensions.glColorSpace && hasWideColorSpaceExtension && + mEglConfigWideGamut != EGL_NO_CONFIG_KHR; +} + +EGLConfig EglManager::load8BitsConfig(EGLDisplay display, EglManager::SwapBehavior swapBehavior) { + EGLint eglSwapBehavior = + (swapBehavior == SwapBehavior::Preserved) ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; + EGLint attribs[] = {EGL_RENDERABLE_TYPE, + EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_ALPHA_SIZE, + 8, + EGL_DEPTH_SIZE, + 0, + EGL_CONFIG_CAVEAT, + EGL_NONE, + EGL_STENCIL_SIZE, + STENCIL_BUFFER_SIZE, + EGL_SURFACE_TYPE, + EGL_WINDOW_BIT | eglSwapBehavior, + EGL_NONE}; + EGLConfig config = EGL_NO_CONFIG_KHR; + EGLint numConfigs = 1; + if (!eglChooseConfig(display, attribs, &config, numConfigs, &numConfigs) || + numConfigs != 1) { + return EGL_NO_CONFIG_KHR; + } + return config; +} + +EGLConfig EglManager::loadFP16Config(EGLDisplay display, SwapBehavior swapBehavior) { + EGLint eglSwapBehavior = + (swapBehavior == SwapBehavior::Preserved) ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; + // If we reached this point, we have a valid swap behavior + EGLint attribs[] = {EGL_RENDERABLE_TYPE, + EGL_OPENGL_ES2_BIT, + EGL_COLOR_COMPONENT_TYPE_EXT, + EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT, + EGL_RED_SIZE, + 16, + EGL_GREEN_SIZE, + 16, + EGL_BLUE_SIZE, + 16, + EGL_ALPHA_SIZE, + 16, + EGL_DEPTH_SIZE, + 0, + EGL_STENCIL_SIZE, + STENCIL_BUFFER_SIZE, + EGL_SURFACE_TYPE, + EGL_WINDOW_BIT | eglSwapBehavior, + EGL_NONE}; + EGLConfig config = EGL_NO_CONFIG_KHR; + EGLint numConfigs = 1; + if (!eglChooseConfig(display, attribs, &config, numConfigs, &numConfigs) || + numConfigs != 1) { + return EGL_NO_CONFIG_KHR; + } + return config; } void EglManager::initExtensions() { @@ -146,12 +222,8 @@ void EglManager::initExtensions() { EglExtensions.glColorSpace = extensions.has("EGL_KHR_gl_colorspace"); EglExtensions.noConfigContext = extensions.has("EGL_KHR_no_config_context"); EglExtensions.pixelFormatFloat = extensions.has("EGL_EXT_pixel_format_float"); -#ifdef ANDROID_ENABLE_LINEAR_BLENDING - EglExtensions.scRGB = extensions.has("EGL_EXT_gl_colorspace_scrgb_linear"); -#else EglExtensions.scRGB = extensions.has("EGL_EXT_gl_colorspace_scrgb"); -#endif - EglExtensions.displayP3 = extensions.has("EGL_EXT_gl_colorspace_display_p3"); + EglExtensions.displayP3 = extensions.has("EGL_EXT_gl_colorspace_display_p3_passthrough"); EglExtensions.contextPriority = extensions.has("EGL_IMG_context_priority"); EglExtensions.surfacelessContext = extensions.has("EGL_KHR_surfaceless_context"); } @@ -162,77 +234,35 @@ bool EglManager::hasEglContext() { void EglManager::loadConfigs() { ALOGD("Swap behavior %d", static_cast<int>(mSwapBehavior)); - EGLint swapBehavior = - (mSwapBehavior == SwapBehavior::Preserved) ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; // Note: The default pixel format is RGBA_8888, when other formats are // available, we should check the target pixel format and configure the // attributes list properly. - EGLint attribs[] = {EGL_RENDERABLE_TYPE, - EGL_OPENGL_ES2_BIT, - EGL_RED_SIZE, - 8, - EGL_GREEN_SIZE, - 8, - EGL_BLUE_SIZE, - 8, - EGL_ALPHA_SIZE, - 8, - EGL_DEPTH_SIZE, - 0, - EGL_CONFIG_CAVEAT, - EGL_NONE, - EGL_STENCIL_SIZE, - STENCIL_BUFFER_SIZE, - EGL_SURFACE_TYPE, - EGL_WINDOW_BIT | swapBehavior, - EGL_NONE}; - - EGLint numConfigs = 1; - if (!eglChooseConfig(mEglDisplay, attribs, &mEglConfig, numConfigs, &numConfigs) || - numConfigs != 1) { + mEglConfig = load8BitsConfig(mEglDisplay, mSwapBehavior); + if (mEglConfig == EGL_NO_CONFIG_KHR) { if (mSwapBehavior == SwapBehavior::Preserved) { // Try again without dirty regions enabled ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without..."); mSwapBehavior = SwapBehavior::Discard; - loadConfigs(); - return; // the call to loadConfigs() we just made picks the wide gamut config + ALOGD("Swap behavior %d", static_cast<int>(mSwapBehavior)); + mEglConfig = load8BitsConfig(mEglDisplay, mSwapBehavior); } else { // Failed to get a valid config LOG_ALWAYS_FATAL("Failed to choose config, error = %s", eglErrorString()); } } + SkColorType wideColorType = DeviceInfo::get()->getWideColorType(); - if (EglExtensions.pixelFormatFloat) { - // If we reached this point, we have a valid swap behavior - EGLint attribs16F[] = {EGL_RENDERABLE_TYPE, - EGL_OPENGL_ES2_BIT, - EGL_COLOR_COMPONENT_TYPE_EXT, - EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT, - EGL_RED_SIZE, - 16, - EGL_GREEN_SIZE, - 16, - EGL_BLUE_SIZE, - 16, - EGL_ALPHA_SIZE, - 16, - EGL_DEPTH_SIZE, - 0, - EGL_STENCIL_SIZE, - STENCIL_BUFFER_SIZE, - EGL_SURFACE_TYPE, - EGL_WINDOW_BIT | swapBehavior, - EGL_NONE}; - - numConfigs = 1; - if (!eglChooseConfig(mEglDisplay, attribs16F, &mEglConfigWideGamut, numConfigs, - &numConfigs) || - numConfigs != 1) { + // When we reach this point, we have a valid swap behavior + if (wideColorType == SkColorType::kRGBA_F16_SkColorType && EglExtensions.pixelFormatFloat) { + mEglConfigWideGamut = loadFP16Config(mEglDisplay, mSwapBehavior); + if (mEglConfigWideGamut == EGL_NO_CONFIG_KHR) { ALOGE("Device claims wide gamut support, cannot find matching config, error = %s", eglErrorString()); EglExtensions.pixelFormatFloat = false; } + } else if (wideColorType == SkColorType::kN32_SkColorType) { + mEglConfigWideGamut = load8BitsConfig(mEglDisplay, mSwapBehavior); } } @@ -263,11 +293,12 @@ void EglManager::createPBufferSurface() { } } -Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, ColorMode colorMode) { +Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, + ColorMode colorMode, + SkColorSpace::Gamut colorGamut) { LOG_ALWAYS_FATAL_IF(!hasEglContext(), "Not initialized"); - bool wideColorGamut = colorMode == ColorMode::WideColorGamut && EglExtensions.glColorSpace && - EglExtensions.scRGB && EglExtensions.pixelFormatFloat && + bool wideColorGamut = colorMode == ColorMode::WideColorGamut && mHasWideColorGamutSupport && EglExtensions.noConfigContext; // The color space we want to use depends on whether linear blending is turned @@ -285,8 +316,8 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, // When wide gamut rendering is on we cannot rely on the GPU performing // linear blending for us. We use two different color spaces to tag the // surface appropriately for SurfaceFlinger: - // - Gamma blending (default) requires the use of the scRGB-nl color space - // - Linear blending requires the use of the scRGB color space + // - Gamma blending (default) requires the use of the non-linear color space + // - Linear blending requires the use of the linear color space // Not all Android targets support the EGL_GL_COLORSPACE_KHR extension // We insert to placeholders to set EGL_GL_COLORSPACE_KHR and its value. @@ -296,19 +327,20 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, if (EglExtensions.glColorSpace) { attribs[0] = EGL_GL_COLORSPACE_KHR; -#ifdef ANDROID_ENABLE_LINEAR_BLENDING - if (wideColorGamut) { - attribs[1] = EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT; - } else { - attribs[1] = EGL_GL_COLORSPACE_SRGB_KHR; - } -#else if (wideColorGamut) { - attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT; + switch (colorGamut) { + case SkColorSpace::Gamut::kDCIP3_D65_Gamut: + attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT; + break; + case SkColorSpace::Gamut::kSRGB_Gamut: + attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT; + break; + default: + LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space."); + } } else { attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR; } -#endif } EGLSurface surface = eglCreateWindowSurface( diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h index 2a44f7e10b80..4dd90961b4f7 100644 --- a/libs/hwui/renderthread/EglManager.h +++ b/libs/hwui/renderthread/EglManager.h @@ -48,7 +48,8 @@ public: bool hasEglContext(); - Result<EGLSurface, EGLint> createSurface(EGLNativeWindowType window, ColorMode colorMode); + Result<EGLSurface, EGLint> createSurface(EGLNativeWindowType window, ColorMode colorMode, + SkColorSpace::Gamut colorGamut); void destroySurface(EGLSurface surface); void destroy(); @@ -80,6 +81,14 @@ public: status_t createReleaseFence(bool useFenceSync, EGLSyncKHR* eglFence, sp<Fence>& nativeFence); private: + enum class SwapBehavior { + Discard, + Preserved, + BufferAge, + }; + + static EGLConfig load8BitsConfig(EGLDisplay display, SwapBehavior swapBehavior); + static EGLConfig loadFP16Config(EGLDisplay display, SwapBehavior swapBehavior); void initExtensions(); void createPBufferSurface(); @@ -93,12 +102,7 @@ private: EGLContext mEglContext; EGLSurface mPBufferSurface; EGLSurface mCurrentSurface; - - enum class SwapBehavior { - Discard, - Preserved, - BufferAge, - }; + bool mHasWideColorGamutSupport; SwapBehavior mSwapBehavior = SwapBehavior::Discard; }; diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 4be8bd9a863e..aa7a141f6da3 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -473,8 +473,10 @@ SkSurface* VulkanManager::getBackbufferSurface(VulkanSurface** surfaceOut) { if (windowWidth != surface->mWindowWidth || windowHeight != surface->mWindowHeight) { ColorMode colorMode = surface->mColorMode; sk_sp<SkColorSpace> colorSpace = surface->mColorSpace; + SkColorSpace::Gamut colorGamut = surface->mColorGamut; + SkColorType colorType = surface->mColorType; destroySurface(surface); - *surfaceOut = createSurface(window, colorMode, colorSpace); + *surfaceOut = createSurface(window, colorMode, colorSpace, colorGamut, colorType); surface = *surfaceOut; } @@ -647,8 +649,7 @@ void VulkanManager::createBuffers(VulkanSurface* surface, VkFormat format, VkExt VulkanSurface::ImageInfo& imageInfo = surface->mImageInfos[i]; imageInfo.mSurface = SkSurface::MakeFromBackendRenderTarget( mRenderThread.getGrContext(), backendRT, kTopLeft_GrSurfaceOrigin, - surface->mColorMode == ColorMode::WideColorGamut ? kRGBA_F16_SkColorType - : kRGBA_8888_SkColorType, surface->mColorSpace, &props); + surface->mColorType, surface->mColorSpace, &props); } SkASSERT(mCommandPool != VK_NULL_HANDLE); @@ -767,10 +768,20 @@ bool VulkanManager::createSwapchain(VulkanSurface* surface) { VkFormat surfaceFormat = VK_FORMAT_R8G8B8A8_UNORM; VkColorSpaceKHR colorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; - if (surface->mColorMode == ColorMode::WideColorGamut) { + if (surface->mColorType == SkColorType::kRGBA_F16_SkColorType) { surfaceFormat = VK_FORMAT_R16G16B16A16_SFLOAT; - colorSpace = VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT; } + + if (surface->mColorMode == ColorMode::WideColorGamut) { + if (surface->mColorGamut == SkColorSpace::Gamut::kSRGB_Gamut) { + colorSpace = VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT; + } else if (surface->mColorGamut == SkColorSpace::Gamut::kDCIP3_D65_Gamut) { + colorSpace = VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT; + } else { + LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space."); + } + } + bool foundSurfaceFormat = false; for (uint32_t i = 0; i < surfaceFormatCount; ++i) { if (surfaceFormat == surfaceFormats[i].format @@ -840,14 +851,17 @@ bool VulkanManager::createSwapchain(VulkanSurface* surface) { } VulkanSurface* VulkanManager::createSurface(ANativeWindow* window, ColorMode colorMode, - sk_sp<SkColorSpace> surfaceColorSpace) { + sk_sp<SkColorSpace> surfaceColorSpace, + SkColorSpace::Gamut surfaceColorGamut, + SkColorType surfaceColorType) { initialize(); if (!window) { return nullptr; } - VulkanSurface* surface = new VulkanSurface(colorMode, window, surfaceColorSpace); + VulkanSurface* surface = new VulkanSurface(colorMode, window, surfaceColorSpace, + surfaceColorGamut, surfaceColorType); VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo; memset(&surfaceCreateInfo, 0, sizeof(VkAndroidSurfaceCreateInfoKHR)); diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h index d67d2c81e95c..69ca23acb1fd 100644 --- a/libs/hwui/renderthread/VulkanManager.h +++ b/libs/hwui/renderthread/VulkanManager.h @@ -38,8 +38,10 @@ class RenderThread; class VulkanSurface { public: - VulkanSurface(ColorMode colorMode, ANativeWindow* window, sk_sp<SkColorSpace> colorSpace) - : mColorMode(colorMode), mNativeWindow(window), mColorSpace(colorSpace) {} + VulkanSurface(ColorMode colorMode, ANativeWindow* window, sk_sp<SkColorSpace> colorSpace, + SkColorSpace::Gamut colorGamut, SkColorType colorType) + : mColorMode(colorMode), mNativeWindow(window), mColorSpace(colorSpace), + mColorGamut(colorGamut), mColorType(colorType) {} sk_sp<SkSurface> getBackBufferSurface() { return mBackbuffer; } @@ -80,6 +82,8 @@ private: int mWindowWidth = 0; int mWindowHeight = 0; sk_sp<SkColorSpace> mColorSpace; + SkColorSpace::Gamut mColorGamut; + SkColorType mColorType; }; // This class contains the shared global Vulkan objects, such as VkInstance, VkDevice and VkQueue, @@ -98,7 +102,9 @@ 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, - sk_sp<SkColorSpace> surfaceColorSpace); + sk_sp<SkColorSpace> surfaceColorSpace, + SkColorSpace::Gamut surfaceColorGamut, + SkColorType surfaceColorType); // Destroy the VulkanSurface and all associated vulkan objects. void destroySurface(VulkanSurface* surface); diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp index a6073ebb5c74..75fb0ef0acc1 100644 --- a/libs/hwui/tests/unit/RenderNodeTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeTests.cpp @@ -306,6 +306,7 @@ RENDERTHREAD_TEST(RenderNode, prepareTree_HwLayer_AVD_enqueueDamage) { ContextFactory contextFactory; std::unique_ptr<CanvasContext> canvasContext( CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory)); + canvasContext->setSurface(nullptr); TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get()); DamageAccumulator damageAccumulator; LayerUpdateQueue layerUpdateQueue; diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 32c752064a69..05d49e592747 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -88,6 +88,10 @@ interface ILocationManager boolean providerMeetsCriteria(String provider, in Criteria criteria); ProviderProperties getProviderProperties(String provider); String getNetworkProviderPackage(); + void setLocationControllerExtraPackage(String packageName); + String getLocationControllerExtraPackage(); + void setLocationControllerExtraPackageEnabled(boolean enabled); + boolean isLocationControllerExtraPackageEnabled(); boolean isProviderEnabledForUser(String provider, int userId); boolean setProviderEnabledForUser(String provider, boolean enabled, int userId); diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 334170e5ce03..1cd3d86a0c27 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -2396,4 +2396,65 @@ public class LocationManager { return null; } } + + /** + * Set the extra location controller package for location services on the device. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.LOCATION_HARDWARE) + public void setLocationControllerExtraPackage(String packageName) { + try { + mService.setLocationControllerExtraPackage(packageName); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Returns the extra location controller package on the device. + * + * @hide + */ + @SystemApi + public @Nullable String getLocationControllerExtraPackage() { + try { + return mService.getLocationControllerExtraPackage(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return null; + } + } + + /** + * Set whether the extra location controller package is currently enabled on the device. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.LOCATION_HARDWARE) + public void setLocationControllerExtraPackageEnabled(boolean enabled) { + try { + mService.setLocationControllerExtraPackageEnabled(enabled); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Returns whether extra location controller package is currently enabled on the device. + * + * @hide + */ + @SystemApi + public boolean isLocationControllerExtraPackageEnabled() { + try { + return mService.isLocationControllerExtraPackageEnabled(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return false; + } + } + } diff --git a/media/java/android/media/DataSourceCallback.java b/media/java/android/media/DataSourceCallback.java index 0d4d53106c78..1afcd2075ba4 100644 --- a/media/java/android/media/DataSourceCallback.java +++ b/media/java/android/media/DataSourceCallback.java @@ -47,7 +47,7 @@ public abstract class DataSourceCallback implements Closeable { * @param offset the offset within buffer to read the data into. * @param size the number of bytes to read. * @throws IOException on fatal errors. - * @return the number of bytes read, or -1 if there was an error. + * @return the number of bytes read, or -1 if end of stream is reached. */ public abstract int readAt(long position, byte[] buffer, int offset, int size) throws IOException; diff --git a/media/java/android/media/MediaDataSource.java b/media/java/android/media/MediaDataSource.java index 4ba2120f5b09..4bdc1adcc3f0 100644 --- a/media/java/android/media/MediaDataSource.java +++ b/media/java/android/media/MediaDataSource.java @@ -46,7 +46,7 @@ public abstract class MediaDataSource implements Closeable { * @param offset the offset within buffer to read the data into. * @param size the number of bytes to read. * @throws IOException on fatal errors. - * @return the number of bytes read, or -1 if there was an error. + * @return the number of bytes read, or -1 if end of stream is reached. */ public abstract int readAt(long position, byte[] buffer, int offset, int size) throws IOException; diff --git a/native/webview/plat_support/draw_fn.h b/native/webview/plat_support/draw_fn.h new file mode 100644 index 000000000000..8d48a58ac293 --- /dev/null +++ b/native/webview/plat_support/draw_fn.h @@ -0,0 +1,197 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +//****************************************************************************** +// This is a copy of the coresponding android_webview/public/browser header. +// Any changes to the interface should be made there as well. +//****************************************************************************** + +#ifndef ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_FN_H_ +#define ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_FN_H_ + +#include <vulkan/vulkan.h> + +#ifdef __cplusplus +extern "C" { +#endif + +// In order to make small changes backwards compatible, all structs passed from +// android to chromium are versioned. +// +// 1 is Android Q. This matches kAwDrawGLInfoVersion version 3. +static const int kAwDrawFnVersion = 1; + +struct AwDrawFn_OnSyncParams { + int version; + + bool apply_force_dark; +}; + +struct AwDrawFn_DrawGLParams { + int version; + + // Input: current clip rect in surface coordinates. Reflects the current state + // of the OpenGL scissor rect. Both the OpenGL scissor rect and viewport are + // set by the caller of the draw function and updated during View animations. + int clip_left; + int clip_top; + int clip_right; + int clip_bottom; + + // Input: current width/height of destination surface. + int width; + int height; + + // Input: is the View rendered into an independent layer. + // If false, the surface is likely to hold to the full screen contents, with + // the scissor box set by the caller to the actual View location and size. + // Also the transformation matrix will contain at least a translation to the + // position of the View to render, plus any other transformations required as + // part of any ongoing View animation. View translucency (alpha) is ignored, + // although the framework will set is_layer to true for non-opaque cases. + // Can be requested via the View.setLayerType(View.LAYER_TYPE_NONE, ...) + // Android API method. + // + // If true, the surface is dedicated to the View and should have its size. + // The viewport and scissor box are set by the caller to the whole surface. + // Animation transformations are handled by the caller and not reflected in + // the provided transformation matrix. Translucency works normally. + // Can be requested via the View.setLayerType(View.LAYER_TYPE_HARDWARE, ...) + // Android API method. + bool is_layer; + + // Input: current transformation matrix in surface pixels. + // Uses the column-based OpenGL matrix format. + float transform[16]; +}; + +struct AwDrawFn_InitVkParams { + int version; + VkInstance instance; + VkPhysicalDevice physical_device; + VkDevice device; + VkQueue queue; + uint32_t graphics_queue_index; + uint32_t instance_version; + const char* const* enabled_extension_names; + // Only one of device_features and device_features_2 should be non-null. + // If both are null then no features are enabled. + VkPhysicalDeviceFeatures* device_features; + VkPhysicalDeviceFeatures2* device_features_2; +}; + +struct AwDrawFn_DrawVkParams { + int version; + + // Input: current width/height of destination surface. + int width; + int height; + + // Input: is the render target a FBO + bool is_layer; + + // Input: current transform matrix + float transform[16]; + + // Input WebView should do its main compositing draws into this. It cannot do + // anything that would require stopping the render pass. + VkCommandBuffer secondary_command_buffer; + + // Input: The main color attachment index where secondary_command_buffer will + // eventually be submitted. + uint32_t color_attachment_index; + + // Input: A render pass which will be compatible to the one which the + // secondary_command_buffer will be submitted into. + VkRenderPass compatible_render_pass; + + // Input: Format of the destination surface. + VkFormat format; + + // Input: Color space transformation from linear RGB to D50-adapted XYZ + float matrix[9]; + + // Input: current clip rect + int clip_left; + int clip_top; + int clip_right; + int clip_bottom; +}; + +struct AwDrawFn_PostDrawVkParams { + int version; + + // Input: Fence for the composite command buffer to signal it has finished its + // work on the GPU. + int fd; +}; + +// Called on render thread while UI thread is blocked. Called for both GL and +// VK. +typedef void AwDrawFn_OnSync(int functor, AwDrawFn_OnSyncParams* params); + +// Called on render thread when either the context is destroyed _or_ when the +// functor's last reference goes away. Will always be called with an active +// context. Called for both GL and VK. +typedef void AwDrawFn_OnContextDestroyed(int functor); + +// Called on render thread when the last reference to the handle goes away and +// the handle is considered irrevocably destroyed. Will always be proceeded by +// a call to OnContextDestroyed if this functor had ever been drawn. Called for +// both GL and VK. +typedef void AwDrawFn_OnDestroyed(int functor); + +// Only called for GL. +typedef void AwDrawFn_DrawGL(int functor, AwDrawFn_DrawGLParams* params); + +// Initialize vulkan state. Needs to be called again after any +// OnContextDestroyed. Only called for Vulkan. +typedef void AwDrawFn_InitVk(int functor, AwDrawFn_InitVkParams* params); + +// Only called for Vulkan. +typedef void AwDrawFn_DrawVk(int functor, AwDrawFn_DrawVkParams* params); + +// Only called for Vulkan. +typedef void AwDrawFn_PostDrawVk(int functor, + AwDrawFn_PostDrawVkParams* params); + +struct AwDrawFnFunctorCallbacks { + // No version here since this is passed from chromium to android. + AwDrawFn_OnSync* on_sync; + AwDrawFn_OnContextDestroyed* on_context_destroyed; + AwDrawFn_OnDestroyed* on_destroyed; + AwDrawFn_DrawGL* draw_gl; + AwDrawFn_InitVk* init_vk; + AwDrawFn_DrawVk* draw_vk; + AwDrawFn_PostDrawVk* post_draw_vk; +}; + +enum AwDrawFnRenderMode { + AW_DRAW_FN_RENDER_MODE_OPENGL_ES = 0, + AW_DRAW_FN_RENDER_MODE_VULKAN = 1, +}; + +// Get the render mode. Result is static for the process. +typedef AwDrawFnRenderMode AwDrawFn_QueryRenderMode(void); + +// Create a functor. |functor_callbacks| should be valid until OnDestroyed. +typedef int AwDrawFn_CreateFunctor(AwDrawFnFunctorCallbacks* functor_callbacks); + +// May be called on any thread to signal that the functor should be destroyed. +// The functor will receive an onDestroyed when the last usage of it is +// released, and it should be considered alive & active until that point. +typedef void AwDrawFn_ReleaseFunctor(int functor); + +struct AwDrawFnFunctionTable { + int version; + AwDrawFn_QueryRenderMode* query_render_mode; + AwDrawFn_CreateFunctor* create_functor; + AwDrawFn_ReleaseFunctor* release_functor; +}; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_FN_H_ diff --git a/native/webview/plat_support/draw_vk_functor.cpp b/native/webview/plat_support/draw_vk_functor.cpp index 1ba559d9afdf..eab134020f71 100644 --- a/native/webview/plat_support/draw_vk_functor.cpp +++ b/native/webview/plat_support/draw_vk_functor.cpp @@ -20,6 +20,7 @@ #define LOG_TAG "webviewchromium_plat_support" +#include "draw_fn.h" #include "draw_vk.h" #include <jni.h> diff --git a/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java b/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java index cb92c4259504..5e63b90203a5 100644 --- a/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java +++ b/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java @@ -16,6 +16,7 @@ package com.android.systemui.notifications; +import android.app.ActivityManager; import android.car.Car; import android.car.CarNotConnectedException; import android.car.drivingstate.CarUxRestrictionsManager; @@ -24,6 +25,7 @@ import android.content.Context; import android.content.ServiceConnection; import android.graphics.PixelFormat; import android.os.IBinder; +import android.os.ServiceManager; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -31,8 +33,10 @@ import android.view.WindowManager; import com.android.car.notification.CarNotificationListener; import com.android.car.notification.CarUxRestrictionManagerWrapper; +import com.android.car.notification.NotificationClickHandlerFactory; import com.android.car.notification.NotificationViewController; import com.android.car.notification.PreprocessingManager; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.R; import com.android.systemui.SystemUI; @@ -44,6 +48,7 @@ public class NotificationsUI extends SystemUI { private static final String TAG = "NotificationsUI"; private CarNotificationListener mCarNotificationListener; private CarUxRestrictionsManager mCarUxRestrictionsManager; + private NotificationClickHandlerFactory mClickHandlerFactory; private Car mCar; private ViewGroup mCarNotificationWindow; private NotificationViewController mNotificationViewController; @@ -60,7 +65,17 @@ public class NotificationsUI extends SystemUI { WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mCarNotificationListener = new CarNotificationListener(); - mCarNotificationListener.registerAsSystemService(mContext, mCarUxRestrictionManagerWrapper); + mClickHandlerFactory = new NotificationClickHandlerFactory( + IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)), + launchResult -> { + if (launchResult == ActivityManager.START_TASK_TO_FRONT + || launchResult == ActivityManager.START_SUCCESS) { + closeCarNotifications(); + } + }); + mCarNotificationListener.registerAsSystemService(mContext, mCarUxRestrictionManagerWrapper, + mClickHandlerFactory); mCar = Car.createCar(mContext, mCarConnectionListener); mCar.connect(); diff --git a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java index 1737b644f5d3..85dab57b5f21 100644 --- a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java +++ b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java @@ -107,36 +107,44 @@ public class CarVolumeDialogImpl implements VolumeDialog { private CarAudioManager mCarAudioManager; private final CarAudioManager.CarVolumeCallback mVolumeChangeCallback = new CarAudioManager.CarVolumeCallback() { - @Override - public void onGroupVolumeChanged(int zoneId, int groupId, int flags) { - // TODO: Include zoneId into consideration. - // For instance - // - single display + single-zone, ignore zoneId - // - multi-display + single-zone, zoneId is fixed, may show volume bar on all displays - // - single-display + multi-zone, may show volume bar on primary display only - // - multi-display + multi-zone, may show volume bar on display specified by zoneId - VolumeItem volumeItem = mAvailableVolumeItems.get(groupId); - int value = getSeekbarValue(mCarAudioManager, groupId); - // Do not update the progress if it is the same as before. When car audio manager sets - // its group volume caused by the seekbar progress changed, it also triggers this - // callback. Updating the seekbar at the same time could block the continuous seeking. - if (value != volumeItem.progress) { - volumeItem.listItem.setProgress(value); - volumeItem.progress = value; - } - if ((flags & AudioManager.FLAG_SHOW_UI) != 0) { - mHandler.obtainMessage(H.SHOW, Events.SHOW_REASON_VOLUME_CHANGED).sendToTarget(); - } - } + @Override + public void onGroupVolumeChanged(int zoneId, int groupId, int flags) { + // TODO: Include zoneId into consideration. + // For instance + // - single display + single-zone, ignore zoneId + // - multi-display + single-zone, zoneId is fixed, may show volume bar on all + // displays + // - single-display + multi-zone, may show volume bar on primary display only + // - multi-display + multi-zone, may show volume bar on display specified by + // zoneId + VolumeItem volumeItem = mAvailableVolumeItems.get(groupId); + int value = getSeekbarValue(mCarAudioManager, groupId); + // Do not update the progress if it is the same as before. When car audio + // manager sets + // its group volume caused by the seekbar progress changed, it also triggers + // this + // callback. Updating the seekbar at the same time could block the continuous + // seeking. + if (value != volumeItem.progress) { + volumeItem.listItem.setProgress(value); + volumeItem.progress = value; + } + if ((flags & AudioManager.FLAG_SHOW_UI) != 0) { + mHandler.obtainMessage(H.SHOW, + Events.SHOW_REASON_VOLUME_CHANGED).sendToTarget(); + } + } - @Override - public void onMasterMuteChanged(int zoneId, int flags) { - // ignored - } - }; + @Override + public void onMasterMuteChanged(int zoneId, int flags) { + // ignored + } + }; private boolean mHovering; private boolean mShowing; private boolean mExpanded; + private View mExpandIcon; + private VolumeItem mDefaultVolumeItem; private final ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { @@ -151,10 +159,8 @@ public class CarVolumeDialogImpl implements VolumeDialog { mAvailableVolumeItems.add(volumeItem); // The first one is the default item. if (groupId == 0) { - volumeItem.defaultItem = true; - addSeekbarListItem(volumeItem, groupId, - R.drawable.car_ic_keyboard_arrow_down, - new ExpandIconListener()); + mDefaultVolumeItem = volumeItem; + setupDefaultListItem(); } } @@ -178,6 +184,13 @@ public class CarVolumeDialogImpl implements VolumeDialog { } }; + private void setupDefaultListItem() { + mDefaultVolumeItem.defaultItem = true; + addSeekbarListItem(mDefaultVolumeItem, /* volumeGroupId = */0, + R.drawable.car_ic_keyboard_arrow_down, new ExpandIconListener() + ); + } + public CarVolumeDialogImpl(Context context) { mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme); mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); @@ -294,7 +307,9 @@ public class CarVolumeDialogImpl implements VolumeDialog { return; } mShowing = true; - + if (mVolumeLineItems.isEmpty()) { + setupDefaultListItem(); + } mDialog.show(); Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked()); } @@ -340,6 +355,13 @@ public class CarVolumeDialogImpl implements VolumeDialog { } mDialog.dismiss(); mShowing = false; + mShowing = false; + // if mExpandIcon is null that means user never clicked on the expanded arrow + // which implies that the dialog is still not expanded. In that case we do + // not want to reset the state + if (mExpandIcon != null && mExpanded) { + toggleDialogExpansion(/* isClicked = */ false); + } }, DISMISS_DELAY_IN_MILLIS)) .start(); @@ -517,50 +539,60 @@ public class CarVolumeDialogImpl implements VolumeDialog { } private final class ExpandIconListener implements View.OnClickListener { - @Override public void onClick(final View v) { - mExpanded = !mExpanded; - Animator inAnimator; - if (mExpanded) { - for (int groupId = 0; groupId < mAvailableVolumeItems.size(); ++groupId) { - // Adding the items which are not coming from the default item. - VolumeItem volumeItem = mAvailableVolumeItems.get(groupId); - if (volumeItem.defaultItem) { - // Set progress here due to the progress of seekbar may not be updated. - volumeItem.listItem.setProgress(volumeItem.progress); - } else { - addSeekbarListItem(volumeItem, groupId, 0, null); - } + mExpandIcon = v; + toggleDialogExpansion(true); + } + } + + private void toggleDialogExpansion(boolean isClicked) { + mExpanded = !mExpanded; + Animator inAnimator; + if (mExpanded) { + for (int groupId = 0; groupId < mAvailableVolumeItems.size(); ++groupId) { + // Adding the items which are not coming from the default item. + VolumeItem volumeItem = mAvailableVolumeItems.get(groupId); + if (volumeItem.defaultItem) { + // Set progress here due to the progress of seekbar may not be updated. + volumeItem.listItem.setProgress(volumeItem.progress); + } else { + addSeekbarListItem(volumeItem, groupId, 0, null); } - inAnimator = AnimatorInflater.loadAnimator( - mContext, R.anim.car_arrow_fade_in_rotate_up); - } else { - // Only keeping the default stream if it is not expended. - Iterator itr = mVolumeLineItems.iterator(); - while (itr.hasNext()) { - SeekbarListItem seekbarListItem = (SeekbarListItem) itr.next(); - VolumeItem volumeItem = findVolumeItem(seekbarListItem); - if (!volumeItem.defaultItem) { - itr.remove(); - } else { - // Set progress here due to the progress of seekbar may not be updated. - seekbarListItem.setProgress(volumeItem.progress); - } + } + inAnimator = AnimatorInflater.loadAnimator( + mContext, R.anim.car_arrow_fade_in_rotate_up); + + } else { + // Only keeping the default stream if it is not expended. + Iterator itr = mVolumeLineItems.iterator(); + while (itr.hasNext()) { + SeekbarListItem seekbarListItem = (SeekbarListItem) itr.next(); + VolumeItem volumeItem = findVolumeItem(seekbarListItem); + if (!volumeItem.defaultItem) { + itr.remove(); + } else { + // Set progress here due to the progress of seekbar may not be updated. + seekbarListItem.setProgress(volumeItem.progress); } - inAnimator = AnimatorInflater.loadAnimator( - mContext, R.anim.car_arrow_fade_in_rotate_down); } + inAnimator = AnimatorInflater.loadAnimator( + mContext, R.anim.car_arrow_fade_in_rotate_down); + } - Animator outAnimator = AnimatorInflater.loadAnimator( - mContext, R.anim.car_arrow_fade_out); - inAnimator.setStartDelay(ARROW_FADE_IN_START_DELAY_IN_MILLIS); - AnimatorSet animators = new AnimatorSet(); - animators.playTogether(outAnimator, inAnimator); - animators.setTarget(v); - animators.start(); - mPagedListAdapter.notifyDataSetChanged(); + Animator outAnimator = AnimatorInflater.loadAnimator( + mContext, R.anim.car_arrow_fade_out); + inAnimator.setStartDelay(ARROW_FADE_IN_START_DELAY_IN_MILLIS); + AnimatorSet animators = new AnimatorSet(); + animators.playTogether(outAnimator, inAnimator); + if (!isClicked) { + // Do not animate when the state is called to reset the dialogs view and not clicked + // by user. + animators.setDuration(0); } + animators.setTarget(mExpandIcon); + animators.start(); + mPagedListAdapter.notifyDataSetChanged(); } private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener { @@ -601,4 +633,4 @@ public class CarVolumeDialogImpl implements VolumeDialog { public void onStopTrackingTouch(SeekBar seekBar) { } } -} +}
\ No newline at end of file diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml index 4d9aaecb4092..f1165468c0ad 100644 --- a/packages/CarrierDefaultApp/AndroidManifest.xml +++ b/packages/CarrierDefaultApp/AndroidManifest.xml @@ -31,7 +31,8 @@ <application android:label="@string/app_name" android:directBootAware="true" - android:usesCleartextTraffic="true"> + android:usesCleartextTraffic="true" + android:icon="@mipmap/ic_launcher_android"> <receiver android:name="com.android.carrierdefaultapp.CarrierDefaultBroadcastReceiver"> <intent-filter> <action android:name="com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED" /> @@ -45,6 +46,7 @@ <activity android:name="com.android.carrierdefaultapp.CaptivePortalLoginActivity" android:label="@string/action_bar_label" + android:exported="true" android:theme="@style/AppTheme" android:configChanges="keyboardHidden|orientation|screenSize"> <intent-filter> diff --git a/packages/CarrierDefaultApp/res/mipmap/ic_launcher_android.png b/packages/CarrierDefaultApp/res/mipmap/ic_launcher_android.png Binary files differnew file mode 100644 index 000000000000..2e9b196c9625 --- /dev/null +++ b/packages/CarrierDefaultApp/res/mipmap/ic_launcher_android.png diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java index 4f67350b5adc..f36b4aa87636 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java @@ -195,19 +195,7 @@ public class CaptivePortalLoginActivity extends Activity { if (success) { // Trigger re-evaluation upon success http response code CarrierActionUtils.applyCarrierAction( - CarrierActionUtils.CARRIER_ACTION_ENABLE_RADIO, getIntent(), - getApplicationContext()); - CarrierActionUtils.applyCarrierAction( - CarrierActionUtils.CARRIER_ACTION_ENABLE_METERED_APNS, getIntent(), - getApplicationContext()); - CarrierActionUtils.applyCarrierAction( - CarrierActionUtils.CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS, getIntent(), - getApplicationContext()); - CarrierActionUtils.applyCarrierAction( - CarrierActionUtils.CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER, getIntent(), - getApplicationContext()); - CarrierActionUtils.applyCarrierAction( - CarrierActionUtils.CARRIER_ACTION_DEREGISTER_DEFAULT_NETWORK_AVAIL, getIntent(), + CarrierActionUtils.CARRIER_ACTION_RESET_ALL, getIntent(), getApplicationContext()); } finishAndRemoveTask(); diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java index 4518d79ff611..3258d57ba8e0 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java @@ -56,6 +56,7 @@ public class CarrierActionUtils { public static final int CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER = 8; public static final int CARRIER_ACTION_REGISTER_DEFAULT_NETWORK_AVAIL = 9; public static final int CARRIER_ACTION_DEREGISTER_DEFAULT_NETWORK_AVAIL = 10; + public static final int CARRIER_ACTION_RESET_ALL = 11; public static void applyCarrierAction(int actionIdx, Intent intent, Context context) { switch (actionIdx) { @@ -92,6 +93,9 @@ public class CarrierActionUtils { case CARRIER_ACTION_DEREGISTER_DEFAULT_NETWORK_AVAIL: onDeregisterDefaultNetworkAvail(intent, context); break; + case CARRIER_ACTION_RESET_ALL: + onResetAllCarrierActions(intent, context); + break; default: loge("unsupported carrier action index: " + actionIdx); } @@ -196,6 +200,14 @@ public class CarrierActionUtils { context.getSystemService(NotificationManager.class).cancelAll(); } + private static void onResetAllCarrierActions(Intent intent, Context context) { + int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + SubscriptionManager.getDefaultVoiceSubscriptionId()); + logd("onResetAllCarrierActions subId: " + subId); + final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class); + telephonyMgr.carrierActionResetAll(subId); + } + private static Notification getNotification(Context context, int titleId, int textId, PendingIntent pendingIntent) { final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class); diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java index 60d31fca8ddb..0352ebcec8b3 100644 --- a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java +++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java @@ -75,7 +75,7 @@ public class SmartActionHelperTest { mContext.getSystemService(TextClassificationManager.class) .setTextClassifier(mTextClassifier); when(mTextClassifier.suggestConversationActions(any(ConversationActions.Request.class))) - .thenReturn(new ConversationActions(Collections.emptyList())); + .thenReturn(new ConversationActions(Collections.emptyList(), null)); when(mNotificationEntry.getSbn()).thenReturn(mStatusBarNotification); // The notification is eligible to have smart suggestions. diff --git a/packages/SettingsLib/ActionButtonsPreference/Android.bp b/packages/SettingsLib/ActionButtonsPreference/Android.bp index e518e0b97c4b..cd3fb0cc2143 100644 --- a/packages/SettingsLib/ActionButtonsPreference/Android.bp +++ b/packages/SettingsLib/ActionButtonsPreference/Android.bp @@ -1,5 +1,5 @@ android_library { - name: "ActionButtonsPreference", + name: "SettingsLibActionButtonsPreference", srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 0126e7e59915..042808a07f8f 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -16,8 +16,8 @@ android_library { "SettingsLibAppPreference", "SettingsLibSearchWidget", "SettingsLibSettingsSpinner", - "SettingsLayoutPreference", - "ActionButtonsPreference", + "SettingsLibLayoutPreference", + "SettingsLibActionButtonsPreference", "SettingsLibEntityHeaderWidgets", ], diff --git a/packages/SettingsLib/SettingsLayoutPreference/Android.bp b/packages/SettingsLib/LayoutPreference/Android.bp index 489d3606ae15..a1f9a768c76c 100644 --- a/packages/SettingsLib/SettingsLayoutPreference/Android.bp +++ b/packages/SettingsLib/LayoutPreference/Android.bp @@ -1,5 +1,5 @@ android_library { - name: "SettingsLayoutPreference", + name: "SettingsLibLayoutPreference", srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/SettingsLayoutPreference/AndroidManifest.xml b/packages/SettingsLib/LayoutPreference/AndroidManifest.xml index 4b9f1ab8d6cc..4b9f1ab8d6cc 100644 --- a/packages/SettingsLib/SettingsLayoutPreference/AndroidManifest.xml +++ b/packages/SettingsLib/LayoutPreference/AndroidManifest.xml diff --git a/packages/SettingsLib/SettingsLayoutPreference/res/layout/layout_preference_frame.xml b/packages/SettingsLib/LayoutPreference/res/layout/layout_preference_frame.xml index ee4ce499396f..ee4ce499396f 100644 --- a/packages/SettingsLib/SettingsLayoutPreference/res/layout/layout_preference_frame.xml +++ b/packages/SettingsLib/LayoutPreference/res/layout/layout_preference_frame.xml diff --git a/packages/SettingsLib/SettingsLayoutPreference/res/layout/settings_entity_header.xml b/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml index e27ae7d6d439..e27ae7d6d439 100644 --- a/packages/SettingsLib/SettingsLayoutPreference/res/layout/settings_entity_header.xml +++ b/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml diff --git a/packages/SettingsLib/SettingsLayoutPreference/res/values/styles.xml b/packages/SettingsLib/LayoutPreference/res/values/styles.xml index 805744baef10..805744baef10 100644 --- a/packages/SettingsLib/SettingsLayoutPreference/res/values/styles.xml +++ b/packages/SettingsLib/LayoutPreference/res/values/styles.xml diff --git a/packages/SettingsLib/SettingsLayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java b/packages/SettingsLib/LayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java index 2a635b0996e6..2a635b0996e6 100644 --- a/packages/SettingsLib/SettingsLayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java +++ b/packages/SettingsLib/LayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 75492f340109..b73c706f2bd7 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -44,7 +44,6 @@ android_library { "SystemUIPluginLib", "SystemUISharedLib", "SettingsLib", - "androidx.car_car", "androidx.legacy_legacy-support-v4", "androidx.recyclerview_recyclerview", "androidx.preference_preference", @@ -67,8 +66,6 @@ android_library { libs: [ "telephony-common", - "android.car", - "android.car.userlib", ], aaptflags: [ @@ -98,7 +95,6 @@ android_library { "SystemUIPluginLib", "SystemUISharedLib", "SettingsLib", - "androidx.car_car", "androidx.legacy_legacy-support-v4", "androidx.recyclerview_recyclerview", "androidx.preference_preference", @@ -123,8 +119,6 @@ android_library { libs: [ "android.test.runner", "telephony-common", - "android.car", - "android.car.userlib", "android.test.base", ], aaptflags: [ @@ -149,8 +143,6 @@ android_app { libs: [ "telephony-common", - "android.car", - "android.car.userlib", ], dxflags: ["--multi-dex"], @@ -186,8 +178,6 @@ android_app { ], libs: [ "telephony-common", - "android.car", - "android.car.userlib", ], srcs: [ diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 7d53c2f78151..1c1a1404eacd 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -77,6 +77,7 @@ <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <uses-permission android:name="android.permission.MASTER_CLEAR" /> <uses-permission android:name="android.permission.VIBRATE" /> + <uses-permission android:name="android.permission.MANAGE_SENSOR_PRIVACY" /> <!-- ActivityManager --> <uses-permission android:name="android.permission.REAL_GET_TASKS" /> diff --git a/packages/SystemUI/res/drawable/ic_signal_sensors.xml b/packages/SystemUI/res/drawable/ic_signal_sensors.xml new file mode 100644 index 000000000000..faaddf6473c4 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_signal_sensors.xml @@ -0,0 +1,28 @@ +<!-- + Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<vector android:height="48dp" android:viewportHeight="5" + android:viewportWidth="5" android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#00000000" + android:pathData="m4.762,0.661 l-4.233,4.233" + android:strokeAlpha="1" android:strokeColor="#000000" + android:strokeLineCap="round" android:strokeLineJoin="miter" android:strokeWidth=".5"/> + <path android:fillColor="#00000000" + android:pathData="M0.265,2.778L1.058,2.778l0.529,-1.323 0.529,2.646 0.529,-3.175 0.529,2.646 0.529,-1.587 0.265,0.794h1.058" + android:strokeAlpha="1" android:strokeColor="#000000" + android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth=".33"/> +</vector> + diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 9e97cd8f4099..61efbd5c2248 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -111,7 +111,7 @@ <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" --> <string name="quick_settings_tiles_stock" translatable="false"> - wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,work,cast,night + wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,work,cast,night,sensorprivacy </string> <!-- The tiles to display in QuickSettings --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 9917257bba8a..d45730714c42 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -598,6 +598,10 @@ <string name="accessibility_quick_settings_data_saver_changed_off">Data Saver turned off.</string> <!-- Announcement made when the Data Saver changes to on (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_quick_settings_data_saver_changed_on">Data Saver turned on.</string> + <!-- Announcement made when the Sensor Privacy changes to off (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_quick_settings_sensor_privacy_changed_off">Sensor Privacy turned off.</string> + <!-- Announcement made when the Sensor Privacy changes to on (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_quick_settings_sensor_privacy_changed_on">Sensor Privacy turned on.</string> <!-- Content description of the display brightness slider (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_brightness">Display brightness</string> @@ -2318,4 +2322,6 @@ <item quantity="one"><xliff:g id="num_apps" example="1">%d</xliff:g> other app</item> <item quantity="other"><xliff:g id="num_apps" example="3">%d</xliff:g> other apps</item> </plurals> + <!-- Text for the quick setting tile for sensor privacy [CHAR LIMIT=30] --> + <string name="sensor_privacy_mode">Sensors off</string> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 5e6d272bd427..327ffcd24762 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -17,6 +17,7 @@ package com.android.systemui; import android.content.Context; import android.content.res.Configuration; import android.hardware.SensorManager; +import android.hardware.SensorPrivacyManager; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -191,6 +192,9 @@ public class Dependency extends SystemUI { new AsyncSensorManager(mContext.getSystemService(SensorManager.class), getDependency(PluginManager.class))); + mProviders.put(SensorPrivacyManager.class, () -> + mContext.getSystemService(SensorPrivacyManager.class)); + mProviders.put(BluetoothController.class, () -> new BluetoothControllerImpl(mContext, getDependency(BG_LOOPER))); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 9ccdf79c37ca..b133346bcf8f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -406,24 +406,6 @@ public class KeyguardViewMediator extends SystemUI { } @Override - public void onPhoneStateChanged(int phoneState) { - synchronized (KeyguardViewMediator.this) { - if (TelephonyManager.CALL_STATE_IDLE == phoneState // call ending - && !mDeviceInteractive // screen off - && mExternallyEnabled) { // not disabled by any app - - // note: this is a way to gracefully reenable the keyguard when the call - // ends and the screen is off without always reenabling the keyguard - // each time the screen turns off while in call (and having an occasional ugly - // flicker while turning back on the screen and disabling the keyguard again). - if (DEBUG) Log.d(TAG, "screen is off and call ended, let's make sure the " - + "keyguard is showing"); - doKeyguardLocked(null); - } - } - } - - @Override public void onClockVisibilityChanged() { adjustStatusBarLocked(); } @@ -1316,15 +1298,7 @@ public class KeyguardViewMediator extends SystemUI { if (!mExternallyEnabled) { if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled"); - // note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes - // for an occasional ugly flicker in this situation: - // 1) receive a call with the screen on (no keyguard) or make a call - // 2) screen times out - // 3) user hits key to turn screen back on - // instead, we reenable the keyguard when we know the screen is off and the call - // ends (see the broadcast receiver below) - // TODO: clean this up when we have better support at the window manager level - // for apps that wish to be on top of the keyguard + mNeedToReshowWhenReenabled = true; return; } 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 7d52f0b2763d..fd2c4e35e49b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -41,6 +41,7 @@ import com.android.systemui.qs.tiles.LocationTile; import com.android.systemui.qs.tiles.NfcTile; import com.android.systemui.qs.tiles.NightDisplayTile; import com.android.systemui.qs.tiles.RotationLockTile; +import com.android.systemui.qs.tiles.SensorPrivacyTile; import com.android.systemui.qs.tiles.UserTile; import com.android.systemui.qs.tiles.WifiTile; import com.android.systemui.qs.tiles.WorkModeTile; @@ -100,6 +101,8 @@ public class QSFactoryImpl implements QSFactory { return new NightDisplayTile(mHost); case "nfc": return new NfcTile(mHost); + case "sensorprivacy": + return new SensorPrivacyTile(mHost); } // Intent tiles. diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java new file mode 100644 index 000000000000..ff368f80d06a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles; + +import android.content.Intent; +import android.hardware.SensorPrivacyManager; +import android.service.quicksettings.Tile; +import android.widget.Switch; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.qs.QSTile.BooleanState; +import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.statusbar.policy.KeyguardMonitor; + +/** Quick settings tile: SensorPrivacy mode **/ +public class SensorPrivacyTile extends QSTileImpl<BooleanState> implements + SensorPrivacyManager.OnSensorPrivacyChangedListener { + private static final String TAG = "SensorPrivacy"; + private final Icon mIcon = + ResourceIcon.get(R.drawable.ic_signal_sensors); + private final KeyguardMonitor mKeyguard; + private final SensorPrivacyManager mSensorPrivacyManager; + + public SensorPrivacyTile(QSHost host) { + super(host); + + mSensorPrivacyManager = Dependency.get(SensorPrivacyManager.class); + mKeyguard = Dependency.get(KeyguardMonitor.class); + } + + @Override + public BooleanState newTileState() { + return new BooleanState(); + } + + @Override + public void handleClick() { + final boolean wasEnabled = mState.value; + // Don't allow disabling from the lockscreen. + if (wasEnabled && mKeyguard.isSecure() && mKeyguard.isShowing()) { + Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> { + MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled); + setEnabled(!wasEnabled); + }); + return; + } + + MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled); + setEnabled(!wasEnabled); + } + + private void setEnabled(boolean enabled) { + mSensorPrivacyManager.setSensorPrivacy(enabled); + } + + @Override + public CharSequence getTileLabel() { + return mContext.getString(R.string.sensor_privacy_mode); + } + + @Override + public Intent getLongClickIntent() { + return null; + } + + @Override + protected void handleUpdateState(BooleanState state, Object arg) { + final boolean enabled = arg instanceof Boolean ? (Boolean) arg + : mSensorPrivacyManager.isSensorPrivacyEnabled(); + state.value = enabled; + state.label = mContext.getString(R.string.sensor_privacy_mode); + state.icon = mIcon; + state.state = enabled ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; + state.contentDescription = state.label; + state.expandedAccessibilityClassName = Switch.class.getName(); + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.QS_SENSOR_PRIVACY; + } + + @Override + protected String composeChangeAnnouncement() { + if (mState.value) { + return mContext + .getString(R.string.accessibility_quick_settings_sensor_privacy_changed_on); + } else { + return mContext + .getString(R.string.accessibility_quick_settings_sensor_privacy_changed_off); + } + } + + @Override + protected void handleSetListening(boolean listening) { + if (listening) { + mSensorPrivacyManager.addSensorPrivacyListener(this); + } else { + mSensorPrivacyManager.removeSensorPrivacyListener(this); + } + } + + @Override + public void onSensorPrivacyChanged(boolean enabled) { + refreshState(enabled); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index dc3a60786ce2..0702f1b9f1fe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -437,7 +437,7 @@ public class NotificationViewHierarchyManager { } row.showAppOpsIcons(entry.mActiveAppOps); - row.setAudiblyAlerted(entry.audiblyAlerted); + row.setLastAudiblyAlertedMs(entry.lastAudiblyAlertedMs); } Trace.beginSection("NotificationPresenter#onUpdateRowStates"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AlertTransferListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AlertTransferListener.java new file mode 100644 index 000000000000..13e991ba2431 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AlertTransferListener.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification; + +/** + * Listener interface for when NotificationEntryManager needs to tell + * NotificationGroupAlertTransferHelper things. Will eventually grow to be a general-purpose + * listening interface for the NotificationEntryManager. + */ +public interface AlertTransferListener { + /** + * Called when a new notification is posted. At this point, the notification is "pending": its + * views haven't been inflated yet and most of the system pretends like it doesn't exist yet. + */ + void onPendingEntryAdded(NotificationData.Entry entry); + + /** + * Called when an existing notification's views are reinflated (usually due to an update being + * posted to that notification). + */ + void onEntryReinflated(NotificationData.Entry entry); + + /** + * Called when a notification has been removed (either because the user swiped it away or + * because the developer retracted it). + */ + void onEntryRemoved(NotificationData.Entry entry); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java index f543b4622611..ae9f323c2ebf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java @@ -103,7 +103,7 @@ public class NotificationData { public String key; public StatusBarNotification notification; public NotificationChannel channel; - public boolean audiblyAlerted; + public long lastAudiblyAlertedMs; public boolean noisy; public int importance; public StatusBarIconView icon; @@ -172,7 +172,7 @@ public class NotificationData { public void populateFromRanking(@NonNull Ranking ranking) { channel = ranking.getChannel(); - audiblyAlerted = ranking.audiblyAlerted(); + lastAudiblyAlertedMs = ranking.getLastAudiblyAlertedMillis(); importance = ranking.getImportance(); snoozeCriteria = ranking.getSnoozeCriteria(); userSentiment = ranking.getUserSentiment(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index 16a3849a6eec..aab3fc4c7a0e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -32,6 +32,7 @@ import android.content.pm.PackageManager; import android.database.ContentObserver; import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; @@ -61,7 +62,6 @@ import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.EventLogTags; import com.android.systemui.ForegroundServiceController; -import com.android.systemui.InitController; import com.android.systemui.R; import com.android.systemui.UiOffloadThread; import com.android.systemui.bubbles.BubbleController; @@ -83,7 +83,6 @@ import com.android.systemui.statusbar.notification.row.NotificationInflater; import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; import com.android.systemui.statusbar.notification.row.RowInflaterTask; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; -import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; @@ -96,6 +95,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.concurrent.TimeUnit; /** * NotificationEntryManager is responsible for the adding, removing, and updating of notifications. @@ -110,6 +110,8 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. private static final boolean ENABLE_HEADS_UP = true; private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up"; + public static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30); + private final NotificationMessagingUtil mMessagingUtil; protected final Context mContext; protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>(); @@ -117,8 +119,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. private final NotificationGroupManager mGroupManager = Dependency.get(NotificationGroupManager.class); - private final NotificationGroupAlertTransferHelper mGroupAlertTransferHelper = - Dependency.get(NotificationGroupAlertTransferHelper.class); private final NotificationGutsManager mGutsManager = Dependency.get(NotificationGutsManager.class); private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); @@ -139,6 +139,9 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. private NotificationListener mNotificationListener; private ShadeController mShadeController; + private final Handler mDeferredNotificationViewUpdateHandler; + private Runnable mUpdateNotificationViewsCallback; + protected IDreamManager mDreamManager; protected IStatusBarService mBarService; private NotificationPresenter mPresenter; @@ -157,6 +160,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. = new ArrayList<>(); private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; private NotificationViewHierarchyManager.StatusBarStateListener mStatusBarStateListener; + @Nullable private AlertTransferListener mAlertTransferListener; private final class NotificationClicker implements View.OnClickListener { @@ -258,12 +262,11 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. mMessagingUtil = new NotificationMessagingUtil(context); mBubbleController.setDismissListener(this /* bubbleEventListener */); mNotificationData = new NotificationData(); - Dependency.get(InitController.class).addPostInitTask(this::onPostInit); + mDeferredNotificationViewUpdateHandler = new Handler(); } - private void onPostInit() { - mGroupAlertTransferHelper.setPendingEntries(mPendingNotifications); - mGroupManager.addOnGroupChangeListener(mGroupAlertTransferHelper); + public void setAlertTransferListener(AlertTransferListener listener) { + mAlertTransferListener = listener; } /** @@ -301,6 +304,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. NotificationListContainer listContainer, Callback callback, HeadsUpManager headsUpManager) { mPresenter = presenter; + mUpdateNotificationViewsCallback = mPresenter::updateNotificationViews; mCallback = callback; mHeadsUpManager = headsUpManager; mNotificationData.setHeadsUpManager(mHeadsUpManager); @@ -540,6 +544,17 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. tagForeground(shadeEntry.notification); updateNotifications(); mCallback.onNotificationAdded(shadeEntry); + + maybeScheduleUpdateNotificationViews(shadeEntry); + } + + private void maybeScheduleUpdateNotificationViews(NotificationData.Entry entry) { + long audibleAlertTimeout = RECENTLY_ALERTED_THRESHOLD_MS + - (System.currentTimeMillis() - entry.lastAudiblyAlertedMs); + if (audibleAlertTimeout > 0) { + mDeferredNotificationViewUpdateHandler.postDelayed( + mUpdateNotificationViewsCallback, audibleAlertTimeout); + } } /** @@ -587,7 +602,9 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. mVisualStabilityManager.onLowPriorityUpdated(entry); mPresenter.updateNotificationViews(); } - mGroupAlertTransferHelper.onInflationFinished(entry); + if (mAlertTransferListener != null) { + mAlertTransferListener.onEntryReinflated(entry); + } } } entry.setLowPriorityStateUpdated(false); @@ -600,8 +617,12 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. private void removeNotificationInternal(String key, @Nullable NotificationListenerService.RankingMap ranking, boolean forceRemove) { + final NotificationData.Entry entry = mNotificationData.get(key); + abortExistingInflation(key); - mGroupAlertTransferHelper.cleanUpPendingAlertInfo(key); + if (mAlertTransferListener != null && entry != null) { + mAlertTransferListener.onEntryRemoved(entry); + } // Attempt to remove notifications from their alert managers (heads up, ambient pulse). // Though the remove itself may fail, it lets the manager know to remove as soon as @@ -620,8 +641,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. mAmbientPulseManager.removeNotification(key, false /* ignoreEarliestRemovalTime */); } - NotificationData.Entry entry = mNotificationData.get(key); - if (entry == null) { mCallback.onNotificationRemoved(key, null /* old */); return; @@ -846,7 +865,9 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. mNotificationData.getImportance(key)); mPendingNotifications.put(key, shadeEntry); - mGroupAlertTransferHelper.onPendingEntryAdded(shadeEntry); + if (mAlertTransferListener != null) { + mAlertTransferListener.onPendingEntryAdded(shadeEntry); + } } @VisibleForTesting @@ -937,6 +958,8 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. } mCallback.onNotificationUpdated(notification); + + maybeScheduleUpdateNotificationViews(entry); } @Override @@ -1231,6 +1254,15 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. } /** + * @return An iterator for all "pending" notifications. Pending notifications are newly-posted + * notifications whose views have not yet been inflated. In general, the system pretends like + * these don't exist, although there are a couple exceptions. + */ + public Iterable<NotificationData.Entry> getPendingNotificationsIterator() { + return mPendingNotifications.values(); + } + + /** * Callback for NotificationEntryManager. */ public interface Callback { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 383446c00d37..91d08fffa642 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -87,6 +87,7 @@ import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.AboveShelfChangedListener; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.logging.NotificationCounters; @@ -1689,14 +1690,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mPublicLayout.showAppOpsIcons(activeOps); } - /** Sets whether the notification being displayed audibly alerted the user. */ - public void setAudiblyAlerted(boolean audiblyAlerted) { + /** Sets the last time the notification being displayed audibly alerted the user. */ + public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) { if (NotificationUtils.useNewInterruptionModel(mContext)) { + boolean recentlyAudiblyAlerted = System.currentTimeMillis() - lastAudiblyAlertedMs + < NotificationEntryManager.RECENTLY_ALERTED_THRESHOLD_MS; if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) { - mChildrenContainer.getHeaderView().setAudiblyAlerted(audiblyAlerted); + mChildrenContainer.getHeaderView().setRecentlyAudiblyAlerted( + recentlyAudiblyAlerted); } - mPrivateLayout.setAudiblyAlerted(audiblyAlerted); - mPublicLayout.setAudiblyAlerted(audiblyAlerted); + mPrivateLayout.setRecentlyAudiblyAlerted(recentlyAudiblyAlerted); + mPublicLayout.setRecentlyAudiblyAlerted(recentlyAudiblyAlerted); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index edd54ca936fa..6bc39c82f8bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -1615,15 +1615,15 @@ public class NotificationContentView extends FrameLayout { } /** Sets whether the notification being displayed audibly alerted the user. */ - public void setAudiblyAlerted(boolean audiblyAlerted) { + public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) { if (mContractedChild != null && mContractedWrapper.getNotificationHeader() != null) { - mContractedWrapper.getNotificationHeader().setAudiblyAlerted(audiblyAlerted); + mContractedWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted); } if (mExpandedChild != null && mExpandedWrapper.getNotificationHeader() != null) { - mExpandedWrapper.getNotificationHeader().setAudiblyAlerted(audiblyAlerted); + mExpandedWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted); } if (mHeadsUpChild != null && mHeadsUpWrapper.getNotificationHeader() != null) { - mHeadsUpWrapper.getNotificationHeader().setAudiblyAlerted(audiblyAlerted); + mHeadsUpWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java index 80c4eb043d24..5906dc29fa08 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java @@ -26,6 +26,9 @@ import android.widget.ImageView; import com.android.systemui.R; import com.android.systemui.statusbar.policy.DarkIconDispatcher; +import java.io.FileDescriptor; +import java.io.PrintWriter; + public class DarkIconDispatcherImpl implements DarkIconDispatcher { private final LightBarTransitionsController mTransitionsController; @@ -74,7 +77,7 @@ public class DarkIconDispatcherImpl implements DarkIconDispatcher { } /** - * Sets the dark area so {@link #setIconsDark} only affects the icons in the specified area. + * Sets the dark area so {@link #applyDark} only affects the icons in the specified area. * * @param darkArea the area in which icons should change it's tint, in logical screen * coordinates @@ -103,4 +106,12 @@ public class DarkIconDispatcherImpl implements DarkIconDispatcher { mReceivers.valueAt(i).onDarkChanged(mTintArea, mDarkIntensity, mIconTint); } } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("DarkIconDispatcher: "); + pw.println(" mIconTint: 0x" + Integer.toHexString(mIconTint)); + pw.println(" mDarkIntensity: " + mDarkIntensity + "f"); + pw.println(" mTintArea: " + mTintArea); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java index e0c5516417ba..67b077e729df 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java @@ -259,6 +259,10 @@ public class LightBarController implements BatteryController.BatteryStateChangeC pw.println(); + if (mStatusBarIconController != null) { + mStatusBarIconController.dump(fd, pw, args); + } + LightBarTransitionsController transitionsController = mStatusBarIconController.getTransitionsController(); if (transitionsController != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java index 2a68fa598603..dd81c4e20d6d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java @@ -29,7 +29,9 @@ import com.android.systemui.statusbar.AmbientPulseManager.OnAmbientChangedListen import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarStateController.StateListener; +import com.android.systemui.statusbar.notification.AlertTransferListener; import com.android.systemui.statusbar.notification.NotificationData.Entry; +import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.row.NotificationInflater.AsyncInflationTask; import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup; @@ -38,8 +40,6 @@ import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; import java.util.Objects; /** @@ -47,8 +47,8 @@ import java.util.Objects; * {@link HeadsUpManager}, {@link AmbientPulseManager}. In particular, this class deals with keeping * the correct notification in a group alerting based off the group suppression. */ -public class NotificationGroupAlertTransferHelper implements OnGroupChangeListener, - OnHeadsUpChangedListener, OnAmbientChangedListener, StateListener { +public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedListener, + OnAmbientChangedListener, StateListener { private static final long ALERT_TRANSFER_TIMEOUT = 300; @@ -69,15 +69,7 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen private final NotificationGroupManager mGroupManager = Dependency.get(NotificationGroupManager.class); - // TODO(b/119637830): It would be good if GroupManager already had all pending notifications as - // normal children (i.e. add notifications to GroupManager before inflation) so that we don't - // have to have this dependency. We'd also have to worry less about the suppression not being up - // to date. - /** - * Notifications that are currently inflating for the first time. Used to remove an incorrectly - * alerting notification faster. - */ - private HashMap<String, Entry> mPendingNotifications; + private NotificationEntryManager mEntryManager; private boolean mIsDozing; @@ -85,6 +77,23 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen Dependency.get(StatusBarStateController.class).addCallback(this); } + /** Causes the TransferHelper to register itself as a listener to the appropriate classes. */ + public void bind(NotificationEntryManager entryManager, + NotificationGroupManager groupManager) { + if (mEntryManager != null) { + throw new IllegalStateException("Already bound."); + } + + // TODO(b/119637830): It would be good if GroupManager already had all pending notifications + // as normal children (i.e. add notifications to GroupManager before inflation) so that we + // don't have to have this dependency. We'd also have to worry less about the suppression + // not being up to date. + mEntryManager = entryManager; + + mEntryManager.setAlertTransferListener(mAlertTransferListener); + groupManager.addOnGroupChangeListener(mOnGroupChangeListener); + } + /** * Whether or not a notification has transferred its alert state to the notification and * the notification should alert after inflating. @@ -97,25 +106,10 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen return alertInfo != null && alertInfo.isStillValid(); } - /** - * Removes any alerts pending on this entry. Note that this will not stop any inflation tasks - * started by a transfer, so this should only be used as clean-up for when inflation is stopped - * and the pending alert no longer needs to happen. - * - * @param key notification key that may have info that needs to be cleaned up - */ - public void cleanUpPendingAlertInfo(@NonNull String key) { - mPendingAlerts.remove(key); - } - public void setHeadsUpManager(HeadsUpManager headsUpManager) { mHeadsUpManager = headsUpManager; } - public void setPendingEntries(HashMap<String, Entry> pendingNotifications) { - mPendingNotifications = pendingNotifications; - } - @Override public void onStateChanged(int newState) {} @@ -130,43 +124,45 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen mIsDozing = isDozing; } - @Override - public void onGroupCreated(NotificationGroup group, String groupKey) { - mGroupAlertEntries.put(groupKey, new GroupAlertEntry(group)); - } + private final OnGroupChangeListener mOnGroupChangeListener = new OnGroupChangeListener() { + @Override + public void onGroupCreated(NotificationGroup group, String groupKey) { + mGroupAlertEntries.put(groupKey, new GroupAlertEntry(group)); + } - @Override - public void onGroupRemoved(NotificationGroup group, String groupKey) { - mGroupAlertEntries.remove(groupKey); - } + @Override + public void onGroupRemoved(NotificationGroup group, String groupKey) { + mGroupAlertEntries.remove(groupKey); + } - @Override - public void onGroupSuppressionChanged(NotificationGroup group, boolean suppressed) { - AlertingNotificationManager alertManager = getActiveAlertManager(); - if (suppressed) { - if (alertManager.isAlerting(group.summary.key)) { - handleSuppressedSummaryAlerted(group.summary, alertManager); - } - } else { - // Group summary can be null if we are no longer suppressed because the summary was - // removed. In that case, we don't need to alert the summary. - if (group.summary == null) { - return; - } - GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey( - group.summary.notification)); - // Group is no longer suppressed. We should check if we need to transfer the alert - // back to the summary now that it's no longer suppressed. - if (groupAlertEntry.mAlertSummaryOnNextAddition) { - if (!alertManager.isAlerting(group.summary.key)) { - alertNotificationWhenPossible(group.summary, alertManager); + @Override + public void onGroupSuppressionChanged(NotificationGroup group, boolean suppressed) { + AlertingNotificationManager alertManager = getActiveAlertManager(); + if (suppressed) { + if (alertManager.isAlerting(group.summary.key)) { + handleSuppressedSummaryAlerted(group.summary, alertManager); } - groupAlertEntry.mAlertSummaryOnNextAddition = false; } else { - checkShouldTransferBack(groupAlertEntry); + // Group summary can be null if we are no longer suppressed because the summary was + // removed. In that case, we don't need to alert the summary. + if (group.summary == null) { + return; + } + GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey( + group.summary.notification)); + // Group is no longer suppressed. We should check if we need to transfer the alert + // back to the summary now that it's no longer suppressed. + if (groupAlertEntry.mAlertSummaryOnNextAddition) { + if (!alertManager.isAlerting(group.summary.key)) { + alertNotificationWhenPossible(group.summary, alertManager); + } + groupAlertEntry.mAlertSummaryOnNextAddition = false; + } else { + checkShouldTransferBack(groupAlertEntry); + } } } - } + }; @Override public void onAmbientStateChanged(Entry entry, boolean isAmbient) { @@ -185,37 +181,42 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen } } - /** - * Called when the entry's reinflation has finished. If there is an alert pending, we then - * show the alert. - * - * @param entry entry whose inflation has finished - */ - public void onInflationFinished(@NonNull Entry entry) { - PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.key); - if (alertInfo != null) { - if (alertInfo.isStillValid()) { - alertNotificationWhenPossible(entry, getActiveAlertManager()); - } else { - // The transfer is no longer valid. Free the content. - entry.getRow().freeContentViewWhenSafe(alertInfo.mAlertManager.getContentFlag()); + private final AlertTransferListener mAlertTransferListener = new AlertTransferListener() { + // Called when a new notification has been posted but is not inflated yet. We use this to + // see as early as we can if we need to abort a transfer. + @Override + public void onPendingEntryAdded(Entry entry) { + String groupKey = mGroupManager.getGroupKey(entry.notification); + GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey); + if (groupAlertEntry != null) { + checkShouldTransferBack(groupAlertEntry); } } - } - /** - * Called when a new notification has been posted but is not inflated yet. We use this to see - * as early as we can if we need to abort a transfer. - * - * @param entry entry that has been added - */ - public void onPendingEntryAdded(@NonNull Entry entry) { - String groupKey = mGroupManager.getGroupKey(entry.notification); - GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey); - if (groupAlertEntry != null) { - checkShouldTransferBack(groupAlertEntry); + // Called when the entry's reinflation has finished. If there is an alert pending, we + // then show the alert. + @Override + public void onEntryReinflated(Entry entry) { + PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.key); + if (alertInfo != null) { + if (alertInfo.isStillValid()) { + alertNotificationWhenPossible(entry, getActiveAlertManager()); + } else { + // The transfer is no longer valid. Free the content. + entry.getRow().freeContentViewWhenSafe( + alertInfo.mAlertManager.getContentFlag()); + } + } } - } + + @Override + public void onEntryRemoved(Entry entry) { + // Removes any alerts pending on this entry. Note that this will not stop any inflation + // tasks started by a transfer, so this should only be used as clean-up for when + // inflation is stopped and the pending alert no longer needs to happen. + mPendingAlerts.remove(entry.key); + } + }; /** * Gets the number of new notifications pending inflation that will be added to the group @@ -225,11 +226,11 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen * @return the number of new notifications that will be added to the group */ private int getPendingChildrenNotAlerting(@NonNull NotificationGroup group) { - if (mPendingNotifications == null) { + if (mEntryManager == null) { return 0; } int number = 0; - Collection<Entry> values = mPendingNotifications.values(); + Iterable<Entry> values = mEntryManager.getPendingNotificationsIterator(); for (Entry entry : values) { if (isPendingNotificationInGroup(entry, group) && onlySummaryAlerts(entry)) { number++; @@ -245,10 +246,10 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen * @return true if a pending notification will add to this group */ private boolean pendingInflationsWillAddChildren(@NonNull NotificationGroup group) { - if (mPendingNotifications == null) { + if (mEntryManager == null) { return false; } - Collection<Entry> values = mPendingNotifications.values(); + Iterable<Entry> values = mEntryManager.getPendingNotificationsIterator(); for (Entry entry : values) { if (isPendingNotificationInGroup(entry, group)) { return true; 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 1d6a1e81246c..a508f1bdbd34 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -628,6 +628,8 @@ public class StatusBar extends SystemUI implements DemoMode, mBubbleController = Dependency.get(BubbleController.class); mBubbleController.setExpandListener(mBubbleExpandListener); + mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager); + mColorExtractor.addOnColorsChangedListener(this); mStatusBarStateController.addCallback(this, StatusBarStateController.RANK_STATUS_BAR); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DarkIconDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DarkIconDispatcher.java index 945ed761c2b4..0823db9aa7a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DarkIconDispatcher.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DarkIconDispatcher.java @@ -19,9 +19,17 @@ import android.graphics.Rect; import android.view.View; import android.widget.ImageView; +import com.android.systemui.Dumpable; import com.android.systemui.statusbar.phone.LightBarTransitionsController; -public interface DarkIconDispatcher { +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * Dispatches events to {@link DarkReceiver}s about changes in darkness, tint area and dark + * intensity + */ +public interface DarkIconDispatcher extends Dumpable { void setIconsDarkArea(Rect r); LightBarTransitionsController getTransitionsController(); @@ -37,6 +45,11 @@ public interface DarkIconDispatcher { // addDarkReceiver. void applyDark(DarkReceiver object); + /** + * Dumpable interface + */ + default void dump(FileDescriptor fd, PrintWriter pw, String[] args) {} + int DEFAULT_ICON_TINT = Color.WHITE; Rect sTmpRect = new Rect(); int[] sTmpInt2 = new int[2]; diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/SensorPrivacyTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/SensorPrivacyTileTest.java new file mode 100644 index 000000000000..90792e3d958f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/SensorPrivacyTileTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles; + +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.SensorPrivacyManager; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.systemui.Dependency; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.qs.QSTileHost; +import com.android.systemui.statusbar.policy.KeyguardMonitor; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +@SmallTest +public class SensorPrivacyTileTest extends SysuiTestCase { + + @Mock + private KeyguardMonitor mKeyguard; + @Mock + private QSTileHost mHost; + @Mock + SensorPrivacyManager mSensorPrivacyManager; + + private TestableLooper mTestableLooper; + + private SensorPrivacyTile mSensorPrivacyTile; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mTestableLooper = TestableLooper.get(this); + mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); + mKeyguard = mDependency.injectMockDependency(KeyguardMonitor.class); + + mSensorPrivacyManager = mDependency.injectMockDependency(SensorPrivacyManager.class); + + when(mHost.getContext()).thenReturn(mContext); + + mSensorPrivacyTile = new SensorPrivacyTile(mHost); + } + + @Test + public void testSensorPrivacyListenerAdded_handleListeningTrue() { + // To prevent access to privacy related features from apps with WRITE_SECURE_SETTINGS the + // sensor privacy state is not stored in Settings; to receive notification apps must add + // themselves as a listener with the SensorPrivacyManager. This test verifies when + // setListening is called with a value of true the tile adds itself as a listener. + mSensorPrivacyTile.handleSetListening(true); + mTestableLooper.processAllMessages(); + verify(mSensorPrivacyManager).addSensorPrivacyListener(mSensorPrivacyTile); + } + + @Test + public void testSensorPrivacyListenerRemoved_handleListeningFalse() { + // Similar to the test above verifies that the tile removes itself as a listener when + // setListening is called with a value of false. + mSensorPrivacyTile.handleSetListening(false); + mTestableLooper.processAllMessages(); + verify(mSensorPrivacyManager).removeSensorPrivacyListener((mSensorPrivacyTile)); + } + + @Test + public void testSensorPrivacyEnabled_handleClick() { + // Verifies when the SensorPrivacy tile is clicked it invokes the SensorPrivacyManager to + // set sensor privacy. + mSensorPrivacyTile.getState().value = false; + mSensorPrivacyTile.handleClick(); + mTestableLooper.processAllMessages(); + verify(mSensorPrivacyManager).setSensorPrivacy(true); + + mSensorPrivacyTile.getState().value = true; + mSensorPrivacyTile.handleClick(); + mTestableLooper.processAllMessages(); + verify(mSensorPrivacyManager).setSensorPrivacy(false); + } + + @Test + public void testSensorPrivacyNotDisabled_keyguard() { + // Verifies when the device is locked that sensor privacy cannot be disabled + when(mKeyguard.isSecure()).thenReturn(true); + when(mKeyguard.isShowing()).thenReturn(true); + mSensorPrivacyTile.getState().value = true; + mSensorPrivacyTile.handleClick(); + mTestableLooper.processAllMessages(); + verify(mSensorPrivacyManager, never()).setSensorPrivacy(false); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java index 8e88ed0556bf..def7513bc7dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java @@ -475,14 +475,14 @@ public class NotificationDataTest extends SysuiTestCase { outRanking.getImportance(), outRanking.getImportanceExplanation(), outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null, outRanking.canShowBadge(), outRanking.getUserSentiment(), true, - false, false, null, null); + -1, false, null, null); } else if (key.equals(TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY)) { outRanking.populate(key, outRanking.getRank(), outRanking.matchesInterruptionFilter(), outRanking.getVisibilityOverride(), 255, outRanking.getImportance(), outRanking.getImportanceExplanation(), outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null, - outRanking.canShowBadge(), outRanking.getUserSentiment(), true, false, + outRanking.canShowBadge(), outRanking.getUserSentiment(), true, -1, false, null, null); } else { outRanking.populate(key, outRanking.getRank(), @@ -490,7 +490,7 @@ public class NotificationDataTest extends SysuiTestCase { outRanking.getVisibilityOverride(), outRanking.getSuppressedVisualEffects(), outRanking.getImportance(), outRanking.getImportanceExplanation(), outRanking.getOverrideGroupKey(), NOTIFICATION_CHANNEL, null, null, - outRanking.canShowBadge(), outRanking.getUserSentiment(), false, false, + outRanking.canShowBadge(), outRanking.getUserSentiment(), false, -1, false, null, null); } return true; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index 8706e214db8e..904e5b99b0ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -167,7 +167,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { 0, NotificationManager.IMPORTANCE_DEFAULT, null, null, - null, null, null, true, sentiment, false, false, false, null, null); + null, null, null, true, sentiment, false, -1, false, null, null); return true; }).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class)); } @@ -185,7 +185,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { NotificationManager.IMPORTANCE_DEFAULT, null, null, null, null, null, true, - NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL, false, false, + NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL, false, -1, false, smartActions, null); return true; }).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java index c3bc5110febe..ee39e10b4165 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java @@ -32,14 +32,19 @@ import android.testing.TestableLooper; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.AmbientPulseManager; +import com.android.systemui.statusbar.notification.AlertTransferListener; import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationData.Entry; +import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.policy.HeadsUpManager; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -49,13 +54,15 @@ import java.util.HashMap; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { - @Rule - public MockitoRule rule = MockitoJUnit.rule(); + @Rule public MockitoRule rule = MockitoJUnit.rule(); private NotificationGroupAlertTransferHelper mGroupAlertTransferHelper; private NotificationGroupManager mGroupManager; private AmbientPulseManager mAmbientPulseManager; private HeadsUpManager mHeadsUpManager; + @Mock private NotificationEntryManager mNotificationEntryManager; + @Captor private ArgumentCaptor<AlertTransferListener> mListenerCaptor; + private AlertTransferListener mAlertTransferListener; private final HashMap<String, Entry> mPendingEntries = new HashMap<>(); private final NotificationGroupTestHelper mGroupTestHelper = new NotificationGroupTestHelper(mContext); @@ -67,15 +74,19 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mDependency.injectTestDependency(AmbientPulseManager.class, mAmbientPulseManager); mHeadsUpManager = new HeadsUpManager(mContext) {}; + when(mNotificationEntryManager.getPendingNotificationsIterator()) + .thenReturn(mPendingEntries.values()); + mGroupManager = new NotificationGroupManager(); mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager); mGroupManager.setHeadsUpManager(mHeadsUpManager); mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper(); mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager); - mGroupAlertTransferHelper.setPendingEntries(mPendingEntries); - mGroupManager.addOnGroupChangeListener(mGroupAlertTransferHelper); + mGroupAlertTransferHelper.bind(mNotificationEntryManager, mGroupManager); + verify(mNotificationEntryManager).setAlertTransferListener(mListenerCaptor.capture()); + mAlertTransferListener = mListenerCaptor.getValue(); mHeadsUpManager.addListener(mGroupAlertTransferHelper); mAmbientPulseManager.addListener(mGroupAlertTransferHelper); } @@ -110,7 +121,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { // Add second child notification so that summary is no longer suppressed. mPendingEntries.put(childEntry2.key, childEntry2); - mGroupAlertTransferHelper.onPendingEntryAdded(childEntry2); + mAlertTransferListener.onPendingEntryAdded(childEntry2); mGroupManager.onEntryAdded(childEntry2); // The alert state should transfer back to the summary as there is now more than one @@ -137,7 +148,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { // Add second child notification so that summary is no longer suppressed. mPendingEntries.put(childEntry2.key, childEntry2); - mGroupAlertTransferHelper.onPendingEntryAdded(childEntry2); + mAlertTransferListener.onPendingEntryAdded(childEntry2); mGroupManager.onEntryAdded(childEntry2); // Dozing changed so no reason to re-alert summary. @@ -175,7 +186,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) .thenReturn(true); - mGroupAlertTransferHelper.onInflationFinished(childEntry); + mAlertTransferListener.onEntryReinflated(childEntry); // Alert is immediately removed from summary, and we show child as its content is inflated. assertFalse(mHeadsUpManager.isAlerting(summaryEntry.key)); @@ -199,13 +210,13 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { // Add second child notification so that summary is no longer suppressed. mPendingEntries.put(childEntry2.key, childEntry2); - mGroupAlertTransferHelper.onPendingEntryAdded(childEntry2); + mAlertTransferListener.onPendingEntryAdded(childEntry2); mGroupManager.onEntryAdded(childEntry2); // Child entry finishes its inflation. when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) .thenReturn(true); - mGroupAlertTransferHelper.onInflationFinished(childEntry); + mAlertTransferListener.onEntryReinflated(childEntry); verify(childEntry.getRow(), times(1)).freeContentViewWhenSafe(mHeadsUpManager .getContentFlag()); @@ -225,7 +236,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mGroupManager.onEntryAdded(summaryEntry); mGroupManager.onEntryAdded(childEntry); - mGroupAlertTransferHelper.cleanUpPendingAlertInfo(childEntry.key); + mAlertTransferListener.onEntryRemoved(childEntry); assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry)); } diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto index eb0090be4747..66d64b1a5f7a 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -6642,6 +6642,13 @@ message MetricsEvent { // OS: Q SETTINGS_FINANCIAL_APPS_SMS_ACCESS = 1597; + // OPEN: QS Sensor Privacy Mode tile shown + // ACTION: QS Sensor Privacy Mode tile tapped + // SUBTYPE: 0 is off, 1 is on + // CATEGORY: QUICK_SETTINGS + // OS: Q + QS_SENSOR_PRIVACY = 1598; + // ---- End Q Constants, all Q constants go above this line ---- // Add new aosp constants above this line. diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 8b992ebcf059..4e8ef54a6465 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -22,6 +22,7 @@ import static android.provider.Settings.Global.LOCATION_DISABLE_STATUS_CALLBACKS import static com.android.internal.util.Preconditions.checkState; +import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -189,6 +190,8 @@ public class LocationManagerService extends ILocationManager.Stub { private LocationBlacklist mBlacklist; private GnssMeasurementsProvider mGnssMeasurementsProvider; private GnssNavigationMessageProvider mGnssNavigationMessageProvider; + private String mLocationControllerExtraPackage; + private boolean mLocationControllerExtraPackageEnabled; private IGpsGeofenceHardware mGpsGeofenceProxy; // --- fields below are protected by mLock --- @@ -2717,6 +2720,39 @@ public class LocationManagerService extends ILocationManager.Stub { return null; } + @Override + public void setLocationControllerExtraPackage(String packageName) { + mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE, + Manifest.permission.LOCATION_HARDWARE + " permission required"); + synchronized (mLock) { + mLocationControllerExtraPackage = packageName; + } + } + + @Override + public String getLocationControllerExtraPackage() { + synchronized (mLock) { + return mLocationControllerExtraPackage; + } + } + + @Override + public void setLocationControllerExtraPackageEnabled(boolean enabled) { + mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE, + Manifest.permission.LOCATION_HARDWARE + " permission required"); + synchronized (mLock) { + mLocationControllerExtraPackageEnabled = enabled; + } + } + + @Override + public boolean isLocationControllerExtraPackageEnabled() { + synchronized (mLock) { + return mLocationControllerExtraPackageEnabled + && (mLocationControllerExtraPackage != null); + } + } + /** * Returns the current location enabled/disabled status for a user * @@ -2763,7 +2799,7 @@ public class LocationManagerService extends ILocationManager.Stub { */ @Override public void setLocationEnabledForUser(boolean enabled, int userId) { - mContext.enforceCallingPermission( + mContext.enforceCallingOrSelfPermission( android.Manifest.permission.WRITE_SECURE_SETTINGS, "Requires WRITE_SECURE_SETTINGS permission"); @@ -3492,6 +3528,11 @@ public class LocationManagerService extends ILocationManager.Stub { } } + if (mLocationControllerExtraPackage != null) { + pw.println(" Location controller extra package: " + mLocationControllerExtraPackage + + " enabled: " + mLocationControllerExtraPackageEnabled); + } + if (!mBackgroundThrottlePackageWhitelist.isEmpty()) { pw.println(" Throttling Whitelisted Packages:"); for (String packageName : mBackgroundThrottlePackageWhitelist) { diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 4678fec377d0..8869af4e334a 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -164,8 +164,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub private static final int MAX_UID_RANGES_PER_COMMAND = 10; - private static final String[] EMPTY_STRING_ARRAY = new String[0]; - /** * Name representing {@link #setGlobalAlert(long)} limit when delivered to * {@link INetworkManagementEventObserver#limitReached(String, String)}. @@ -959,8 +957,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub public String[] listInterfaces() { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - final List<String> result = mNetdService.interfaceGetList(); - return result.toArray(EMPTY_STRING_ARRAY); + return mNetdService.interfaceGetList(); } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); } @@ -1252,8 +1249,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub public String[] listTetheredInterfaces() { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - final List<String> result = mNetdService.tetherInterfaceList(); - return result.toArray(EMPTY_STRING_ARRAY); + return mNetdService.tetherInterfaceList(); } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); } @@ -1276,8 +1272,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub public String[] getDnsForwarders() { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - final List<String> result = mNetdService.tetherDnsList(); - return result.toArray(EMPTY_STRING_ARRAY); + return mNetdService.tetherDnsList(); } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); } diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java new file mode 100644 index 000000000000..1cbcbe5a8bdb --- /dev/null +++ b/services/core/java/com/android/server/SensorPrivacyService.java @@ -0,0 +1,426 @@ +/* + * 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 static android.content.pm.PackageManager.PERMISSION_GRANTED; + +import android.app.ActivityManager; +import android.content.Context; +import android.hardware.ISensorPrivacyListener; +import android.hardware.ISensorPrivacyManager; +import android.location.LocationManager; +import android.net.ConnectivityManager; +import android.os.Environment; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.ArrayMap; +import android.util.AtomicFile; +import android.util.Log; +import android.util.Xml; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.XmlUtils; +import com.android.internal.util.function.pooled.PooledLambda; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.NoSuchElementException; + +/** @hide */ +public final class SensorPrivacyService extends SystemService { + + private static final String TAG = "SensorPrivacyService"; + + private static final String SENSOR_PRIVACY_XML_FILE = "sensor_privacy.xml"; + private static final String XML_TAG_SENSOR_PRIVACY = "sensor-privacy"; + private static final String XML_ATTRIBUTE_ENABLED = "enabled"; + + private final SensorPrivacyServiceImpl mSensorPrivacyServiceImpl; + + public SensorPrivacyService(Context context) { + super(context); + mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl(context); + } + + @Override + public void onStart() { + publishBinderService(Context.SENSOR_PRIVACY_SERVICE, mSensorPrivacyServiceImpl); + } + + class SensorPrivacyServiceImpl extends ISensorPrivacyManager.Stub { + + private final SensorPrivacyHandler mHandler; + private final Context mContext; + private final Object mLock = new Object(); + @GuardedBy("mLock") + private final AtomicFile mAtomicFile; + @GuardedBy("mLock") + private boolean mEnabled; + + SensorPrivacyServiceImpl(Context context) { + mContext = context; + mHandler = new SensorPrivacyHandler(FgThread.get().getLooper(), mContext); + File sensorPrivacyFile = new File(Environment.getDataSystemDirectory(), + SENSOR_PRIVACY_XML_FILE); + mAtomicFile = new AtomicFile(sensorPrivacyFile); + synchronized (mLock) { + mEnabled = readPersistedSensorPrivacyEnabledLocked(); + } + } + + /** + * Sets the sensor privacy to the provided state and notifies all listeners of the new + * state. + */ + @Override + public void setSensorPrivacy(boolean enable) { + enforceSensorPrivacyPermission(); + synchronized (mLock) { + mEnabled = enable; + FileOutputStream outputStream = null; + try { + XmlSerializer serializer = new FastXmlSerializer(); + outputStream = mAtomicFile.startWrite(); + serializer.setOutput(outputStream, StandardCharsets.UTF_8.name()); + serializer.startDocument(null, true); + serializer.startTag(null, XML_TAG_SENSOR_PRIVACY); + serializer.attribute(null, XML_ATTRIBUTE_ENABLED, String.valueOf(enable)); + serializer.endTag(null, XML_TAG_SENSOR_PRIVACY); + serializer.endDocument(); + mAtomicFile.finishWrite(outputStream); + } catch (IOException e) { + Log.e(TAG, "Caught an exception persisting the sensor privacy state: ", e); + mAtomicFile.failWrite(outputStream); + } + } + mHandler.onSensorPrivacyChanged(enable); + } + + /** + * Enforces the caller contains the necessary permission to change the state of sensor + * privacy. + */ + private void enforceSensorPrivacyPermission() { + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MANAGE_SENSOR_PRIVACY) == PERMISSION_GRANTED) { + return; + } + throw new SecurityException( + "Changing sensor privacy requires the following permission: " + + android.Manifest.permission.MANAGE_SENSOR_PRIVACY); + } + + /** + * Returns whether sensor privacy is enabled. + */ + @Override + public boolean isSensorPrivacyEnabled() { + synchronized (mLock) { + return mEnabled; + } + } + + /** + * Returns the state of sensor privacy from persistent storage. + */ + private boolean readPersistedSensorPrivacyEnabledLocked() { + // if the file does not exist then sensor privacy has not yet been enabled on + // the device. + if (!mAtomicFile.exists()) { + return false; + } + boolean enabled; + try (FileInputStream inputStream = mAtomicFile.openRead()) { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(inputStream, StandardCharsets.UTF_8.name()); + XmlUtils.beginDocument(parser, XML_TAG_SENSOR_PRIVACY); + parser.next(); + String tagName = parser.getName(); + enabled = Boolean.valueOf(parser.getAttributeValue(null, XML_ATTRIBUTE_ENABLED)); + } catch (IOException | XmlPullParserException e) { + Log.e(TAG, "Caught an exception reading the state from storage: ", e); + // Delete the file to prevent the same error on subsequent calls and assume sensor + // privacy is not enabled. + mAtomicFile.delete(); + enabled = false; + } + return enabled; + } + + /** + * Persists the state of sensor privacy. + */ + private void persistSensorPrivacyState() { + synchronized (mLock) { + FileOutputStream outputStream = null; + try { + XmlSerializer serializer = new FastXmlSerializer(); + outputStream = mAtomicFile.startWrite(); + serializer.setOutput(outputStream, StandardCharsets.UTF_8.name()); + serializer.startDocument(null, true); + serializer.startTag(null, XML_TAG_SENSOR_PRIVACY); + serializer.attribute(null, XML_ATTRIBUTE_ENABLED, String.valueOf(mEnabled)); + serializer.endTag(null, XML_TAG_SENSOR_PRIVACY); + serializer.endDocument(); + mAtomicFile.finishWrite(outputStream); + } catch (IOException e) { + Log.e(TAG, "Caught an exception persisting the sensor privacy state: ", e); + mAtomicFile.failWrite(outputStream); + } + } + } + + /** + * Registers a listener to be notified when the sensor privacy state changes. + */ + @Override + public void addSensorPrivacyListener(ISensorPrivacyListener listener) { + if (listener == null) { + throw new NullPointerException("listener cannot be null"); + } + mHandler.addListener(listener); + } + + /** + * Unregisters a listener from sensor privacy state change notifications. + */ + @Override + public void removeSensorPrivacyListener(ISensorPrivacyListener listener) { + if (listener == null) { + throw new NullPointerException("listener cannot be null"); + } + mHandler.removeListener(listener); + } + } + + /** + * Handles sensor privacy state changes and notifying listeners of the change. + */ + private final class SensorPrivacyHandler extends Handler { + private static final int MESSAGE_SENSOR_PRIVACY_CHANGED = 1; + + private final Object mListenerLock = new Object(); + + @GuardedBy("mListenerLock") + private final RemoteCallbackList<ISensorPrivacyListener> mListeners = + new RemoteCallbackList<>(); + private final ArrayMap<ISensorPrivacyListener, DeathRecipient> mDeathRecipients; + private final Context mContext; + + SensorPrivacyHandler(Looper looper, Context context) { + super(looper); + mDeathRecipients = new ArrayMap<>(); + mContext = context; + } + + public void onSensorPrivacyChanged(boolean enabled) { + sendMessage(PooledLambda.obtainMessage(SensorPrivacyHandler::handleSensorPrivacyChanged, + this, enabled)); + sendMessage( + PooledLambda.obtainMessage(SensorPrivacyServiceImpl::persistSensorPrivacyState, + mSensorPrivacyServiceImpl)); + } + + public void addListener(ISensorPrivacyListener listener) { + synchronized (mListenerLock) { + DeathRecipient deathRecipient = new DeathRecipient(listener); + mDeathRecipients.put(listener, deathRecipient); + mListeners.register(listener); + } + } + + public void removeListener(ISensorPrivacyListener listener) { + synchronized (mListenerLock) { + DeathRecipient deathRecipient = mDeathRecipients.remove(listener); + if (deathRecipient != null) { + deathRecipient.destroy(); + } + mListeners.unregister(listener); + } + } + + public void handleSensorPrivacyChanged(boolean enabled) { + final int count = mListeners.beginBroadcast(); + for (int i = 0; i < count; i++) { + ISensorPrivacyListener listener = mListeners.getBroadcastItem(i); + try { + listener.onSensorPrivacyChanged(enabled); + } catch (RemoteException e) { + Log.e(TAG, "Caught an exception notifying listener " + listener + ": ", e); + } + } + mListeners.finishBroadcast(); + // Handle the state of all sensors managed by this service. + SensorState.handleSensorPrivacyToggled(mContext, enabled); + } + } + + private final class DeathRecipient implements IBinder.DeathRecipient { + + private ISensorPrivacyListener mListener; + + DeathRecipient(ISensorPrivacyListener listener) { + mListener = listener; + try { + mListener.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + } + } + + @Override + public void binderDied() { + mSensorPrivacyServiceImpl.removeSensorPrivacyListener(mListener); + } + + public void destroy() { + try { + mListener.asBinder().unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + } + } + } + + /** + * Maintains the state of the sensors when sensor privacy is enabled to return them to their + * original state when sensor privacy is disabled. + */ + private static final class SensorState { + + private static Object sLock = new Object(); + @GuardedBy("sLock") + private static SensorState sPreviousState; + + private boolean mAirplaneEnabled; + private boolean mLocationEnabled; + + SensorState(boolean airplaneEnabled, boolean locationEnabled) { + mAirplaneEnabled = airplaneEnabled; + mLocationEnabled = locationEnabled; + } + + public static void handleSensorPrivacyToggled(Context context, boolean enabled) { + synchronized (sLock) { + SensorState state; + if (enabled) { + // if sensor privacy is being enabled then obtain the current state of the + // sensors to be persisted and restored when sensor privacy is disabled. + state = getCurrentSensorState(context); + } else { + // else obtain the previous sensor state to be restored, first from the saved + // state if available, otherwise attempt to read it from Settings. + if (sPreviousState != null) { + state = sPreviousState; + } else { + state = getPersistedSensorState(context); + } + // if the previous state is not available then return without attempting to + // modify the sensor state. + if (state == null) { + return; + } + } + // The SensorState represents the state of the sensor before sensor privacy was + // enabled; if airplane mode was not enabled then the state of airplane mode should + // be the same as the state of sensor privacy. + if (!state.mAirplaneEnabled) { + setAirplaneMode(context, enabled); + } + // Similar to airplane mode the state of location should be the opposite of sensor + // privacy mode, if it was enabled when sensor privacy was enabled then it should be + // disabled. If location is disabled when sensor privacy is enabled then it will be + // left disabled when sensor privacy is disabled. + if (state.mLocationEnabled) { + setLocationEnabled(context, !enabled); + } + + // if sensor privacy is being enabled then persist the current state. + if (enabled) { + sPreviousState = state; + persistState(context, sPreviousState); + } + } + } + + public static SensorState getCurrentSensorState(Context context) { + LocationManager locationManager = (LocationManager) context.getSystemService( + Context.LOCATION_SERVICE); + boolean airplaneEnabled = Settings.Global.getInt(context.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) != 0; + boolean locationEnabled = locationManager.isLocationEnabled(); + return new SensorState(airplaneEnabled, locationEnabled); + } + + public static void persistState(Context context, SensorState state) { + StringBuilder stateValue = new StringBuilder(); + stateValue.append(state.mAirplaneEnabled + ? Settings.Secure.MAINTAIN_AIRPLANE_MODE_AFTER_SP_DISABLED + : Settings.Secure.DISABLE_AIRPLANE_MODE_AFTER_SP_DISABLED); + stateValue.append(","); + stateValue.append( + state.mLocationEnabled ? Settings.Secure.REENABLE_LOCATION_AFTER_SP_DISABLED + : Settings.Secure.MAINTAIN_LOCATION_AFTER_SP_DISABLED); + Settings.Secure.putString(context.getContentResolver(), + Settings.Secure.SENSOR_PRIVACY_SENSOR_STATE, stateValue.toString()); + } + + public static SensorState getPersistedSensorState(Context context) { + String persistedState = Settings.Secure.getString(context.getContentResolver(), + Settings.Secure.SENSOR_PRIVACY_SENSOR_STATE); + if (persistedState == null) { + Log.e(TAG, "The persisted sensor state could not be obtained from Settings"); + return null; + } + String[] sensorStates = persistedState.split(","); + if (sensorStates.length < 2) { + Log.e(TAG, "The persisted sensor state does not contain the expected values: " + + persistedState); + return null; + } + boolean airplaneEnabled = sensorStates[0].equals( + Settings.Secure.MAINTAIN_AIRPLANE_MODE_AFTER_SP_DISABLED); + boolean locationEnabled = sensorStates[1].equals( + Settings.Secure.REENABLE_LOCATION_AFTER_SP_DISABLED); + return new SensorState(airplaneEnabled, locationEnabled); + } + + private static void setAirplaneMode(Context context, boolean enable) { + ConnectivityManager connectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + connectivityManager.setAirplaneMode(enable); + } + + private static void setLocationEnabled(Context context, boolean enable) { + LocationManager locationManager = (LocationManager) context.getSystemService( + Context.LOCATION_SERVICE); + locationManager.setLocationEnabledForUser(enable, + UserHandle.of(ActivityManager.getCurrentUser())); + } + } +} diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 581d4350d28d..6c62725bb5bd 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -57,6 +57,7 @@ import android.app.KeyguardManager; import android.app.admin.SecurityLog; import android.app.usage.StorageStatsManager; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -777,6 +778,18 @@ class StorageManagerService extends IStorageManager.Stub } }); refreshZramSettings(); + + // Toggle isolated-enable system property in response to settings + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.ISOLATED_STORAGE_REMOTE), + false /*notifyForDescendants*/, + new ContentObserver(null /* current thread */) { + @Override + public void onChange(boolean selfChange) { + refreshIsolatedStorageSettings(); + } + }); + refreshIsolatedStorageSettings(); } /** @@ -802,6 +815,32 @@ class StorageManagerService extends IStorageManager.Stub } } + private void refreshIsolatedStorageSettings() { + final int local = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.ISOLATED_STORAGE_LOCAL, 0); + final int remote = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.ISOLATED_STORAGE_REMOTE, 0); + + // Walk down precedence chain; we prefer local settings first, then + // remote settings, before finally falling back to hard-coded default. + final boolean res; + if (local == -1) { + res = false; + } else if (local == 1) { + res = true; + } else if (remote == -1) { + res = false; + } else if (remote == 1) { + res = true; + } else { + res = false; + } + + Slog.d(TAG, "Isolated storage local flag " + local + " and remote flag " + + remote + " resolved to " + res); + SystemProperties.set(StorageManager.PROP_ISOLATED_STORAGE, Boolean.toString(res)); + } + /** * MediaProvider has a ton of code that makes assumptions about storage * paths never changing, so we outright kill them to pick up new state. @@ -2208,18 +2247,22 @@ class StorageManagerService extends IStorageManager.Stub } } - if ((mask & StorageManager.DEBUG_ISOLATED_STORAGE) != 0) { - final boolean enabled = (flags & StorageManager.DEBUG_ISOLATED_STORAGE) != 0; + if ((mask & (StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_ON + | StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_OFF)) != 0) { + final int value; + if ((flags & StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_ON) != 0) { + value = 1; + } else if ((flags & StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_OFF) != 0) { + value = -1; + } else { + value = 0; + } final long token = Binder.clearCallingIdentity(); try { - SystemProperties.set(StorageManager.PROP_ISOLATED_STORAGE, - Boolean.toString(enabled)); - - // Some of the storage related permissions get fiddled with during - // package scanning. So, delete the package cache to force PackageManagerService - // to do package scanning. - FileUtils.deleteContents(Environment.getPackageCacheDirectory()); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.ISOLATED_STORAGE_LOCAL, value); + refreshIsolatedStorageSettings(); // Perform hard reboot to kick policy into place mContext.getSystemService(PowerManager.class).reboot(null); @@ -3758,6 +3801,8 @@ class StorageManagerService extends IStorageManager.Stub pw.println(); pw.println("Primary storage UUID: " + mPrimaryStorageUuid); + + pw.println(); final Pair<String, Long> pair = StorageManager.getPrimaryStoragePathAndSize(); if (pair == null) { pw.println("Internal storage total size: N/A"); @@ -3770,8 +3815,18 @@ class StorageManagerService extends IStorageManager.Stub pw.print(DataUnit.MEBIBYTES.toBytes(pair.second)); pw.println(" MiB)"); } + + pw.println(); pw.println("Local unlocked users: " + Arrays.toString(mLocalUnlockedUsers)); pw.println("System unlocked users: " + Arrays.toString(mSystemUnlockedUsers)); + + final ContentResolver cr = mContext.getContentResolver(); + pw.println(); + pw.println("Isolated storage, local feature flag: " + + Settings.Global.getInt(cr, Settings.Global.ISOLATED_STORAGE_LOCAL, 0)); + pw.println("Isolated storage, remote feature flag: " + + Settings.Global.getInt(cr, Settings.Global.ISOLATED_STORAGE_REMOTE, 0)); + pw.println("Isolated storage, resolved: " + StorageManager.hasIsolatedStorage()); } synchronized (mObbMounts) { diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index b04ae1746d18..b6c49217f4dc 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -1996,6 +1996,13 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null); } + if ((events & PhoneStateListener.LISTEN_PREFERRED_DATA_SUBID_CHANGE) != 0) { + // It can have either READ_PHONE_STATE or READ_PRIVILEGED_PHONE_STATE. + TelephonyPermissions.checkReadPhoneState(mContext, + SubscriptionManager.INVALID_SUBSCRIPTION_ID, Binder.getCallingPid(), + Binder.getCallingUid(), callingPackage, "listen to " + + "LISTEN_PREFERRED_DATA_SUBID_CHANGE"); + } return true; } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 8751d24bcf67..5afb90d681af 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1964,7 +1964,8 @@ public final class ActiveServices { + " type=" + resolvedType + " callingUid=" + callingUid); userId = mAm.mUserController.handleIncomingUser(callingPid, callingUid, userId, false, - ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE, "service", null); + ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE, "service", + callingPackage); ServiceMap smap = getServiceMapLocked(userId); final ComponentName comp; diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 8571ae6b07f6..1c04a94be7df 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -69,6 +69,7 @@ final class ActivityManagerConstants extends ContentObserver { static final String KEY_PROCESS_START_ASYNC = "process_start_async"; static final String KEY_MEMORY_INFO_THROTTLE_TIME = "memory_info_throttle_time"; static final String KEY_TOP_TO_FGS_GRACE_DURATION = "top_to_fgs_grace_duration"; + static final String KEY_USE_COMPACTION = "use_compaction"; private static final int DEFAULT_MAX_CACHED_PROCESSES = 32; private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000; @@ -99,6 +100,7 @@ final class ActivityManagerConstants extends ContentObserver { private static final boolean DEFAULT_PROCESS_START_ASYNC = true; private static final long DEFAULT_MEMORY_INFO_THROTTLE_TIME = 5*60*1000; private static final long DEFAULT_TOP_TO_FGS_GRACE_DURATION = 15 * 1000; + private static final boolean DEFAULT_USE_COMPACTION = false; // Maximum number of cached processes we will allow. public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES; @@ -218,6 +220,9 @@ final class ActivityManagerConstants extends ContentObserver { // this long. public long TOP_TO_FGS_GRACE_DURATION = DEFAULT_TOP_TO_FGS_GRACE_DURATION; + // Use compaction for background apps. + public boolean USE_COMPACTION = DEFAULT_USE_COMPACTION; + // Indicates whether the activity starts logging is enabled. // Controlled by Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED volatile boolean mFlagActivityStartsLoggingEnabled; @@ -375,6 +380,7 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_MEMORY_INFO_THROTTLE_TIME); TOP_TO_FGS_GRACE_DURATION = mParser.getDurationMillis(KEY_TOP_TO_FGS_GRACE_DURATION, DEFAULT_TOP_TO_FGS_GRACE_DURATION); + USE_COMPACTION = mParser.getBoolean(KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION); updateMaxCachedProcesses(); } @@ -465,6 +471,8 @@ final class ActivityManagerConstants extends ContentObserver { pw.println(MEMORY_INFO_THROTTLE_TIME); pw.print(" "); pw.print(KEY_TOP_TO_FGS_GRACE_DURATION); pw.print("="); pw.println(TOP_TO_FGS_GRACE_DURATION); + pw.print(" "); pw.print(KEY_USE_COMPACTION); pw.print("="); + pw.println(USE_COMPACTION); pw.println(); if (mOverrideMaxCachedProcesses >= 0) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 6700a530edc0..739dbbc65f6f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -795,6 +795,11 @@ public class ActivityManagerService extends IActivityManager.Stub */ final ArrayList<ProcessRecord> mPendingPssProcesses = new ArrayList<ProcessRecord>(); + /** + * Processes to compact. + */ + final ArrayList<ProcessRecord> mPendingCompactionProcesses = new ArrayList<ProcessRecord>(); + private boolean mBinderTransactionTrackingEnabled = false; /** @@ -1452,6 +1457,7 @@ public class ActivityManagerService extends IActivityManager.Stub final Handler mUiHandler; final ServiceThread mProcStartHandlerThread; final Handler mProcStartHandler; + final ServiceThread mCompactionThread; final ActivityManagerConstants mConstants; @@ -1789,6 +1795,11 @@ public class ActivityManagerService extends IActivityManager.Stub } }; + static final int COMPACT_PROCESS_SOME = 1; + static final int COMPACT_PROCESS_FULL = 2; + static final int COMPACT_PROCESS_MSG = 1; + final Handler mCompactionHandler; + static final int COLLECT_PSS_BG_MSG = 1; final Handler mBgHandler = new Handler(BackgroundThread.getHandler().getLooper()) { @@ -2224,6 +2235,8 @@ public class ActivityManagerService extends IActivityManager.Stub ? new PendingIntentController(handlerThread.getLooper(), mUserController) : null; mProcStartHandlerThread = null; mProcStartHandler = null; + mCompactionThread = null; + mCompactionHandler = null; mHiddenApiBlacklist = null; mFactoryTest = FACTORY_TEST_OFF; } @@ -2252,6 +2265,88 @@ public class ActivityManagerService extends IActivityManager.Stub mProcStartHandlerThread.start(); mProcStartHandler = new Handler(mProcStartHandlerThread.getLooper()); + mCompactionThread = new ServiceThread("CompactionThread", + THREAD_PRIORITY_FOREGROUND, true); + mCompactionThread.start(); + mCompactionHandler = new Handler(mCompactionThread.getLooper()) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case COMPACT_PROCESS_MSG: { + long start = SystemClock.uptimeMillis(); + ProcessRecord proc; + int pid; + String action; + final String name; + int pendingAction, lastCompactAction; + long lastCompactTime; + synchronized(ActivityManagerService.this) { + proc = mPendingCompactionProcesses.remove(0); + + // don't compact if the process has returned to perceptible + if (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) { + return; + } + + pid = proc.pid; + name = proc.processName; + pendingAction = proc.reqCompactAction; + lastCompactAction = proc.lastCompactAction; + lastCompactTime = proc.lastCompactTime; + } + if (pid == 0) { + // not a real process, either one being launched or one being killed + return; + } + + // basic throttling + if (pendingAction == COMPACT_PROCESS_SOME) { + // if we're compacting some, then compact if >10s after last full + // or >5s after last some + if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < 5000)) || + (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < 10000))) + return; + } else { + // if we're compacting full, then compact if >10s after last full + // or >.5s after last some + if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < 500)) || + (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < 10000))) + return; + } + + try { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact " + + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full") + + ": " + name); + long[] rssBefore = Process.getRss(pid); + FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim"); + if (pendingAction == COMPACT_PROCESS_SOME) { + action = "file"; + } else { + action = "all"; + } + fos.write(action.getBytes()); + fos.close(); + long[] rssAfter = Process.getRss(pid); + long end = SystemClock.uptimeMillis(); + EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action, + rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3], + rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], end-start); + synchronized(ActivityManagerService.this) { + proc.lastCompactTime = end; + proc.lastCompactAction = pendingAction; + } + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } catch (Exception e) { + // nothing to do, presumably the process died + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + } + } + } + }; + + mConstants = new ActivityManagerConstants(this, mHandler); mProcessList.init(this); @@ -2345,13 +2440,15 @@ public class ActivityManagerService extends IActivityManager.Stub Watchdog.getInstance().addMonitor(this); Watchdog.getInstance().addThread(mHandler); - // bind background thread to little cores + // bind background threads to little cores // this is expected to fail inside of framework tests because apps can't touch cpusets directly // make sure we've already adjusted system_server's internal view of itself first updateOomAdjLocked(); try { Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(), - Process.THREAD_GROUP_BG_NONINTERACTIVE); + Process.THREAD_GROUP_SYSTEM); + Process.setThreadGroupAndCpuset(mCompactionThread.getThreadId(), + Process.THREAD_GROUP_SYSTEM); } catch (Exception e) { Slog.w(TAG, "Setting background thread cpuset failed"); } @@ -16921,6 +17018,24 @@ public class ActivityManagerService extends IActivityManager.Stub int changes = 0; if (app.curAdj != app.setAdj) { + // don't compact during bootup + if (mConstants.USE_COMPACTION && mBooted) { + // Perform a minor compaction when a perceptible app becomes the prev/home app + // Perform a major compaction when any app enters cached + // reminder: here, setAdj is previous state, curAdj is upcoming state + if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ && + (app.curAdj == ProcessList.PREVIOUS_APP_ADJ || + app.curAdj == ProcessList.HOME_APP_ADJ)) { + app.reqCompactAction = COMPACT_PROCESS_SOME; + mPendingCompactionProcesses.add(app); + mCompactionHandler.sendEmptyMessage(COMPACT_PROCESS_MSG); + } else if (app.setAdj < ProcessList.CACHED_APP_MIN_ADJ && + app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ) { + app.reqCompactAction = COMPACT_PROCESS_FULL; + mPendingCompactionProcesses.add(app); + mCompactionHandler.sendEmptyMessage(COMPACT_PROCESS_MSG); + } + } ProcessList.setOomAdj(app.pid, app.uid, app.curAdj); if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mCurOomAdjUid == app.info.uid) { String msg = "Set " + app.pid + " " + app.processName + " adj " diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags index 09064f2fc441..fa7a4c532f42 100644 --- a/services/core/java/com/android/server/am/EventLogTags.logtags +++ b/services/core/java/com/android/server/am/EventLogTags.logtags @@ -135,4 +135,7 @@ option java_package com.android.server.am 30062 am_on_activity_result_called (User|1|5),(Component Name|3),(Reason|3) # The task is being removed from its parent stack -30061 am_remove_task (Task ID|1|5), (Stack ID|1|5)
\ No newline at end of file +30061 am_remove_task (Task ID|1|5), (Stack ID|1|5) + +# The task is being compacted +30063 am_compact (Pid|1|5),(Process Name|3),(Action|3),(BeforeRssTotal|2|2),(BeforeRssFile|2|2),(BeforeRssAnon|2|2),(BeforeRssSwap|2|2),(AfterRssTotal|2|2),(AfterRssFile|2|2),(AfterRssAnon|2|2),(AfterRssSwap|2|2),(Time|2|3)
\ No newline at end of file diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 4826f486da89..c4b715042953 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -145,6 +145,9 @@ final class ProcessRecord implements WindowProcessListener { int curAdj; // Current OOM adjustment for this process int setAdj; // Last set OOM adjustment for this process int verifiedAdj; // The last adjustment that was verified as actually being set + long lastCompactTime; // The last time that this process was compacted + int reqCompactAction; // The most recent compaction action requested for this app. + int lastCompactAction; // The most recent compaction action performed for this app. private int mCurSchedGroup; // Currently desired scheduling class int setSchedGroup; // Last set to background scheduling class int trimMemoryLevel; // Last selected memory trimming level @@ -382,6 +385,8 @@ final class ProcessRecord implements WindowProcessListener { pw.print(" setRaw="); pw.print(setRawAdj); pw.print(" cur="); pw.print(curAdj); pw.print(" set="); pw.println(setAdj); + pw.print(prefix); pw.print("lastCompactTime="); pw.print(lastCompactTime); + pw.print(" lastCompactAction="); pw.print(lastCompactAction); pw.print(prefix); pw.print("mCurSchedGroup="); pw.print(mCurSchedGroup); pw.print(" setSchedGroup="); pw.print(setSchedGroup); pw.print(" systemNoUi="); pw.print(systemNoUi); diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index 1325f049e737..10dc156fbc01 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -67,6 +67,7 @@ import android.os.SystemClock; import android.os.Temperature; import android.os.UserHandle; import android.os.UserManagerInternal; +import android.os.WorkSource; import android.provider.Settings; import android.text.format.DateUtils; import android.util.KeyValueListParser; @@ -364,6 +365,8 @@ public class JobSchedulerService extends com.android.server.SystemService private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac"; private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac"; private static final String KEY_USE_HEARTBEATS = "use_heartbeats"; + private static final String KEY_TIME_CONTROLLER_SKIP_NOT_READY_JOBS = + "tc_skip_not_ready_jobs"; private static final String KEY_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = "qc_allowed_time_per_period_ms"; private static final String KEY_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = @@ -376,6 +379,8 @@ public class JobSchedulerService extends com.android.server.SystemService "qc_window_size_frequent_ms"; private static final String KEY_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = "qc_window_size_rare_ms"; + private static final String KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = + "qc_max_execution_time_ms"; private static final int DEFAULT_MIN_IDLE_COUNT = 1; private static final int DEFAULT_MIN_CHARGING_COUNT = 1; @@ -402,6 +407,7 @@ public class JobSchedulerService extends com.android.server.SystemService private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f; private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f; private static final boolean DEFAULT_USE_HEARTBEATS = true; + private static final boolean DEFAULT_TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false; private static final long DEFAULT_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 10 * 60 * 1000L; // 10 minutes private static final long DEFAULT_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = @@ -414,6 +420,8 @@ public class JobSchedulerService extends com.android.server.SystemService 8 * 60 * 60 * 1000L; // 8 hours private static final long DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 24 * 60 * 60 * 1000L; // 24 hours + private static final long DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = + 4 * 60 * 60 * 1000L; // 4 hours /** * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things @@ -538,6 +546,13 @@ public class JobSchedulerService extends com.android.server.SystemService */ public boolean USE_HEARTBEATS = DEFAULT_USE_HEARTBEATS; + /** + * Whether or not TimeController should skip setting wakeup alarms for jobs that aren't + * ready now. + */ + public boolean TIME_CONTROLLER_SKIP_NOT_READY_JOBS = + DEFAULT_TIME_CONTROLLER_SKIP_NOT_READY_JOBS; + /** How much time each app will have to run jobs within their standby bucket window. */ public long QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = DEFAULT_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS; @@ -581,6 +596,12 @@ public class JobSchedulerService extends com.android.server.SystemService public long QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS; + /** + * The maximum amount of time an app can have its jobs running within a 24 hour window. + */ + public long QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = + DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS; + private final KeyValueListParser mParser = new KeyValueListParser(','); void updateConstantsLocked(String value) { @@ -653,6 +674,9 @@ public class JobSchedulerService extends com.android.server.SystemService CONN_PREFETCH_RELAX_FRAC = mParser.getFloat(KEY_CONN_PREFETCH_RELAX_FRAC, DEFAULT_CONN_PREFETCH_RELAX_FRAC); USE_HEARTBEATS = mParser.getBoolean(KEY_USE_HEARTBEATS, DEFAULT_USE_HEARTBEATS); + TIME_CONTROLLER_SKIP_NOT_READY_JOBS = mParser.getBoolean( + KEY_TIME_CONTROLLER_SKIP_NOT_READY_JOBS, + DEFAULT_TIME_CONTROLLER_SKIP_NOT_READY_JOBS); QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = mParser.getDurationMillis( KEY_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS, DEFAULT_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS); @@ -671,6 +695,9 @@ public class JobSchedulerService extends com.android.server.SystemService QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = mParser.getDurationMillis( KEY_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS, DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS); + QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = mParser.getDurationMillis( + KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS, + DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS); } void dump(IndentingPrintWriter pw) { @@ -705,6 +732,8 @@ public class JobSchedulerService extends com.android.server.SystemService pw.printPair(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println(); pw.printPair(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println(); pw.printPair(KEY_USE_HEARTBEATS, USE_HEARTBEATS).println(); + pw.printPair(KEY_TIME_CONTROLLER_SKIP_NOT_READY_JOBS, + TIME_CONTROLLER_SKIP_NOT_READY_JOBS).println(); pw.printPair(KEY_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS, QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS).println(); pw.printPair(KEY_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS, @@ -717,6 +746,8 @@ public class JobSchedulerService extends com.android.server.SystemService QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS).println(); pw.printPair(KEY_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS, QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS).println(); + pw.printPair(KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS, + QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS).println(); pw.decreaseIndent(); } @@ -748,6 +779,11 @@ public class JobSchedulerService extends com.android.server.SystemService proto.write(ConstantsProto.CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC); proto.write(ConstantsProto.USE_HEARTBEATS, USE_HEARTBEATS); + final long tcToken = proto.start(ConstantsProto.TIME_CONTROLLER); + proto.write(ConstantsProto.TimeController.SKIP_NOT_READY_JOBS, + TIME_CONTROLLER_SKIP_NOT_READY_JOBS); + proto.end(tcToken); + final long qcToken = proto.start(ConstantsProto.QUOTA_CONTROLLER); proto.write(ConstantsProto.QuotaController.ALLOWED_TIME_PER_PERIOD_MS, QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS); @@ -761,6 +797,8 @@ public class JobSchedulerService extends com.android.server.SystemService QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS); proto.write(ConstantsProto.QuotaController.RARE_WINDOW_SIZE_MS, QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS); + proto.write(ConstantsProto.QuotaController.MAX_EXECUTION_TIME_MS, + QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS); proto.end(qcToken); proto.end(token); @@ -952,9 +990,15 @@ public class JobSchedulerService extends com.android.server.SystemService return mConstants; } + public boolean isChainedAttributionEnabled() { + return WorkSource.isChainedBatteryAttributionEnabled(getContext()); + } + @Override public void onStartUser(int userHandle) { - mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle); + synchronized (mLock) { + mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle); + } // Let's kick any outstanding jobs for this user. mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); } @@ -967,7 +1011,9 @@ public class JobSchedulerService extends com.android.server.SystemService @Override public void onStopUser(int userHandle) { - mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle); + synchronized (mLock) { + mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle); + } } /** @@ -2150,8 +2196,7 @@ public class JobSchedulerService extends com.android.server.SystemService final boolean jobExists = mJobs.containsJob(job); - final int userId = job.getUserId(); - final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId); + final boolean userStarted = areUsersStartedLocked(job); if (DEBUG) { Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString() @@ -2239,7 +2284,7 @@ public class JobSchedulerService extends com.android.server.SystemService try { componentPresent = (AppGlobals.getPackageManager().getServiceInfo( job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING, - userId) != null); + job.getUserId()) != null); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -3052,6 +3097,13 @@ public class JobSchedulerService extends com.android.server.SystemService printed = true; pw.println("user-stopped"); } + if (!ArrayUtils.contains(mStartedUsers, js.getSourceUserId())) { + if (printed) { + pw.print(" "); + } + printed = true; + pw.println("source-user-stopped"); + } if (mBackingUpUids.indexOfKey(js.getSourceUid()) >= 0) { if (printed) { pw.print(" "); @@ -3203,7 +3255,7 @@ public class JobSchedulerService extends com.android.server.SystemService pw.print(" (job="); pw.print(job.isReady()); pw.print(" user="); - pw.print(ArrayUtils.contains(mStartedUsers, job.getUserId())); + pw.print(areUsersStartedLocked(job)); pw.print(" !pending="); pw.print(!mPendingJobs.contains(job)); pw.print(" !active="); @@ -3373,7 +3425,7 @@ public class JobSchedulerService extends com.android.server.SystemService proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_READY, job.isReady()); proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_USER_STARTED, - ArrayUtils.contains(mStartedUsers, job.getUserId())); + areUsersStartedLocked(job)); proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_PENDING, mPendingJobs.contains(job)); proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_CURRENTLY_ACTIVE, diff --git a/services/core/java/com/android/server/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java index 58ee21795d99..ac2dbdf9450e 100644 --- a/services/core/java/com/android/server/job/controllers/QuotaController.java +++ b/services/core/java/com/android/server/job/controllers/QuotaController.java @@ -54,14 +54,13 @@ import com.android.server.job.JobSchedulerService; import com.android.server.job.StateControllerProto; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.function.Consumer; import java.util.function.Predicate; /** - * Controller that tracks whether a package has exceeded its standby bucket quota. + * Controller that tracks whether an app has exceeded its standby bucket quota. * * Each job in each bucket is given 10 minutes to run within its respective time window. Active * jobs can run indefinitely, working set jobs can run for 10 minutes within a 2 hour window, @@ -203,6 +202,80 @@ public final class QuotaController extends StateController { } } + private static int hashLong(long val) { + return (int) (val ^ (val >>> 32)); + } + + @VisibleForTesting + static class ExecutionStats { + /** + * The time at which this record should be considered invalid, in the elapsed realtime + * timebase. + */ + public long invalidTimeElapsed; + + public long windowSizeMs; + + /** The total amount of time the app ran in its respective bucket window size. */ + public long executionTimeInWindowMs; + public int bgJobCountInWindow; + + /** The total amount of time the app ran in the last {@link MAX_PERIOD_MS}. */ + public long executionTimeInMaxPeriodMs; + public int bgJobCountInMaxPeriod; + + /** + * The time after which the sum of all the app's sessions plus {@link mQuotaBufferMs} equals + * the quota. This is only valid if + * executionTimeInWindowMs >= {@link mAllowedTimePerPeriodMs} or + * executionTimeInMaxPeriodMs >= {@link mMaxExecutionTimeMs}. + */ + public long quotaCutoffTimeElapsed; + + @Override + public String toString() { + return new StringBuilder() + .append("invalidTime=").append(invalidTimeElapsed).append(", ") + .append("windowSize=").append(windowSizeMs).append(", ") + .append("executionTimeInWindow=").append(executionTimeInWindowMs).append(", ") + .append("bgJobCountInWindow=").append(bgJobCountInWindow).append(", ") + .append("executionTimeInMaxPeriod=").append(executionTimeInMaxPeriodMs) + .append(", ") + .append("bgJobCountInMaxPeriod=").append(bgJobCountInMaxPeriod).append(", ") + .append("quotaCutoffTime=").append(quotaCutoffTimeElapsed) + .toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ExecutionStats) { + ExecutionStats other = (ExecutionStats) obj; + return this.invalidTimeElapsed == other.invalidTimeElapsed + && this.windowSizeMs == other.windowSizeMs + && this.executionTimeInWindowMs == other.executionTimeInWindowMs + && this.bgJobCountInWindow == other.bgJobCountInWindow + && this.executionTimeInMaxPeriodMs == other.executionTimeInMaxPeriodMs + && this.bgJobCountInMaxPeriod == other.bgJobCountInMaxPeriod + && this.quotaCutoffTimeElapsed == other.quotaCutoffTimeElapsed; + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = 0; + result = 31 * result + hashLong(invalidTimeElapsed); + result = 31 * result + hashLong(windowSizeMs); + result = 31 * result + hashLong(executionTimeInWindowMs); + result = 31 * result + bgJobCountInWindow; + result = 31 * result + hashLong(executionTimeInMaxPeriodMs); + result = 31 * result + bgJobCountInMaxPeriod; + result = 31 * result + hashLong(quotaCutoffTimeElapsed); + return result; + } + } + /** List of all tracked jobs keyed by source package-userId combo. */ private final UserPackageMap<ArraySet<JobStatus>> mTrackedJobs = new UserPackageMap<>(); @@ -218,6 +291,9 @@ public final class QuotaController extends StateController { */ private final UserPackageMap<QcAlarmListener> mInQuotaAlarmListeners = new UserPackageMap<>(); + /** Cached calculation results for each app, with the standby buckets as the array indices. */ + private final UserPackageMap<ExecutionStats[]> mExecutionStatsCache = new UserPackageMap<>(); + private final AlarmManager mAlarmManager; private final ChargingTracker mChargeTracker; private final Handler mHandler; @@ -235,11 +311,29 @@ public final class QuotaController extends StateController { private long mAllowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; /** - * How much time the package should have before transitioning from out-of-quota to in-quota. - * This should not affect processing if the package is already in-quota. + * The maximum amount of time an app can have its jobs running within a {@link MAX_PERIOD_MS} + * window. + */ + private long mMaxExecutionTimeMs = 4 * 60 * MINUTE_IN_MILLIS; + + /** + * How much time the app should have before transitioning from out-of-quota to in-quota. + * This should not affect processing if the app is already in-quota. */ private long mQuotaBufferMs = 30 * 1000L; // 30 seconds + /** + * {@link mAllowedTimePerPeriodMs} - {@link mQuotaBufferMs}. This can be used to determine when + * an app will have enough quota to transition from out-of-quota to in-quota. + */ + private long mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs; + + /** + * {@link mMaxExecutionTimeMs} - {@link mQuotaBufferMs}. This can be used to determine when an + * app will have enough quota to transition from out-of-quota to in-quota. + */ + private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; + private long mNextCleanupTimeElapsed = 0; private final AlarmManager.OnAlarmListener mSessionCleanupAlarmListener = new AlarmManager.OnAlarmListener() { @@ -263,7 +357,7 @@ public final class QuotaController extends StateController { /** The maximum period any bucket can have. */ private static final long MAX_PERIOD_MS = 24 * 60 * MINUTE_IN_MILLIS; - /** A package has reached its quota. The message should contain a {@link Package} object. */ + /** An app has reached its quota. The message should contain a {@link Package} object. */ private static final int MSG_REACHED_QUOTA = 0; /** Drop any old timing sessions. */ private static final int MSG_CLEAN_UP_SESSIONS = 1; @@ -341,12 +435,15 @@ public final class QuotaController extends StateController { Math.max(MINUTE_IN_MILLIS, mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS)); if (mAllowedTimePerPeriodMs != newAllowedTimeMs) { mAllowedTimePerPeriodMs = newAllowedTimeMs; + mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs; changed = true; } long newQuotaBufferMs = Math.max(0, Math.min(5 * MINUTE_IN_MILLIS, mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS)); if (mQuotaBufferMs != newQuotaBufferMs) { mQuotaBufferMs = newQuotaBufferMs; + mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs; + mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; changed = true; } long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs, @@ -373,6 +470,13 @@ public final class QuotaController extends StateController { mBucketPeriodsMs[RARE_INDEX] = newRarePeriodMs; changed = true; } + long newMaxExecutionTimeMs = Math.max(60 * MINUTE_IN_MILLIS, + Math.min(MAX_PERIOD_MS, mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS)); + if (mMaxExecutionTimeMs != newMaxExecutionTimeMs) { + mMaxExecutionTimeMs = newMaxExecutionTimeMs; + mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; + changed = true; + } if (changed) { // Update job bookkeeping out of band. @@ -406,6 +510,7 @@ public final class QuotaController extends StateController { mAlarmManager.cancel(alarmListener); mInQuotaAlarmListeners.delete(userId, packageName); } + mExecutionStatsCache.delete(userId, packageName); } @Override @@ -414,6 +519,7 @@ public final class QuotaController extends StateController { mPkgTimers.delete(userId); mTimingSessions.delete(userId); mInQuotaAlarmListeners.delete(userId); + mExecutionStatsCache.delete(userId); } /** @@ -439,7 +545,6 @@ public final class QuotaController extends StateController { private boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName, final int standbyBucket) { if (standbyBucket == NEVER_INDEX) return false; - if (standbyBucket == ACTIVE_INDEX) return true; // This check is needed in case the flag is toggled after a job has been registered. if (!mShouldThrottle) return true; @@ -472,46 +577,152 @@ public final class QuotaController extends StateController { if (standbyBucket == NEVER_INDEX) { return 0; } + final ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); + return Math.min(mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs, + mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs); + } + + /** Returns the execution stats of the app in the most recent window. */ + @VisibleForTesting + @NonNull + ExecutionStats getExecutionStatsLocked(final int userId, @NonNull final String packageName, + final int standbyBucket) { + if (standbyBucket == NEVER_INDEX) { + Slog.wtf(TAG, "getExecutionStatsLocked called for a NEVER app."); + return new ExecutionStats(); + } + ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName); + if (appStats == null) { + appStats = new ExecutionStats[mBucketPeriodsMs.length]; + mExecutionStatsCache.add(userId, packageName, appStats); + } + ExecutionStats stats = appStats[standbyBucket]; + if (stats == null) { + stats = new ExecutionStats(); + appStats[standbyBucket] = stats; + } final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket]; - final long trailingRunDurationMs = getTrailingExecutionTimeLocked( - userId, packageName, bucketWindowSizeMs); - return mAllowedTimePerPeriodMs - trailingRunDurationMs; + Timer timer = mPkgTimers.get(userId, packageName); + if ((timer != null && timer.isActive()) + || stats.invalidTimeElapsed <= sElapsedRealtimeClock.millis() + || stats.windowSizeMs != bucketWindowSizeMs) { + // The stats are no longer valid. + stats.windowSizeMs = bucketWindowSizeMs; + updateExecutionStatsLocked(userId, packageName, stats); + } + + return stats; } - /** Returns how long the uid has had jobs running within the most recent window. */ @VisibleForTesting - long getTrailingExecutionTimeLocked(final int userId, @NonNull final String packageName, - final long windowSizeMs) { - long totalTime = 0; + void updateExecutionStatsLocked(final int userId, @NonNull final String packageName, + @NonNull ExecutionStats stats) { + stats.executionTimeInWindowMs = 0; + stats.bgJobCountInWindow = 0; + stats.executionTimeInMaxPeriodMs = 0; + stats.bgJobCountInMaxPeriod = 0; + stats.quotaCutoffTimeElapsed = 0; Timer timer = mPkgTimers.get(userId, packageName); final long nowElapsed = sElapsedRealtimeClock.millis(); + stats.invalidTimeElapsed = nowElapsed + MAX_PERIOD_MS; if (timer != null && timer.isActive()) { - totalTime = timer.getCurrentDuration(nowElapsed); + stats.executionTimeInWindowMs = + stats.executionTimeInMaxPeriodMs = timer.getCurrentDuration(nowElapsed); + stats.bgJobCountInWindow = stats.bgJobCountInMaxPeriod = timer.getBgJobCount(); + // If the timer is active, the value will be stale at the next method call, so + // invalidate now. + stats.invalidTimeElapsed = nowElapsed; + if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) { + stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed, + nowElapsed - mAllowedTimeIntoQuotaMs); + } + if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) { + stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed, + nowElapsed - mMaxExecutionTimeIntoQuotaMs); + } } List<TimingSession> sessions = mTimingSessions.get(userId, packageName); if (sessions == null || sessions.size() == 0) { - return totalTime; + return; } - final long startElapsed = nowElapsed - windowSizeMs; + final long startWindowElapsed = nowElapsed - stats.windowSizeMs; + final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS; + // The minimum time between the start time and the beginning of the sessions that were + // looked at --> how much time the stats will be valid for. + long emptyTimeMs = Long.MAX_VALUE; // Sessions are non-overlapping and in order of occurrence, so iterating backwards will get // the most recent ones. for (int i = sessions.size() - 1; i >= 0; --i) { TimingSession session = sessions.get(i); - if (startElapsed < session.startTimeElapsed) { - totalTime += session.endTimeElapsed - session.startTimeElapsed; - } else if (startElapsed < session.endTimeElapsed) { + + // Window management. + if (startWindowElapsed < session.startTimeElapsed) { + stats.executionTimeInWindowMs += session.endTimeElapsed - session.startTimeElapsed; + stats.bgJobCountInWindow += session.bgJobCount; + emptyTimeMs = Math.min(emptyTimeMs, session.startTimeElapsed - startWindowElapsed); + if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) { + stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed, + session.startTimeElapsed + stats.executionTimeInWindowMs + - mAllowedTimeIntoQuotaMs); + } + } else if (startWindowElapsed < session.endTimeElapsed) { // The session started before the window but ended within the window. Only include // the portion that was within the window. - totalTime += session.endTimeElapsed - startElapsed; + stats.executionTimeInWindowMs += session.endTimeElapsed - startWindowElapsed; + stats.bgJobCountInWindow += session.bgJobCount; + emptyTimeMs = 0; + if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) { + stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed, + startWindowElapsed + stats.executionTimeInWindowMs + - mAllowedTimeIntoQuotaMs); + } + } + + // Max period check. + if (startMaxElapsed < session.startTimeElapsed) { + stats.executionTimeInMaxPeriodMs += + session.endTimeElapsed - session.startTimeElapsed; + stats.bgJobCountInMaxPeriod += session.bgJobCount; + emptyTimeMs = Math.min(emptyTimeMs, session.startTimeElapsed - startMaxElapsed); + if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) { + stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed, + session.startTimeElapsed + stats.executionTimeInMaxPeriodMs + - mMaxExecutionTimeIntoQuotaMs); + } + } else if (startMaxElapsed < session.endTimeElapsed) { + // The session started before the window but ended within the window. Only include + // the portion that was within the window. + stats.executionTimeInMaxPeriodMs += session.endTimeElapsed - startMaxElapsed; + stats.bgJobCountInMaxPeriod += session.bgJobCount; + emptyTimeMs = 0; + if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) { + stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed, + startMaxElapsed + stats.executionTimeInMaxPeriodMs + - mMaxExecutionTimeIntoQuotaMs); + } } else { // This session ended before the window. No point in going any further. - return totalTime; + break; + } + } + stats.invalidTimeElapsed = nowElapsed + emptyTimeMs; + } + + private void invalidateAllExecutionStatsLocked(final int userId, + @NonNull final String packageName) { + ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName); + if (appStats != null) { + final long nowElapsed = sElapsedRealtimeClock.millis(); + for (int i = 0; i < appStats.length; ++i) { + ExecutionStats stats = appStats[i]; + if (stats != null) { + stats.invalidTimeElapsed = nowElapsed; + } } } - return totalTime; } @VisibleForTesting @@ -524,6 +735,8 @@ public final class QuotaController extends StateController { mTimingSessions.add(userId, packageName, sessions); } sessions.add(session); + // Adding a new session means that the current stats are now incorrect. + invalidateAllExecutionStatsLocked(userId, packageName); maybeScheduleCleanupAlarmLocked(); } @@ -657,87 +870,43 @@ public final class QuotaController extends StateController { @VisibleForTesting void maybeScheduleStartAlarmLocked(final int userId, @NonNull final String packageName, final int standbyBucket) { - final String pkgString = string(userId, packageName); if (standbyBucket == NEVER_INDEX) { return; - } else if (standbyBucket == ACTIVE_INDEX) { - // ACTIVE apps are "always" in quota. + } + + final String pkgString = string(userId, packageName); + ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); + QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName); + if (stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs + && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs) { + // Already in quota. Why was this method called? if (DEBUG) { - Slog.w(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString - + " even though it is active"); + Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString + + " even though it already has " + + getRemainingExecutionTimeLocked(userId, packageName, standbyBucket) + + "ms in its quota."); } - mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget(); - - QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName); if (alarmListener != null) { // Cancel any pending alarm. mAlarmManager.cancel(alarmListener); // Set the trigger time to 0 so that the alarm doesn't think it's still waiting. alarmListener.setTriggerTime(0); } - return; - } - - List<TimingSession> sessions = mTimingSessions.get(userId, packageName); - if (sessions == null || sessions.size() == 0) { - // If there are no sessions, then the job is probably in quota. - if (DEBUG) { - Slog.wtf(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString - + " even though it is likely within its quota."); - } mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget(); return; } - - final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket]; - final long nowElapsed = sElapsedRealtimeClock.millis(); - // How far back we need to look. - final long startElapsed = nowElapsed - bucketWindowSizeMs; - - long totalTime = 0; - long cutoffTimeElapsed = nowElapsed; - for (int i = sessions.size() - 1; i >= 0; i--) { - TimingSession session = sessions.get(i); - if (startElapsed < session.startTimeElapsed) { - cutoffTimeElapsed = session.startTimeElapsed; - totalTime += session.endTimeElapsed - session.startTimeElapsed; - } else if (startElapsed < session.endTimeElapsed) { - // The session started before the window but ended within the window. Only - // include the portion that was within the window. - cutoffTimeElapsed = startElapsed; - totalTime += session.endTimeElapsed - startElapsed; - } else { - // This session ended before the window. No point in going any further. - break; - } - if (totalTime >= mAllowedTimePerPeriodMs) { - break; - } - } - if (totalTime < mAllowedTimePerPeriodMs) { - // Already in quota. Why was this method called? - if (DEBUG) { - Slog.w(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString - + " even though it already has " + (mAllowedTimePerPeriodMs - totalTime) - + "ms in its quota."); - } - mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget(); - return; - } - - QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName); if (alarmListener == null) { alarmListener = new QcAlarmListener(userId, packageName); mInQuotaAlarmListeners.add(userId, packageName, alarmListener); } - // We add all the way back to the beginning of a session (or the window) even when we don't - // need to (in order to simplify the for loop above), so there might be some extra we - // need to add back. - final long extraTimeMs = totalTime - mAllowedTimePerPeriodMs; // The time this app will have quota again. - final long inQuotaTimeElapsed = - cutoffTimeElapsed + extraTimeMs + mQuotaBufferMs + bucketWindowSizeMs; + long inQuotaTimeElapsed = + stats.quotaCutoffTimeElapsed + stats.windowSizeMs; + if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeMs) { + inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed, + stats.quotaCutoffTimeElapsed + MAX_PERIOD_MS); + } // Only schedule the alarm if: // 1. There isn't one currently scheduled // 2. The new alarm is significantly earlier than the previous alarm (which could be the @@ -747,13 +916,15 @@ public final class QuotaController extends StateController { // TODO: this might be overengineering. Simplify if proven safe. if (!alarmListener.isWaiting() || inQuotaTimeElapsed < alarmListener.getTriggerTimeElapsed() - 3 * MINUTE_IN_MILLIS - || alarmListener.getTriggerTimeElapsed() < inQuotaTimeElapsed - mQuotaBufferMs) { + || alarmListener.getTriggerTimeElapsed() < inQuotaTimeElapsed) { if (DEBUG) Slog.d(TAG, "Scheduling start alarm for " + pkgString); // If the next time this app will have quota is at least 3 minutes before the // alarm is supposed to go off, reschedule the alarm. mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, inQuotaTimeElapsed, ALARM_TAG_QUOTA_CHECK, alarmListener, mHandler); alarmListener.setTriggerTime(inQuotaTimeElapsed); + } else if (DEBUG) { + Slog.d(TAG, "No need to scheduling start alarm for " + pkgString); } } @@ -816,10 +987,18 @@ public final class QuotaController extends StateController { // How many background jobs ran during this session. public final int bgJobCount; - TimingSession(long startElapsed, long endElapsed, int jobCount) { + private final int mHashCode; + + TimingSession(long startElapsed, long endElapsed, int bgJobCount) { this.startTimeElapsed = startElapsed; this.endTimeElapsed = endElapsed; - this.bgJobCount = jobCount; + this.bgJobCount = bgJobCount; + + int hashCode = 0; + hashCode = 31 * hashCode + hashLong(startTimeElapsed); + hashCode = 31 * hashCode + hashLong(endTimeElapsed); + hashCode = 31 * hashCode + bgJobCount; + mHashCode = hashCode; } @Override @@ -842,7 +1021,7 @@ public final class QuotaController extends StateController { @Override public int hashCode() { - return Arrays.hashCode(new long[] {startTimeElapsed, endTimeElapsed, bgJobCount}); + return mHashCode; } public void dump(IndentingPrintWriter pw) { @@ -902,6 +1081,9 @@ public final class QuotaController extends StateController { if (mRunningBgJobs.size() == 1) { // Started tracking the first job. mStartTimeElapsed = sElapsedRealtimeClock.millis(); + // Starting the timer means that all cached execution stats are now + // incorrect. + invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName); scheduleCutoff(); } } @@ -966,6 +1148,12 @@ public final class QuotaController extends StateController { } } + int getBgJobCount() { + synchronized (mLock) { + return mBgJobCount; + } + } + void onChargingChanged(long nowElapsed, boolean isCharging) { synchronized (mLock) { if (isCharging) { @@ -978,6 +1166,9 @@ public final class QuotaController extends StateController { // repeatedly plugged in and unplugged, the job count for a package may be // artificially high. mBgJobCount = mRunningBgJobs.size(); + // Starting the timer means that all cached execution stats are now + // incorrect. + invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName); // Schedule cutoff since we're now actively tracking for quotas again. scheduleCutoff(); } @@ -1239,6 +1430,11 @@ public final class QuotaController extends StateController { } @VisibleForTesting + long getMaxExecutionTimeMs() { + return mMaxExecutionTimeMs; + } + + @VisibleForTesting @Nullable List<TimingSession> getTimingSessions(int userId, String packageName) { return mTimingSessions.get(userId, packageName); diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java index 04d579500df9..26f3caf2e487 100644 --- a/services/core/java/com/android/server/job/controllers/TimeController.java +++ b/services/core/java/com/android/server/job/controllers/TimeController.java @@ -30,6 +30,7 @@ import android.util.Slog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.server.job.JobSchedulerService; import com.android.server.job.StateControllerProto; @@ -68,7 +69,7 @@ public final class TimeController extends StateController { mNextJobExpiredElapsedMillis = Long.MAX_VALUE; mNextDelayExpiredElapsedMillis = Long.MAX_VALUE; - mChainedAttributionEnabled = WorkSource.isChainedBatteryAttributionEnabled(mContext); + mChainedAttributionEnabled = mService.isChainedAttributionEnabled(); } /** @@ -110,11 +111,24 @@ public final class TimeController extends StateController { it.next(); } it.add(job); + job.setTrackingController(JobStatus.TRACKING_TIME); - maybeUpdateAlarmsLocked( - job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE, - job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE, - deriveWorkSource(job.getSourceUid(), job.getSourcePackageName())); + WorkSource ws = deriveWorkSource(job.getSourceUid(), job.getSourcePackageName()); + final long deadlineExpiredElapsed = + job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE; + final long delayExpiredElapsed = + job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE; + if (mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS) { + if (wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_TIMING_DELAY)) { + maybeUpdateDelayAlarmLocked(delayExpiredElapsed, ws); + } + if (wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_DEADLINE)) { + maybeUpdateDeadlineAlarmLocked(deadlineExpiredElapsed, ws); + } + } else { + maybeUpdateDelayAlarmLocked(delayExpiredElapsed, ws); + maybeUpdateDeadlineAlarmLocked(deadlineExpiredElapsed, ws); + } } } @@ -133,6 +147,34 @@ public final class TimeController extends StateController { } } + @Override + public void onConstantsUpdatedLocked() { + checkExpiredDelaysAndResetAlarm(); + checkExpiredDeadlinesAndResetAlarm(); + } + + @Override + public void evaluateStateLocked(JobStatus job) { + if (!mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS) { + return; + } + + if (job.hasTimingDelayConstraint() + && job.getEarliestRunTime() <= mNextDelayExpiredElapsedMillis) { + checkExpiredDelaysAndResetAlarm(); + } + if (job.hasDeadlineConstraint() + && job.getLatestRunTimeElapsed() <= mNextJobExpiredElapsedMillis) { + checkExpiredDeadlinesAndResetAlarm(); + } + } + + @Override + public void reevaluateStateLocked(int uid) { + checkExpiredDelaysAndResetAlarm(); + checkExpiredDeadlinesAndResetAlarm(); + } + /** * Determines whether this controller can stop tracking the given job. * The controller is no longer interested in a job once its time constraint is satisfied, and @@ -156,14 +198,15 @@ public final class TimeController extends StateController { * Checks list of jobs for ones that have an expired deadline, sending them to the JobScheduler * if so, removing them from this list, and updating the alarm for the next expiry time. */ - private void checkExpiredDeadlinesAndResetAlarm() { + @VisibleForTesting + void checkExpiredDeadlinesAndResetAlarm() { synchronized (mLock) { long nextExpiryTime = Long.MAX_VALUE; int nextExpiryUid = 0; String nextExpiryPackageName = null; final long nowElapsedMillis = sElapsedRealtimeClock.millis(); - Iterator<JobStatus> it = mTrackedJobs.iterator(); + ListIterator<JobStatus> it = mTrackedJobs.listIterator(); while (it.hasNext()) { JobStatus job = it.next(); if (!job.hasDeadlineConstraint()) { @@ -171,9 +214,22 @@ public final class TimeController extends StateController { } if (evaluateDeadlineConstraint(job, nowElapsedMillis)) { - mStateChangedListener.onRunJobNow(job); + if (job.isReady()) { + // If the job still isn't ready, there's no point trying to rush the + // Scheduler. + mStateChangedListener.onRunJobNow(job); + } it.remove(); } else { // Sorted by expiry time, so take the next one and stop. + if (mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS + && !wouldBeReadyWithConstraintLocked( + job, JobStatus.CONSTRAINT_DEADLINE)) { + if (DEBUG) { + Slog.i(TAG, + "Skipping " + job + " because deadline won't make it ready."); + } + continue; + } nextExpiryTime = job.getLatestRunTimeElapsed(); nextExpiryUid = job.getSourceUid(); nextExpiryPackageName = job.getSourcePackageName(); @@ -202,7 +258,8 @@ public final class TimeController extends StateController { * Handles alarm that notifies us that a job's delay has expired. Iterates through the list of * tracked jobs and marks them as ready as appropriate. */ - private void checkExpiredDelaysAndResetAlarm() { + @VisibleForTesting + void checkExpiredDelaysAndResetAlarm() { synchronized (mLock) { final long nowElapsedMillis = sElapsedRealtimeClock.millis(); long nextDelayTime = Long.MAX_VALUE; @@ -223,6 +280,15 @@ public final class TimeController extends StateController { ready = true; } } else if (!job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)) { + if (mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS + && !wouldBeReadyWithConstraintLocked( + job, JobStatus.CONSTRAINT_TIMING_DELAY)) { + if (DEBUG) { + Slog.i(TAG, + "Skipping " + job + " because delay won't make it ready."); + } + continue; + } // If this job still doesn't have its delay constraint satisfied, // then see if it is the next upcoming delay time for the alarm. final long jobDelayTime = job.getEarliestRunTime(); @@ -262,11 +328,13 @@ public final class TimeController extends StateController { return false; } - private void maybeUpdateAlarmsLocked(long delayExpiredElapsed, long deadlineExpiredElapsed, - WorkSource ws) { + private void maybeUpdateDelayAlarmLocked(long delayExpiredElapsed, WorkSource ws) { if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) { setDelayExpiredAlarmLocked(delayExpiredElapsed, ws); } + } + + private void maybeUpdateDeadlineAlarmLocked(long deadlineExpiredElapsed, WorkSource ws) { if (deadlineExpiredElapsed < mNextJobExpiredElapsedMillis) { setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed, ws); } @@ -297,11 +365,7 @@ public final class TimeController extends StateController { } private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) { - final long earliestWakeupTimeElapsed = sElapsedRealtimeClock.millis(); - if (proposedAlarmTimeElapsedMillis < earliestWakeupTimeElapsed) { - return earliestWakeupTimeElapsed; - } - return proposedAlarmTimeElapsedMillis; + return Math.max(proposedAlarmTimeElapsedMillis, sElapsedRealtimeClock.millis()); } private void updateAlarmWithListenerLocked(String tag, OnAlarmListener listener, diff --git a/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java index b211948f73cf..3903f2a69547 100644 --- a/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java +++ b/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java @@ -23,7 +23,6 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkRequest; -import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; @@ -65,6 +64,10 @@ class GnssNetworkConnectivityHandler { private static final int APN_IPV6 = 2; private static final int APN_IPV4V6 = 3; + // these must match the NetworkCapability enum flags in IAGnssRil.hal + private static final int AGNSS_NET_CAPABILITY_NOT_METERED = 1 << 0; + private static final int AGNSS_NET_CAPABILITY_NOT_ROAMING = 1 << 1; + // Default time limit in milliseconds for the ConnectivityManager to find a suitable // network with SUPL connectivity or report an error. private static final int SUPL_NETWORK_REQUEST_TIMEOUT_MILLIS = 10 * 1000; @@ -95,23 +98,42 @@ class GnssNetworkConnectivityHandler { * Network attributes needed when updating HAL about network connectivity status changes. */ private static class NetworkAttributes { - NetworkCapabilities mCapabilities; - String mApn; - int mType = ConnectivityManager.TYPE_NONE; + private NetworkCapabilities mCapabilities; + private String mApn; + private int mType = ConnectivityManager.TYPE_NONE; /** * Returns true if the capabilities that we pass on to HAL change between {@curCapabilities} * and {@code newCapabilities}. */ - static boolean hasCapabilitiesChanged(NetworkCapabilities curCapabilities, + private static boolean hasCapabilitiesChanged(NetworkCapabilities curCapabilities, NetworkCapabilities newCapabilities) { if (curCapabilities == null || newCapabilities == null) { return true; } - return curCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING) - != newCapabilities.hasCapability( - NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); + // Monitor for roaming and metered capability changes. + return hasCapabilityChanged(curCapabilities, newCapabilities, + NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING) + || hasCapabilityChanged(curCapabilities, newCapabilities, + NetworkCapabilities.NET_CAPABILITY_NOT_METERED); + } + + private static boolean hasCapabilityChanged(NetworkCapabilities curCapabilities, + NetworkCapabilities newCapabilities, int capability) { + return curCapabilities.hasCapability(capability) + != newCapabilities.hasCapability(capability); + } + + private static short getCapabilityFlags(NetworkCapabilities capabilities) { + short capabilityFlags = 0; + if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)) { + capabilityFlags |= AGNSS_NET_CAPABILITY_NOT_ROAMING; + } + if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)) { + capabilityFlags |= AGNSS_NET_CAPABILITY_NOT_METERED; + } + return capabilityFlags; } } @@ -328,36 +350,31 @@ class GnssNetworkConnectivityHandler { boolean networkAvailable = isConnected && TelephonyManager.getDefault().getDataEnabled(); NetworkAttributes networkAttributes = updateTrackedNetworksState(isConnected, network, capabilities); - String apnName = networkAttributes.mApn; + String apn = networkAttributes.mApn; int type = networkAttributes.mType; // When isConnected is false, capabilities argument is null. So, use last received // capabilities. capabilities = networkAttributes.mCapabilities; - boolean isRoaming = !capabilities.hasTransport( - NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); - Log.i(TAG, String.format( "updateNetworkState, state=%s, connected=%s, network=%s, capabilities=%s" - + ", availableNetworkCount: %d", + + ", apn: %s, availableNetworkCount: %d", agpsDataConnStateAsString(), isConnected, network, capabilities, + apn, mAvailableNetworkAttributes.size())); if (native_is_agps_ril_supported()) { - String defaultApn = getSelectedApn(); - if (defaultApn == null) { - defaultApn = "dummy-apn"; - } - native_update_network_state( isConnected, type, - isRoaming, + !capabilities.hasTransport( + NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING), /* isRoaming */ networkAvailable, - apnName, - defaultApn); + apn != null ? apn : "", + network.getNetworkHandle(), + NetworkAttributes.getCapabilityFlags(capabilities)); } else if (DEBUG) { Log.d(TAG, "Skipped network state update because GPS HAL AGPS-RIL is not supported"); } @@ -398,9 +415,9 @@ class GnssNetworkConnectivityHandler { // TODO(b/119278134): The synchronous method ConnectivityManager.getNetworkInfo() must // not be called inside the asynchronous ConnectivityManager.NetworkCallback methods. NetworkInfo info = mConnMgr.getNetworkInfo(network); - String apnName = null; + String apn = null; if (info != null) { - apnName = info.getExtraInfo(); + apn = info.getExtraInfo(); } if (DEBUG) { @@ -413,21 +430,21 @@ class GnssNetworkConnectivityHandler { } if (mAGpsDataConnectionState == AGPS_DATA_CONNECTION_OPENING) { - if (apnName == null) { + if (apn == null) { // assign a dummy value in the case of C2K as otherwise we will have a runtime // exception in the following call to native_agps_data_conn_open - apnName = "dummy-apn"; + apn = "dummy-apn"; } - int apnIpType = getApnIpType(apnName); + int apnIpType = getApnIpType(apn); setRouting(); if (DEBUG) { String message = String.format( "native_agps_data_conn_open: mAgpsApn=%s, mApnIpType=%s", - apnName, + apn, apnIpType); Log.d(TAG, message); } - native_agps_data_conn_open(apnName, apnIpType); + native_agps_data_conn_open(apn, apnIpType); mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN; } } @@ -596,26 +613,6 @@ class GnssNetworkConnectivityHandler { return APN_INVALID; } - private String getSelectedApn() { - Uri uri = Uri.parse("content://telephony/carriers/preferapn"); - try (Cursor cursor = mContext.getContentResolver().query( - uri, - new String[]{"apn"}, - null /* selection */, - null /* selectionArgs */, - Carriers.DEFAULT_SORT_ORDER)) { - if (cursor != null && cursor.moveToFirst()) { - return cursor.getString(0); - } else { - Log.e(TAG, "No APN found to select."); - } - } catch (Exception e) { - Log.e(TAG, "Error encountered on selecting the APN.", e); - } - - return null; - } - // AGPS support private native void native_agps_data_conn_open(String apn, int apnIpType); @@ -626,6 +623,6 @@ class GnssNetworkConnectivityHandler { // AGPS ril support private static native boolean native_is_agps_ril_supported(); - private native void native_update_network_state(boolean connected, int type, - boolean roaming, boolean available, String extraInfo, String defaultAPN); -} + private native void native_update_network_state(boolean connected, int type, boolean roaming, + boolean available, String apn, long networkHandle, short capabilities); +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index adfa8d546166..ea8c7922d831 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -112,6 +112,7 @@ import com.android.server.locksettings.LockSettingsStorage.PersistentData; import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager; import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult; import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken; +import com.android.server.wm.WindowManagerInternal; import libcore.util.HexEncoding; @@ -1870,6 +1871,7 @@ public class LockSettingsService extends ILockSettings.Stub { DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); dpm.reportPasswordChanged(userId); + LocalServices.getService(WindowManagerInternal.class).reportPasswordChanged(userId); }); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 36a027a8fcd0..3d8763439086 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -47,17 +47,12 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; import static android.os.UserHandle.USER_NULL; import static android.os.UserHandle.USER_SYSTEM; -import static android.service.notification.NotificationListenerService - .HINT_HOST_DISABLE_CALL_EFFECTS; +import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; -import static android.service.notification.NotificationListenerService - .HINT_HOST_DISABLE_NOTIFICATION_EFFECTS; -import static android.service.notification.NotificationListenerService - .NOTIFICATION_CHANNEL_OR_GROUP_ADDED; -import static android.service.notification.NotificationListenerService - .NOTIFICATION_CHANNEL_OR_GROUP_DELETED; -import static android.service.notification.NotificationListenerService - .NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; +import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_ADDED; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL; import static android.service.notification.NotificationListenerService.REASON_CANCEL; @@ -65,8 +60,7 @@ import static android.service.notification.NotificationListenerService.REASON_CA import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED; import static android.service.notification.NotificationListenerService.REASON_CLICK; import static android.service.notification.NotificationListenerService.REASON_ERROR; -import static android.service.notification.NotificationListenerService - .REASON_GROUP_SUMMARY_CANCELED; +import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED; import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL; import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL_ALL; import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED; @@ -6695,7 +6689,7 @@ public class NotificationManagerService extends SystemService { Bundle hidden = new Bundle(); Bundle systemGeneratedSmartActions = new Bundle(); Bundle smartReplies = new Bundle(); - Bundle audiblyAlerted = new Bundle(); + Bundle lastAudiblyAlerted = new Bundle(); Bundle noisy = new Bundle(); for (int i = 0; i < N; i++) { NotificationRecord record = mNotificationList.get(i); @@ -6727,7 +6721,7 @@ public class NotificationManagerService extends SystemService { systemGeneratedSmartActions.putParcelableArrayList(key, record.getSystemGeneratedSmartActions()); smartReplies.putCharSequenceArrayList(key, record.getSmartReplies()); - audiblyAlerted.putBoolean(key, record.getAudiblyAlerted()); + lastAudiblyAlerted.putLong(key, record.getLastAudiblyAlertedMs()); noisy.putBoolean(key, record.getSound() != null || record.getVibration() != null); } final int M = keys.size(); @@ -6740,7 +6734,7 @@ public class NotificationManagerService extends SystemService { return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides, suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys, channels, overridePeople, snoozeCriteria, showBadge, userSentiment, hidden, - systemGeneratedSmartActions, smartReplies, audiblyAlerted, noisy); + systemGeneratedSmartActions, smartReplies, lastAudiblyAlerted, noisy); } boolean hasCompanionDevice(ManagedServiceInfo info) { diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 50810ccc8cbc..39451d40a97e 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -132,6 +132,9 @@ public final class NotificationRecord { // user private long mInterruptionTimeMs; + // The most recent time the notification made noise or buzzed the device, or -1 if it did not. + private long mLastAudiblyAlertedMs; + // Is this record an update of an old record? public boolean isUpdate; private int mPackagePriority; @@ -172,7 +175,6 @@ public final class NotificationRecord { private final NotificationStats mStats; private int mUserSentiment; private boolean mIsInterruptive; - private boolean mAudiblyAlerted; private boolean mTextChanged; private boolean mRecordedInterruption; private int mNumberOfSmartRepliesAdded; @@ -1051,7 +1053,7 @@ public final class NotificationRecord { } public void setAudiblyAlerted(boolean audiblyAlerted) { - mAudiblyAlerted = audiblyAlerted; + mLastAudiblyAlertedMs = audiblyAlerted ? System.currentTimeMillis() : -1; } public void setTextChanged(boolean textChanged) { @@ -1070,9 +1072,9 @@ public final class NotificationRecord { return mIsInterruptive; } - /** Returns true if the notification audibly alerted the user. */ - public boolean getAudiblyAlerted() { - return mAudiblyAlerted; + /** Returns the time the notification audibly alerted the user. */ + public long getLastAudiblyAlertedMs() { + return mLastAudiblyAlertedMs; } protected void setPeopleOverride(ArrayList<String> people) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6cfb846e3ade..2826e7b8fe2b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -323,6 +323,7 @@ import dalvik.system.VMRuntime; import libcore.io.IoUtils; import libcore.util.EmptyArray; +import libcore.util.HexEncoding; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -591,12 +592,6 @@ public class PackageManagerService extends IPackageManager.Stub public static final int REASON_LAST = REASON_SHARED; /** - * Version number for the package parser cache. Increment this whenever the format or - * extent of cached data changes. See {@code PackageParser#setCacheDir}. - */ - private static final String PACKAGE_PARSER_CACHE_VERSION = "1"; - - /** * Whether the package parser cache is enabled. */ private static final boolean DEFAULT_PACKAGE_PARSER_CACHE_ENABLED = true; @@ -1064,9 +1059,6 @@ public class PackageManagerService extends IPackageManager.Stub + verificationId + " packageName:" + packageName); return; } - if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, - "Updating IntentFilterVerificationInfo for package " + packageName - +" verificationId:" + verificationId); synchronized (mPackages) { if (verified) { @@ -1084,19 +1076,47 @@ public class PackageManagerService extends IPackageManager.Stub int updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; boolean needUpdate = false; - // We cannot override the STATUS_ALWAYS / STATUS_NEVER states if they have - // already been set by the User thru the Disambiguation dialog + if (DEBUG_DOMAIN_VERIFICATION) { + Slog.d(TAG, + "Updating IntentFilterVerificationInfo for package " + packageName + + " verificationId:" + verificationId + + " verified=" + verified); + } + + // In a success case, we promote from undefined or ASK to ALWAYS. This + // supports a flow where the app fails validation but then ships an updated + // APK that passes, and therefore deserves to be in ALWAYS. + // + // If validation failed, the undefined state winds up in the basic ASK behavior, + // but apps that previously passed and became ALWAYS are *demoted* out of + // that state, since they would not deserve the ALWAYS behavior in case of a + // clean install. switch (userStatus) { + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: + if (!verified) { + // updatedStatus is already UNDEFINED + needUpdate = true; + + if (DEBUG_DOMAIN_VERIFICATION) { + Slog.d(TAG, "Formerly validated but now failing; demoting"); + } + } + break; + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: + // Stay in 'undefined' on verification failure if (verified) { updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; - } else { - updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK; } needUpdate = true; + if (DEBUG_DOMAIN_VERIFICATION) { + Slog.d(TAG, "Applying update; old=" + userStatus + + " new=" + updatedStatus); + } break; case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: + // Keep in 'ask' on failure if (verified) { updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; needUpdate = true; @@ -1112,6 +1132,8 @@ public class PackageManagerService extends IPackageManager.Stub packageName, updatedStatus, userId); scheduleWritePackageRestrictionsLocked(userId); } + } else { + Slog.i(TAG, "autoVerify ignored when installing for all users"); } } } @@ -2302,7 +2324,7 @@ public class PackageManagerService extends IPackageManager.Stub } } - mCacheDir = preparePackageParserCache(mIsUpgrade); + mCacheDir = preparePackageParserCache(); // Set flag to monitor and not change apk file paths when // scanning install directories. @@ -3169,7 +3191,7 @@ public class PackageManagerService extends IPackageManager.Stub setUpInstantAppInstallerActivityLP(getInstantAppInstallerLPr()); } - private static File preparePackageParserCache(boolean isUpgrade) { + private static @Nullable File preparePackageParserCache() { if (!DEFAULT_PACKAGE_PARSER_CACHE_ENABLED) { return null; } @@ -3190,17 +3212,25 @@ public class PackageManagerService extends IPackageManager.Stub return null; } - // If this is a system upgrade scenario, delete the contents of the package cache dir. - // This also serves to "GC" unused entries when the package cache version changes (which - // can only happen during upgrades). - if (isUpgrade) { - FileUtils.deleteContents(cacheBaseDir); - } + // There are several items that need to be combined together to safely + // identify cached items. In particular, changing the value of certain + // feature flags should cause us to invalidate any caches. + final String cacheName = SystemProperties.digestOf( + "ro.build.fingerprint", + "persist.sys.isolated_storage"); + // Reconcile cache directories, keeping only what we'd actually use. + for (File cacheDir : FileUtils.listFilesOrEmpty(cacheBaseDir)) { + if (Objects.equals(cacheName, cacheDir.getName())) { + Slog.d(TAG, "Keeping known cache " + cacheDir.getName()); + } else { + Slog.d(TAG, "Destroying unknown cache " + cacheDir.getName()); + FileUtils.deleteContentsAndDir(cacheDir); + } + } - // Return the versioned package cache directory. This is something like - // "/data/system/package_cache/1" - File cacheDir = FileUtils.createDir(cacheBaseDir, PACKAGE_PARSER_CACHE_VERSION); + // Return the versioned package cache directory. + File cacheDir = FileUtils.createDir(cacheBaseDir, cacheName); if (cacheDir == null) { // Something went wrong. Attempt to delete everything and return. @@ -3226,7 +3256,7 @@ public class PackageManagerService extends IPackageManager.Stub File frameworkDir = new File(Environment.getRootDirectory(), "framework"); if (cacheDir.lastModified() < frameworkDir.lastModified()) { FileUtils.deleteContents(cacheBaseDir); - cacheDir = FileUtils.createDir(cacheBaseDir, PACKAGE_PARSER_CACHE_VERSION); + cacheDir = FileUtils.createDir(cacheBaseDir, cacheName); } } @@ -15269,7 +15299,7 @@ public class PackageManagerService extends IPackageManager.Stub | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP); deletePackageAction = mayDeletePackageLocked(res.removedInfo, prepareResult.originalPs, prepareResult.disabledPs, - prepareResult.childPackageSettings, deleteFlags, installArgs.user); + prepareResult.childPackageSettings, deleteFlags, null /* all users */); if (deletePackageAction == null) { throw new ReconcileFailure( PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE, @@ -16708,6 +16738,7 @@ public class PackageManagerService extends IPackageManager.Stub int status = ivi.getStatus(); switch (status) { case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: return true; @@ -17356,28 +17387,22 @@ public class PackageManagerService extends IPackageManager.Stub * make sure this flag is set for partially installed apps. If not its meaningless to * delete a partially installed application. */ - private void removePackageDataLIF(PackageSetting ps, int[] allUserHandles, + private void removePackageDataLIF(final PackageSetting deletedPs, int[] allUserHandles, PackageRemovedInfo outInfo, int flags, boolean writeSettings) { - String packageName = ps.name; - if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + ps); + String packageName = deletedPs.name; + if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + deletedPs); // Retrieve object to delete permissions for shared user later on - final PackageParser.Package deletedPkg; - final PackageSetting deletedPs; - // reader - synchronized (mPackages) { - deletedPkg = mPackages.get(packageName); - deletedPs = mSettings.mPackages.get(packageName); - if (outInfo != null) { - outInfo.removedPackage = packageName; - outInfo.installerPackageName = ps.installerPackageName; - outInfo.isStaticSharedLib = deletedPkg != null - && deletedPkg.staticSharedLibName != null; - outInfo.populateUsers(deletedPs == null ? null - : deletedPs.queryInstalledUsers(sUserManager.getUserIds(), true), deletedPs); - } + final PackageParser.Package deletedPkg = deletedPs.pkg; + if (outInfo != null) { + outInfo.removedPackage = packageName; + outInfo.installerPackageName = deletedPs.installerPackageName; + outInfo.isStaticSharedLib = deletedPkg != null + && deletedPkg.staticSharedLibName != null; + outInfo.populateUsers(deletedPs == null ? null + : deletedPs.queryInstalledUsers(sUserManager.getUserIds(), true), deletedPs); } - removePackageLI(ps.name, (flags & PackageManager.DELETE_CHATTY) != 0); + removePackageLI(deletedPs.name, (flags & PackageManager.DELETE_CHATTY) != 0); if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) { final PackageParser.Package resolvedPkg; @@ -17386,8 +17411,8 @@ public class PackageManagerService extends IPackageManager.Stub } else { // We don't have a parsed package when it lives on an ejected // adopted storage device, so fake something together - resolvedPkg = new PackageParser.Package(ps.name); - resolvedPkg.setVolumeUuid(ps.volumeUuid); + resolvedPkg = new PackageParser.Package(deletedPs.name); + resolvedPkg.setVolumeUuid(deletedPs.volumeUuid); } destroyAppDataLIF(resolvedPkg, UserHandle.USER_ALL, StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE); @@ -17447,10 +17472,10 @@ public class PackageManagerService extends IPackageManager.Stub if (DEBUG_REMOVE) { Slog.d(TAG, " user " + userId + " => " + installed); } - if (installed != ps.getInstalled(userId)) { + if (installed != deletedPs.getInstalled(userId)) { installedStateChanged = true; } - ps.setInstalled(installed, userId); + deletedPs.setInstalled(installed, userId); } } } @@ -17460,7 +17485,7 @@ public class PackageManagerService extends IPackageManager.Stub mSettings.writeLPr(); } if (installedStateChanged) { - mSettings.writeKernelMappingLPr(ps); + mSettings.writeKernelMappingLPr(deletedPs); } } if (removedAppId != -1) { @@ -17530,12 +17555,12 @@ public class PackageManagerService extends IPackageManager.Stub /* * Tries to delete system package. */ - private void deleteSystemPackageLIF(DeletePackageAction action, - PackageParser.Package deletedPkg, PackageSetting deletedPs, int[] allUserHandles, - int flags, PackageRemovedInfo outInfo, boolean writeSettings) + private void deleteSystemPackageLIF(DeletePackageAction action, PackageSetting deletedPs, + int[] allUserHandles, int flags, PackageRemovedInfo outInfo, boolean writeSettings) throws SystemDeleteException { final boolean applyUserRestrictions = (allUserHandles != null) && (outInfo.origUsers != null); + final PackageParser.Package deletedPkg = deletedPs.pkg; // Confirm if the system package has been updated // An updated system app can be deleted. This will also have to restore // the system pkg from system partition @@ -17895,16 +17920,18 @@ public class PackageManagerService extends IPackageManager.Stub PackageSetting[] children = mSettings.getChildSettingsLPr(ps); action = mayDeletePackageLocked(outInfo, ps, disabledPs, children, flags, user); } + if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user); if (null == action) { + if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: action was null"); return false; } - if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user); try { executeDeletePackageLIF(action, packageName, deleteCodeAndResources, allUserHandles, writeSettings, replacingPackage); } catch (SystemDeleteException e) { + if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: system deletion failure", e); return false; } return true; @@ -17951,42 +17978,48 @@ public class PackageManagerService extends IPackageManager.Stub unsuspendForSuspendingPackage(packageName, userId); } - if (!systemApp || action.mayDeleteUnupdatedSystemApp) { // The caller is asking that the package only be deleted for a single // user. To do this, we just mark its uninstalled state and delete // its data. If this is a system app, we only allow this to happen if // they have set the special DELETE_SYSTEM_APP which requests different // semantics than normal for uninstalling system apps. - markPackageUninstalledForUserLPw(ps, user); - - if (!systemApp) { - // Do not uninstall the APK if an app should be cached - boolean keepUninstalledPackage = shouldKeepUninstalledPackageLPr(packageName); - if (ps.isAnyInstalled(sUserManager.getUserIds()) || keepUninstalledPackage) { - // Other user still have this package installed, so all + synchronized (mPackages) { + markPackageUninstalledForUserLPw(ps, user); + if (!systemApp) { + // Do not uninstall the APK if an app should be cached + boolean keepUninstalledPackage = shouldKeepUninstalledPackageLPr(packageName); + if (ps.isAnyInstalled(sUserManager.getUserIds()) || keepUninstalledPackage) { + // Other users still have this package installed, so all + // we need to do is clear this user's data and save that + // it is uninstalled. + if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users"); + clearPackageStateForUserLIF(ps, userId, outInfo, flags); + scheduleWritePackageRestrictionsLocked(user); + return; + } else { + // We need to set it back to 'installed' so the uninstall + // broadcasts will be sent correctly. + if (DEBUG_REMOVE) Slog.d(TAG, "Not installed by other users, full delete"); + if (userId != UserHandle.USER_ALL) { + ps.setInstalled(true, userId); + } else { + for (int origUserId : outInfo.origUsers) { + ps.setInstalled(true, origUserId); + } + } + mSettings.writeKernelMappingLPr(ps); + } + } else { + // This is a system app, so we assume that the + // other users still have this package installed, so all // we need to do is clear this user's data and save that // it is uninstalled. - if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users"); - clearPackageStateForUserLIF(ps, user.getIdentifier(), outInfo, flags); + if (DEBUG_REMOVE) Slog.d(TAG, "Deleting system app"); + clearPackageStateForUserLIF(ps, userId, outInfo, flags); scheduleWritePackageRestrictionsLocked(user); return; - } else { - // We need to set it back to 'installed' so the uninstall - // broadcasts will be sent correctly. - if (DEBUG_REMOVE) Slog.d(TAG, "Not installed by other users, full delete"); - ps.setInstalled(true, user.getIdentifier()); - mSettings.writeKernelMappingLPr(ps); } - } else { - // This is a system app, so we assume that the - // other users still have this package installed, so all - // we need to do is clear this user's data and save that - // it is uninstalled. - if (DEBUG_REMOVE) Slog.d(TAG, "Deleting system app"); - clearPackageStateForUserLIF(ps, user.getIdentifier(), outInfo, flags); - scheduleWritePackageRestrictionsLocked(user); - return; } } @@ -18015,8 +18048,7 @@ public class PackageManagerService extends IPackageManager.Stub if (DEBUG_REMOVE) Slog.d(TAG, "Removing system package: " + ps.name); // When an updated system application is deleted we delete the existing resources // as well and fall back to existing code in system partition - deleteSystemPackageLIF( - action, ps.pkg, ps, allUserHandles, flags, outInfo, writeSettings); + deleteSystemPackageLIF(action, ps, allUserHandles, flags, outInfo, writeSettings); } else { if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package: " + ps.name); deleteInstalledPackageLIF(ps, deleteCodeAndResources, flags, allUserHandles, diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index d1d5818b8c46..4f20590a47ff 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -2691,10 +2691,12 @@ public class UserManagerService extends IUserManager.Stub { if (!isGuest && !isManagedProfile && !isDemo && isUserLimitReached()) { // If we're not adding a guest/demo user or a managed profile and the limit has // been reached, cannot add a user. + Log.e(LOG_TAG, "Cannot add user. Maximum user limit is reached."); return null; } // If we're adding a guest and there already exists one, bail. if (isGuest && findCurrentGuestUser() != null) { + Log.e(LOG_TAG, "Cannot add guest user. Guest user already exists."); return null; } // In legacy mode, restricted profile's parent can only be the owner user @@ -2937,13 +2939,26 @@ public class UserManagerService extends IUserManager.Stub { final UserData userData; int currentUser = ActivityManager.getCurrentUser(); if (currentUser == userHandle) { - Log.w(LOG_TAG, "Current user cannot be removed"); + Log.w(LOG_TAG, "Current user cannot be removed."); return false; } synchronized (mPackagesLock) { synchronized (mUsersLock) { userData = mUsers.get(userHandle); - if (userHandle == 0 || userData == null || mRemovingUserIds.get(userHandle)) { + if (userHandle == UserHandle.USER_SYSTEM) { + Log.e(LOG_TAG, "System user cannot be removed."); + return false; + } + + if (userData == null) { + Log.e(LOG_TAG, String.format( + "Cannot remove user %d, invalid user id provided.", userHandle)); + return false; + } + + if (mRemovingUserIds.get(userHandle)) { + Log.e(LOG_TAG, String.format( + "User %d is already scheduled for removal.", userHandle)); return false; } @@ -2962,7 +2977,7 @@ public class UserManagerService extends IUserManager.Stub { try { mAppOpsService.removeUser(userHandle); } catch (RemoteException e) { - Log.w(LOG_TAG, "Unable to notify AppOpsService of removing user", e); + Log.w(LOG_TAG, "Unable to notify AppOpsService of removing user.", e); } if (userData.info.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID @@ -2986,6 +3001,7 @@ public class UserManagerService extends IUserManager.Stub { } }); } catch (RemoteException e) { + Log.w(LOG_TAG, "Failed to stop user during removal.", e); return false; } return res == ActivityManager.USER_OP_SUCCESS; diff --git a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java index f74aa1d69bc8..6d4bc8291611 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java +++ b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java @@ -62,6 +62,13 @@ class PackageDynamicCodeLoading extends AbstractStatsBase<Void> { private static final String PACKAGE_SEPARATOR = ","; /** + * Limit on how many files we store for a single owner, to avoid one app causing + * unbounded memory consumption. + */ + @VisibleForTesting + static final int MAX_FILES_PER_OWNER = 100; + + /** * Regular expression to match the expected format of an input line describing one file. * <p>Example: {@code D:10:package.name1,package.name2:/escaped/path} * <p>The capturing groups are the file type, user ID, loading packages and escaped file path @@ -515,6 +522,9 @@ class PackageDynamicCodeLoading extends AbstractStatsBase<Void> { private boolean add(String path, char fileType, int userId, String loadingPackage) { DynamicCodeFile fileInfo = mFileUsageMap.get(path); if (fileInfo == null) { + if (mFileUsageMap.size() >= MAX_FILES_PER_OWNER) { + return false; + } fileInfo = new DynamicCodeFile(fileType, userId, loadingPackage); mFileUsageMap.put(path, fileInfo); return true; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 3ba1155d2316..ebe202eda45a 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -599,6 +599,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { private boolean mAodShowing; + private boolean mPerDisplayFocusEnabled = false; + private int mTopFocusedDisplayId = INVALID_DISPLAY; + private static final int MSG_ENABLE_POINTER_LOCATION = 1; private static final int MSG_DISABLE_POINTER_LOCATION = 2; private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3; @@ -1811,6 +1814,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { mHandleVolumeKeysInWM = mContext.getResources().getBoolean( com.android.internal.R.bool.config_handleVolumeKeysInWindowManager); + mPerDisplayFocusEnabled = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_perDisplayFocusEnabled); + readConfigurationDependentBehaviors(); mAccessibilityManager = (AccessibilityManager) context.getSystemService( @@ -2542,6 +2548,20 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** {@inheritDoc} */ @Override public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) { + final long result = interceptKeyBeforeDispatchingInner(win, event, policyFlags); + if (result == 0 && !mPerDisplayFocusEnabled + && event.getDisplayId() != mTopFocusedDisplayId) { + // Someone tries to send a key event to a display which doesn't have a focused window. + // We drop the event here, or it will cause ANR. + // TODO (b/121057974): The user may be confused about why the key doesn't work, so we + // may need to deal with this problem. + return -1; + } + return result; + } + + private long interceptKeyBeforeDispatchingInner(WindowState win, KeyEvent event, + int policyFlags) { final boolean keyguardOn = keyguardOn(); final int keyCode = event.getKeyCode(); final int repeatCount = event.getRepeatCount(); @@ -3123,6 +3143,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override + public void setTopFocusedDisplay(int displayId) { + mTopFocusedDisplayId = displayId; + } + + @Override public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService) throws RemoteException { synchronized (mLock) { diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 3d474e331fb3..3da325c55b32 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -1034,6 +1034,13 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags); /** + * Called when the top focused display is changed. + * + * @param displayId The ID of the top focused display. + */ + void setTopFocusedDisplay(int displayId); + + /** * Apply the keyguard policy to a specific window. * * @param win The window to apply the keyguard policy. diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java index 6b0419e0f7ad..2cab63aa680a 100644 --- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -42,6 +42,7 @@ import android.view.textclassifier.TextClassification; import android.view.textclassifier.TextClassificationContext; import android.view.textclassifier.TextClassificationManager; import android.view.textclassifier.TextClassificationSessionId; +import android.view.textclassifier.TextClassifierEvent; import android.view.textclassifier.TextLanguage; import android.view.textclassifier.TextLinks; import android.view.textclassifier.TextSelection; @@ -214,6 +215,27 @@ public final class TextClassificationManagerService extends ITextClassifierServi } } } + @Override + public void onTextClassifierEvent( + TextClassificationSessionId sessionId, + TextClassifierEvent event) throws RemoteException { + Preconditions.checkNotNull(event); + final String packageName = event.getEventContext() == null + ? null + : event.getEventContext().getPackageName(); + validateInput(mContext, packageName); + + synchronized (mLock) { + UserState userState = getCallingUserStateLocked(); + if (userState.isBoundLocked()) { + userState.mService.onTextClassifierEvent(sessionId, event); + } else { + userState.mPendingRequests.add(new PendingRequest( + () -> onTextClassifierEvent(sessionId, event), + null /* onServiceFailure */, null /* binder */, this, userState)); + } + } + } @Override public void onDetectLanguage( diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 3291a45c94c4..ced593565983 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -19,12 +19,16 @@ package com.android.server.trust; import android.Manifest; import android.annotation.UserIdInt; import android.app.ActivityManager; +import android.app.AlarmManager; +import android.app.AlarmManager.OnAlarmListener; import android.app.admin.DevicePolicyManager; import android.hardware.biometrics.BiometricSourceType; import android.app.trust.ITrustListener; import android.app.trust.ITrustManager; +import android.app.UserSwitchObserver; import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -35,7 +39,9 @@ import android.content.pm.UserInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; +import android.database.ContentObserver; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.DeadObjectException; @@ -50,6 +56,7 @@ import android.os.UserManager; import android.provider.Settings; import android.service.trust.TrustAgentService; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; @@ -58,11 +65,13 @@ import android.util.SparseBooleanArray; import android.util.Xml; import android.view.IWindowManager; import android.view.WindowManagerGlobal; + import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; import com.android.internal.util.DumpUtils; import com.android.internal.widget.LockPatternUtils; import com.android.server.SystemService; + import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; @@ -106,8 +115,11 @@ public class TrustManagerService extends SystemService { private static final int MSG_STOP_USER = 12; private static final int MSG_DISPATCH_UNLOCK_LOCKOUT = 13; private static final int MSG_REFRESH_DEVICE_LOCKED_FOR_USER = 14; + private static final int MSG_SCHEDULE_TRUST_TIMEOUT = 15; private static final int TRUST_USUALLY_MANAGED_FLUSH_DELAY = 2 * 60 * 1000; + private static final String TRUST_TIMEOUT_ALARM_TAG = "TrustManagerService.trustTimeoutForUser"; + private static final long TRUST_TIMEOUT_IN_MILLIS = 20 * 1000; //4 * 60 * 60 * 1000; private final ArraySet<AgentInfo> mActiveAgents = new ArraySet<>(); private final ArrayList<ITrustListener> mTrustListeners = new ArrayList<>(); @@ -132,6 +144,11 @@ public class TrustManagerService extends SystemService { @GuardedBy("mUsersUnlockedByBiometric") private final SparseBooleanArray mUsersUnlockedByBiometric = new SparseBooleanArray(); + private final ArrayMap<Integer, TrustTimeoutAlarmListener> mTrustTimeoutAlarmListenerForUser = + new ArrayMap<>(); + private AlarmManager mAlarmManager; + private final SettingsObserver mSettingsObserver; + private final StrongAuthTracker mStrongAuthTracker; private boolean mTrustAgentsCanRun = false; @@ -144,6 +161,8 @@ public class TrustManagerService extends SystemService { mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); mLockPatternUtils = new LockPatternUtils(context); mStrongAuthTracker = new StrongAuthTracker(context); + mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + mSettingsObserver = new SettingsObserver(mHandler); } @Override @@ -170,7 +189,130 @@ public class TrustManagerService extends SystemService { } } - // Agent management + // Extend unlock config and logic + + private final class SettingsObserver extends ContentObserver { + private final Uri TRUST_AGENTS_EXTEND_UNLOCK = + Settings.Secure.getUriFor(Settings.Secure.TRUST_AGENTS_EXTEND_UNLOCK); + + private final Uri LOCK_SCREEN_WHEN_TRUST_LOST = + Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_WHEN_TRUST_LOST); + + private final ContentResolver mContentResolver; + private boolean mTrustAgentsExtendUnlock; + private boolean mLockWhenTrustLost; + + /** + * Creates a settings observer + * + * @param handler The handler to run {@link #onChange} on, or null if none. + */ + SettingsObserver(Handler handler) { + super(handler); + mContentResolver = getContext().getContentResolver(); + updateContentObserver(); + } + + void updateContentObserver() { + mContentResolver.unregisterContentObserver(this); + mContentResolver.registerContentObserver(TRUST_AGENTS_EXTEND_UNLOCK, + false /* notifyForDescendents */, + this /* observer */, + mCurrentUser); + mContentResolver.registerContentObserver(LOCK_SCREEN_WHEN_TRUST_LOST, + false /* notifyForDescendents */, + this /* observer */, + mCurrentUser); + + // Update the value immediately + onChange(true /* selfChange */, TRUST_AGENTS_EXTEND_UNLOCK); + onChange(true /* selfChange */, LOCK_SCREEN_WHEN_TRUST_LOST); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (TRUST_AGENTS_EXTEND_UNLOCK.equals(uri)) { + mTrustAgentsExtendUnlock = + Settings.Secure.getIntForUser( + mContentResolver, + Settings.Secure.TRUST_AGENTS_EXTEND_UNLOCK, + 0 /* default */, + mCurrentUser) != 0; + } else if (LOCK_SCREEN_WHEN_TRUST_LOST.equals(uri)) { + mLockWhenTrustLost = + Settings.Secure.getIntForUser( + mContentResolver, + Settings.Secure.LOCK_SCREEN_WHEN_TRUST_LOST, + 0 /* default */, + mCurrentUser) != 0; + } + } + + boolean getTrustAgentsExtendUnlock() { + return mTrustAgentsExtendUnlock; + } + + boolean getLockWhenTrustLost() { + return mLockWhenTrustLost; + } + } + + private void maybeLockScreen(int userId) { + if (userId != mCurrentUser) { + return; + } + + if (mSettingsObserver.getLockWhenTrustLost()) { + if (DEBUG) Slog.d(TAG, "Locking device because trust was lost"); + try { + WindowManagerGlobal.getWindowManagerService().lockNow(null); + } catch (RemoteException e) { + Slog.e(TAG, "Error locking screen when trust was lost"); + } + + // If active unlocking is not allowed, cancel any pending trust timeouts because the + // screen is already locked. + TrustTimeoutAlarmListener alarm = mTrustTimeoutAlarmListenerForUser.get(userId); + if (alarm != null && mSettingsObserver.getTrustAgentsExtendUnlock()) { + mAlarmManager.cancel(alarm); + alarm.setQueued(false /* isQueued */); + } + } + } + + private void scheduleTrustTimeout(int userId, boolean override) { + int shouldOverride = override ? 1 : 0; + if (override) { + shouldOverride = 1; + } + mHandler.obtainMessage(MSG_SCHEDULE_TRUST_TIMEOUT, userId, shouldOverride).sendToTarget(); + } + + private void handleScheduleTrustTimeout(int userId, int shouldOverride) { + long when = SystemClock.elapsedRealtime() + TRUST_TIMEOUT_IN_MILLIS; + userId = mCurrentUser; + TrustTimeoutAlarmListener alarm = mTrustTimeoutAlarmListenerForUser.get(userId); + + // Cancel existing trust timeouts for this user if needed. + if (alarm != null) { + if (shouldOverride == 0 && alarm.isQueued()) { + if (DEBUG) Slog.d(TAG, "Found existing trust timeout alarm. Skipping."); + return; + } + mAlarmManager.cancel(alarm); + } else { + alarm = new TrustTimeoutAlarmListener(userId); + mTrustTimeoutAlarmListenerForUser.put(userId, alarm); + } + + if (DEBUG) Slog.d(TAG, "\tSetting up trust timeout alarm"); + alarm.setQueued(true /* isQueued */); + mAlarmManager.setExact( + AlarmManager.ELAPSED_REALTIME_WAKEUP, when, TRUST_TIMEOUT_ALARM_TAG, alarm, + mHandler); + } + + // Agent management private static final class AgentInfo { CharSequence label; @@ -202,14 +344,36 @@ public class TrustManagerService extends SystemService { } } + public void updateTrust(int userId, int flags) { + updateTrust(userId, flags, false /* isFromUnlock */); + } + + private void updateTrust(int userId, int flags, boolean isFromUnlock) { boolean managed = aggregateIsTrustManaged(userId); dispatchOnTrustManagedChanged(managed, userId); if (mStrongAuthTracker.isTrustAllowedForUser(userId) && isTrustUsuallyManagedInternal(userId) != managed) { updateTrustUsuallyManaged(userId, managed); } + boolean trusted = aggregateIsTrusted(userId); + IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); + boolean showingKeyguard = true; + try { + showingKeyguard = wm.isKeyguardLocked(); + } catch (RemoteException e) { + } + + if (mSettingsObserver.getTrustAgentsExtendUnlock()) { + trusted = trusted && (!showingKeyguard || isFromUnlock) && userId == mCurrentUser; + if (DEBUG) { + Slog.d(TAG, "Extend unlock setting trusted as " + Boolean.toString(trusted) + + " && " + Boolean.toString(!showingKeyguard) + + " && " + Boolean.toString(userId == mCurrentUser)); + } + } + boolean changed; synchronized (mUserIsTrusted) { changed = mUserIsTrusted.get(userId) != trusted; @@ -218,6 +382,11 @@ public class TrustManagerService extends SystemService { dispatchOnTrustChanged(trusted, userId, flags); if (changed) { refreshDeviceLockedForUser(userId); + if (!trusted) { + maybeLockScreen(userId); + } else { + scheduleTrustTimeout(userId, false /* override */); + } } } @@ -704,6 +873,8 @@ public class TrustManagerService extends SystemService { private void dispatchUnlockAttempt(boolean successful, int userId) { if (successful) { mStrongAuthTracker.allowTrustFromUnlock(userId); + // Allow the presence of trust on a successful unlock attempt to extend unlock. + updateTrust(userId, 0 /* flags */, true); } for (int i = 0; i < mActiveAgents.size(); i++) { @@ -1033,8 +1204,11 @@ public class TrustManagerService extends SystemService { synchronized(mUsersUnlockedByBiometric) { mUsersUnlockedByBiometric.put(userId, true); } + // In extend unlock mode we need to refresh trust state here, which will call + // refreshDeviceLockedForUser() + int updateTrustOnUnlock = mSettingsObserver.getTrustAgentsExtendUnlock() ? 1 : 0; mHandler.obtainMessage(MSG_REFRESH_DEVICE_LOCKED_FOR_USER, userId, - 0 /* arg2 */).sendToTarget(); + updateTrustOnUnlock).sendToTarget(); } @Override @@ -1114,6 +1288,7 @@ public class TrustManagerService extends SystemService { break; case MSG_SWITCH_USER: mCurrentUser = msg.arg1; + mSettingsObserver.updateContentObserver(); refreshDeviceLockedForUser(UserHandle.USER_ALL); break; case MSG_STOP_USER: @@ -1134,8 +1309,14 @@ public class TrustManagerService extends SystemService { } break; case MSG_REFRESH_DEVICE_LOCKED_FOR_USER: + if (msg.arg2 == 1) { + updateTrust(msg.arg1, 0 /* flags */, true); + } refreshDeviceLockedForUser(msg.arg1); break; + case MSG_SCHEDULE_TRUST_TIMEOUT: + handleScheduleTrustTimeout(msg.arg1, msg.arg2); + break; } } }; @@ -1245,6 +1426,15 @@ public class TrustManagerService extends SystemService { + " agentsCanRun=" + canAgentsRunForUser(userId)); } + // Cancel pending alarms if we require some auth anyway. + if (!isTrustAllowedForUser(userId)) { + TrustTimeoutAlarmListener alarm = mTrustTimeoutAlarmListenerForUser.get(userId); + if (alarm != null && alarm.isQueued()) { + alarm.setQueued(false /* isQueued */); + mAlarmManager.cancel(alarm); + } + } + refreshAgentList(userId); // The list of active trust agents may not have changed, if there was a previous call @@ -1283,4 +1473,35 @@ public class TrustManagerService extends SystemService { } } } + + private class TrustTimeoutAlarmListener implements OnAlarmListener { + private final int mUserId; + private boolean mIsQueued = false; + + TrustTimeoutAlarmListener(int userId) { + mUserId = userId; + } + + @Override + public void onAlarm() { + mIsQueued = false; + int strongAuthState = mStrongAuthTracker.getStrongAuthForUser(mUserId); + + // Only fire if trust can unlock. + if (mStrongAuthTracker.isTrustAllowedForUser(mUserId)) { + if (DEBUG) Slog.d(TAG, "Revoking all trust because of trust timeout"); + mLockPatternUtils.requireStrongAuth( + mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, mUserId); + } + maybeLockScreen(mUserId); + } + + public void setQueued(boolean isQueued) { + mIsQueued = isQueued; + } + + public boolean isQueued() { + return mIsQueued; + } + } } diff --git a/services/core/java/com/android/server/utils/UserTokenWatcher.java b/services/core/java/com/android/server/utils/UserTokenWatcher.java new file mode 100644 index 000000000000..a3e58f802845 --- /dev/null +++ b/services/core/java/com/android/server/utils/UserTokenWatcher.java @@ -0,0 +1,166 @@ +/* + * 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.utils; + +import android.annotation.UserIdInt; +import android.os.Handler; +import android.os.IBinder; +import android.os.TokenWatcher; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.IndentingPrintWriter; + +import java.io.PrintWriter; + +/** + * Multi-user aware {@link TokenWatcher}. + * + * {@link UserTokenWatcher} is thread-safe. + */ +public final class UserTokenWatcher { + + private final Callback mCallback; + private final Handler mHandler; + private final String mTag; + + @GuardedBy("mWatchers") + private final SparseArray<TokenWatcher> mWatchers = new SparseArray<>(1); + + public UserTokenWatcher(Callback callback, Handler handler, String tag) { + mCallback = callback; + mHandler = handler; + mTag = tag; + } + + /** + * Record that this token has been acquired for the given user. When acquire is called, and + * the user's count goes from 0 to 1, the acquired callback is called on the given + * handler. + * + * Note that the same {@code token} can only be acquired once per user. If this + * {@code token} has already been acquired for the given user, no action is taken. The first + * subsequent call to {@link #release} will release this {@code token} + * immediately. + * + * @param token An IBinder object. + * @param tag A string used by the {@link #dump} method for debugging, + * to see who has references. + * @param userId A user id + */ + public void acquire(IBinder token, String tag, @UserIdInt int userId) { + synchronized (mWatchers) { + TokenWatcher watcher = mWatchers.get(userId); + if (watcher == null) { + watcher = new InnerTokenWatcher(userId, mHandler, mTag); + mWatchers.put(userId, watcher); + } + watcher.acquire(token, tag); + } + } + + /** + * Record that this token has been released for the given user. When release is called, and + * the user's count goes from 1 to 0, the released callback is called on the given + * handler. + * + * @param token An IBinder object. + * @param userId A user id + */ + public void release(IBinder token, @UserIdInt int userId) { + synchronized (mWatchers) { + TokenWatcher watcher = mWatchers.get(userId); + if (watcher != null) { + watcher.release(token); + } + } + } + + /** + * Returns whether the given user has any registered tokens that have not been cleaned up. + * + * @return true, if the given user has registered tokens. + */ + public boolean isAcquired(@UserIdInt int userId) { + synchronized (mWatchers) { + TokenWatcher watcher = mWatchers.get(userId); + return watcher != null && watcher.isAcquired(); + } + } + + /** + * Dumps the current state. + */ + public void dump(PrintWriter pw) { + synchronized (mWatchers) { + for (int i = 0; i < mWatchers.size(); i++) { + int userId = mWatchers.keyAt(i); + TokenWatcher watcher = mWatchers.valueAt(i); + if (watcher.isAcquired()) { + pw.print("User "); + pw.print(userId); + pw.println(":"); + watcher.dump(new IndentingPrintWriter(pw, " ")); + } + } + } + } + + /** + * Callback for {@link UserTokenWatcher}. + */ + public interface Callback { + + /** + * Reports that the first token has been acquired for the given user. + */ + void acquired(@UserIdInt int userId); + + /** + * Reports that the last token has been release for the given user. + */ + void released(@UserIdInt int userId); + } + + private final class InnerTokenWatcher extends TokenWatcher { + private final int mUserId; + + private InnerTokenWatcher(int userId, Handler handler, String tag) { + super(handler, tag); + this.mUserId = userId; + } + + @Override + public void acquired() { + // We MUST NOT hold any locks while invoking the callbacks. + mCallback.acquired(mUserId); + } + + @Override + public void released() { + // We MUST NOT hold any locks while invoking the callbacks. + mCallback.released(mUserId); + + synchronized (mWatchers) { + final TokenWatcher watcher = mWatchers.get(mUserId); + if (watcher != null && !watcher.isAcquired()) { + mWatchers.remove(mUserId); + } + } + } + } +} diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 43736755cf88..5f00bcc26984 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -86,6 +86,7 @@ import static android.os.Build.VERSION_CODES.O; import static android.os.Process.SYSTEM_UID; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT; import static com.android.server.am.ActivityRecordProto.CONFIGURATION_CONTAINER; import static com.android.server.am.ActivityRecordProto.FRONT_OF_TASK; @@ -583,6 +584,9 @@ final class ActivityRecord extends ConfigurationContainer { if (info.maxAspectRatio != 0) { pw.println(prefix + "maxAspectRatio=" + info.maxAspectRatio); } + if (info.minAspectRatio != 0) { + pw.println(prefix + "minAspectRatio=" + info.minAspectRatio); + } } } @@ -2593,7 +2597,10 @@ final class ActivityRecord extends ConfigurationContainer { outBounds.setEmpty(); final float maxAspectRatio = info.maxAspectRatio; final ActivityStack stack = getActivityStack(); - if (task == null || stack == null || task.inMultiWindowMode() || maxAspectRatio == 0 + final float minAspectRatio = info.minAspectRatio; + + if (task == null || stack == null || task.inMultiWindowMode() + || (maxAspectRatio == 0 && minAspectRatio == 0) || isInVrUiMode(getConfiguration())) { // We don't set override configuration if that activity task isn't fullscreen. I.e. the // activity is in multi-window mode. Or, there isn't a max aspect ratio specified for @@ -2608,20 +2615,35 @@ final class ActivityRecord extends ConfigurationContainer { final Rect appBounds = getParent().getWindowConfiguration().getAppBounds(); final int containingAppWidth = appBounds.width(); final int containingAppHeight = appBounds.height(); - int maxActivityWidth = containingAppWidth; - int maxActivityHeight = containingAppHeight; + final float containingRatio = Math.max(containingAppWidth, containingAppHeight) + / (float) Math.min(containingAppWidth, containingAppHeight); - if (containingAppWidth < containingAppHeight) { - // Width is the shorter side, so we use that to figure-out what the max. height - // should be given the aspect ratio. - maxActivityHeight = (int) ((maxActivityWidth * maxAspectRatio) + 0.5f); - } else { - // Height is the shorter side, so we use that to figure-out what the max. width - // should be given the aspect ratio. - maxActivityWidth = (int) ((maxActivityHeight * maxAspectRatio) + 0.5f); + int activityWidth = containingAppWidth; + int activityHeight = containingAppHeight; + + if (containingRatio > maxAspectRatio && maxAspectRatio != 0) { + if (containingAppWidth < containingAppHeight) { + // Width is the shorter side, so we use that to figure-out what the max. height + // should be given the aspect ratio. + activityHeight = (int) ((activityWidth * maxAspectRatio) + 0.5f); + } else { + // Height is the shorter side, so we use that to figure-out what the max. width + // should be given the aspect ratio. + activityWidth = (int) ((activityHeight * maxAspectRatio) + 0.5f); + } + } else if (containingRatio < minAspectRatio && minAspectRatio != 0) { + if (containingAppWidth < containingAppHeight) { + // Width is the shorter side, so we use the height to figure-out what the max. width + // should be given the aspect ratio. + activityWidth = (int) ((activityHeight / minAspectRatio) + 0.5f); + } else { + // Height is the shorter side, so we use the width to figure-out what the max. + // height should be given the aspect ratio. + activityHeight = (int) ((activityWidth / minAspectRatio) + 0.5f); + } } - if (containingAppWidth <= maxActivityWidth && containingAppHeight <= maxActivityHeight) { + if (containingAppWidth <= activityWidth && containingAppHeight <= activityHeight) { // The display matches or is less than the activity aspect ratio, so nothing else to do. // Return the existing bounds. If this method is running for the first time, // {@link #getRequestedOverrideBounds()} will be empty (representing no override). If @@ -2636,12 +2658,21 @@ final class ActivityRecord extends ConfigurationContainer { // Also account for the left / top insets (e.g. from display cutouts), which will be clipped // away later in StackWindowController.adjustConfigurationForBounds(). Otherwise, the app // bounds would end up too small. - outBounds.set(0, 0, maxActivityWidth + appBounds.left, maxActivityHeight + appBounds.top); + outBounds.set(0, 0, activityWidth + appBounds.left, activityHeight + appBounds.top); - if (mAtmService.mWindowManager.getNavBarPosition(getDisplayId()) == NAV_BAR_LEFT) { + final int navBarPosition = mAtmService.mWindowManager.getNavBarPosition(getDisplayId()); + if (navBarPosition == NAV_BAR_LEFT) { // Position the activity frame on the opposite side of the nav bar. - outBounds.left = appBounds.right - maxActivityWidth; + outBounds.left = appBounds.right - activityWidth; outBounds.right = appBounds.right; + } else if (navBarPosition == NAV_BAR_RIGHT) { + // Position the activity frame on the opposite side of the nav bar. + outBounds.left = 0; + outBounds.right = activityWidth + appBounds.left; + } else { + // Horizontally center the frame. + outBounds.left = appBounds.left + (containingAppWidth - activityWidth) / 2; + outBounds.right = outBounds.left + activityWidth; } } diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 8624bff11a18..8d49bf374baf 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -91,6 +91,7 @@ import android.graphics.GraphicBuffer; import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; +import android.os.Build; import android.os.Debug; import android.os.IBinder; import android.os.RemoteException; @@ -733,6 +734,17 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } boolean windowsAreFocusable() { + if (mTargetSdk < Build.VERSION_CODES.Q) { + final int pid = mActivityRecord != null + ? (mActivityRecord.app != null ? mActivityRecord.app.getPid() : 0) : 0; + final AppWindowToken topFocusedAppOfMyProcess = + mWmService.mRoot.mTopFocusedAppByProcess.get(pid); + if (topFocusedAppOfMyProcess != null && topFocusedAppOfMyProcess != this) { + // For the apps below Q, there can be only one app which has the focused window per + // process, because legacy apps may not be ready for a multi-focus system. + return false; + } + } return getWindowConfiguration().canReceiveKeys() || mAlwaysFocusable; } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 2f4c5cab2559..415357d6ceaf 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -61,9 +61,9 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN; import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; - import static android.view.WindowManager.TRANSIT_TASK_OPEN; import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT; + import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; @@ -103,6 +103,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.CUSTOM_SCREEN_ROTATION; +import static com.android.server.wm.WindowManagerService.H.REPORT_FOCUS_CHANGE; import static com.android.server.wm.WindowManagerService.H.REPORT_HARD_KEYBOARD_STATUS_CHANGE; import static com.android.server.wm.WindowManagerService.H.REPORT_LOSING_FOCUS; import static com.android.server.wm.WindowManagerService.H.SEND_NEW_CONFIGURATION; @@ -2832,6 +2833,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo forAllWindows(mScheduleToastTimeout, false /* traverseTopToBottom */); } + WindowState findFocusedWindowIfNeeded() { + return (mWmService.mPerDisplayFocusEnabled + || mWmService.mRoot.mTopFocusedAppByProcess.isEmpty()) ? findFocusedWindow() : null; + } + WindowState findFocusedWindow() { mTmpWindow = null; @@ -2844,7 +2850,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mTmpWindow; } - /** * Update the focused window and make some adjustments if the focus has changed. * @@ -2856,31 +2861,31 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * @param updateInputWindows Whether to sync the window information to the input module. * @return {@code true} if the focused window has changed. */ - boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows, boolean focusFound) { - final WindowState newFocus = findFocusedWindow(); + boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) { + WindowState newFocus = findFocusedWindowIfNeeded(); if (mCurrentFocus == newFocus) { return false; } boolean imWindowChanged = false; - // TODO (b/111080190): Multi-Session IME - if (!focusFound) { - final WindowState imWindow = mInputMethodWindow; - if (imWindow != null) { - final WindowState prevTarget = mInputMethodTarget; - - final WindowState newTarget = computeImeTarget(true /* updateImeTarget*/); - imWindowChanged = prevTarget != newTarget; - - if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS - && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) { - assignWindowLayers(false /* setLayoutNeeded */); - } + final WindowState imWindow = mInputMethodWindow; + if (imWindow != null) { + final WindowState prevTarget = mInputMethodTarget; + final WindowState newTarget = computeImeTarget(true /* updateImeTarget*/); + imWindowChanged = prevTarget != newTarget; + + if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS + && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) { + assignWindowLayers(false /* setLayoutNeeded */); } } if (imWindowChanged) { mWmService.mWindowsChanged = true; setLayoutNeeded(); + newFocus = findFocusedWindowIfNeeded(); + } + if (mCurrentFocus != newFocus) { + mWmService.mH.obtainMessage(REPORT_FOCUS_CHANGE, this).sendToTarget(); } if (DEBUG_FOCUS_LIGHT || mWmService.localLOGV) Slog.v(TAG_WM, "Changing focus from " diff --git a/services/core/java/com/android/server/wm/KeyguardDisableHandler.java b/services/core/java/com/android/server/wm/KeyguardDisableHandler.java index 4a20f1a039ef..c9173a6430cc 100644 --- a/services/core/java/com/android/server/wm/KeyguardDisableHandler.java +++ b/services/core/java/com/android/server/wm/KeyguardDisableHandler.java @@ -19,113 +19,142 @@ package com.android.server.wm; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import android.app.ActivityManager; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.os.Handler; import android.os.IBinder; -import android.os.Message; -import android.os.RemoteException; -import android.os.TokenWatcher; -import android.util.Log; -import android.util.Pair; +import android.os.Process; +import android.os.UserHandle; +import android.os.UserManagerInternal; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; import com.android.server.policy.WindowManagerPolicy; +import com.android.server.utils.UserTokenWatcher; +import com.android.server.wm.LockTaskController.LockTaskToken; -public class KeyguardDisableHandler extends Handler { +class KeyguardDisableHandler { private static final String TAG = TAG_WITH_CLASS_NAME ? "KeyguardDisableHandler" : TAG_WM; - private static final int ALLOW_DISABLE_YES = 1; - private static final int ALLOW_DISABLE_NO = 0; - private static final int ALLOW_DISABLE_UNKNOWN = -1; // check with DevicePolicyManager - private int mAllowDisableKeyguard = ALLOW_DISABLE_UNKNOWN; // sync'd by mKeyguardTokenWatcher + private final UserTokenWatcher mAppTokenWatcher; + private final UserTokenWatcher mSystemTokenWatcher; - // Message.what constants - static final int KEYGUARD_DISABLE = 1; - static final int KEYGUARD_REENABLE = 2; - static final int KEYGUARD_POLICY_CHANGED = 3; + private int mCurrentUser = UserHandle.USER_SYSTEM; + private Injector mInjector; - final Context mContext; - final WindowManagerPolicy mPolicy; - KeyguardTokenWatcher mKeyguardTokenWatcher; + @VisibleForTesting + KeyguardDisableHandler(Injector injector, Handler handler) { + mInjector = injector; + mAppTokenWatcher = new UserTokenWatcher(mCallback, handler, TAG); + mSystemTokenWatcher = new UserTokenWatcher(mCallback, handler, TAG); + } - public KeyguardDisableHandler(final Context context, final WindowManagerPolicy policy) { - mContext = context; - mPolicy = policy; + public void setCurrentUser(int user) { + synchronized (this) { + mCurrentUser = user; + updateKeyguardEnabledLocked(UserHandle.USER_ALL); + } } - @SuppressWarnings("unchecked") - @Override - public void handleMessage(Message msg) { - if (mKeyguardTokenWatcher == null) { - mKeyguardTokenWatcher = new KeyguardTokenWatcher(this); + void updateKeyguardEnabled(int userId) { + synchronized (this) { + updateKeyguardEnabledLocked(userId); } + } - switch (msg.what) { - case KEYGUARD_DISABLE: - final Pair<IBinder, String> pair = (Pair<IBinder, String>)msg.obj; - mKeyguardTokenWatcher.acquire(pair.first, pair.second); - break; - - case KEYGUARD_REENABLE: - mKeyguardTokenWatcher.release((IBinder)msg.obj); - break; - - case KEYGUARD_POLICY_CHANGED: - mAllowDisableKeyguard = ALLOW_DISABLE_UNKNOWN; - if (mKeyguardTokenWatcher.isAcquired()) { - // If we are currently disabled we need to know if the keyguard - // should be re-enabled, so determine the allow state immediately. - mKeyguardTokenWatcher.updateAllowState(); - if (mAllowDisableKeyguard != ALLOW_DISABLE_YES) { - mPolicy.enableKeyguard(true); - } - } else { - // lazily evaluate this next time we're asked to disable keyguard - mPolicy.enableKeyguard(true); - } - break; + private void updateKeyguardEnabledLocked(int userId) { + if (mCurrentUser == userId || userId == UserHandle.USER_ALL) { + mInjector.enableKeyguard(shouldKeyguardBeEnabled(mCurrentUser)); } } - class KeyguardTokenWatcher extends TokenWatcher { + void disableKeyguard(IBinder token, String tag, int callingUid, int userId) { + UserTokenWatcher watcherForCaller = watcherForCallingUid(token, callingUid); + watcherForCaller.acquire(token, tag, mInjector.getProfileParentId(userId)); + } - public KeyguardTokenWatcher(final Handler handler) { - super(handler, TAG); - } + void reenableKeyguard(IBinder token, int callingUid, int userId) { + UserTokenWatcher watcherForCaller = watcherForCallingUid(token, callingUid); + watcherForCaller.release(token, mInjector.getProfileParentId(userId)); + } - public void updateAllowState() { - // We fail safe and prevent disabling keyguard in the unlikely event this gets - // called before DevicePolicyManagerService has started. - DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService( - Context.DEVICE_POLICY_SERVICE); - if (dpm != null) { - try { - mAllowDisableKeyguard = dpm.getPasswordQuality(null, - ActivityManager.getService().getCurrentUser().id) - == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED ? - ALLOW_DISABLE_YES : ALLOW_DISABLE_NO; - } catch (RemoteException re) { - // Nothing much we can do - } - } + private UserTokenWatcher watcherForCallingUid(IBinder token, int callingUid) { + if (Process.isApplicationUid(callingUid)) { + return mAppTokenWatcher; + } else if (callingUid == Process.SYSTEM_UID && token instanceof LockTaskToken) { + // We allow the lock task token here as a legacy case, because it enforces its own + // security guarantees. + // NOTE: DO NOT add new usages of this API in system server. It is deprecated and + // easily misused. + return mSystemTokenWatcher; + } else { + throw new UnsupportedOperationException("Only apps can use the KeyguardLock API"); } + } + + private boolean shouldKeyguardBeEnabled(int userId) { + final boolean dpmRequiresPassword = mInjector.dpmRequiresPassword(mCurrentUser); + final boolean keyguardSecure = mInjector.isKeyguardSecure(mCurrentUser); + + final boolean allowedFromApps = !dpmRequiresPassword && !keyguardSecure; + // The system can disable the keyguard for lock task mode even if the keyguard is secure, + // because it enforces its own security guarantees. + final boolean allowedFromSystem = !dpmRequiresPassword; + final boolean shouldBeDisabled = allowedFromApps && mAppTokenWatcher.isAcquired(userId) + || allowedFromSystem && mSystemTokenWatcher.isAcquired(userId); + return !shouldBeDisabled; + } + + // Callback happens on mHandler thread. + private final UserTokenWatcher.Callback mCallback = new UserTokenWatcher.Callback() { @Override - public void acquired() { - if (mAllowDisableKeyguard == ALLOW_DISABLE_UNKNOWN) { - updateAllowState(); - } - if (mAllowDisableKeyguard == ALLOW_DISABLE_YES) { - mPolicy.enableKeyguard(false); - } else { - Log.v(TAG, "Not disabling keyguard since device policy is enforced"); - } + public void acquired(int userId) { + updateKeyguardEnabled(userId); } @Override - public void released() { - mPolicy.enableKeyguard(true); + public void released(int userId) { + updateKeyguardEnabled(userId); } + }; + + static KeyguardDisableHandler create(Context context, WindowManagerPolicy policy, + Handler handler) { + final UserManagerInternal userManager = LocalServices.getService(UserManagerInternal.class); + return new KeyguardDisableHandler(new Injector() { + @Override + public boolean dpmRequiresPassword(int userId) { + DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( + Context.DEVICE_POLICY_SERVICE); + return dpm == null || dpm.getPasswordQuality(null, userId) + != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; + } + + @Override + public boolean isKeyguardSecure(int userId) { + return policy.isKeyguardSecure(userId); + } + + @Override + public int getProfileParentId(int userId) { + return userManager.getProfileParentId(userId); + } + + @Override + public void enableKeyguard(boolean enabled) { + policy.enableKeyguard(enabled); + } + }, handler); + } + + interface Injector { + boolean dpmRequiresPassword(int userId); + + boolean isKeyguardSecure(int userId); + + int getProfileParentId(int userId); + + void enableKeyguard(boolean enabled); } } diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java index 3b66f7d79752..e6e6275feacd 100644 --- a/services/core/java/com/android/server/wm/LockTaskController.java +++ b/services/core/java/com/android/server/wm/LockTaskController.java @@ -54,6 +54,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.provider.Settings; import android.telecom.TelecomManager; import android.util.Pair; @@ -126,7 +127,7 @@ public class LockTaskController { /** Tag used for disabling of keyguard */ private static final String LOCK_TASK_TAG = "Lock-to-App"; - private final IBinder mToken = new Binder(); + private final IBinder mToken = new LockTaskToken(); private final ActivityStackSupervisor mSupervisor; private final Context mContext; @@ -180,6 +181,17 @@ public class LockTaskController { */ private final Handler mHandler; + /** + * Stores the user for which we're trying to dismiss the keyguard and then subsequently + * disable it. + * + * Tracking this ensures we don't mistakenly disable the keyguard if we've stopped trying to + * between the dismiss request and when it succeeds. + * + * Must only be accessed from the Handler thread. + */ + private int mPendingDisableFromDismiss = UserHandle.USER_NULL; + LockTaskController(Context context, ActivityStackSupervisor supervisor, Handler handler) { mContext = context; @@ -740,16 +752,18 @@ public class LockTaskController { * Should only be called on the handler thread to avoid race. */ private void setKeyguardState(int lockTaskModeState, int userId) { + mPendingDisableFromDismiss = UserHandle.USER_NULL; if (lockTaskModeState == LOCK_TASK_MODE_NONE) { - mWindowManager.reenableKeyguard(mToken); + mWindowManager.reenableKeyguard(mToken, userId); } else if (lockTaskModeState == LOCK_TASK_MODE_LOCKED) { if (isKeyguardAllowed(userId)) { - mWindowManager.reenableKeyguard(mToken); + mWindowManager.reenableKeyguard(mToken, userId); } else { // If keyguard is not secure and it is locked, dismiss the keyguard before // disabling it, which avoids the platform to think the keyguard is still on. if (mWindowManager.isKeyguardLocked() && !mWindowManager.isKeyguardSecure()) { + mPendingDisableFromDismiss = userId; mWindowManager.dismissKeyguard(new IKeyguardDismissCallback.Stub() { @Override public void onDismissError() throws RemoteException { @@ -759,7 +773,13 @@ public class LockTaskController { @Override public void onDismissSucceeded() throws RemoteException { mHandler.post( - () -> mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG)); + () -> { + if (mPendingDisableFromDismiss == userId) { + mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG, + userId); + mPendingDisableFromDismiss = UserHandle.USER_NULL; + } + }); } @Override @@ -768,12 +788,12 @@ public class LockTaskController { } }, null); } else { - mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG); + mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG, userId); } } } else { // lockTaskModeState == LOCK_TASK_MODE_PINNED - mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG); + mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG, userId); } } @@ -898,4 +918,10 @@ public class LockTaskController { default: return "unknown=" + mLockTaskModeState; } } + + /** Marker class for the token used to disable keyguard. */ + static class LockTaskToken extends Binder { + private LockTaskToken() { + } + } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 3bbef9248f23..6e4f69ea3828 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -79,6 +79,7 @@ import com.android.server.EventLogTags; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashMap; import java.util.function.Consumer; /** Root {@link WindowContainer} for the device. */ @@ -122,7 +123,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // The ID of the display which is responsible for receiving display-unspecified key and pointer // events. - int mTopFocusedDisplayId = INVALID_DISPLAY; + private int mTopFocusedDisplayId = INVALID_DISPLAY; + + // Map from the PID to the top most app which has a focused window of the process. + final HashMap<Integer, AppWindowToken> mTopFocusedAppByProcess = new HashMap<>(); // Only a separate transaction until we separate the apply surface changes // transaction from the global transaction. @@ -157,50 +161,32 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) { + mTopFocusedAppByProcess.clear(); boolean changed = false; int topFocusedDisplayId = INVALID_DISPLAY; - for (int i = mChildren.size() - 1; i >= 0; --i) { final DisplayContent dc = mChildren.get(i); - changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows, - topFocusedDisplayId != INVALID_DISPLAY /* focusFound */); - if (topFocusedDisplayId == INVALID_DISPLAY && dc.mCurrentFocus != null) { - topFocusedDisplayId = dc.getDisplayId(); + changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows); + final WindowState newFocus = dc.mCurrentFocus; + if (newFocus != null) { + final int pidOfNewFocus = newFocus.mSession.mPid; + if (mTopFocusedAppByProcess.get(pidOfNewFocus) == null) { + mTopFocusedAppByProcess.put(pidOfNewFocus, newFocus.mAppToken); + } + if (topFocusedDisplayId == INVALID_DISPLAY) { + topFocusedDisplayId = dc.getDisplayId(); + } } } if (topFocusedDisplayId == INVALID_DISPLAY) { topFocusedDisplayId = DEFAULT_DISPLAY; } - // TODO(b/118865114): Review if need callback top focus display change to view component. - // (i.e. Activity or View) - // Currently we only tracked topFocusedDisplayChanged for notifying InputMethodManager via - // ViewRootImpl.windowFocusChanged to refocus IME window when top display focus changed - // but window focus remain the same case. - // It may need to review if any use case that need to add new callback for reporting - // this change. - final boolean topFocusedDisplayChanged = - mTopFocusedDisplayId != topFocusedDisplayId && mode == UPDATE_FOCUS_NORMAL; if (mTopFocusedDisplayId != topFocusedDisplayId) { mTopFocusedDisplayId = topFocusedDisplayId; - mWmService.mInputManager.setFocusedDisplay(mTopFocusedDisplayId); + mWmService.mInputManager.setFocusedDisplay(topFocusedDisplayId); if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "New topFocusedDisplayId=" - + mTopFocusedDisplayId); - } - - // Report window focus or top display focus changed through REPORT_FOCUS_CHANGE. - forAllDisplays((dc) -> { - final boolean windowFocusChanged = - dc.mCurrentFocus != null && dc.mCurrentFocus != dc.mLastFocus; - final boolean isTopFocusedDisplay = - topFocusedDisplayChanged && dc.getDisplayId() == mTopFocusedDisplayId; - if (windowFocusChanged || isTopFocusedDisplay) { - final Message msg = mWmService.mH.obtainMessage( - WindowManagerService.H.REPORT_FOCUS_CHANGE, dc); - msg.arg1 = topFocusedDisplayChanged ? 1 : 0; - mWmService.mH.sendMessage(msg); - } - }); - + + topFocusedDisplayId); + } return changed; } diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java index 53d2cb094c44..c006a7ba44d7 100644 --- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java +++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java @@ -16,6 +16,12 @@ package com.android.server.wm; +import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW; +import static android.view.PointerIcon.TYPE_NOT_SPECIFIED; +import static android.view.PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW; +import static android.view.PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW; +import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW; + import android.graphics.Rect; import android.graphics.Region; import android.hardware.input.InputManager; @@ -25,21 +31,18 @@ import android.view.WindowManagerPolicyConstants.PointerEventListener; import com.android.server.wm.WindowManagerService.H; -import static android.view.PointerIcon.TYPE_NOT_SPECIFIED; -import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW; -import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW; -import static android.view.PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW; -import static android.view.PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW; - public class TaskTapPointerEventListener implements PointerEventListener { - final private Region mTouchExcludeRegion = new Region(); + private final Region mTouchExcludeRegion = new Region(); + private final Region mTmpRegion = new Region(); private final WindowManagerService mService; private final DisplayContent mDisplayContent; private final Handler mHandler; private final Runnable mMoveDisplayToTop; private final Rect mTmpRect = new Rect(); private int mPointerIconType = TYPE_NOT_SPECIFIED; + private int mLastDownX; + private int mLastDownY; public TaskTapPointerEventListener(WindowManagerService service, DisplayContent displayContent) { @@ -47,7 +50,22 @@ public class TaskTapPointerEventListener implements PointerEventListener { mDisplayContent = displayContent; mHandler = new Handler(mService.mH.getLooper()); mMoveDisplayToTop = () -> { + int x; + int y; + synchronized (this) { + x = mLastDownX; + y = mLastDownY; + } synchronized (mService.mGlobalLock) { + if (!mService.mPerDisplayFocusEnabled + && mService.mRoot.getTopFocusedDisplayContent() != mDisplayContent + && inputMethodWindowContains(x, y)) { + // In a single focus system, if the input method window and the input method + // target window are on the different displays, when the user is tapping on the + // input method window, we don't move its display to top. Otherwise, the input + // method target window will lose the focus. + return; + } mDisplayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP, mDisplayContent, true /* includingParents */); } @@ -70,6 +88,8 @@ public class TaskTapPointerEventListener implements PointerEventListener { mService.mTaskPositioningController.handleTapOutsideTask( mDisplayContent, x, y); } + mLastDownX = x; + mLastDownY = y; mHandler.post(mMoveDisplayToTop); } } @@ -122,4 +142,13 @@ public class TaskTapPointerEventListener implements PointerEventListener { private int getDisplayId() { return mDisplayContent.getDisplayId(); } + + private boolean inputMethodWindowContains(int x, int y) { + final WindowState inputMethodWindow = mDisplayContent.mInputMethodWindow; + if (inputMethodWindow == null || !inputMethodWindow.isVisibleLw()) { + return false; + } + inputMethodWindow.getTouchableRegion(mTmpRegion); + return mTmpRegion.contains(x, y); + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 9f1a58770611..646fdd960adc 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -334,6 +334,11 @@ public abstract class WindowManagerInternal { public abstract void registerAppTransitionListener(AppTransitionListener listener); /** + * Reports that the password for the given user has changed. + */ + public abstract void reportPasswordChanged(int userId); + + /** * Retrieves a height of input method window for given display. */ public abstract int getInputMethodWindowVisibleHeight(int displayId); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 002d6d409abe..e3ced8350f0d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -22,6 +22,7 @@ import static android.Manifest.permission.MANAGE_APP_TOKENS; import static android.Manifest.permission.READ_FRAME_BUFFER; import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS; import static android.Manifest.permission.RESTRICTED_VR_ACCESS; +import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; import static android.app.StatusBarManager.DISABLE_MASK; @@ -72,7 +73,6 @@ import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN; import static com.android.server.LockGuard.INDEX_WINDOW; import static com.android.server.LockGuard.installLock; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; -import static com.android.server.wm.KeyguardDisableHandler.KEYGUARD_POLICY_CHANGED; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT; @@ -181,7 +181,6 @@ import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.util.MergedConfiguration; -import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -235,6 +234,7 @@ import com.android.internal.policy.IShortcutService; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.LatencyTracker; +import com.android.internal.util.Preconditions; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.view.WindowManagerPolicyThread; import com.android.server.AnimationThread; @@ -396,7 +396,7 @@ public class WindowManagerService extends IWindowManager.Stub public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { case ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED: - mKeyguardDisableHandler.sendEmptyMessage(KEYGUARD_POLICY_CHANGED); + mKeyguardDisableHandler.updateKeyguardEnabled(getSendingUserId()); break; } } @@ -611,6 +611,9 @@ public class WindowManagerService extends IWindowManager.Stub boolean mClientFreezingScreen = false; int mAppsFreezingScreen = 0; + @VisibleForTesting + boolean mPerDisplayFocusEnabled; + // State while inside of layoutAndPlaceSurfacesLocked(). boolean mFocusMayChange; @@ -944,6 +947,8 @@ public class WindowManagerService extends IWindowManager.Stub com.android.internal.R.integer.config_maxUiWidth); mDisableTransitionAnimation = context.getResources().getBoolean( com.android.internal.R.bool.config_disableTransitionAnimation); + mPerDisplayFocusEnabled = context.getResources().getBoolean( + com.android.internal.R.bool.config_perDisplayFocusEnabled); mInputManager = inputManager; // Must be before createDisplayContentLocked. mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); mDisplayWindowSettings = new DisplayWindowSettings(this); @@ -961,7 +966,7 @@ public class WindowManagerService extends IWindowManager.Stub mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); - mKeyguardDisableHandler = new KeyguardDisableHandler(mContext, mPolicy); + mKeyguardDisableHandler = KeyguardDisableHandler.create(mContext, mPolicy, mH); mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); @@ -1040,7 +1045,7 @@ public class WindowManagerService extends IWindowManager.Stub IntentFilter filter = new IntentFilter(); // Track changes to DevicePolicyManager state so we can enable/disable keyguard. filter.addAction(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); - mContext.registerReceiver(mBroadcastReceiver, filter); + mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null); mLatencyTracker = LatencyTracker.getInstance(context); @@ -2817,45 +2822,38 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void disableKeyguard(IBinder token, String tag) { + public void disableKeyguard(IBinder token, String tag, int userId) { + userId = mAmInternal.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), + userId, false /* allowAll */, ALLOW_FULL_ONLY, "disableKeyguard", null); if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires DISABLE_KEYGUARD permission"); } - // If this isn't coming from the system then don't allow disabling the lockscreen - // to bypass security. - if (Binder.getCallingUid() != SYSTEM_UID && isKeyguardSecure()) { - Log.d(TAG_WM, "current mode is SecurityMode, ignore disableKeyguard"); - return; - } - - // If this isn't coming from the current profiles, ignore it. - if (!isCurrentProfileLocked(UserHandle.getCallingUserId())) { - Log.d(TAG_WM, "non-current profiles, ignore disableKeyguard"); - return; - } - - if (token == null) { - throw new IllegalArgumentException("token == null"); + final int callingUid = Binder.getCallingUid(); + final long origIdentity = Binder.clearCallingIdentity(); + try { + mKeyguardDisableHandler.disableKeyguard(token, tag, callingUid, userId); + } finally { + Binder.restoreCallingIdentity(origIdentity); } - - mKeyguardDisableHandler.sendMessage(mKeyguardDisableHandler.obtainMessage( - KeyguardDisableHandler.KEYGUARD_DISABLE, new Pair<IBinder, String>(token, tag))); } @Override - public void reenableKeyguard(IBinder token) { + public void reenableKeyguard(IBinder token, int userId) { + userId = mAmInternal.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), + userId, false /* allowAll */, ALLOW_FULL_ONLY, "reenableKeyguard", null); if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires DISABLE_KEYGUARD permission"); } - - if (token == null) { - throw new IllegalArgumentException("token == null"); + Preconditions.checkNotNull(token, "token is null"); + final int callingUid = Binder.getCallingUid(); + final long origIdentity = Binder.clearCallingIdentity(); + try { + mKeyguardDisableHandler.reenableKeyguard(token, callingUid, userId); + } finally { + Binder.restoreCallingIdentity(origIdentity); } - - mKeyguardDisableHandler.sendMessage(mKeyguardDisableHandler.obtainMessage( - KeyguardDisableHandler.KEYGUARD_REENABLE, token)); } /** @@ -3139,11 +3137,7 @@ public class WindowManagerService extends IWindowManager.Stub mCurrentUserId = newUserId; mCurrentProfileIds = currentProfileIds; mPolicy.setCurrentUserLw(newUserId); - - // If keyguard was disabled, re-enable it - // TODO: Keep track of keyguardEnabled state per user and use here... - // e.g. enabled = mKeyguardDisableHandler.getEnabledStateForUser(newUserId); - mPolicy.enableKeyguard(true); + mKeyguardDisableHandler.setCurrentUser(newUserId); // Hide windows that should not be seen by the new user. mRoot.switchUser(); @@ -4484,7 +4478,6 @@ public class WindowManagerService extends IWindowManager.Stub AccessibilityController accessibilityController = null; - final boolean topFocusedDisplayChanged = msg.arg1 != 0; synchronized (mGlobalLock) { // TODO(multidisplay): Accessibility supported only of default desiplay. if (mAccessibilityController != null && displayContent.isDefaultDisplay) { @@ -4495,19 +4488,7 @@ public class WindowManagerService extends IWindowManager.Stub newFocus = displayContent.mCurrentFocus; } if (lastFocus == newFocus) { - // Report focus to ViewRootImpl when top focused display changes. - // Or, nothing to do for no window focus change. - if (topFocusedDisplayChanged && newFocus != null) { - if (DEBUG_FOCUS_LIGHT) { - Slog.d(TAG, "Reporting focus: " + newFocus - + " due to top focused display change."); - } - // See {@link IWindow#windowFocusChanged} to know why set - // reportToClient as false. - newFocus.reportFocusChangedSerialized(true, mInTouchMode, - false /* reportToClient */); - notifyFocusChanged(); - } + // Focus is not changing, so nothing to do. return; } synchronized (mGlobalLock) { @@ -4529,15 +4510,13 @@ public class WindowManagerService extends IWindowManager.Stub if (newFocus != null) { if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Gaining focus: " + newFocus); - newFocus.reportFocusChangedSerialized(true, mInTouchMode, - true /* reportToClient */); + newFocus.reportFocusChangedSerialized(true, mInTouchMode); notifyFocusChanged(); } if (lastFocus != null) { if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Losing focus: " + lastFocus); - lastFocus.reportFocusChangedSerialized(false, mInTouchMode, - true /* reportToClient */); + lastFocus.reportFocusChangedSerialized(false, mInTouchMode); } } break; @@ -4554,8 +4533,7 @@ public class WindowManagerService extends IWindowManager.Stub for (int i = 0; i < N; i++) { if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Losing delayed focus: " + losers.get(i)); - losers.get(i).reportFocusChangedSerialized(false, mInTouchMode, - true /* reportToClient */); + losers.get(i).reportFocusChangedSerialized(false, mInTouchMode); } } break; @@ -7124,6 +7102,11 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void reportPasswordChanged(int userId) { + mKeyguardDisableHandler.updateKeyguardEnabled(userId); + } + + @Override public int getInputMethodWindowVisibleHeight(int displayId) { synchronized (mGlobalLock) { final DisplayContent dc = mRoot.getDisplayContent(displayId); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index d2dfa76d7b34..f86d08965e32 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2866,13 +2866,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * Report a focus change. Must be called with no locks held, and consistently * from the same serialized thread (such as dispatched from a handler). */ - void reportFocusChangedSerialized(boolean focused, boolean inTouchMode, - boolean reportToClient) { + void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) { try { - mClient.windowFocusChanged(focused, inTouchMode, reportToClient); + mClient.windowFocusChanged(focused, inTouchMode); } catch (RemoteException e) { } - if (mFocusCallbacks != null && reportToClient) { + if (mFocusCallbacks != null) { final int N = mFocusCallbacks.beginBroadcast(); for (int i=0; i<N; i++) { IWindowFocusObserver obs = mFocusCallbacks.getBroadcastItem(i); diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index 0e349b7893e7..58fd30e225c1 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -92,7 +92,6 @@ using android::hardware::gnss::V1_0::GnssLocationFlags; using android::hardware::gnss::V1_0::IAGnss; using android::hardware::gnss::V1_0::IAGnssCallback; using android::hardware::gnss::V1_0::IAGnssCallback; -using android::hardware::gnss::V1_0::IAGnssRil; using android::hardware::gnss::V1_0::IAGnssRilCallback; using android::hardware::gnss::V1_0::IGnssBatching; using android::hardware::gnss::V1_0::IGnssBatchingCallback; @@ -121,6 +120,8 @@ using IGnssMeasurement_V2_0 = android::hardware::gnss::V2_0::IGnssMeasurement; using IGnssMeasurementCallback_V1_0 = android::hardware::gnss::V1_0::IGnssMeasurementCallback; using IGnssMeasurementCallback_V1_1 = android::hardware::gnss::V1_1::IGnssMeasurementCallback; using IGnssMeasurementCallback_V2_0 = android::hardware::gnss::V2_0::IGnssMeasurementCallback; +using IAGnssRil_V1_0 = android::hardware::gnss::V1_0::IAGnssRil; +using IAGnssRil_V2_0 = android::hardware::gnss::V2_0::IAGnssRil; struct GnssDeathRecipient : virtual public hidl_death_recipient { @@ -141,7 +142,8 @@ sp<IGnss_V1_0> gnssHal = nullptr; sp<IGnss_V1_1> gnssHal_V1_1 = nullptr; sp<IGnss_V2_0> gnssHal_V2_0 = nullptr; sp<IGnssXtra> gnssXtraIface = nullptr; -sp<IAGnssRil> agnssRilIface = nullptr; +sp<IAGnssRil_V1_0> agnssRilIface = nullptr; +sp<IAGnssRil_V2_0> agnssRilIface_V2_0 = nullptr; sp<IGnssGeofencing> gnssGeofencingIface = nullptr; sp<IAGnss> agnssIface = nullptr; sp<IGnssBatching> gnssBatchingIface = nullptr; @@ -1247,11 +1249,21 @@ static void android_location_GnssLocationProvider_init_once(JNIEnv* env, jclass gnssXtraIface = gnssXtra; } - auto gnssRil = gnssHal->getExtensionAGnssRil(); - if (!gnssRil.isOk()) { - ALOGD("Unable to get a handle to AGnssRil"); + if (gnssHal_V2_0 != nullptr) { + auto agnssRil_V2_0 = gnssHal_V2_0->getExtensionAGnssRil_2_0(); + if (!agnssRil_V2_0.isOk()) { + ALOGD("Unable to get a handle to AGnssRil_V2_0"); + } else { + agnssRilIface_V2_0 = agnssRil_V2_0; + agnssRilIface = agnssRilIface_V2_0; + } } else { - agnssRilIface = gnssRil; + auto agnssRil_V1_0 = gnssHal->getExtensionAGnssRil(); + if (!agnssRil_V1_0.isOk()) { + ALOGD("Unable to get a handle to AGnssRil"); + } else { + agnssRilIface = agnssRil_V1_0; + } } auto gnssAgnss = gnssHal->getExtensionAGnss(); @@ -1496,17 +1508,17 @@ static void android_location_GnssLocationProvider_delete_aiding_data(JNIEnv* /* static void android_location_GnssLocationProvider_agps_set_reference_location_cellid( JNIEnv* /* env */, jobject /* obj */, jint type, jint mcc, jint mnc, jint lac, jint cid) { - IAGnssRil::AGnssRefLocation location; + IAGnssRil_V1_0::AGnssRefLocation location; if (agnssRilIface == nullptr) { ALOGE("No AGPS RIL interface in agps_set_reference_location_cellid"); return; } - switch (static_cast<IAGnssRil::AGnssRefLocationType>(type)) { - case IAGnssRil::AGnssRefLocationType::GSM_CELLID: - case IAGnssRil::AGnssRefLocationType::UMTS_CELLID: - location.type = static_cast<IAGnssRil::AGnssRefLocationType>(type); + switch (static_cast<IAGnssRil_V1_0::AGnssRefLocationType>(type)) { + case IAGnssRil_V1_0::AGnssRefLocationType::GSM_CELLID: + case IAGnssRil_V1_0::AGnssRefLocationType::UMTS_CELLID: + location.type = static_cast<IAGnssRil_V1_0::AGnssRefLocationType>(type); location.cellID.mcc = mcc; location.cellID.mnc = mnc; location.cellID.lac = lac; @@ -1529,7 +1541,7 @@ static void android_location_GnssLocationProvider_agps_set_id(JNIEnv *env, jobje } const char *setid = env->GetStringUTFChars(setid_string, nullptr); - agnssRilIface->setSetId((IAGnssRil::SetIDType)type, setid); + agnssRilIface->setSetId((IAGnssRil_V1_0::SetIDType)type, setid); env->ReleaseStringUTFChars(setid_string, setid); } @@ -1771,26 +1783,44 @@ static void android_location_GnssNetworkConnectivityHandler_update_network_state jint type, jboolean roaming, jboolean available, - jstring extraInfo, - jstring apn) { - if (agnssRilIface != nullptr) { + jstring apn, + jlong networkHandle, + jshort capabilities) { + if (agnssRilIface == nullptr) { + ALOGE("AGnssRilInterface does not exist"); + return; + } + + const char *c_apn = env->GetStringUTFChars(apn, nullptr); + const android::hardware::hidl_string hidl_apn{c_apn}; + if (agnssRilIface_V2_0 != nullptr) { + IAGnssRil_V2_0::NetworkAttributes networkAttributes = { + .networkHandle = static_cast<uint64_t>(networkHandle), + .isConnected = static_cast<bool>(connected), + .capabilities = static_cast<uint16_t>(capabilities), + .apn = hidl_apn + }; + + auto result = agnssRilIface_V2_0->updateNetworkState_2_0(networkAttributes); + if (!result.isOk() || !result) { + ALOGE("updateNetworkState_2_0 failed"); + } + } else { auto result = agnssRilIface->updateNetworkState(connected, - static_cast<IAGnssRil::NetworkType>(type), - roaming); + static_cast<IAGnssRil_V1_0::NetworkType>(type), roaming); if (!result.isOk() || !result) { ALOGE("updateNetworkState failed"); } - const char *c_apn = env->GetStringUTFChars(apn, nullptr); - result = agnssRilIface->updateNetworkAvailability(available, c_apn); - if (!result.isOk() || !result) { - ALOGE("updateNetworkAvailability failed"); + if (!hidl_apn.empty()) { + result = agnssRilIface->updateNetworkAvailability(available, hidl_apn); + if (!result.isOk() || !result) { + ALOGE("updateNetworkAvailability failed"); + } } - - env->ReleaseStringUTFChars(apn, c_apn); - } else { - ALOGE("AGnssRilInterface does not exist"); } + + env->ReleaseStringUTFChars(apn, c_apn); } static jboolean android_location_GnssGeofenceProvider_is_geofence_supported( @@ -2098,7 +2128,6 @@ static jboolean android_location_GnssLocationProvider_set_satellite_blacklist( } } - static jint android_location_GnssBatchingProvider_get_batch_size(JNIEnv*, jclass) { if (gnssBatchingIface == nullptr) { return 0; // batching not supported, size = 0 @@ -2317,7 +2346,7 @@ static const JNINativeMethod sNetworkConnectivityMethods[] = { {"native_is_agps_ril_supported", "()Z", reinterpret_cast<void *>(android_location_GnssNetworkConnectivityHandler_is_agps_ril_supported)}, {"native_update_network_state", - "(ZIZZLjava/lang/String;Ljava/lang/String;)V", + "(ZIZZLjava/lang/String;JS)V", reinterpret_cast<void *>(android_location_GnssNetworkConnectivityHandler_update_network_state)}, {"native_agps_data_conn_open", "(Ljava/lang/String;I)V", diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 7186cdf96dee..b3afacbf15ec 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -164,7 +164,6 @@ import android.net.NetworkUtils; import android.net.ProxyInfo; import android.net.Uri; import android.net.metrics.IpConnectivityLog; -import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Binder; import android.os.Build; @@ -478,7 +477,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { /** * Strings logged with {@link - * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB}. + * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB} + * and {@link DevicePolicyEnums#PROVISIONING_ENTRY_POINT_ADB}. */ private static final String LOG_TAG_PROFILE_OWNER = "profile-owner"; private static final String LOG_TAG_DEVICE_OWNER = "device-owner"; @@ -4328,6 +4328,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.ADD_CROSS_PROFILE_WIDGET_PROVIDER) + .setAdmin(admin) + .write(); + if (changedProviders != null) { mLocalService.notifyCrossProfileProvidersChanged(userId, changedProviders); return true; @@ -4355,6 +4360,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.REMOVE_CROSS_PROFILE_WIDGET_PROVIDER) + .setAdmin(admin) + .write(); + if (changedProviders != null) { mLocalService.notifyCrossProfileProvidersChanged(userId, changedProviders); return true; @@ -5491,6 +5501,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final long id = mInjector.binderClearCallingIdentity(); try { mCertificateMonitor.uninstallCaCerts(UserHandle.of(userId), aliases); + final boolean isDelegate = (admin == null); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.UNINSTALL_CA_CERTS) + .setAdmin(callerPackage) + .setBoolean(isDelegate) + .write(); } finally { mInjector.binderRestoreCallingIdentity(id); } @@ -5556,7 +5572,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext, userHandle); try { IKeyChainService keyChain = keyChainConnection.getService(); - return keyChain.removeKeyPair(alias); + final boolean result = keyChain.removeKeyPair(alias); + final boolean isDelegate = (who == null); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.REMOVE_KEY_PAIR) + .setAdmin(callerPackage) + .setBoolean(isDelegate) + .write(); + return result; } catch (RemoteException e) { Log.e(LOG_TAG, "Removing keypair", e); } finally { @@ -5740,6 +5763,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } } + final boolean isDelegate = (who == null); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.GENERATE_KEY_PAIR) + .setAdmin(callerPackage) + .setBoolean(isDelegate) + .setInt(idAttestationFlags) + .setStrings(algorithm) + .write(); return true; } } catch (RemoteException e) { @@ -5768,6 +5799,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } keyChain.setUserSelectable(alias, isUserSelectable); + final boolean isDelegate = (who == null); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_KEY_PAIR_CERTIFICATE) + .setAdmin(callerPackage) + .setBoolean(isDelegate) + .write(); return true; } catch (InterruptedException e) { Log.w(LOG_TAG, "Interrupted while setting keypair certificate", e); @@ -5830,6 +5867,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { sendPrivateKeyAliasResponse(chosenAlias, response); } }, null, Activity.RESULT_OK, null, null); + final String adminPackageName = + (aliasChooser != null ? aliasChooser.getPackageName() : null); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.CHOOSE_PRIVATE_KEY_ALIAS) + .setAdmin(adminPackageName) + .write(); } finally { mInjector.binderRestoreCallingIdentity(id); } @@ -6219,6 +6262,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public void setCertInstallerPackage(ComponentName who, String installerPackage) throws SecurityException { setDelegatedScopePreO(who, installerPackage, DELEGATION_CERT_INSTALL); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_CERT_INSTALLER_PACKAGE) + .setAdmin(who) + .setStrings(installerPackage) + .write(); } @Override @@ -6250,6 +6298,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!connectivityManager.setAlwaysOnVpnPackageForUser(userId, vpnPackage, lockdown)) { throw new UnsupportedOperationException(); } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_ALWAYS_ON_VPN_PACKAGE) + .setAdmin(admin) + .setStrings(vpnPackage) + .setBoolean(lockdown) + .setInt(/* number of vpn packages */ 0) + .write(); } finally { mInjector.binderRestoreCallingIdentity(token); } @@ -6965,6 +7020,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { updateScreenCaptureDisabled(userHandle, disabled); } } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_SCREEN_CAPTURE_DISABLED) + .setAdmin(who) + .setBoolean(disabled) + .write(); } /** @@ -7036,6 +7096,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.binderRestoreCallingIdentity(ident); } } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_AUTO_TIME_REQUIRED) + .setAdmin(who) + .setBoolean(required) + .write(); } /** @@ -7167,6 +7232,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { DevicePolicyManager.NOTIFICATION_BUGREPORT_STARTED), UserHandle.ALL); mHandler.postDelayed(mRemoteBugreportTimeoutRunnable, RemoteBugreportUtils.REMOTE_BUGREPORT_TIMEOUT_MILLIS); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.REQUEST_BUGREPORT) + .setAdmin(who) + .write(); return true; } catch (RemoteException re) { // should never happen @@ -7377,6 +7446,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } // Tell the user manager that the restrictions have changed. pushUserRestrictions(userHandle); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_CAMERA_DISABLED) + .setAdmin(who) + .setBoolean(disabled) + .write(); } /** @@ -7528,6 +7602,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // Notify package manager. mInjector.getPackageManagerInternal().setKeepUninstalledPackages(packageList); } + final boolean isDelegate = (who == null); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_KEEP_UNINSTALLED_PACKAGES) + .setAdmin(callerPackage) + .setBoolean(isDelegate) + .setStrings(packageList.toArray(new String[0])) + .write(); } @Override @@ -7585,6 +7666,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (isAdb()) { // Log device owner provisioning was started using adb. MetricsLogger.action(mContext, PROVISIONING_ENTRY_POINT_ADB, LOG_TAG_DEVICE_OWNER); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.PROVISIONING_ENTRY_POINT_ADB) + .setAdmin(admin) + .setStrings(LOG_TAG_DEVICE_OWNER) + .write(); } mOwners.setDeviceOwner(admin, ownerName, userId); @@ -7854,6 +7940,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (isAdb()) { // Log profile owner provisioning was started using adb. MetricsLogger.action(mContext, PROVISIONING_ENTRY_POINT_ADB, LOG_TAG_PROFILE_OWNER); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.PROVISIONING_ENTRY_POINT_ADB) + .setAdmin(who) + .setStrings(LOG_TAG_PROFILE_OWNER) + .write(); } mOwners.setProfileOwner(who, ownerName, userHandle); @@ -7941,6 +8032,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.binderRestoreCallingIdentity(token); } } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_DEVICE_OWNER_LOCK_SCREEN_INFO) + .setAdmin(who) + .write(); } @Override @@ -8127,6 +8222,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final long id = mInjector.binderClearCallingIdentity(); try { mUserManager.setUserName(userId, profileName); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_PROFILE_NAME) + .setAdmin(who) + .write(); } finally { mInjector.binderRestoreCallingIdentity(id); } @@ -8601,6 +8700,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.binderRestoreCallingIdentity(id); } } + final String activityPackage = + (activity != null ? activity.getPackageName() : null); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.ADD_PERSISTENT_PREFERRED_ACTIVITY) + .setAdmin(who) + .setStrings(activityPackage, getIntentFilterActions(filter)) + .write(); } @Override @@ -8664,6 +8770,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final long id = mInjector.binderClearCallingIdentity(); try { mUserManager.setApplicationRestrictions(packageName, settings, userHandle); + final boolean isDelegate = (who == null); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_APPLICATION_RESTRICTIONS) + .setAdmin(callerPackage) + .setBoolean(isDelegate) + .setStrings(packageName) + .write(); } finally { mInjector.binderRestoreCallingIdentity(id); } @@ -8795,6 +8908,24 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.binderRestoreCallingIdentity(id); } } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.ADD_CROSS_PROFILE_INTENT_FILTER) + .setAdmin(who) + .setStrings(getIntentFilterActions(filter)) + .setInt(flags) + .write(); + } + + private static String[] getIntentFilterActions(IntentFilter filter) { + if (filter == null) { + return null; + } + final int actionsCount = filter.countActions(); + final String[] actions = new String[actionsCount]; + for (int i = 0; i < actionsCount; i++) { + actions[i] = filter.getAction(i); + } + return actions; } @Override @@ -8914,6 +9045,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { admin.permittedAccessiblityServices = packageList; saveSettingsLocked(UserHandle.getCallingUserId()); } + final String[] packageArray = + packageList != null ? ((List<String>) packageList).toArray(new String[0]) : null; + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_PERMITTED_ACCESSIBILITY_SERVICES) + .setAdmin(who) + .setStrings(packageArray) + .write(); return true; } @@ -9088,6 +9226,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { admin.permittedInputMethods = packageList; saveSettingsLocked(callingUserId); } + final String[] packageArray = + packageList != null ? ((List<String>) packageList).toArray(new String[0]) : null; + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_PERMITTED_INPUT_METHODS) + .setAdmin(who) + .setStrings(packageArray) + .write(); return true; } @@ -9628,6 +9773,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public String[] setPackagesSuspended(ComponentName who, String callerPackage, String[] packageNames, boolean suspended) { int callingUserId = UserHandle.getCallingUserId(); + String[] result = null; synchronized (getLockObject()) { // Ensure the caller is a DO/PO or a package access delegate. enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, @@ -9635,7 +9781,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { long id = mInjector.binderClearCallingIdentity(); try { - return mIPackageManager.setPackagesSuspendedAsUser(packageNames, suspended, + result = mIPackageManager + .setPackagesSuspendedAsUser(packageNames, suspended, null, null, null, PLATFORM_PACKAGE_NAME, callingUserId); } catch (RemoteException re) { // Shouldn't happen. @@ -9643,8 +9790,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } finally { mInjector.binderRestoreCallingIdentity(id); } - return packageNames; } + final boolean isDelegate = (who == null); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_PACKAGES_SUSPENDED) + .setAdmin(callerPackage) + .setBoolean(isDelegate) + .setStrings(packageNames) + .write(); + if (result != null) { + return result; + } + return packageNames; } @Override @@ -9781,6 +9938,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public boolean setApplicationHidden(ComponentName who, String callerPackage, String packageName, boolean hidden) { int callingUserId = UserHandle.getCallingUserId(); + boolean result = false; synchronized (getLockObject()) { // Ensure the caller is a DO/PO or a package access delegate. enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, @@ -9788,16 +9946,23 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { long id = mInjector.binderClearCallingIdentity(); try { - return mIPackageManager.setApplicationHiddenSettingAsUser( - packageName, hidden, callingUserId); + result = mIPackageManager + .setApplicationHiddenSettingAsUser(packageName, hidden, callingUserId); } catch (RemoteException re) { // shouldn't happen Slog.e(LOG_TAG, "Failed to setApplicationHiddenSetting", re); } finally { mInjector.binderRestoreCallingIdentity(id); } - return false; } + final boolean isDelegate = (who == null); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_APPLICATION_HIDDEN) + .setAdmin(callerPackage) + .setBoolean(isDelegate) + .setStrings(packageName, hidden ? "hidden" : "not_hidden") + .write(); + return result; } @Override @@ -9862,10 +10027,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.binderRestoreCallingIdentity(id); } } + final boolean isDelegate = (who == null); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.ENABLE_SYSTEM_APP) + .setAdmin(callerPackage) + .setBoolean(isDelegate) + .setStrings(packageName) + .write(); } @Override public int enableSystemAppWithIntent(ComponentName who, String callerPackage, Intent intent) { + int numberOfAppsInstalled = 0; synchronized (getLockObject()) { // Ensure the caller is a DO/PO or an enable system app delegate. enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, @@ -9887,7 +10060,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (VERBOSE_LOG) { Slog.d(LOG_TAG, "Enabling system activities: " + activitiesToEnable); } - int numberOfAppsInstalled = 0; if (activitiesToEnable != null) { for (ResolveInfo info : activitiesToEnable) { if (info.activityInfo != null) { @@ -9903,7 +10075,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } } - return numberOfAppsInstalled; } catch (RemoteException e) { // shouldn't happen Slog.wtf(LOG_TAG, "Failed to resolve intent for: " + intent); @@ -9912,6 +10083,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.binderRestoreCallingIdentity(id); } } + final boolean isDelegate = (who == null); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.ENABLE_SYSTEM_APP_WITH_INTENT) + .setAdmin(callerPackage) + .setBoolean(isDelegate) + .setStrings(intent.getAction()) + .write(); + return numberOfAppsInstalled; } private boolean isSystemApp(IPackageManager pm, String packageName, int userId) @@ -9928,6 +10107,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean installExistingPackage(ComponentName who, String callerPackage, String packageName) { + boolean result; synchronized (getLockObject()) { // Ensure the caller is a PO or an install existing package delegate enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, @@ -9946,7 +10126,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } // Install the package. - return mIPackageManager.installExistingPackageAsUser(packageName, callingUserId, + result = mIPackageManager + .installExistingPackageAsUser(packageName, callingUserId, 0 /*installFlags*/, PackageManager.INSTALL_REASON_POLICY) == PackageManager.INSTALL_SUCCEEDED; } catch (RemoteException re) { @@ -9956,6 +10137,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.binderRestoreCallingIdentity(id); } } + if (result) { + final boolean isDelegate = (who == null); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.INSTALL_EXISTING_PACKAGE) + .setAdmin(callerPackage) + .setBoolean(isDelegate) + .setStrings(packageName) + .write(); + } + return result; } @Override @@ -10019,6 +10210,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.binderRestoreCallingIdentity(id); } } + final boolean isDelegate = (who == null); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_UNINSTALL_BLOCKED) + .setAdmin(callerPackage) + .setBoolean(isDelegate) + .setStrings(packageName) + .write(); } @Override @@ -10060,6 +10258,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { saveSettingsLocked(mInjector.userHandleGetCallingUserId()); } } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_CROSS_PROFILE_CALLER_ID_DISABLED) + .setAdmin(who) + .setBoolean(disabled) + .write(); } @Override @@ -10098,6 +10301,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { saveSettingsLocked(mInjector.userHandleGetCallingUserId()); } } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED) + .setAdmin(who) + .setBoolean(disabled) + .write(); } @Override @@ -10197,6 +10405,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { saveSettingsLocked(UserHandle.getCallingUserId()); } } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_BLUETOOTH_CONTACT_SHARING_DISABLED) + .setAdmin(who) + .setBoolean(disabled) + .write(); } @Override @@ -10356,6 +10569,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } else { sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_LOCK_TASK_EXITING); } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_LOCKTASK_MODE_ENABLED) + .setAdmin(admin.info.getPackageName()) + .setBoolean(isEnabled) + .setStrings(pkg) + .write(); } } } @@ -10524,6 +10743,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); setUserRestriction(who, UserManager.DISALLOW_UNMUTE_DEVICE, on); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_MASTER_VOLUME_MUTED) + .setAdmin(who) + .setBoolean(on) + .write(); } } @@ -10553,6 +10777,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.binderRestoreCallingIdentity(id); } } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_USER_ICON) + .setAdmin(who) + .write(); } @Override @@ -10578,6 +10806,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } mLockPatternUtils.setLockScreenDisabled(disabled, userId); mInjector.getIWindowManager().dismissKeyguard(null /* callback */, null /* message */); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_KEYGUARD_DISABLED) + .setAdmin(who) + .setBoolean(disabled) + .write(); } catch (RemoteException e) { // Same process, does not happen. } finally { @@ -10616,6 +10849,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { saveSettingsLocked(userId); } } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_STATUS_BAR_DISABLED) + .setAdmin(who) + .setBoolean(disabled) + .write(); return true; } @@ -11036,6 +11274,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mContext.sendBroadcastAsUser( new Intent(DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED), UserHandle.SYSTEM); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_SYSTEM_UPDATE_POLICY) + .setAdmin(who) + .setInt(policy != null ? policy.getPolicyType() : 0) + .write(); } @Override @@ -11584,11 +11827,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final long ident = mInjector.binderClearCallingIdentity(); try { - final WifiInfo wifiInfo = mInjector.getWifiManager().getConnectionInfo(); - if (wifiInfo == null) { + String[] macAddresses = mInjector.getWifiManager().getFactoryMacAddresses(); + if (macAddresses == null) { return null; } - return wifiInfo.hasRealMacAddress() ? wifiInfo.getMacAddress() : null; + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.GET_WIFI_MAC_ADDRESS) + .setAdmin(admin) + .write(); + return macAddresses.length > 0 ? macAddresses[0] : null; } finally { mInjector.binderRestoreCallingIdentity(ident); } @@ -11634,6 +11881,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) { throw new IllegalStateException("Cannot be called with ongoing call on the device"); } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.REBOOT) + .setAdmin(admin) + .write(); mInjector.powerManagerReboot(PowerManager.REBOOT_REQUESTED_BY_DEVICE_OWNER); } finally { mInjector.binderRestoreCallingIdentity(ident); @@ -11655,6 +11906,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { saveSettingsLocked(userHandle); } } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_SHORT_SUPPORT_MESSAGE) + .setAdmin(who) + .write(); } @Override @@ -11685,6 +11940,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { saveSettingsLocked(userHandle); } } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_LONG_SUPPORT_MESSAGE) + .setAdmin(who) + .write(); } @Override @@ -11750,6 +12009,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { admin.organizationColor = color; saveSettingsLocked(userHandle); } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_ORGANIZATION_COLOR) + .setAdmin(who) + .write(); } @Override @@ -13153,6 +13416,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } final long id = mInjector.binderClearCallingIdentity(); + String ownerType = null; try { synchronized (getLockObject()) { /* @@ -13173,6 +13437,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { bundle = new PersistableBundle(); } if (isProfileOwner(admin, callingUserId)) { + ownerType = ADMIN_TYPE_PROFILE_OWNER; prepareTransfer(admin, target, bundle, callingUserId, ADMIN_TYPE_PROFILE_OWNER); transferProfileOwnershipLocked(admin, target, callingUserId); @@ -13183,6 +13448,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { notifyAffiliatedProfileTransferOwnershipComplete(callingUserId); } } else if (isDeviceOwner(admin, callingUserId)) { + ownerType = ADMIN_TYPE_DEVICE_OWNER; prepareTransfer(admin, target, bundle, callingUserId, ADMIN_TYPE_DEVICE_OWNER); transferDeviceOwnershipLocked(admin, target, callingUserId); @@ -13194,6 +13460,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } finally { mInjector.binderRestoreCallingIdentity(id); } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.TRANSFER_OWNERSHIP) + .setAdmin(admin) + .setStrings(target.getPackageName(), ownerType) + .write(); } private void prepareTransfer(ComponentName admin, ComponentName target, @@ -13669,6 +13940,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mContext, updateFileDescriptor, callback, mInjector, mConstants); } updateInstaller.startInstallUpdate(); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.INSTALL_SYSTEM_UPDATE) + .setAdmin(admin) + .setBoolean(isDeviceAB()) + .write(); } finally { mInjector.binderRestoreCallingIdentity(id); } @@ -13694,6 +13970,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { saveSettingsLocked(mInjector.userHandleGetCallingUserId()); } } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.ADD_CROSS_PROFILE_CALENDAR_PACKAGE) + .setAdmin(who) + .setStrings(packageName) + .write(); } @Override @@ -13713,6 +13994,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { saveSettingsLocked(mInjector.userHandleGetCallingUserId()); } } + if (isRemoved) { + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.REMOVE_CROSS_PROFILE_CALENDAR_PACKAGE) + .setAdmin(who) + .setStrings(packageName) + .write(); + } return isRemoved; } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index e1b83fc99897..cf03d613634f 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -724,6 +724,10 @@ public final class SystemServer { mSystemServiceManager.startService(new OverlayManagerService(mSystemContext, installer)); traceEnd(); + traceBeginAndSlog("StartSensorPrivacyService"); + mSystemServiceManager.startService(new SensorPrivacyService(mSystemContext)); + traceEnd(); + // The sensor service needs access to package manager service, app ops // service, and permissions service, therefore we start it after them. // Start sensor service in a separate thread. Completion should be checked diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index effb5a72bd66..f1cd0cd6d30c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -30,6 +30,7 @@ import static com.android.server.job.JobSchedulerService.WORKING_INDEX; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -62,6 +63,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.server.LocalServices; import com.android.server.job.JobSchedulerService; import com.android.server.job.JobSchedulerService.Constants; +import com.android.server.job.controllers.QuotaController.ExecutionStats; import com.android.server.job.controllers.QuotaController.TimingSession; import org.junit.After; @@ -131,13 +133,18 @@ public class QuotaControllerTest { doReturn(mock(PackageManagerInternal.class)) .when(() -> LocalServices.getService(PackageManagerInternal.class)); - // Freeze the clocks at this moment in time + // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions + // in the past, and QuotaController sometimes floors values at 0, so if the test time + // causes sessions with negative timestamps, they will fail. JobSchedulerService.sSystemClock = - Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC); - JobSchedulerService.sUptimeMillisClock = - Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC); - JobSchedulerService.sElapsedRealtimeClock = - Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC); + getAdvancedClock(Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC), + 24 * HOUR_IN_MILLIS); + JobSchedulerService.sUptimeMillisClock = getAdvancedClock( + Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC), + 24 * HOUR_IN_MILLIS); + JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock( + Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC), + 24 * HOUR_IN_MILLIS); // Initialize real objects. // Capture the listeners. @@ -291,9 +298,17 @@ public class QuotaControllerTest { mQuotaController.saveTimingSession(0, "com.android.test.stay", two); mQuotaController.saveTimingSession(0, "com.android.test.stay", one); + ExecutionStats expectedStats = new ExecutionStats(); + expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS; + expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; + mQuotaController.onAppRemovedLocked("com.android.test.remove", 10001); assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove")); assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay")); + assertEquals(expectedStats, + mQuotaController.getExecutionStatsLocked(0, "com.android.test.remove", RARE_INDEX)); + assertNotEquals(expectedStats, + mQuotaController.getExecutionStatsLocked(0, "com.android.test.stay", RARE_INDEX)); } @Test @@ -318,13 +333,21 @@ public class QuotaControllerTest { mQuotaController.saveTimingSession(10, "com.android.test", two); mQuotaController.saveTimingSession(10, "com.android.test", one); + ExecutionStats expectedStats = new ExecutionStats(); + expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS; + expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; + mQuotaController.onUserRemovedLocked(0); assertNull(mQuotaController.getTimingSessions(0, "com.android.test")); assertEquals(expected, mQuotaController.getTimingSessions(10, "com.android.test")); + assertEquals(expectedStats, + mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX)); + assertNotEquals(expectedStats, + mQuotaController.getExecutionStatsLocked(10, "com.android.test", RARE_INDEX)); } @Test - public void testGetTrailingExecutionTimeLocked_NoTimer() { + public void testUpdateExecutionStatsLocked_NoTimer() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); // Added in chronological order. mQuotaController.saveTimingSession(0, "com.android.test", @@ -340,32 +363,288 @@ public class QuotaControllerTest { mQuotaController.saveTimingSession(0, "com.android.test", createTimingSession(now - 5 * MINUTE_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3)); - assertEquals(0, mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test", - MINUTE_IN_MILLIS)); - assertEquals(2 * MINUTE_IN_MILLIS, - mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test", - 3 * MINUTE_IN_MILLIS)); - assertEquals(4 * MINUTE_IN_MILLIS, - mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test", - 5 * MINUTE_IN_MILLIS)); - assertEquals(4 * MINUTE_IN_MILLIS, - mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test", - 49 * MINUTE_IN_MILLIS)); - assertEquals(5 * MINUTE_IN_MILLIS, - mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test", - 50 * MINUTE_IN_MILLIS)); - assertEquals(6 * MINUTE_IN_MILLIS, - mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test", - HOUR_IN_MILLIS)); - assertEquals(11 * MINUTE_IN_MILLIS, - mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test", - 2 * HOUR_IN_MILLIS)); - assertEquals(12 * MINUTE_IN_MILLIS, - mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test", - 3 * HOUR_IN_MILLIS)); - assertEquals(22 * MINUTE_IN_MILLIS, - mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test", - 6 * HOUR_IN_MILLIS)); + // Test an app that hasn't had any activity. + ExecutionStats expectedStats = new ExecutionStats(); + ExecutionStats inputStats = new ExecutionStats(); + + inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS; + // Invalid time is now +24 hours since there are no sessions at all for the app. + expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS; + mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats); + assertEquals(expectedStats, inputStats); + + inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS; + // Invalid time is now +18 hours since there are no sessions in the window but the earliest + // session is 6 hours ago. + expectedStats.invalidTimeElapsed = now + 18 * HOUR_IN_MILLIS; + expectedStats.executionTimeInWindowMs = 0; + expectedStats.bgJobCountInWindow = 0; + expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 15; + mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); + assertEquals(expectedStats, inputStats); + + inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS; + // Invalid time is now since the session straddles the window cutoff time. + expectedStats.invalidTimeElapsed = now; + expectedStats.executionTimeInWindowMs = 2 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 3; + expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 15; + mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); + assertEquals(expectedStats, inputStats); + + inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * MINUTE_IN_MILLIS; + // Invalid time is now since the start of the session is at the very edge of the window + // cutoff time. + expectedStats.invalidTimeElapsed = now; + expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 3; + expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 15; + mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); + assertEquals(expectedStats, inputStats); + + inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS; + // Invalid time is now +44 minutes since the earliest session in the window is now-5 + // minutes. + expectedStats.invalidTimeElapsed = now + 44 * MINUTE_IN_MILLIS; + expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 3; + expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 15; + mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); + assertEquals(expectedStats, inputStats); + + inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS; + // Invalid time is now since the session is at the very edge of the window cutoff time. + expectedStats.invalidTimeElapsed = now; + expectedStats.executionTimeInWindowMs = 5 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 4; + expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 15; + mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); + assertEquals(expectedStats, inputStats); + + inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS; + // Invalid time is now since the start of the session is at the very edge of the window + // cutoff time. + expectedStats.invalidTimeElapsed = now; + expectedStats.executionTimeInWindowMs = 6 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 5; + expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 15; + mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); + assertEquals(expectedStats, inputStats); + + inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; + // Invalid time is now since the session straddles the window cutoff time. + expectedStats.invalidTimeElapsed = now; + expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 10; + expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 15; + expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS) + + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS; + mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); + assertEquals(expectedStats, inputStats); + + inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * HOUR_IN_MILLIS; + // Invalid time is now +59 minutes since the earliest session in the window is now-121 + // minutes. + expectedStats.invalidTimeElapsed = now + 59 * MINUTE_IN_MILLIS; + expectedStats.executionTimeInWindowMs = 12 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 10; + expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 15; + expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS) + + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS; + mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); + assertEquals(expectedStats, inputStats); + + inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS; + // Invalid time is now since the start of the session is at the very edge of the window + // cutoff time. + expectedStats.invalidTimeElapsed = now; + expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 15; + expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 15; + expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS) + + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS; + mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); + assertEquals(expectedStats, inputStats); + + // Make sure invalidTimeElapsed is set correctly when it's dependent on the max period. + mQuotaController.getTimingSessions(0, "com.android.test") + .add(0, + createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3)); + inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; + // Invalid time is now +1 hour since the earliest session in the max period is 1 hour + // before the end of the max period cutoff time. + expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS; + expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 15; + expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 18; + expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS) + + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS; + mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); + assertEquals(expectedStats, inputStats); + + mQuotaController.getTimingSessions(0, "com.android.test") + .add(0, + createTimingSession(now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), + 2 * MINUTE_IN_MILLIS, 2)); + inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; + // Invalid time is now since the earlist session straddles the max period cutoff time. + expectedStats.invalidTimeElapsed = now; + expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 15; + expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 20; + expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS) + + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS; + mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); + assertEquals(expectedStats, inputStats); + } + + /** + * Tests that getExecutionStatsLocked returns the correct stats. + */ + @Test + public void testGetExecutionStatsLocked_Values() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); + + ExecutionStats expectedStats = new ExecutionStats(); + + // Active + expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS; + expectedStats.invalidTimeElapsed = now + 4 * MINUTE_IN_MILLIS; + expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 5; + expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 20; + assertEquals(expectedStats, + mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX)); + + // Working + expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; + expectedStats.invalidTimeElapsed = now; + expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 10; + expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 20; + expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS) + + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS; + assertEquals(expectedStats, + mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX)); + + // Frequent + expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; + expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS; + expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 15; + expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 20; + expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS) + + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS; + assertEquals(expectedStats, + mQuotaController.getExecutionStatsLocked(0, "com.android.test", FREQUENT_INDEX)); + + // Rare + expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; + expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS; + expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 20; + expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 20; + expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS) + + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS; + assertEquals(expectedStats, + mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX)); + } + + /** + * Tests that getExecutionStatsLocked properly caches the stats and returns the cached object. + */ + @Test + public void testGetExecutionStatsLocked_Caching() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); + final ExecutionStats originalStatsActive = mQuotaController.getExecutionStatsLocked(0, + "com.android.test", ACTIVE_INDEX); + final ExecutionStats originalStatsWorking = mQuotaController.getExecutionStatsLocked(0, + "com.android.test", WORKING_INDEX); + final ExecutionStats originalStatsFrequent = mQuotaController.getExecutionStatsLocked(0, + "com.android.test", FREQUENT_INDEX); + final ExecutionStats originalStatsRare = mQuotaController.getExecutionStatsLocked(0, + "com.android.test", RARE_INDEX); + + // Advance clock so that the working stats shouldn't be the same. + advanceElapsedClock(MINUTE_IN_MILLIS); + // Change frequent bucket size so that the stats need to be recalculated. + mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 6 * HOUR_IN_MILLIS; + mQuotaController.onConstantsUpdatedLocked(); + + ExecutionStats expectedStats = new ExecutionStats(); + expectedStats.windowSizeMs = originalStatsActive.windowSizeMs; + expectedStats.invalidTimeElapsed = originalStatsActive.invalidTimeElapsed; + expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs; + expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow; + expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs; + expectedStats.bgJobCountInMaxPeriod = originalStatsActive.bgJobCountInMaxPeriod; + expectedStats.quotaCutoffTimeElapsed = originalStatsActive.quotaCutoffTimeElapsed; + final ExecutionStats newStatsActive = mQuotaController.getExecutionStatsLocked(0, + "com.android.test", ACTIVE_INDEX); + // Stats for the same bucket should use the same object. + assertTrue(originalStatsActive == newStatsActive); + assertEquals(expectedStats, newStatsActive); + + expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs; + expectedStats.invalidTimeElapsed = originalStatsWorking.invalidTimeElapsed; + expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs; + expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow; + expectedStats.quotaCutoffTimeElapsed = originalStatsWorking.quotaCutoffTimeElapsed; + final ExecutionStats newStatsWorking = mQuotaController.getExecutionStatsLocked(0, + "com.android.test", WORKING_INDEX); + assertTrue(originalStatsWorking == newStatsWorking); + assertNotEquals(expectedStats, newStatsWorking); + + expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs; + expectedStats.invalidTimeElapsed = originalStatsFrequent.invalidTimeElapsed; + expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs; + expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow; + expectedStats.quotaCutoffTimeElapsed = originalStatsFrequent.quotaCutoffTimeElapsed; + final ExecutionStats newStatsFrequent = mQuotaController.getExecutionStatsLocked(0, + "com.android.test", FREQUENT_INDEX); + assertTrue(originalStatsFrequent == newStatsFrequent); + assertNotEquals(expectedStats, newStatsFrequent); + + expectedStats.windowSizeMs = originalStatsRare.windowSizeMs; + expectedStats.invalidTimeElapsed = originalStatsRare.invalidTimeElapsed; + expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs; + expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow; + expectedStats.quotaCutoffTimeElapsed = originalStatsRare.quotaCutoffTimeElapsed; + final ExecutionStats newStatsRare = mQuotaController.getExecutionStatsLocked(0, + "com.android.test", RARE_INDEX); + assertTrue(originalStatsRare == newStatsRare); + assertEquals(expectedStats, newStatsRare); } @Test @@ -394,6 +673,56 @@ public class QuotaControllerTest { } @Test + public void testMaybeScheduleStartAlarmLocked_Active() { + // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests + // because it schedules an alarm too. Prevent it from doing so. + spyOn(mQuotaController); + doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); + + // Active window size is 10 minutes. + final int standbyBucket = ACTIVE_INDEX; + + // No sessions saved yet. + mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE, + standbyBucket); + verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + // Test with timing sessions out of window but still under max execution limit. + final long expectedAlarmTime = + (now - 18 * HOUR_IN_MILLIS) + 24 * HOUR_IN_MILLIS + + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS; + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - 18 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1)); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - 12 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1)); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - 7 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1)); + mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE, + standbyBucket); + verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - 2 * HOUR_IN_MILLIS, 55 * MINUTE_IN_MILLIS, 1)); + mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE, + standbyBucket); + verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + + JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1); + mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); + mQuotaController.prepareForExecutionLocked(jobStatus); + advanceElapsedClock(5 * MINUTE_IN_MILLIS); + // Timer has only been going for 5 minutes in the past 10 minutes, which is under the window + // size limit, but the total execution time for the past 24 hours is 6 hours, so the job no + // longer has quota. + assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked(jobStatus)); + mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE, + standbyBucket); + verify(mAlarmManager, times(1)).set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), + any(), any()); + } + + @Test public void testMaybeScheduleStartAlarmLocked_WorkingSet() { // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests // because it schedules an alarm too. Prevent it from doing so. @@ -579,8 +908,8 @@ public class QuotaControllerTest { // Start in ACTIVE bucket. mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX); - inOrder.verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), - any()); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class)); // And down from there. @@ -620,6 +949,124 @@ public class QuotaControllerTest { inOrder.verify(mAlarmManager, times(1)).cancel(any(AlarmManager.OnAlarmListener.class)); } + + /** + * Tests that the start alarm is properly rescheduled if the earliest session that contributes + * to the app being out of quota contributes less than the quota buffer time. + */ + @Test + public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_DefaultValues() { + // Use the default values + runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck(); + mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); + runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck(); + } + + @Test + public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedBufferSize() { + // Make sure any new value is used correctly. + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS *= 2; + mQuotaController.onConstantsUpdatedLocked(); + runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck(); + mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); + runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck(); + } + + @Test + public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime() { + // Make sure any new value is used correctly. + mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS /= 2; + mQuotaController.onConstantsUpdatedLocked(); + runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck(); + mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); + runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck(); + } + + @Test + public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedMaxTime() { + // Make sure any new value is used correctly. + mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS /= 2; + mQuotaController.onConstantsUpdatedLocked(); + runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck(); + mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); + runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck(); + } + + @Test + public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedEverything() { + // Make sure any new value is used correctly. + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS *= 2; + mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS /= 2; + mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS /= 2; + mQuotaController.onConstantsUpdatedLocked(); + runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck(); + mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); + runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck(); + } + + private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck() { + // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests + // because it schedules an alarm too. Prevent it from doing so. + spyOn(mQuotaController); + doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); + + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + // Working set window size is 2 hours. + final int standbyBucket = WORKING_INDEX; + final long contributionMs = mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS / 2; + final long remainingTimeMs = + mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS - contributionMs; + + // Session straddles edge of bucket window. Only the contribution should be counted towards + // the quota. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (2 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS), + 3 * MINUTE_IN_MILLIS + contributionMs, 3)); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - HOUR_IN_MILLIS, remainingTimeMs, 2)); + // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which + // is 2 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session. + final long expectedAlarmTime = now - HOUR_IN_MILLIS + 2 * HOUR_IN_MILLIS + + (mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS - contributionMs); + mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE, + standbyBucket); + verify(mAlarmManager, times(1)) + .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); + } + + + private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck() { + // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests + // because it schedules an alarm too. Prevent it from doing so. + spyOn(mQuotaController); + doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); + + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + // Working set window size is 2 hours. + final int standbyBucket = WORKING_INDEX; + final long contributionMs = mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS / 2; + final long remainingTimeMs = + mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS - contributionMs; + + // Session straddles edge of 24 hour window. Only the contribution should be counted towards + // the quota. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (24 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS), + 3 * MINUTE_IN_MILLIS + contributionMs, 3)); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - 20 * HOUR_IN_MILLIS, remainingTimeMs, 300)); + // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which + // is 24 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session. + final long expectedAlarmTime = now - 20 * HOUR_IN_MILLIS + //+ mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS + + 24 * HOUR_IN_MILLIS + + (mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS - contributionMs); + mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE, + standbyBucket); + verify(mAlarmManager, times(1)) + .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); + } + /** Tests that QuotaController doesn't throttle if throttling is turned off. */ @Test public void testThrottleToggling() throws Exception { @@ -652,6 +1099,7 @@ public class QuotaControllerTest { mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = 30 * MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 45 * MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 60 * MINUTE_IN_MILLIS; + mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 3 * HOUR_IN_MILLIS; mQuotaController.onConstantsUpdatedLocked(); @@ -662,6 +1110,7 @@ public class QuotaControllerTest { assertEquals(45 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]); + assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs()); } @Test @@ -673,6 +1122,7 @@ public class QuotaControllerTest { mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = -MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = -MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = -MINUTE_IN_MILLIS; + mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = -MINUTE_IN_MILLIS; mQuotaController.onConstantsUpdatedLocked(); @@ -682,6 +1132,7 @@ public class QuotaControllerTest { assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]); + assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs()); // Test larger than a day. Controller should cap at one day. mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 25 * HOUR_IN_MILLIS; @@ -690,6 +1141,7 @@ public class QuotaControllerTest { mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = 25 * HOUR_IN_MILLIS; mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 25 * HOUR_IN_MILLIS; mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 25 * HOUR_IN_MILLIS; + mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 25 * HOUR_IN_MILLIS; mQuotaController.onConstantsUpdatedLocked(); @@ -699,6 +1151,7 @@ public class QuotaControllerTest { assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]); + assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs()); } /** Tests that TimingSessions aren't saved when the device is charging. */ diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java new file mode 100644 index 000000000000..494677d38c0e --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java @@ -0,0 +1,886 @@ +/* + * 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.job.controllers; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.AlarmManager; +import android.app.job.JobInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManagerInternal; +import android.os.SystemClock; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; +import com.android.server.job.JobSchedulerService; +import com.android.server.job.JobSchedulerService.Constants; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + +import java.time.Clock; +import java.time.Duration; +import java.time.ZoneOffset; + +@RunWith(AndroidJUnit4.class) +public class TimeControllerTest { + private static final long SECOND_IN_MILLIS = 1000L; + private static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS; + private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS; + private static final String TAG_DEADLINE = "*job.deadline*"; + private static final String TAG_DELAY = "*job.delay*"; + private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests"; + private static final int SOURCE_USER_ID = 0; + + private Constants mConstants; + private TimeController mTimeController; + + private MockitoSession mMockingSession; + @Mock + private AlarmManager mAlarmManager; + @Mock + private Context mContext; + @Mock + private JobSchedulerService mJobSchedulerService; + + @Before + public void setUp() { + mMockingSession = mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .mockStatic(LocalServices.class) + .startMocking(); + // Use default constants for now. + mConstants = new Constants(); + + // Called in StateController constructor. + when(mJobSchedulerService.getTestableContext()).thenReturn(mContext); + when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService); + when(mJobSchedulerService.getConstants()).thenReturn(mConstants); + // Called in TimeController constructor. + when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager); + // Used in JobStatus. + doReturn(mock(PackageManagerInternal.class)) + .when(() -> LocalServices.getService(PackageManagerInternal.class)); + + // Freeze the clocks at this moment in time + JobSchedulerService.sSystemClock = + Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC); + JobSchedulerService.sUptimeMillisClock = + Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC); + JobSchedulerService.sElapsedRealtimeClock = + Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC); + + // Initialize real objects. + mTimeController = new TimeController(mJobSchedulerService); + spyOn(mTimeController); + } + + @After + public void tearDown() { + if (mMockingSession != null) { + mMockingSession.finishMocking(); + } + } + + private Clock getAdvancedClock(Clock clock, long incrementMs) { + return Clock.offset(clock, Duration.ofMillis(incrementMs)); + } + + private void advanceElapsedClock(long incrementMs) { + JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock( + JobSchedulerService.sElapsedRealtimeClock, incrementMs); + } + + private static JobInfo.Builder createJob() { + return new JobInfo.Builder(101, new ComponentName("foo", "bar")); + } + + private JobStatus createJobStatus(String testTag, JobInfo.Builder job) { + JobInfo jobInfo = job.build(); + return JobStatus.createFromJobInfo( + jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, testTag); + } + + @Test + public void testMaybeStartTrackingJobLocked_AlreadySatisfied() { + JobStatus delaySatisfied = createJobStatus( + "testMaybeStartTrackingJobLocked_AlreadySatisfied", + createJob().setMinimumLatency(1)); + JobStatus deadlineSatisfied = createJobStatus( + "testMaybeStartTrackingJobLocked_AlreadySatisfied", + createJob().setOverrideDeadline(1)); + + advanceElapsedClock(5); + + mTimeController.maybeStartTrackingJobLocked(delaySatisfied, null); + mTimeController.maybeStartTrackingJobLocked(deadlineSatisfied, null); + verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any()); + } + + @Test + public void testMaybeStartTrackingJobLocked_DelayInOrder_NoSkipping() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false; + mTimeController.onConstantsUpdatedLocked(); + + runTestMaybeStartTrackingJobLocked_DelayInOrder(); + } + + @Test + public void testMaybeStartTrackingJobLocked_DelayInOrder_WithSkipping_AllReady() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true; + mTimeController.onConstantsUpdatedLocked(); + + doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt()); + + runTestMaybeStartTrackingJobLocked_DelayInOrder(); + } + + private void runTestMaybeStartTrackingJobLocked_DelayInOrder() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + + JobStatus jobLatest = createJobStatus("testMaybeStartTrackingJobLocked_DelayInOrder", + createJob().setMinimumLatency(HOUR_IN_MILLIS)); + JobStatus jobMiddle = createJobStatus("testMaybeStartTrackingJobLocked_DelayInOrder", + createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS)); + JobStatus jobEarliest = createJobStatus("testMaybeStartTrackingJobLocked_DelayInOrder", + createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS)); + + InOrder inOrder = inOrder(mAlarmManager); + + mTimeController.maybeStartTrackingJobLocked(jobEarliest, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), + any(), any(), any()); + mTimeController.maybeStartTrackingJobLocked(jobMiddle, null); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any()); + mTimeController.maybeStartTrackingJobLocked(jobLatest, null); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any()); + } + + @Test + public void testMaybeStartTrackingJobLocked_DelayInOrder_WithSkipping_SomeNotReady() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true; + mTimeController.onConstantsUpdatedLocked(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + + JobStatus jobLatest = createJobStatus("testMaybeStartTrackingJobLocked_DelayInOrder", + createJob().setMinimumLatency(HOUR_IN_MILLIS)); + JobStatus jobMiddle = createJobStatus("testMaybeStartTrackingJobLocked_DelayInOrder", + createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS)); + JobStatus jobEarliest = createJobStatus("testMaybeStartTrackingJobLocked_DelayInOrder", + createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS)); + + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt()); + doReturn(false).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt()); + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt()); + + InOrder inOrder = inOrder(mAlarmManager); + + mTimeController.maybeStartTrackingJobLocked(jobEarliest, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), + any(), any(), any()); + mTimeController.maybeStartTrackingJobLocked(jobMiddle, null); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any()); + mTimeController.maybeStartTrackingJobLocked(jobLatest, null); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any()); + } + + @Test + public void testMaybeStartTrackingJobLocked_DelayReverseOrder_NoSkipping() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false; + mTimeController.onConstantsUpdatedLocked(); + + runTestMaybeStartTrackingJobLocked_DelayReverseOrder(); + } + + @Test + public void testMaybeStartTrackingJobLocked_DelayReverseOrder_WithSkipping_AllReady() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true; + mTimeController.onConstantsUpdatedLocked(); + + doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt()); + + runTestMaybeStartTrackingJobLocked_DelayReverseOrder(); + } + + private void runTestMaybeStartTrackingJobLocked_DelayReverseOrder() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + + JobStatus jobLatest = createJobStatus("testMaybeStartTrackingJobLocked_DelayReverseOrder", + createJob().setMinimumLatency(HOUR_IN_MILLIS)); + JobStatus jobMiddle = createJobStatus("testMaybeStartTrackingJobLocked_DelayReverseOrder", + createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS)); + JobStatus jobEarliest = createJobStatus("testMaybeStartTrackingJobLocked_DelayReverseOrder", + createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS)); + + InOrder inOrder = inOrder(mAlarmManager); + + mTimeController.maybeStartTrackingJobLocked(jobLatest, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), any(), + any(), any()); + mTimeController.maybeStartTrackingJobLocked(jobMiddle, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), + any(), any(), any()); + mTimeController.maybeStartTrackingJobLocked(jobEarliest, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), + any(), any(), any()); + } + + @Test + public void testMaybeStartTrackingJobLocked_DelayReverseOrder_WithSkipping_SomeNotReady() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true; + mTimeController.onConstantsUpdatedLocked(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + + JobStatus jobLatest = createJobStatus("testMaybeStartTrackingJobLocked_DelayReverseOrder", + createJob().setMinimumLatency(HOUR_IN_MILLIS)); + JobStatus jobMiddle = createJobStatus("testMaybeStartTrackingJobLocked_DelayReverseOrder", + createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS)); + JobStatus jobEarliest = createJobStatus("testMaybeStartTrackingJobLocked_DelayReverseOrder", + createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS)); + + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt()); + doReturn(false).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt()); + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt()); + + InOrder inOrder = inOrder(mAlarmManager); + + mTimeController.maybeStartTrackingJobLocked(jobLatest, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), any(), + any(), any()); + mTimeController.maybeStartTrackingJobLocked(jobMiddle, null); + // Middle alarm shouldn't be set since it won't be ready. + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), eq(TAG_DELAY), any(), any(), any()); + mTimeController.maybeStartTrackingJobLocked(jobEarliest, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), + any(), any(), any()); + } + + @Test + public void testMaybeStartTrackingJobLocked_DeadlineInOrder_NoSkipping() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false; + mTimeController.onConstantsUpdatedLocked(); + + runTestMaybeStartTrackingJobLocked_DeadlineInOrder(); + } + + @Test + public void testMaybeStartTrackingJobLocked_DeadlineInOrder_WithSkipping_AllReady() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true; + mTimeController.onConstantsUpdatedLocked(); + + doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt()); + + runTestMaybeStartTrackingJobLocked_DeadlineInOrder(); + } + + private void runTestMaybeStartTrackingJobLocked_DeadlineInOrder() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + + JobStatus jobLatest = createJobStatus("testMaybeStartTrackingJobLocked_DeadlineInOrder", + createJob().setOverrideDeadline(HOUR_IN_MILLIS)); + JobStatus jobMiddle = createJobStatus("testMaybeStartTrackingJobLocked_DeadlineInOrder", + createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS)); + JobStatus jobEarliest = createJobStatus("testMaybeStartTrackingJobLocked_DeadlineInOrder", + createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS)); + + InOrder inOrder = inOrder(mAlarmManager); + + mTimeController.maybeStartTrackingJobLocked(jobEarliest, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), + eq(TAG_DEADLINE), any(), any(), any()); + mTimeController.maybeStartTrackingJobLocked(jobMiddle, null); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any()); + mTimeController.maybeStartTrackingJobLocked(jobLatest, null); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any()); + } + + @Test + public void testMaybeStartTrackingJobLocked_DeadlineInOrder_WithSkipping_SomeNotReady() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true; + mTimeController.onConstantsUpdatedLocked(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + + JobStatus jobLatest = createJobStatus("testMaybeStartTrackingJobLocked_DeadlineInOrder", + createJob().setOverrideDeadline(HOUR_IN_MILLIS)); + JobStatus jobMiddle = createJobStatus("testMaybeStartTrackingJobLocked_DeadlineInOrder", + createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS)); + JobStatus jobEarliest = createJobStatus("testMaybeStartTrackingJobLocked_DeadlineInOrder", + createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS)); + + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt()); + doReturn(false).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt()); + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt()); + + InOrder inOrder = inOrder(mAlarmManager); + + mTimeController.maybeStartTrackingJobLocked(jobEarliest, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), + eq(TAG_DEADLINE), any(), any(), any()); + mTimeController.maybeStartTrackingJobLocked(jobMiddle, null); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any()); + mTimeController.maybeStartTrackingJobLocked(jobLatest, null); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any()); + } + + @Test + public void testMaybeStartTrackingJobLocked_DeadlineReverseOrder_NoSkipping() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false; + mTimeController.onConstantsUpdatedLocked(); + + runTestMaybeStartTrackingJobLocked_DeadlineReverseOrder(); + } + + @Test + public void testMaybeStartTrackingJobLocked_DeadlineReverseOrder_WithSkipping_AllReady() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true; + mTimeController.onConstantsUpdatedLocked(); + + doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt()); + + runTestMaybeStartTrackingJobLocked_DeadlineReverseOrder(); + } + + private void runTestMaybeStartTrackingJobLocked_DeadlineReverseOrder() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + + JobStatus jobLatest = createJobStatus( + "testMaybeStartTrackingJobLocked_DeadlineReverseOrder", + createJob().setOverrideDeadline(HOUR_IN_MILLIS)); + JobStatus jobMiddle = createJobStatus( + "testMaybeStartTrackingJobLocked_DeadlineReverseOrder", + createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS)); + JobStatus jobEarliest = createJobStatus( + "testMaybeStartTrackingJobLocked_DeadlineReverseOrder", + createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS)); + + InOrder inOrder = inOrder(mAlarmManager); + + mTimeController.maybeStartTrackingJobLocked(jobLatest, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DEADLINE), + any(), any(), any()); + mTimeController.maybeStartTrackingJobLocked(jobMiddle, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(), + eq(TAG_DEADLINE), any(), any(), any()); + mTimeController.maybeStartTrackingJobLocked(jobEarliest, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), + eq(TAG_DEADLINE), any(), any(), any()); + } + + @Test + public void testMaybeStartTrackingJobLocked_DeadlineReverseOrder_WithSkipping_SomeNotReady() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true; + mTimeController.onConstantsUpdatedLocked(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + + JobStatus jobLatest = createJobStatus( + "testMaybeStartTrackingJobLocked_DeadlineReverseOrder", + createJob().setOverrideDeadline(HOUR_IN_MILLIS)); + JobStatus jobMiddle = createJobStatus( + "testMaybeStartTrackingJobLocked_DeadlineReverseOrder", + createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS)); + JobStatus jobEarliest = createJobStatus( + "testMaybeStartTrackingJobLocked_DeadlineReverseOrder", + createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS)); + + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt()); + doReturn(false).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt()); + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt()); + + InOrder inOrder = inOrder(mAlarmManager); + + mTimeController.maybeStartTrackingJobLocked(jobLatest, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DEADLINE), + any(), any(), any()); + mTimeController.maybeStartTrackingJobLocked(jobMiddle, null); + // Middle alarm should be skipped since the job wouldn't be ready. + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), eq(TAG_DEADLINE), any(), any(), + any()); + mTimeController.maybeStartTrackingJobLocked(jobEarliest, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), + eq(TAG_DEADLINE), any(), any(), any()); + } + + @Test + public void testJobSkipToggling() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + + JobStatus jobLatest = createJobStatus( + "testMaybeStartTrackingJobLocked_DeadlineReverseOrder", + createJob().setOverrideDeadline(HOUR_IN_MILLIS)); + JobStatus jobEarliest = createJobStatus( + "testMaybeStartTrackingJobLocked_DeadlineReverseOrder", + createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS)); + + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt()); + doReturn(false).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt()); + + // Starting off with the skipping off, we should still set an alarm for the earlier job. + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false; + mTimeController.onConstantsUpdatedLocked(); + InOrder inOrder = inOrder(mAlarmManager); + + mTimeController.maybeStartTrackingJobLocked(jobEarliest, null); + mTimeController.maybeStartTrackingJobLocked(jobLatest, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), + eq(TAG_DEADLINE), any(), any(), any()); + + // Turn it on, use alarm for later job. + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true; + mTimeController.onConstantsUpdatedLocked(); + + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DEADLINE), + any(), any(), any()); + + // Back off, use alarm for earlier job. + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false; + mTimeController.onConstantsUpdatedLocked(); + + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), + eq(TAG_DEADLINE), any(), any(), any()); + } + + @Test + public void testCheckExpiredDelaysAndResetAlarm_NoSkipping() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false; + mTimeController.onConstantsUpdatedLocked(); + + runTestCheckExpiredDelaysAndResetAlarm(); + } + + @Test + public void testCheckExpiredDelaysAndResetAlarm_WithSkipping_AllReady() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true; + mTimeController.onConstantsUpdatedLocked(); + + doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt()); + + runTestCheckExpiredDelaysAndResetAlarm(); + } + + private void runTestCheckExpiredDelaysAndResetAlarm() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + + JobStatus jobLatest = createJobStatus("testCheckExpiredDelaysAndResetAlarm", + createJob().setMinimumLatency(HOUR_IN_MILLIS)); + JobStatus jobMiddle = createJobStatus("testCheckExpiredDelaysAndResetAlarm", + createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS)); + JobStatus jobEarliest = createJobStatus("testCheckExpiredDelaysAndResetAlarm", + createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS)); + + InOrder inOrder = inOrder(mAlarmManager); + + mTimeController.maybeStartTrackingJobLocked(jobLatest, null); + mTimeController.maybeStartTrackingJobLocked(jobMiddle, null); + mTimeController.maybeStartTrackingJobLocked(jobEarliest, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), + any(), any(), any()); + + advanceElapsedClock(10 * MINUTE_IN_MILLIS); + + mTimeController.checkExpiredDelaysAndResetAlarm(); + assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)); + assertFalse(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)); + assertFalse(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), + any(), any(), any()); + + advanceElapsedClock(30 * MINUTE_IN_MILLIS); + + mTimeController.checkExpiredDelaysAndResetAlarm(); + assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)); + assertTrue(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)); + assertFalse(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), any(), + any(), any()); + + advanceElapsedClock(30 * MINUTE_IN_MILLIS); + + mTimeController.checkExpiredDelaysAndResetAlarm(); + assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)); + assertTrue(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)); + assertTrue(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any()); + } + + @Test + public void testCheckExpiredDelaysAndResetAlarm_WithSkipping_SomeNotReady() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true; + mTimeController.onConstantsUpdatedLocked(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + + JobStatus jobLatest = createJobStatus("testCheckExpiredDelaysAndResetAlarm", + createJob().setMinimumLatency(HOUR_IN_MILLIS)); + JobStatus jobMiddle = createJobStatus("testCheckExpiredDelaysAndResetAlarm", + createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS)); + JobStatus jobEarliest = createJobStatus("testCheckExpiredDelaysAndResetAlarm", + createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS)); + + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt()); + doReturn(false).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt()); + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt()); + + InOrder inOrder = inOrder(mAlarmManager); + + mTimeController.maybeStartTrackingJobLocked(jobLatest, null); + mTimeController.maybeStartTrackingJobLocked(jobMiddle, null); + mTimeController.maybeStartTrackingJobLocked(jobEarliest, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), + any(), any(), any()); + + advanceElapsedClock(10 * MINUTE_IN_MILLIS); + + mTimeController.checkExpiredDelaysAndResetAlarm(); + assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)); + assertFalse(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)); + assertFalse(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)); + // Middle job wouldn't be ready, so its alarm should be skipped. + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), any(), + any(), any()); + + advanceElapsedClock(55 * MINUTE_IN_MILLIS); + + mTimeController.checkExpiredDelaysAndResetAlarm(); + assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)); + assertTrue(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)); + assertTrue(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any()); + } + + @Test + public void testCheckExpiredDeadlinesAndResetAlarm_NoSkipping() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false; + mTimeController.onConstantsUpdatedLocked(); + + runTestCheckExpiredDeadlinesAndResetAlarm(); + } + + @Test + public void testCheckExpiredDeadlinesAndResetAlarm_WithSkipping_AllReady() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true; + mTimeController.onConstantsUpdatedLocked(); + + doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt()); + + runTestCheckExpiredDeadlinesAndResetAlarm(); + } + + private void runTestCheckExpiredDeadlinesAndResetAlarm() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + + JobStatus jobLatest = createJobStatus("testCheckExpiredDeadlinesAndResetAlarm", + createJob().setOverrideDeadline(HOUR_IN_MILLIS)); + JobStatus jobMiddle = createJobStatus("testCheckExpiredDeadlinesAndResetAlarm", + createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS)); + JobStatus jobEarliest = createJobStatus("testCheckExpiredDeadlinesAndResetAlarm", + createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS)); + + InOrder inOrder = inOrder(mAlarmManager); + + mTimeController.maybeStartTrackingJobLocked(jobLatest, null); + mTimeController.maybeStartTrackingJobLocked(jobMiddle, null); + mTimeController.maybeStartTrackingJobLocked(jobEarliest, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), + eq(TAG_DEADLINE), any(), any(), any()); + + advanceElapsedClock(10 * MINUTE_IN_MILLIS); + + mTimeController.checkExpiredDeadlinesAndResetAlarm(); + assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE)); + assertFalse(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE)); + assertFalse(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE)); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(), + eq(TAG_DEADLINE), any(), any(), any()); + + advanceElapsedClock(30 * MINUTE_IN_MILLIS); + + mTimeController.checkExpiredDeadlinesAndResetAlarm(); + assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE)); + assertTrue(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE)); + assertFalse(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE)); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DEADLINE), + any(), any(), any()); + + advanceElapsedClock(30 * MINUTE_IN_MILLIS); + + mTimeController.checkExpiredDeadlinesAndResetAlarm(); + assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE)); + assertTrue(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE)); + assertTrue(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE)); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any()); + } + + @Test + public void testCheckExpiredDeadlinesAndResetAlarm_WithSkipping_SomeNotReady() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true; + mTimeController.onConstantsUpdatedLocked(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + + JobStatus jobLatest = createJobStatus("testCheckExpiredDeadlinesAndResetAlarm", + createJob().setOverrideDeadline(HOUR_IN_MILLIS)); + JobStatus jobMiddle = createJobStatus("testCheckExpiredDeadlinesAndResetAlarm", + createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS)); + JobStatus jobEarliest = createJobStatus("testCheckExpiredDeadlinesAndResetAlarm", + createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS)); + + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt()); + doReturn(false).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt()); + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt()); + + InOrder inOrder = inOrder(mAlarmManager); + + mTimeController.maybeStartTrackingJobLocked(jobLatest, null); + mTimeController.maybeStartTrackingJobLocked(jobMiddle, null); + mTimeController.maybeStartTrackingJobLocked(jobEarliest, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), + eq(TAG_DEADLINE), any(), any(), any()); + + advanceElapsedClock(10 * MINUTE_IN_MILLIS); + + mTimeController.checkExpiredDeadlinesAndResetAlarm(); + assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE)); + assertFalse(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE)); + assertFalse(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE)); + // Middle job wouldn't be ready, so its alarm should be skipped. + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DEADLINE), + any(), any(), any()); + + advanceElapsedClock(55 * MINUTE_IN_MILLIS); + + mTimeController.checkExpiredDeadlinesAndResetAlarm(); + assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE)); + assertTrue(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE)); + assertTrue(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE)); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any()); + } + + @Test + public void testEvaluateStateLocked_SkippingOff() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false; + mTimeController.onConstantsUpdatedLocked(); + JobStatus job = createJobStatus("testEvaluateStateLocked_SkippingOff", + createJob().setOverrideDeadline(HOUR_IN_MILLIS)); + + mTimeController.evaluateStateLocked(job); + verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any()); + } + + @Test + public void testEvaluateStateLocked_SkippingOn_Delay() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true; + mTimeController.onConstantsUpdatedLocked(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + + JobStatus jobLatest = createJobStatus("testEvaluateStateLocked_SkippingOn_Delay", + createJob().setMinimumLatency(HOUR_IN_MILLIS)); + JobStatus jobMiddle = createJobStatus("testEvaluateStateLocked_SkippingOn_Delay", + createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS)); + JobStatus jobEarliest = createJobStatus("testEvaluateStateLocked_SkippingOn_Delay", + createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS)); + + doReturn(false).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt()); + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt()); + doReturn(false).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt()); + + InOrder inOrder = inOrder(mAlarmManager); + + mTimeController.maybeStartTrackingJobLocked(jobLatest, null); + mTimeController.maybeStartTrackingJobLocked(jobMiddle, null); + mTimeController.maybeStartTrackingJobLocked(jobEarliest, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), + any(), any(), any()); + + // Test evaluating something after the current deadline. + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt()); + mTimeController.evaluateStateLocked(jobLatest); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any()); + + // Test evaluating something before the current deadline. + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt()); + mTimeController.evaluateStateLocked(jobEarliest); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), + any(), any(), any()); + // Job goes back to not being ready. Middle is still true, so use that alarm. + doReturn(false).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt()); + mTimeController.evaluateStateLocked(jobEarliest); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(), + eq(TAG_DELAY), any(), any(), any()); + // Turn middle off. Latest is true, so use that alarm. + doReturn(false).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt()); + mTimeController.evaluateStateLocked(jobMiddle); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), + eq(TAG_DELAY), any(), any(), any()); + } + + @Test + public void testEvaluateStateLocked_SkippingOn_Deadline() { + mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true; + mTimeController.onConstantsUpdatedLocked(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + + JobStatus jobLatest = createJobStatus("testEvaluateStateLocked_SkippingOn_Deadline", + createJob().setOverrideDeadline(HOUR_IN_MILLIS)); + JobStatus jobMiddle = createJobStatus("testEvaluateStateLocked_SkippingOn_Deadline", + createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS)); + JobStatus jobEarliest = createJobStatus("testEvaluateStateLocked_SkippingOn_Deadline", + createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS)); + + doReturn(false).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt()); + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt()); + doReturn(false).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt()); + + InOrder inOrder = inOrder(mAlarmManager); + + mTimeController.maybeStartTrackingJobLocked(jobLatest, null); + mTimeController.maybeStartTrackingJobLocked(jobMiddle, null); + mTimeController.maybeStartTrackingJobLocked(jobEarliest, null); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(), + eq(TAG_DEADLINE), any(), any(), any()); + + // Test evaluating something after the current deadline. + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt()); + mTimeController.evaluateStateLocked(jobLatest); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any()); + + // Test evaluating something before the current deadline. + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt()); + mTimeController.evaluateStateLocked(jobEarliest); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), + eq(TAG_DEADLINE), any(), any(), any()); + // Job goes back to not being ready. Middle is still true, so use that alarm. + doReturn(false).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt()); + mTimeController.evaluateStateLocked(jobEarliest); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(), + eq(TAG_DEADLINE), any(), any(), any()); + // Turn middle off. Latest is true, so use that alarm. + doReturn(false).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt()); + mTimeController.evaluateStateLocked(jobMiddle); + inOrder.verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), + eq(TAG_DEADLINE), any(), any(), any()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 729fac5b1dff..38e8ac2d8f4c 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -72,7 +72,6 @@ import android.content.pm.StringParceledListSlice; import android.content.pm.UserInfo; import android.graphics.Color; import android.net.Uri; -import android.net.wifi.WifiInfo; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Process; @@ -1989,17 +1988,16 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Test 4, Caller is DO now. assertTrue(dpm.setDeviceOwner(admin1, null, UserHandle.USER_SYSTEM)); - // 4-1. But no WifiInfo. + // 4-1. But WifiManager is not ready. assertNull(dpm.getWifiMacAddress(admin1)); - // 4-2. Returns WifiInfo, but with the default MAC. - when(getServices().wifiManager.getConnectionInfo()).thenReturn(new WifiInfo()); + // 4-2. When WifiManager returns an empty array, dpm should also output null. + when(getServices().wifiManager.getFactoryMacAddresses()).thenReturn(new String[0]); assertNull(dpm.getWifiMacAddress(admin1)); // 4-3. With a real MAC address. - final WifiInfo wi = new WifiInfo(); - wi.setMacAddress("11:22:33:44:55:66"); - when(getServices().wifiManager.getConnectionInfo()).thenReturn(wi); + final String[] macAddresses = new String[]{"11:22:33:44:55:66"}; + when(getServices().wifiManager.getFactoryMacAddresses()).thenReturn(macAddresses); assertEquals("11:22:33:44:55:66", dpm.getWifiMacAddress(admin1)); } diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java index eb4cc4e36616..f4cdc8cd4d9a 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java @@ -16,9 +16,12 @@ package com.android.server.pm.dex; +import static com.android.server.pm.dex.PackageDynamicCodeLoading.MAX_FILES_PER_OWNER; import static com.android.server.pm.dex.PackageDynamicCodeLoading.escape; import static com.android.server.pm.dex.PackageDynamicCodeLoading.unescape; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @@ -119,6 +122,24 @@ public class PackageDynamicCodeLoadingTests { } @Test + public void testRecord_tooManyFiles_ignored() { + PackageDynamicCodeLoading info = new PackageDynamicCodeLoading(); + int tooManyFiles = MAX_FILES_PER_OWNER + 1; + for (int i = 1; i <= tooManyFiles; i++) { + Entry entry = new Entry("owning.package", "/path/file" + i, 'D', 10, "loading.package"); + boolean added = record(info, entry); + Set<Entry> entries = entriesFrom(info); + if (i < tooManyFiles) { + assertThat(entries).contains(entry); + assertTrue(added); + } else { + assertThat(entries).doesNotContain(entry); + assertFalse(added); + } + } + } + + @Test public void testClear() { Entry[] entries = { new Entry("owner1", "file1", 'D', 10, "loader1"), diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index 41d5a1c2fac4..afbe6bc21d0d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -27,6 +27,8 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; @@ -417,7 +419,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyLights(); assertTrue(r.isInterruptive()); - assertFalse(r.getAudiblyAlerted()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -430,7 +432,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyNeverVibrate(); verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt()); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -441,7 +443,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyBeep(); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -452,7 +454,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyNeverBeep(); assertFalse(r.isInterruptive()); - assertFalse(r.getAudiblyAlerted()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -490,7 +492,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyNeverBeep(); verifyNeverVibrate(); assertFalse(r.isInterruptive()); - assertFalse(r.getAudiblyAlerted()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -503,7 +505,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyNeverBeep(); verifyNeverVibrate(); assertFalse(r.isInterruptive()); - assertFalse(r.getAudiblyAlerted()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -520,7 +522,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyBeepLooped(); verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt()); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -549,7 +551,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyNeverStopAudio(); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -563,9 +565,9 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyNeverStopAudio(); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); assertFalse(s.isInterruptive()); - assertFalse(s.getAudiblyAlerted()); + assertEquals(-1, s.getLastAudiblyAlertedMs()); } /** @@ -602,7 +604,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(s); // this no longer owns the stream verifyNeverStopAudio(); assertTrue(other.isInterruptive()); - assertTrue(other.getAudiblyAlerted()); + assertNotEquals(-1, other.getLastAudiblyAlertedMs()); } @Test @@ -628,14 +630,14 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // set up internal state mService.buzzBeepBlinkLocked(r); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); Mockito.reset(mRingtonePlayer); // quiet update should stop making noise mService.buzzBeepBlinkLocked(s); verifyStopAudio(); assertFalse(s.isInterruptive()); - assertFalse(s.getAudiblyAlerted()); + assertEquals(-1, s.getLastAudiblyAlertedMs()); } @Test @@ -647,14 +649,14 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // set up internal state mService.buzzBeepBlinkLocked(r); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); Mockito.reset(mRingtonePlayer); // stop making noise - this is a weird corner case, but quiet should override once mService.buzzBeepBlinkLocked(s); verifyStopAudio(); assertFalse(s.isInterruptive()); - assertFalse(s.getAudiblyAlerted()); + assertEquals(-1, s.getLastAudiblyAlertedMs()); } @Test @@ -671,7 +673,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verify(mService, times(1)).playInCallNotification(); verifyNeverBeep(); // doesn't play normal beep assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -691,7 +693,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { eq(effect), anyString(), (AudioAttributes) anyObject()); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -709,7 +711,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyNeverVibrate(); verifyBeepLooped(); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -729,7 +731,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verify(mRingtonePlayer, never()).playAsync (anyObject(), anyObject(), anyBoolean(), anyObject()); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -746,7 +748,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyDelayedVibrateLooped(); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -758,7 +760,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyNeverBeep(); verifyVibrate(); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -768,7 +770,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyVibrateLooped(); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -784,7 +786,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyVibrate(); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -795,7 +797,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyNeverBeep(); assertFalse(child.isInterruptive()); - assertFalse(child.getAudiblyAlerted()); + assertEquals(-1, child.getLastAudiblyAlertedMs()); } @Test @@ -808,7 +810,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyBeepLooped(); // summaries are never interruptive for notification counts assertFalse(summary.isInterruptive()); - assertTrue(summary.getAudiblyAlerted()); + assertNotEquals(-1, summary.getLastAudiblyAlertedMs()); } @Test @@ -819,7 +821,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyBeepLooped(); assertTrue(nonGroup.isInterruptive()); - assertTrue(nonGroup.getAudiblyAlerted()); + assertNotEquals(-1, nonGroup.getLastAudiblyAlertedMs()); } @Test @@ -831,7 +833,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyNeverBeep(); assertFalse(summary.isInterruptive()); - assertFalse(summary.getAudiblyAlerted()); + assertEquals(-1, summary.getLastAudiblyAlertedMs()); } @Test @@ -842,7 +844,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyBeepLooped(); assertTrue(child.isInterruptive()); - assertTrue(child.getAudiblyAlerted()); + assertNotEquals(-1, child.getLastAudiblyAlertedMs()); } @Test @@ -853,7 +855,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyBeepLooped(); assertTrue(nonGroup.isInterruptive()); - assertTrue(nonGroup.getAudiblyAlerted()); + assertNotEquals(-1, nonGroup.getLastAudiblyAlertedMs()); } @Test @@ -864,7 +866,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyBeepLooped(); assertTrue(group.isInterruptive()); - assertTrue(group.getAudiblyAlerted()); + assertNotEquals(-1, group.getLastAudiblyAlertedMs()); } @Test @@ -877,13 +879,13 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); Mockito.reset(mVibrator); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); // update should not beep mService.buzzBeepBlinkLocked(s); verifyNeverVibrate(); assertFalse(s.isInterruptive()); - assertFalse(s.getAudiblyAlerted()); + assertEquals(-1, s.getLastAudiblyAlertedMs()); } @Test @@ -896,7 +898,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyNeverStopVibrate(); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -910,9 +912,9 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyNeverStopVibrate(); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); assertFalse(s.isInterruptive()); - assertFalse(s.getAudiblyAlerted()); + assertEquals(-1, s.getLastAudiblyAlertedMs()); } @Test @@ -931,11 +933,11 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(s); // this no longer owns the stream verifyNeverStopVibrate(); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); assertTrue(other.isInterruptive()); - assertTrue(other.getAudiblyAlerted()); + assertNotEquals(-1, other.getLastAudiblyAlertedMs()); assertFalse(s.isInterruptive()); - assertFalse(s.getAudiblyAlerted()); + assertEquals(-1, s.getLastAudiblyAlertedMs()); } @Test @@ -951,7 +953,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(other); verifyNeverStopVibrate(); assertFalse(other.isInterruptive()); - assertFalse(other.getAudiblyAlerted()); + assertEquals(-1, other.getLastAudiblyAlertedMs()); } @Test @@ -968,9 +970,9 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(s); verifyStopVibrate(); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); assertFalse(s.isInterruptive()); - assertFalse(s.getAudiblyAlerted()); + assertEquals(-1, s.getLastAudiblyAlertedMs()); } @Test @@ -987,9 +989,9 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(s); verifyStopVibrate(); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); assertFalse(s.isInterruptive()); - assertFalse(s.getAudiblyAlerted()); + assertEquals(-1, s.getLastAudiblyAlertedMs()); } @Test @@ -1007,9 +1009,9 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(s); verifyStopVibrate(); assertTrue(r.isInterruptive()); - assertTrue(r.getAudiblyAlerted()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); assertFalse(s.isInterruptive()); - assertFalse(s.getAudiblyAlerted()); + assertEquals(-1, s.getLastAudiblyAlertedMs()); } @Test @@ -1027,7 +1029,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyNeverBeep(); assertFalse(r.isInterruptive()); - assertFalse(r.getAudiblyAlerted()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -1039,7 +1041,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyNeverBeep(); assertFalse(r.isInterruptive()); - assertFalse(r.getAudiblyAlerted()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -1082,7 +1084,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyNeverBeep(); assertFalse(r.isInterruptive()); - assertFalse(r.getAudiblyAlerted()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -1116,7 +1118,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyNeverLights(); assertFalse(r.isInterruptive()); - assertFalse(r.getAudiblyAlerted()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -1126,7 +1128,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyNeverLights(); assertFalse(r.isInterruptive()); - assertFalse(r.getAudiblyAlerted()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -1135,7 +1137,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyLights(); assertTrue(r.isInterruptive()); - assertFalse(r.getAudiblyAlerted()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); r = getLightsOnceNotification(); r.isUpdate = true; @@ -1143,7 +1145,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // checks that lights happened once, i.e. this new call didn't trigger them again verifyLights(); assertFalse(r.isInterruptive()); - assertFalse(r.getAudiblyAlerted()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -1153,7 +1155,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyNeverLights(); assertFalse(r.isInterruptive()); - assertFalse(r.getAudiblyAlerted()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -1162,7 +1164,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyNeverLights(); assertFalse(r.isInterruptive()); - assertFalse(r.getAudiblyAlerted()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -1172,7 +1174,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyNeverLights(); assertFalse(r.isInterruptive()); - assertFalse(r.getAudiblyAlerted()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -1182,7 +1184,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyNeverLights(); assertFalse(r.isInterruptive()); - assertFalse(r.getAudiblyAlerted()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -1192,7 +1194,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyNeverLights(); assertFalse(r.isInterruptive()); - assertFalse(r.getAudiblyAlerted()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); } @Test @@ -1203,7 +1205,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyNeverLights(); assertFalse(child.isInterruptive()); - assertFalse(child.getAudiblyAlerted()); + assertEquals(-1, child.getLastAudiblyAlertedMs()); } @Test @@ -1216,7 +1218,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyLights(); // summaries should never count for interruptiveness counts assertFalse(summary.isInterruptive()); - assertFalse(summary.getAudiblyAlerted()); + assertEquals(-1, summary.getLastAudiblyAlertedMs()); } @Test @@ -1227,7 +1229,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyLights(); assertTrue(nonGroup.isInterruptive()); - assertFalse(nonGroup.getAudiblyAlerted()); + assertEquals(-1, nonGroup.getLastAudiblyAlertedMs()); } @Test @@ -1239,7 +1241,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyNeverLights(); assertFalse(summary.isInterruptive()); - assertFalse(summary.getAudiblyAlerted()); + assertEquals(-1, summary.getLastAudiblyAlertedMs()); } @Test @@ -1250,7 +1252,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyLights(); assertTrue(child.isInterruptive()); - assertFalse(child.getAudiblyAlerted()); + assertEquals(-1, child.getLastAudiblyAlertedMs()); } @Test @@ -1261,7 +1263,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyLights(); assertTrue(nonGroup.isInterruptive()); - assertFalse(nonGroup.getAudiblyAlerted()); + assertEquals(-1, nonGroup.getLastAudiblyAlertedMs()); } @Test @@ -1272,7 +1274,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyLights(); assertTrue(group.isInterruptive()); - assertFalse(group.getAudiblyAlerted()); + assertEquals(-1, group.getLastAudiblyAlertedMs()); } static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java index bcba15df8756..daca9cb28051 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java @@ -16,12 +16,9 @@ package com.android.server.notification; -import static android.service.notification.NotificationListenerService.Ranking - .USER_SENTIMENT_NEGATIVE; -import static android.service.notification.NotificationListenerService.Ranking - .USER_SENTIMENT_NEUTRAL; -import static android.service.notification.NotificationListenerService.Ranking - .USER_SENTIMENT_POSITIVE; +import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; +import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; +import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -92,7 +89,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { assertEquals(getShowBadge(i), ranking.canShowBadge()); assertEquals(getUserSentiment(i), ranking.getUserSentiment()); assertEquals(getHidden(i), ranking.isSuspended()); - assertEquals(audiblyAlerted(i), ranking.audiblyAlerted()); + assertEquals(lastAudiblyAlerted(i), ranking.getLastAudiblyAlertedMillis()); assertActionsEqual(getSmartActions(key, i), ranking.getSmartActions()); assertEquals(getSmartReplies(key, i), ranking.getSmartReplies()); } @@ -113,7 +110,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { Bundle mHidden = new Bundle(); Bundle smartActions = new Bundle(); Bundle smartReplies = new Bundle(); - Bundle audiblyAlerted = new Bundle(); + Bundle lastAudiblyAlerted = new Bundle(); Bundle noisy = new Bundle(); for (int i = 0; i < mKeys.length; i++) { @@ -134,14 +131,14 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { mHidden.putBoolean(key, getHidden(i)); smartActions.putParcelableArrayList(key, getSmartActions(key, i)); smartReplies.putCharSequenceArrayList(key, getSmartReplies(key, i)); - audiblyAlerted.putBoolean(key, audiblyAlerted(i)); + lastAudiblyAlerted.putLong(key, lastAudiblyAlerted(i)); noisy.putBoolean(key, getNoisy(i)); } NotificationRankingUpdate update = new NotificationRankingUpdate(mKeys, interceptedKeys.toArray(new String[0]), visibilityOverrides, suppressedVisualEffects, importance, explanation, overrideGroupKeys, channels, overridePeople, snoozeCriteria, showBadge, userSentiment, mHidden, - smartActions, smartReplies, audiblyAlerted, noisy); + smartActions, smartReplies, lastAudiblyAlerted, noisy); return update; } @@ -193,8 +190,8 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { return index % 2 == 0; } - private boolean audiblyAlerted(int index) { - return index < 2; + private long lastAudiblyAlerted(int index) { + return index * 2000; } private boolean getNoisy(int index) { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java index 0eeeeed0b227..65e640f5f73c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java @@ -19,12 +19,9 @@ import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; -import static android.service.notification.NotificationListenerService.Ranking - .USER_SENTIMENT_NEGATIVE; -import static android.service.notification.NotificationListenerService.Ranking - .USER_SENTIMENT_NEUTRAL; -import static android.service.notification.NotificationListenerService.Ranking - .USER_SENTIMENT_POSITIVE; +import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; +import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; +import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; @@ -806,6 +803,18 @@ public class NotificationRecordTest extends UiServiceTestCase { } @Test + public void testSetDidNotAudiblyAlert() { + StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */, + true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */, + false /* lights */, false /* defaultLights */, groupId /* group */); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); + + record.setAudiblyAlerted(false); + + assertEquals(-1, record.getLastAudiblyAlertedMs()); + } + + @Test public void testSetAudiblyAlerted() { StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */, true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */, @@ -814,6 +823,6 @@ public class NotificationRecordTest extends UiServiceTestCase { record.setAudiblyAlerted(true); - assertTrue(record.getAudiblyAlerted()); + assertNotEquals(-1, record.getLastAudiblyAlertedMs()); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 7c83ecc22c90..8430616731d2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -19,6 +19,8 @@ package com.android.server.wm; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.os.Build.VERSION_CODES.P; +import static android.os.Build.VERSION_CODES.Q; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT; import static android.view.DisplayCutout.BOUNDS_POSITION_TOP; @@ -309,9 +311,27 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testFocusedWindowMultipleDisplays() { + doTestFocusedWindowMultipleDisplays(false /* perDisplayFocusEnabled */, Q); + } + + @Test + public void testFocusedWindowMultipleDisplaysPerDisplayFocusEnabled() { + doTestFocusedWindowMultipleDisplays(true /* perDisplayFocusEnabled */, Q); + } + + @Test + public void testFocusedWindowMultipleDisplaysPerDisplayFocusEnabledLegacyApp() { + doTestFocusedWindowMultipleDisplays(true /* perDisplayFocusEnabled */, P); + } + + private void doTestFocusedWindowMultipleDisplays(boolean perDisplayFocusEnabled, + int targetSdk) { + mWm.mPerDisplayFocusEnabled = perDisplayFocusEnabled; + // Create a focusable window and check that focus is calculated correctly final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "window1"); + window1.mAppToken.mTargetSdk = targetSdk; updateFocusedWindow(); assertTrue(window1.isFocused()); assertEquals(window1, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus); @@ -324,16 +344,17 @@ public class DisplayContentTests extends WindowTestsBase { // Add a window to the second display, and it should be focused final WindowState window2 = createWindow(null, TYPE_BASE_APPLICATION, dc, "window2"); + window2.mAppToken.mTargetSdk = targetSdk; updateFocusedWindow(); - assertTrue(window1.isFocused()); assertTrue(window2.isFocused()); + assertEquals(perDisplayFocusEnabled && targetSdk >= Q, window1.isFocused()); assertEquals(window2, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus); - // Move the first window to the to including parents, and make sure focus is updated + // Move the first window to top including parents, and make sure focus is updated window1.getParent().positionChildAt(POSITION_TOP, window1, true); updateFocusedWindow(); assertTrue(window1.isFocused()); - assertTrue(window2.isFocused()); + assertEquals(perDisplayFocusEnabled && targetSdk >= Q, window2.isFocused()); assertEquals(window1, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus); } diff --git a/services/tests/wmtests/src/com/android/server/wm/KeyguardDisableHandlerTest.java b/services/tests/wmtests/src/com/android/server/wm/KeyguardDisableHandlerTest.java new file mode 100644 index 000000000000..ce22788f7cb9 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/KeyguardDisableHandlerTest.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.os.Process.FIRST_APPLICATION_UID; +import static android.os.Process.NFC_UID; +import static android.os.Process.SYSTEM_UID; +import static android.os.UserHandle.USER_ALL; +import static android.os.UserHandle.USER_SYSTEM; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.UserHandle; +import android.util.SparseBooleanArray; + +import com.android.server.wm.LockTaskController.LockTaskToken; + +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.Constructor; + +public class KeyguardDisableHandlerTest { + + private KeyguardDisableHandler mKeyguardDisable; + + private boolean mKeyguardEnabled; + private SparseBooleanArray mKeyguardSecure = new SparseBooleanArray(); + private SparseBooleanArray mDpmRequiresPassword = new SparseBooleanArray(); + + @Before + public void setUp() throws Exception { + mKeyguardEnabled = true; + + mKeyguardDisable = new KeyguardDisableHandler(new KeyguardDisableHandler.Injector() { + @Override + public boolean dpmRequiresPassword(int userId) { + return mDpmRequiresPassword.get(userId); + } + + @Override + public boolean isKeyguardSecure(int userId) { + return mKeyguardSecure.get(userId); + } + + @Override + public int getProfileParentId(int userId) { + return userId; + } + + @Override + public void enableKeyguard(boolean enabled) { + mKeyguardEnabled = enabled; + } + }, mock(Handler.class)) { + @Override + public void disableKeyguard(IBinder token, String tag, int callingUid, int userId) { + super.disableKeyguard(token, tag, callingUid, userId); + // In the actual code, the update is posted to the handler thread. Eagerly update + // here to simplify the test. + updateKeyguardEnabled(userId); + } + + @Override + public void reenableKeyguard(IBinder token, int callingUid, int userId) { + super.reenableKeyguard(token, callingUid, userId); + // In the actual code, the update is posted to the handler thread. Eagerly update + // here to simplify the test. + updateKeyguardEnabled(userId); + } + }; + } + + @Test + public void starts_enabled() { + assertTrue(mKeyguardEnabled); + mKeyguardDisable.updateKeyguardEnabled(USER_ALL); + assertTrue(mKeyguardEnabled); + } + + @Test + public void disable_fromApp_disables() { + mKeyguardDisable.disableKeyguard(new Binder(), "Tag", FIRST_APPLICATION_UID, USER_SYSTEM); + assertFalse(mKeyguardEnabled); + } + + @Test + public void disable_fromApp_secondaryUser_disables() { + mKeyguardDisable.setCurrentUser(1); + mKeyguardDisable.disableKeyguard(new Binder(), "Tag", + UserHandle.getUid(1, FIRST_APPLICATION_UID), 1); + assertFalse(mKeyguardEnabled); + } + + @Test + public void disable_fromSystem_LockTask_disables() { + mKeyguardDisable.disableKeyguard(createLockTaskToken(), "Tag", SYSTEM_UID, USER_SYSTEM); + assertFalse(mKeyguardEnabled); + } + + @Test + public void disable_fromSystem_genericToken_fails() { + try { + mKeyguardDisable.disableKeyguard(new Binder(), "Tag", SYSTEM_UID, USER_SYSTEM); + fail("Expected exception not thrown"); + } catch (UnsupportedOperationException e) { + assertThat(e.getMessage(), containsString("Only apps can use the KeyguardLock API")); + } + assertTrue(mKeyguardEnabled); + } + + @Test + public void disable_fromNonApp_genericToken_fails() { + try { + mKeyguardDisable.disableKeyguard(new Binder(), "Tag", NFC_UID, USER_SYSTEM); + fail("Expected exception not thrown"); + } catch (UnsupportedOperationException e) { + assertThat(e.getMessage(), containsString("Only apps can use the KeyguardLock API")); + } + assertTrue(mKeyguardEnabled); + } + + @Test + public void disable_fromApp_secure_staysEnabled() { + configureIsSecure(true, USER_SYSTEM); + mKeyguardDisable.disableKeyguard(new Binder(), "Tag", FIRST_APPLICATION_UID, USER_SYSTEM); + assertTrue(mKeyguardEnabled); + } + + @Test + public void disable_fromApp_dpmRequiresPassword_staysEnabled() { + configureDpmRequiresPassword(true, USER_SYSTEM); + mKeyguardDisable.disableKeyguard(new Binder(), "Tag", FIRST_APPLICATION_UID, USER_SYSTEM); + assertTrue(mKeyguardEnabled); + } + + @Test + public void disable_fromSystem_LockTask_secure_disables() { + configureIsSecure(true, USER_SYSTEM); + mKeyguardDisable.disableKeyguard(createLockTaskToken(), "Tag", SYSTEM_UID, USER_SYSTEM); + assertFalse(mKeyguardEnabled); + } + + @Test + public void disable_fromSystem_LockTask_requiresDpm_staysEnabled() { + configureDpmRequiresPassword(true, USER_SYSTEM); + mKeyguardDisable.disableKeyguard(createLockTaskToken(), "Tag", SYSTEM_UID, USER_SYSTEM); + assertTrue(mKeyguardEnabled); + } + + @Test + public void disable_fromApp_thenSecure_reenables() { + mKeyguardDisable.disableKeyguard(new Binder(), "Tag", FIRST_APPLICATION_UID, USER_SYSTEM); + configureIsSecure(true, USER_SYSTEM); + assertTrue(mKeyguardEnabled); + } + + @Test + public void disable_fromSystem_LockTask_thenRequiresDpm_reenables() { + mKeyguardDisable.disableKeyguard(createLockTaskToken(), "Tag", SYSTEM_UID, USER_SYSTEM); + configureDpmRequiresPassword(true, USER_SYSTEM); + assertTrue(mKeyguardEnabled); + } + + @Test + public void user_switch_to_enabledUser_applies_enabled() { + mKeyguardDisable.disableKeyguard(createLockTaskToken(), "Tag", SYSTEM_UID, USER_SYSTEM); + assertFalse("test setup failed", mKeyguardEnabled); + mKeyguardDisable.setCurrentUser(1); + assertTrue(mKeyguardEnabled); + } + + @Test + public void user_switch_to_disabledUser_applies_disabled() { + mKeyguardDisable.disableKeyguard(createLockTaskToken(), "Tag", + SYSTEM_UID, 1); + assertTrue("test setup failed", mKeyguardEnabled); + mKeyguardDisable.setCurrentUser(1); + assertFalse(mKeyguardEnabled); + } + + private void configureIsSecure(boolean secure, int userId) { + mKeyguardSecure.put(userId, secure); + mKeyguardDisable.updateKeyguardEnabled(userId); + } + + private void configureDpmRequiresPassword(boolean requiresPassword, int userId) { + mDpmRequiresPassword.put(userId, requiresPassword); + mKeyguardDisable.updateKeyguardEnabled(userId); + } + + private LockTaskToken createLockTaskToken() { + try { + final Constructor<LockTaskToken> constructor = + LockTaskToken.class.getDeclaredConstructor(); + constructor.setAccessible(true); + return constructor.newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java index 6259fa669ce8..a9f150b10e73 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java @@ -560,19 +560,20 @@ public class LockTaskControllerTest { mLockTaskController.startLockTaskMode(tr, false, TEST_UID); // THEN keyguard should be disabled - verify(mWindowManager).disableKeyguard(any(IBinder.class), anyString()); + verify(mWindowManager).disableKeyguard(any(IBinder.class), anyString(), eq(TEST_USER_ID)); // WHEN keyguard is enabled for lock task mode mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_KEYGUARD); // THEN keyguard should be enabled - verify(mWindowManager).reenableKeyguard(any(IBinder.class)); + verify(mWindowManager).reenableKeyguard(any(IBinder.class), eq(TEST_USER_ID)); // WHEN keyguard is disabled again for lock task mode mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_NONE); // THEN keyguard should be disabled - verify(mWindowManager, times(2)).disableKeyguard(any(IBinder.class), anyString()); + verify(mWindowManager, times(2)).disableKeyguard(any(IBinder.class), anyString(), + eq(TEST_USER_ID)); } @Test @@ -653,7 +654,7 @@ public class LockTaskControllerTest { private void verifyLockTaskStarted(int statusBarMask, int statusBarMask2) throws Exception { // THEN the keyguard should have been disabled - verify(mWindowManager).disableKeyguard(any(IBinder.class), anyString()); + verify(mWindowManager).disableKeyguard(any(IBinder.class), anyString(), eq(TEST_USER_ID)); // THEN the status bar should have been disabled verify(mStatusBarService).disable(eq(statusBarMask), any(IBinder.class), eq(mContext.getPackageName())); @@ -668,7 +669,7 @@ public class LockTaskControllerTest { private void verifyLockTaskStopped(VerificationMode mode) throws Exception { // THEN the keyguard should have been disabled - verify(mWindowManager, mode).reenableKeyguard(any(IBinder.class)); + verify(mWindowManager, mode).reenableKeyguard(any(IBinder.class), eq(TEST_USER_ID)); // THEN the status bar should have been disabled verify(mStatusBarService, mode).disable(eq(StatusBarManager.DISABLE_NONE), any(IBinder.class), eq(mContext.getPackageName())); 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 29738ff2a63d..c5df85c554f4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java @@ -64,8 +64,7 @@ public class TestIWindow extends IWindow.Stub { } @Override - public void windowFocusChanged(boolean hasFocus, boolean inTouchMode, boolean reportToClient) - throws RemoteException { + public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) throws RemoteException { } @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index ba81bd1c3b12..d1fe48aa63cd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -170,6 +170,10 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } @Override + public void setTopFocusedDisplay(int displayId) { + } + + @Override public void applyKeyguardPolicyLw(WindowState win, WindowState imeTarget) { } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java index 65cde77e7c03..20fc5ae2454e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java @@ -30,6 +30,7 @@ import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.os.Binder; +import android.os.Build; import android.os.IBinder; import android.view.Display; import android.view.IApplicationToken; @@ -161,6 +162,7 @@ public class WindowTestUtils { return null; } }, new ComponentName("", ""), false, dc, true /* fillsParent */); + mTargetSdk = Build.VERSION_CODES.CUR_DEVELOPMENT; } TestAppWindowToken(WindowManagerService service, IApplicationToken token, diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 36d0188048c3..2820836282a1 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -518,6 +518,7 @@ public final class Call { private final Bundle mExtras; private final Bundle mIntentExtras; private final long mCreationTimeMillis; + private final CallIdentification mCallIdentification; /** * Whether the supplied capabilities supports the specified capability. @@ -699,6 +700,12 @@ public final class Call { } /** + * The display name for the caller. + * <p> + * This is the name as reported by the {@link ConnectionService} associated with this call. + * The name reported by a {@link CallScreeningService} can be retrieved using + * {@link CallIdentification#getName()}. + * * @return The display name for the caller. */ public String getCallerDisplayName() { @@ -814,6 +821,23 @@ public final class Call { return mCreationTimeMillis; } + /** + * Returns {@link CallIdentification} information provided by a + * {@link CallScreeningService} for this call. + * <p> + * {@link InCallService} implementations should display the {@link CallIdentification} for + * calls. The name of the call screening service is provided in + * {@link CallIdentification#getCallScreeningAppName()} and should be used to attribute the + * call identification information. + * + * @return The {@link CallIdentification} if it was provided by a + * {@link CallScreeningService}, or {@code null} if no {@link CallScreeningService} has + * provided {@link CallIdentification} information for the call. + */ + public @Nullable CallIdentification getCallIdentification() { + return mCallIdentification; + } + @Override public boolean equals(Object o) { if (o instanceof Details) { @@ -834,7 +858,8 @@ public final class Call { Objects.equals(mStatusHints, d.mStatusHints) && areBundlesEqual(mExtras, d.mExtras) && areBundlesEqual(mIntentExtras, d.mIntentExtras) && - Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis); + Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) && + Objects.equals(mCallIdentification, d.mCallIdentification); } return false; } @@ -855,7 +880,8 @@ public final class Call { mStatusHints, mExtras, mIntentExtras, - mCreationTimeMillis); + mCreationTimeMillis, + mCallIdentification); } /** {@hide} */ @@ -875,7 +901,8 @@ public final class Call { StatusHints statusHints, Bundle extras, Bundle intentExtras, - long creationTimeMillis) { + long creationTimeMillis, + CallIdentification callIdentification) { mTelecomCallId = telecomCallId; mHandle = handle; mHandlePresentation = handlePresentation; @@ -892,6 +919,7 @@ public final class Call { mExtras = extras; mIntentExtras = intentExtras; mCreationTimeMillis = creationTimeMillis; + mCallIdentification = callIdentification; } /** {@hide} */ @@ -912,7 +940,8 @@ public final class Call { parcelableCall.getStatusHints(), parcelableCall.getExtras(), parcelableCall.getIntentExtras(), - parcelableCall.getCreationTimeMillis()); + parcelableCall.getCreationTimeMillis(), + parcelableCall.getCallIdentification()); } @Override diff --git a/telecomm/java/android/telecom/CallIdentification.aidl b/telecomm/java/android/telecom/CallIdentification.aidl new file mode 100644 index 000000000000..532535c44714 --- /dev/null +++ b/telecomm/java/android/telecom/CallIdentification.aidl @@ -0,0 +1,22 @@ +/* + * 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.telecom; + +/** + * {@hide} + */ +parcelable CallIdentification; diff --git a/telecomm/java/android/telecom/CallIdentification.java b/telecomm/java/android/telecom/CallIdentification.java new file mode 100644 index 000000000000..97af06c1d64c --- /dev/null +++ b/telecomm/java/android/telecom/CallIdentification.java @@ -0,0 +1,433 @@ +/* + * 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.telecom; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.ApplicationInfo; +import android.graphics.drawable.Icon; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Encapsulates information about an incoming or outgoing {@link Call} provided by a + * {@link CallScreeningService}. + * <p> + * Call identified information is consumed by the {@link InCallService dialer} app to provide the + * user with more information about a call. This can include information such as the name of the + * caller, address, etc. Call identification information is persisted to the + * {@link android.provider.CallLog}. + */ +public final class CallIdentification implements Parcelable { + /** + * Builder for {@link CallIdentification} instances. + * <p> + * A {@link CallScreeningService} uses this class to create new instances of + * {@link CallIdentification} for a screened call. + */ + public static class Builder { + private String mName; + private String mDescription; + private String mDetails; + private Icon mPhoto; + private int mNuisanceConfidence = CallIdentification.CONFIDENCE_UNKNOWN; + private String mPackageName; + private String mAppName; + + /** + * Default builder constructor. + */ + public Builder() { + // Default constructor + } + + /** + * Create instance of call identification with specified package/app name. + * + * @param callIdPackageName The package name. + * @param callIdAppName The app name. + * @hide + */ + public Builder(String callIdPackageName, String callIdAppName) { + mPackageName = callIdPackageName; + mAppName = callIdAppName; + } + + /** + * Sets the name associated with the {@link CallIdentification} being built. + * <p> + * Could be a business name, for example. + * + * @param name The name associated with the call, or {@code null} if none is provided. + * @return Builder instance. + */ + public Builder setName(@Nullable String name) { + mName = name; + return this; + } + + /** + * Sets the description associated with the {@link CallIdentification} being built. + * <p> + * A description of the call as identified by a {@link CallScreeningService}. The + * description is typically presented by Dialer apps after the + * {@link CallIdentification#getName() name} to provide a short piece of relevant + * information about the call. This could include a location, address, or a message + * regarding the potential nature of the call (e.g. potential telemarketer). + * + * @param description The call description, or {@code null} if none is provided. + * @return Builder instance. + */ + public Builder setDescription(@Nullable String description) { + mDescription = description; + return this; + } + + /** + * Sets the details associated with the {@link CallIdentification} being built. + * <p> + * The details is typically presented by Dialer apps after the + * {@link CallIdentification#getName() name} and + * {@link CallIdentification#getDescription() description} to provide further clarifying + * information about the call. This could include, for example, the opening hours of a + * business, or a stats about the number of times a call has been reported as spam. + * + * @param details The call details, or {@code null} if none is provided. + * @return Builder instance. + */ + public Builder setDetails(@Nullable String details) { + mDetails = details; + return this; + } + + /** + * Sets the photo associated with the {@link CallIdentification} being built. + * <p> + * This could be, for example, a business logo, or a photo of the caller. + * + * @param photo The photo associated with the call, or {@code null} if none was provided. + * @return Builder instance. + */ + public Builder setPhoto(@Nullable Icon photo) { + mPhoto = photo; + return this; + } + + /** + * Sets the nuisance confidence with the {@link CallIdentification} being built. + * <p> + * This can be used to specify how confident the {@link CallScreeningService} is that a call + * is or is not a nuisance call. + * + * @param nuisanceConfidence The nuisance confidence. + * @return The builder. + */ + public Builder setNuisanceConfidence(@NuisanceConfidence int nuisanceConfidence) { + mNuisanceConfidence = nuisanceConfidence; + return this; + } + + /** + * Creates a new instance of {@link CallIdentification} based on the parameters set in this + * builder. + * + * @return {@link CallIdentification} instance. + */ + public CallIdentification build() { + return new CallIdentification(mName, mDescription, mDetails, mPhoto, + mNuisanceConfidence, mPackageName, mAppName); + } + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + prefix = { "CONFIDENCE_" }, + value = {CONFIDENCE_NUISANCE, CONFIDENCE_LIKELY_NUISANCE, CONFIDENCE_UNKNOWN, + CONFIDENCE_LIKELY_NOT_NUISANCE, CONFIDENCE_NOT_NUISANCE}) + public @interface NuisanceConfidence {} + + /** + * Call has been identified as a nuisance call. + * <p> + * Returned from {@link #getNuisanceConfidence()} to indicate that a + * {@link CallScreeningService} to indicate how confident it is that a call is or is not a + * nuisance call. + */ + public static final int CONFIDENCE_NUISANCE = 2; + + /** + * Call has been identified as a likely nuisance call. + * <p> + * Returned from {@link #getNuisanceConfidence()} to indicate that a + * {@link CallScreeningService} to indicate how confident it is that a call is or is not a + * nuisance call. + */ + public static final int CONFIDENCE_LIKELY_NUISANCE = 1; + + /** + * Call could not be classified as nuisance or non-nuisance. + * <p> + * Returned from {@link #getNuisanceConfidence()} to indicate that a + * {@link CallScreeningService} to indicate how confident it is that a call is or is not a + * nuisance call. + */ + public static final int CONFIDENCE_UNKNOWN = 0; + + /** + * Call has been identified as not likely to be a nuisance call. + * <p> + * Returned from {@link #getNuisanceConfidence()} to indicate that a + * {@link CallScreeningService} to indicate how confident it is that a call is or is not a + * nuisance call. + */ + public static final int CONFIDENCE_LIKELY_NOT_NUISANCE = -1; + + /** + * Call has been identified as not a nuisance call. + * <p> + * Returned from {@link #getNuisanceConfidence()} to indicate that a + * {@link CallScreeningService} to indicate how confident it is that a call is or is not a + * nuisance call. + */ + public static final int CONFIDENCE_NOT_NUISANCE = -2; + + /** + * Default constructor for {@link CallIdentification}. + * + * @param name The name. + * @param description The description. + * @param details The details. + * @param photo The photo. + * @param nuisanceConfidence Confidence that this is a nuisance call. + * @hide + */ + private CallIdentification(@Nullable String name, @Nullable String description, + @Nullable String details, @Nullable Icon photo, + @NuisanceConfidence int nuisanceConfidence) { + this(name, description, details, photo, nuisanceConfidence, null, null); + } + + /** + * Default constructor for {@link CallIdentification}. + * + * @param name The name. + * @param description The description. + * @param details The details. + * @param photo The photo. + * @param nuisanceConfidence Confidence that this is a nuisance call. + * @param callScreeningPackageName Package name of the {@link CallScreeningService} which + * provided the call identification. + * @param callScreeningAppName App name of the {@link CallScreeningService} which provided the + * call identification. + * @hide + */ + private CallIdentification(@Nullable String name, @Nullable String description, + @Nullable String details, @Nullable Icon photo, + @NuisanceConfidence int nuisanceConfidence, @NonNull String callScreeningPackageName, + @NonNull String callScreeningAppName) { + mName = name; + mDescription = description; + mDetails = details; + mPhoto = photo; + mNuisanceConfidence = nuisanceConfidence; + mCallScreeningAppName = callScreeningPackageName; + mCallScreeningPackageName = callScreeningAppName; + } + + private String mName; + private String mDescription; + private String mDetails; + private Icon mPhoto; + private int mNuisanceConfidence; + private String mCallScreeningPackageName; + private String mCallScreeningAppName; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeString(mName); + parcel.writeString(mDescription); + parcel.writeString(mDetails); + parcel.writeParcelable(mPhoto, 0); + parcel.writeInt(mNuisanceConfidence); + parcel.writeString(mCallScreeningPackageName); + parcel.writeString(mCallScreeningAppName); + } + + /** + * Responsible for creating CallIdentification objects for deserialized Parcels. + */ + public static final Parcelable.Creator<CallIdentification> CREATOR = + new Parcelable.Creator<CallIdentification> () { + + @Override + public CallIdentification createFromParcel(Parcel source) { + String name = source.readString(); + String description = source.readString(); + String details = source.readString(); + Icon photo = source.readParcelable(ClassLoader.getSystemClassLoader()); + int nuisanceConfidence = source.readInt(); + String callScreeningPackageName = source.readString(); + String callScreeningAppName = source.readString(); + return new CallIdentification(name, description, details, photo, + nuisanceConfidence, callScreeningPackageName, callScreeningAppName); + } + + @Override + public CallIdentification[] newArray(int size) { + return new CallIdentification[size]; + } + }; + + /** + * The name associated with the number. + * <p> + * The name of the call as identified by a {@link CallScreeningService}. Could be a business + * name, for example. + * + * @return The name associated with the number, or {@code null} if none was provided. + */ + public final @Nullable String getName() { + return mName; + } + + /** + * Description of the call. + * <p> + * A description of the call as identified by a {@link CallScreeningService}. The description + * is typically presented by Dialer apps after the {@link #getName() name} to provide a short + * piece of relevant information about the call. This could include a location, address, or a + * message regarding the potential nature of the call (e.g. potential telemarketer). + * + * @return The call description, or {@code null} if none was provided. + */ + public final @Nullable String getDescription() { + return mDescription; + } + + /** + * Details of the call. + * <p> + * Details of the call as identified by a {@link CallScreeningService}. The details + * are typically presented by Dialer apps after the {@link #getName() name} and + * {@link #getDescription() description} to provide further clarifying information about the + * call. This could include, for example, the opening hours of a business, or stats about + * the number of times a call has been reported as spam. + * + * @return The call details, or {@code null} if none was provided. + */ + public final @Nullable String getDetails() { + return mDetails; + } + + /** + * Photo associated with the call. + * <p> + * A photo associated with the call as identified by a {@link CallScreeningService}. This + * could be, for example, a business logo, or a photo of the caller. + * + * @return The photo associated with the call, or {@code null} if none was provided. + */ + public final @Nullable Icon getPhoto() { + return mPhoto; + } + + /** + * Indicates the likelihood that this call is a nuisance call. + * <p> + * How likely the call is a nuisance call, as identified by a {@link CallScreeningService}. + * + * @return The nuisance confidence. + */ + public final @NuisanceConfidence + int getNuisanceConfidence() { + return mNuisanceConfidence; + } + + /** + * The package name of the {@link CallScreeningService} which provided the + * {@link CallIdentification}. + * <p> + * A {@link CallScreeningService} may not set this property; it is set by the system. + * @return the package name + */ + public final @NonNull String getCallScreeningPackageName() { + return mCallScreeningPackageName; + } + + /** + * The {@link android.content.pm.PackageManager#getApplicationLabel(ApplicationInfo) name} of + * the {@link CallScreeningService} which provided the {@link CallIdentification}. + * <p> + * A {@link CallScreeningService} may not set this property; it is set by the system. + * + * @return The name of the app. + */ + public final @NonNull String getCallScreeningAppName() { + return mCallScreeningAppName; + } + + /** + * Set the package name of the {@link CallScreeningService} which provided this information. + * + * @param callScreeningPackageName The package name. + * @hide + */ + public void setCallScreeningPackageName(@NonNull String callScreeningPackageName) { + mCallScreeningPackageName = callScreeningPackageName; + } + + /** + * Set the app name of the {@link CallScreeningService} which provided this information. + * + * @param callScreeningAppName The app name. + * @hide + */ + public void setCallScreeningAppName(@NonNull String callScreeningAppName) { + mCallScreeningAppName = callScreeningAppName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CallIdentification that = (CallIdentification) o; + // Note: mPhoto purposely omit as no good comparison exists. + return mNuisanceConfidence == that.mNuisanceConfidence + && Objects.equals(mName, that.mName) + && Objects.equals(mDescription, that.mDescription) + && Objects.equals(mDetails, that.mDetails) + && Objects.equals(mCallScreeningAppName, that.mCallScreeningAppName) + && Objects.equals(mCallScreeningPackageName, that.mCallScreeningPackageName); + } + + @Override + public int hashCode() { + return Objects.hash(mName, mDescription, mDetails, mPhoto, mNuisanceConfidence, + mCallScreeningAppName, mCallScreeningPackageName); + } +} diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java index 662874316a7f..be96b3cac6f6 100644 --- a/telecomm/java/android/telecom/CallScreeningService.java +++ b/telecomm/java/android/telecom/CallScreeningService.java @@ -16,6 +16,7 @@ package android.telecom; +import android.annotation.NonNull; import android.annotation.SdkConstant; import android.app.Service; import android.content.ComponentName; @@ -46,6 +47,15 @@ import com.android.internal.telecom.ICallScreeningService; * </service> * } * </pre> + * <p> + * A CallScreeningService performs two functions: + * <ol> + * <li>Call blocking/screening - the service can choose which calls will ring on the user's + * device, and which will be silently sent to voicemail.</li> + * <li>Call identification - the service can optionally provide {@link CallIdentification} + * information about a {@link Call.Details call} which will be shown to the user in the + * Dialer app.</li> + * </ol> */ public abstract class CallScreeningService extends Service { /** @@ -229,16 +239,23 @@ public abstract class CallScreeningService extends Service { * * @param callDetails Information about a new incoming call, see {@link Call.Details}. */ - public abstract void onScreenCall(Call.Details callDetails); + public abstract void onScreenCall(@NonNull Call.Details callDetails); /** * Responds to the given call, either allowing it or disallowing it. + * <p> + * The {@link CallScreeningService} calls this method to inform the system whether the call + * should be silently blocked or not. * * @param callDetails The call to allow. + * <p> + * Must be the same {@link Call.Details call} which was provided to the + * {@link CallScreeningService} via {@link #onScreenCall(Call.Details)}. * @param response The {@link CallScreeningService.CallResponse} which contains information * about how to respond to a call. */ - public final void respondToCall(Call.Details callDetails, CallResponse response) { + public final void respondToCall(@NonNull Call.Details callDetails, + @NonNull CallResponse response) { try { if (response.getDisallowCall()) { mCallScreeningAdapter.disallowCall( @@ -253,4 +270,32 @@ public abstract class CallScreeningService extends Service { } catch (RemoteException e) { } } + + /** + * Provide {@link CallIdentification} information about a {@link Call.Details call}. + * <p> + * The {@link CallScreeningService} calls this method to provide information it has identified + * about a {@link Call.Details call}. This information will potentially be shown to the user + * in the {@link InCallService dialer} app. It will be logged to the + * {@link android.provider.CallLog}. + * <p> + * A {@link CallScreeningService} should only call this method for calls for which it is able to + * provide some {@link CallIdentification} for. {@link CallIdentification} instances with no + * fields set will be ignored by the system. + * + * @param callDetails The call to provide information for. + * <p> + * Must be the same {@link Call.Details call} which was provided to the + * {@link CallScreeningService} via {@link #onScreenCall(Call.Details)}. + * @param identification An instance of {@link CallIdentification} with information about the + * {@link Call.Details call}. + */ + public final void provideCallIdentification(@NonNull Call.Details callDetails, + @NonNull CallIdentification identification) { + try { + mCallScreeningAdapter.provideCallIdentification(callDetails.getTelecomCallId(), + identification); + } catch (RemoteException e) { + } + } } diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java index 3ad0f0cc56fe..911786e455c2 100644 --- a/telecomm/java/android/telecom/ParcelableCall.java +++ b/telecomm/java/android/telecom/ParcelableCall.java @@ -16,6 +16,7 @@ package android.telecom; +import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.net.Uri; import android.os.Build; @@ -62,6 +63,7 @@ public final class ParcelableCall implements Parcelable { private final Bundle mIntentExtras; private final Bundle mExtras; private final long mCreationTimeMillis; + private final CallIdentification mCallIdentification; public ParcelableCall( String id, @@ -89,7 +91,8 @@ public final class ParcelableCall implements Parcelable { List<String> conferenceableCallIds, Bundle intentExtras, Bundle extras, - long creationTimeMillis) { + long creationTimeMillis, + CallIdentification callIdentification) { mId = id; mState = state; mDisconnectCause = disconnectCause; @@ -116,6 +119,7 @@ public final class ParcelableCall implements Parcelable { mIntentExtras = intentExtras; mExtras = extras; mCreationTimeMillis = creationTimeMillis; + mCallIdentification = callIdentification; } /** The unique ID of the call. */ @@ -133,7 +137,7 @@ public final class ParcelableCall implements Parcelable { * Reason for disconnection, as described by {@link android.telecomm.DisconnectCause}. Valid * when call state is {@link CallState#DISCONNECTED}. */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public DisconnectCause getDisconnectCause() { return mDisconnectCause; } @@ -159,13 +163,13 @@ public final class ParcelableCall implements Parcelable { } /** The time that the call switched to the active state. */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public long getConnectTimeMillis() { return mConnectTimeMillis; } /** The endpoint to which the call is connected. */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public Uri getHandle() { return mHandle; } @@ -305,8 +309,17 @@ public final class ParcelableCall implements Parcelable { return mCreationTimeMillis; } + /** + * Contains call identification information returned by a {@link CallScreeningService}. + * @return The {@link CallIdentification} for this call, or {@code null} if a + * {@link CallScreeningService} did not provide information. + */ + public @Nullable CallIdentification getCallIdentification() { + return mCallIdentification; + } + /** Responsible for creating ParcelableCall objects for deserialized Parcels. */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public static final Parcelable.Creator<ParcelableCall> CREATOR = new Parcelable.Creator<ParcelableCall> () { @Override @@ -342,6 +355,7 @@ public final class ParcelableCall implements Parcelable { boolean isRttCallChanged = source.readByte() == 1; ParcelableRttCall rttCall = source.readParcelable(classLoader); long creationTimeMillis = source.readLong(); + CallIdentification callIdentification = source.readParcelable(classLoader); return new ParcelableCall( id, state, @@ -368,7 +382,8 @@ public final class ParcelableCall implements Parcelable { conferenceableCallIds, intentExtras, extras, - creationTimeMillis); + creationTimeMillis, + callIdentification); } @Override @@ -413,6 +428,7 @@ public final class ParcelableCall implements Parcelable { destination.writeByte((byte) (mIsRttCallChanged ? 1 : 0)); destination.writeParcelable(mRttCall, 0); destination.writeLong(mCreationTimeMillis); + destination.writeParcelable(mCallIdentification, 0); } @Override diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index fb371c176352..6c4b1af8c2a1 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -23,6 +23,7 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.UnsupportedAppUsage; +import android.app.role.RoleManagerCallback; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -31,6 +32,7 @@ import android.os.Build; import android.os.Bundle; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; @@ -42,6 +44,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.Executor; /** * Provides access to information about active calls and registration/call-management functionality. @@ -1188,8 +1191,12 @@ public class TelecomManager { * Requires permission: {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} * * @hide + * @deprecated Use + * {@link android.app.role.RoleManager#addRoleHolderAsUser(String, String, UserHandle, Executor, + * RoleManagerCallback)} instead. */ @SystemApi + @Deprecated @RequiresPermission(allOf = { android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.WRITE_SECURE_SETTINGS}) @@ -1221,79 +1228,6 @@ public class TelecomManager { } /** - * Used to trigger display of the ChangeDefaultCallScreeningApp activity to prompt the user to - * change the call screening app. - * - * A {@link SecurityException} will be thrown if calling package name doesn't match the package - * of the passed {@link ComponentName} - * - * @param componentName to verify that the calling package name matches the package of the - * passed ComponentName. - */ - public void requestChangeDefaultCallScreeningApp(@NonNull ComponentName componentName) { - try { - if (isServiceConnected()) { - getTelecomService().requestChangeDefaultCallScreeningApp(componentName, mContext - .getOpPackageName()); - } - } catch (RemoteException e) { - Log.e(TAG, - "RemoteException calling ITelecomService#requestChangeDefaultCallScreeningApp.", - e); - } - } - - /** - * Used to verify that the passed ComponentName is default call screening app. - * - * @param componentName to verify that the package of the passed ComponentName matched the default - * call screening packageName. - * - * @return {@code true} if the passed componentName matches the default call screening's, {@code - * false} if the passed componentName is null, or it doesn't match default call screening's. - */ - public boolean isDefaultCallScreeningApp(ComponentName componentName) { - try { - if (isServiceConnected()) { - return getTelecomService().isDefaultCallScreeningApp(componentName); - } - } catch (RemoteException e) { - Log.e(TAG, - "RemoteException calling ITelecomService#isDefaultCallScreeningApp.", - e); - } - return false; - } - - /** - * Used to set the default call screening package. - * - * Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} Requires - * permission: {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} - * - * A {@link IllegalArgumentException} will be thrown if the specified package and component name - * of {@link ComponentName} does't exist, or the specified component of {@link ComponentName} - * does't have {@link android.Manifest.permission#BIND_SCREENING_SERVICE}. - * - * @param componentName to set the default call screening to. - * @hide - */ - @RequiresPermission(anyOf = { - android.Manifest.permission.MODIFY_PHONE_STATE, - android.Manifest.permission.WRITE_SECURE_SETTINGS - }) - public void setDefaultCallScreeningApp(ComponentName componentName) { - try { - if (isServiceConnected()) { - getTelecomService().setDefaultCallScreeningApp(componentName); - } - } catch (RemoteException e) { - Log.e(TAG, - "RemoteException calling ITelecomService#setDefaultCallScreeningApp.", e); - } - } - - /** * Return whether a given phone number is the configured voicemail number for a * particular phone account. * diff --git a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl index d255ed169c98..a86c83068c6c 100644 --- a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl +++ b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl @@ -17,6 +17,7 @@ package com.android.internal.telecom; import android.content.ComponentName; +import android.telecom.CallIdentification; /** * Internal remote callback interface for call screening services. @@ -34,4 +35,8 @@ oneway interface ICallScreeningAdapter { boolean shouldAddToCallLog, boolean shouldShowNotification, in ComponentName componentName); + + void provideCallIdentification( + String callId, + in CallIdentification callIdentification); } diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index d64efea5fa63..50204e70f63d 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -256,21 +256,6 @@ interface ITelecomService { boolean setDefaultDialer(in String packageName); /** - * @see TelecomServiceImpl#requestChangeDefaultCallScreeningApp - */ - void requestChangeDefaultCallScreeningApp(in ComponentName componentNamem, String callingPackage); - - /** - * @see TelecomServiceImpl#isDefaultCallScreeningApp - */ - boolean isDefaultCallScreeningApp(in ComponentName componentName); - - /** - * @see TelecomServiceImpl#setDefaultCallScreeningApp - */ - void setDefaultCallScreeningApp(in ComponentName componentName); - - /** * @see TelecomServiceImpl#createManageBlockedNumbersIntent **/ Intent createManageBlockedNumbersIntent(); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 388b5fb43096..fd14916ecac9 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2658,12 +2658,9 @@ public class CarrierConfigManager { //6: CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS //8: CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER }); - sDefaults.putStringArray(KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE, - new String[] { - String.valueOf(false) + ": 7", - //7: CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER - String.valueOf(true) + ": 8" - //8: CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER + sDefaults.putStringArray(KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE, new String[] { + String.valueOf(false) + ": 7", //7: CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER + String.valueOf(true) + ": 8" //8: CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER }); sDefaults.putStringArray(KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY, null); diff --git a/telephony/java/android/telephony/NetworkService.java b/telephony/java/android/telephony/NetworkService.java index 4354314dac29..4bca404d9444 100644 --- a/telephony/java/android/telephony/NetworkService.java +++ b/telephony/java/android/telephony/NetworkService.java @@ -16,7 +16,6 @@ package android.telephony; -import android.annotation.CallSuper; import android.annotation.SystemApi; import android.app.Service; import android.content.Intent; @@ -53,7 +52,6 @@ public abstract class NetworkService extends Service { private final String TAG = NetworkService.class.getSimpleName(); public static final String NETWORK_SERVICE_INTERFACE = "android.telephony.NetworkService"; - public static final String NETWORK_SERVICE_EXTRA_SLOT_ID = "android.telephony.extra.SLOT_ID"; private static final int NETWORK_SERVICE_CREATE_NETWORK_SERVICE_PROVIDER = 1; private static final int NETWORK_SERVICE_REMOVE_NETWORK_SERVICE_PROVIDER = 2; @@ -81,7 +79,7 @@ public abstract class NetworkService extends Service { * must extend this class to support network connection. Note that each instance of network * service is associated with one physical SIM slot. */ - public class NetworkServiceProvider { + public abstract class NetworkServiceProvider implements AutoCloseable { private final int mSlotId; private final List<INetworkServiceCallback> @@ -137,12 +135,12 @@ public abstract class NetworkService extends Service { } /** - * Called when the instance of network service is destroyed (e.g. got unbind or binder died). + * Called when the instance of network service is destroyed (e.g. got unbind or binder died) + * or when the network service provider is removed. The extended class should implement this + * method to perform cleanup works. */ - @CallSuper - protected void onDestroy() { - mNetworkRegistrationStateChangedCallbacks.clear(); - } + @Override + public abstract void close(); } private class NetworkServiceHandler extends Handler { @@ -168,7 +166,7 @@ public abstract class NetworkService extends Service { case NETWORK_SERVICE_REMOVE_NETWORK_SERVICE_PROVIDER: // If the service provider doesn't exist yet, we try to create it. if (serviceProvider != null) { - serviceProvider.onDestroy(); + serviceProvider.close(); mServiceMap.remove(slotId); } break; @@ -176,7 +174,7 @@ public abstract class NetworkService extends Service { for (int i = 0; i < mServiceMap.size(); i++) { serviceProvider = mServiceMap.get(i); if (serviceProvider != null) { - serviceProvider.onDestroy(); + serviceProvider.close(); } } mServiceMap.clear(); diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 387453fa3985..8c65eb6761b3 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -572,7 +572,6 @@ public class SubscriptionManager { * TelephonyProvider column name for whether a subscription is opportunistic, that is, * whether the network it connects to is limited in functionality or coverage. * For example, CBRS. - * IS_EMBEDDED should always be true. * <p>Type: INTEGER (int), 1 for opportunistic or 0 for non-opportunistic. * @hide */ @@ -2379,18 +2378,32 @@ public class SubscriptionManager { } /** - * Set opportunistic by simInfo index + * Set whether a subscription is opportunistic, that is, whether the network it connects + * to has limited coverage. For example, CBRS. Setting a subscription opportunistic has + * following impacts: + * 1) Even if it's active, it will be dormant most of the time. The modem will not try + * to scan or camp until it knows an available network is nearby to save power. + * 2) Telephony relies on system app or carrier input to notify nearby available networks. + * See {@link TelephonyManager#updateAvailableNetworks(List)} for more information. + * 3) In multi-SIM devices, when the network is nearby and camped, system may automatically + * switch internet data between it and default data subscription, based on carrier + * recommendation and its signal strength and metered-ness, etc. + * + * + * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier + * privilege permission of the subscription. * * @param opportunistic whether it’s opportunistic subscription. * @param subId the unique SubscriptionInfo index in database - * @return the number of records updated - * @hide + * @return {@code true} if the operation is succeed, {@code false} otherwise. */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public int setOpportunistic(boolean opportunistic, int subId) { + public boolean setOpportunistic(boolean opportunistic, int subId) { if (VDBG) logd("[setOpportunistic]+ opportunistic:" + opportunistic + " subId:" + subId); return setSubscriptionPropertyHelper(subId, "setOpportunistic", - (iSub)-> iSub.setOpportunistic(opportunistic, subId)); + (iSub)-> iSub.setOpportunistic( + opportunistic, subId, mContext.getOpPackageName())) == 1; } /** @@ -2510,18 +2523,26 @@ public class SubscriptionManager { } /** - * Set metered by simInfo index + * Set if a subscription is metered or not. Similar to Wi-Fi, metered means + * user may be charged more if more data is used. + * + * By default all Cellular networks are considered metered. System or carrier privileged apps + * can set a subscription un-metered which will be considered when system switches data between + * primary subscription and opportunistic subscription. + * + * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier + * privilege permission of the subscription. * * @param isMetered whether it’s a metered subscription. * @param subId the unique SubscriptionInfo index in database - * @return the number of records updated - * @hide + * @return {@code true} if the operation is succeed, {@code false} otherwise. */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public int setMetered(boolean isMetered, int subId) { + public boolean setMetered(boolean isMetered, int subId) { if (VDBG) logd("[setIsMetered]+ isMetered:" + isMetered + " subId:" + subId); return setSubscriptionPropertyHelper(subId, "setIsMetered", - (iSub)-> iSub.setMetered(isMetered, subId)); + (iSub)-> iSub.setMetered(isMetered, subId, mContext.getOpPackageName())) == 1; } /** diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 11b667427aaf..e0632b1e0392 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -6671,8 +6671,8 @@ public class TelephonyManager { try { ITelephony telephony = getITelephony(); if (telephony != null) { - return telephony.getCarrierPrivilegeStatus(subId) == - CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; + return telephony.getCarrierPrivilegeStatus(subId) + == CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; } } catch (RemoteException ex) { Rlog.e(TAG, "hasCarrierPrivileges RemoteException", ex); @@ -8956,6 +8956,23 @@ public class TelephonyManager { } /** + * Action set from carrier signalling broadcast receivers to reset all carrier actions + * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required + * @param subId the subscription ID that this action applies to. + * @hide + */ + public void carrierActionResetAll(int subId) { + try { + ITelephony service = getITelephony(); + if (service != null) { + service.carrierActionResetAll(subId); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#carrierActionResetAll", e); + } + } + + /** * Get aggregated video call data usage since boot. * Permissions android.Manifest.permission.READ_NETWORK_USAGE_HISTORY is required. * diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java index 1db58506bb1c..74d1e838f186 100644 --- a/telephony/java/android/telephony/data/DataService.java +++ b/telephony/java/android/telephony/data/DataService.java @@ -16,7 +16,6 @@ package android.telephony.data; -import android.annotation.CallSuper; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -60,7 +59,6 @@ public abstract class DataService extends Service { private static final String TAG = DataService.class.getSimpleName(); public static final String DATA_SERVICE_INTERFACE = "android.telephony.data.DataService"; - public static final String DATA_SERVICE_EXTRA_SLOT_ID = "android.telephony.data.extra.SLOT_ID"; /** {@hide} */ @IntDef(prefix = "REQUEST_REASON_", value = { @@ -116,7 +114,7 @@ public abstract class DataService extends Service { * must extend this class to support data connection. Note that each instance of data service * provider is associated with one physical SIM slot. */ - public class DataServiceProvider { + public abstract class DataServiceProvider implements AutoCloseable { private final int mSlotId; @@ -250,12 +248,12 @@ public abstract class DataService extends Service { } /** - * Called when the instance of data service is destroyed (e.g. got unbind or binder died). + * Called when the instance of data service is destroyed (e.g. got unbind or binder died) + * or when the data service provider is removed. The extended class should implement this + * method to perform cleanup works. */ - @CallSuper - protected void onDestroy() { - mDataCallListChangedCallbacks.clear(); - } + @Override + public abstract void close(); } private static final class SetupDataCallRequest { @@ -345,7 +343,7 @@ public abstract class DataService extends Service { break; case DATA_SERVICE_REMOVE_DATA_SERVICE_PROVIDER: if (serviceProvider != null) { - serviceProvider.onDestroy(); + serviceProvider.close(); mServiceMap.remove(slotId); } break; @@ -353,7 +351,7 @@ public abstract class DataService extends Service { for (int i = 0; i < mServiceMap.size(); i++) { serviceProvider = mServiceMap.get(i); if (serviceProvider != null) { - serviceProvider.onDestroy(); + serviceProvider.close(); } } mServiceMap.clear(); diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java index 57d9cced53b1..45b4849990c2 100644 --- a/telephony/java/android/telephony/data/QualifiedNetworksService.java +++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java @@ -151,7 +151,7 @@ public abstract class QualifiedNetworksService extends Service { /** * Called when the qualified networks updater is removed. The extended class should - * implement this method to perform clean up works. + * implement this method to perform cleanup works. */ @Override public abstract void close(); diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java index b0c875e0c6f2..0fdca5d24a20 100644 --- a/telephony/java/com/android/internal/telephony/DctConstants.java +++ b/telephony/java/com/android/internal/telephony/DctConstants.java @@ -79,10 +79,7 @@ public class DctConstants { public static final int EVENT_PS_RESTRICT_DISABLED = BASE + 23; public static final int EVENT_CLEAN_UP_CONNECTION = BASE + 24; public static final int EVENT_RESTART_RADIO = BASE + 26; - public static final int EVENT_SET_INTERNAL_DATA_ENABLE = BASE + 27; public static final int EVENT_CLEAN_UP_ALL_CONNECTIONS = BASE + 29; - public static final int CMD_SET_USER_DATA_ENABLE = BASE + 30; - public static final int CMD_SET_POLICY_DATA_ENABLE = BASE + 32; public static final int EVENT_ICC_CHANGED = BASE + 33; public static final int EVENT_DISCONNECT_DC_RETRYING = BASE + 34; public static final int EVENT_DATA_SETUP_COMPLETE_ERROR = BASE + 35; @@ -93,14 +90,12 @@ public class DctConstants { public static final int CMD_NET_STAT_POLL = BASE + 40; public static final int EVENT_DATA_RAT_CHANGED = BASE + 41; public static final int CMD_CLEAR_PROVISIONING_SPINNER = BASE + 42; - public static final int EVENT_DEVICE_PROVISIONED_CHANGE = BASE + 43; public static final int EVENT_REDIRECTION_DETECTED = BASE + 44; public static final int EVENT_PCO_DATA_RECEIVED = BASE + 45; - public static final int EVENT_SET_CARRIER_DATA_ENABLED = BASE + 46; + public static final int EVENT_DATA_ENABLED_CHANGED = BASE + 46; public static final int EVENT_DATA_RECONNECT = BASE + 47; public static final int EVENT_ROAMING_SETTING_CHANGE = BASE + 48; public static final int EVENT_DATA_SERVICE_BINDING_CHANGED = BASE + 49; - public static final int EVENT_DEVICE_PROVISIONING_DATA_SETTING_CHANGE = BASE + 50; /***** Constants *****/ diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl index 65d1a920a324..65eedb83c4cc 100755 --- a/telephony/java/com/android/internal/telephony/ISub.aidl +++ b/telephony/java/com/android/internal/telephony/ISub.aidl @@ -162,7 +162,7 @@ interface ISub { * @param subId the unique SubscriptionInfo index in database * @return the number of records updated */ - int setOpportunistic(boolean opportunistic, int subId); + int setOpportunistic(boolean opportunistic, int subId, String callingPackage); /** * Inform SubscriptionManager that subscriptions in the list are bundled @@ -190,7 +190,7 @@ interface ISub { * @param subId the unique SubscriptionInfo index in database * @return the number of records updated */ - int setMetered(boolean isMetered, int subId); + int setMetered(boolean isMetered, int subId, String callingPackage); /** * Set which subscription is preferred for cellular data. It's diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 46366d66a6eb..399e2553804b 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1408,6 +1408,14 @@ interface ITelephony { void carrierActionReportDefaultNetworkStatus(int subId, boolean report); /** + * Action set from carrier signalling broadcast receivers to reset all carrier actions. + * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required + * @param subId the subscription ID that this action applies to. + * @hide + */ + void carrierActionResetAll(int subId); + + /** * Get aggregated video call data usage since boot. * Permissions android.Manifest.permission.READ_NETWORK_USAGE_HISTORY is required. * diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java index 14312cf84693..369a002fa273 100644 --- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java +++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java @@ -20,6 +20,7 @@ import android.content.res.Configuration; import android.os.Binder; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.test.suitebuilder.annotation.SmallTest; import android.view.IWindowManager; import junit.framework.TestCase; @@ -107,7 +108,7 @@ public class WindowManagerPermissionTests extends TestCase { public void testDISABLE_KEYGUARD() { Binder token = new Binder(); try { - mWm.disableKeyguard(token, "foo"); + mWm.disableKeyguard(token, "foo", UserHandle.myUserId()); fail("IWindowManager.disableKeyguard did not throw SecurityException as" + " expected"); } catch (SecurityException e) { @@ -117,7 +118,7 @@ public class WindowManagerPermissionTests extends TestCase { } try { - mWm.reenableKeyguard(token); + mWm.reenableKeyguard(token, UserHandle.myUserId()); fail("IWindowManager.reenableKeyguard did not throw SecurityException as" + " expected"); } catch (SecurityException e) { diff --git a/wifi/java/android/net/wifi/aware/DiscoverySession.java b/wifi/java/android/net/wifi/aware/DiscoverySession.java index 699f54cf13fb..a47e70b6f30f 100644 --- a/wifi/java/android/net/wifi/aware/DiscoverySession.java +++ b/wifi/java/android/net/wifi/aware/DiscoverySession.java @@ -34,11 +34,11 @@ import java.lang.ref.WeakReference; * {@link PublishDiscoverySession} and {@link SubscribeDiscoverySession}. This * class provides functionality common to both publish and subscribe discovery sessions: * <ul> - * <li>Sending messages: {@link #sendMessage(PeerHandle, int, byte[])} method. - * <li>Creating a network-specifier when requesting a Aware connection: - * {@link #createNetworkSpecifierOpen(PeerHandle)} or - * {@link #createNetworkSpecifierPassphrase(PeerHandle, String)}. + * <li>Sending messages: {@link #sendMessage(PeerHandle, int, byte[])} method. + * <li>Creating a network-specifier when requesting a Aware connection using + * {@link WifiAwareManager.NetworkSpecifierBuilder}. * </ul> + * <p> * The {@link #close()} method must be called to destroy discovery sessions once they are * no longer needed. */ @@ -270,6 +270,7 @@ public class DiscoverySession implements AutoCloseable { * <p> * To set up an encrypted link use the * {@link #createNetworkSpecifierPassphrase(PeerHandle, String)} API. + * @deprecated Use the replacement {@link WifiAwareManager.NetworkSpecifierBuilder}. * * @param peerHandle The peer's handle obtained through * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], java.util.List)} @@ -284,6 +285,7 @@ public class DiscoverySession implements AutoCloseable { * android.net.ConnectivityManager.NetworkCallback)} * [or other varieties of that API]. */ + @Deprecated public NetworkSpecifier createNetworkSpecifierOpen(@NonNull PeerHandle peerHandle) { if (mTerminated) { Log.w(TAG, "createNetworkSpecifierOpen: called on terminated session"); @@ -318,6 +320,7 @@ public class DiscoverySession implements AutoCloseable { * <p> * Note: per the Wi-Fi Aware specification the roles are fixed - a Subscriber is an INITIATOR * and a Publisher is a RESPONDER. + * @deprecated Use the replacement {@link WifiAwareManager.NetworkSpecifierBuilder}. * * @param peerHandle The peer's handle obtained through * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, @@ -336,6 +339,7 @@ public class DiscoverySession implements AutoCloseable { * android.net.ConnectivityManager.NetworkCallback)} * [or other varieties of that API]. */ + @Deprecated public NetworkSpecifier createNetworkSpecifierPassphrase( @NonNull PeerHandle peerHandle, @NonNull String passphrase) { if (!WifiAwareUtils.validatePassphrase(passphrase)) { @@ -376,6 +380,7 @@ public class DiscoverySession implements AutoCloseable { * <p> * Note: per the Wi-Fi Aware specification the roles are fixed - a Subscriber is an INITIATOR * and a Publisher is a RESPONDER. + * @deprecated Use the replacement {@link WifiAwareManager.NetworkSpecifierBuilder}. * * @param peerHandle The peer's handle obtained through * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, @@ -397,6 +402,7 @@ public class DiscoverySession implements AutoCloseable { * * @hide */ + @Deprecated @SystemApi public NetworkSpecifier createNetworkSpecifierPmk(@NonNull PeerHandle peerHandle, @NonNull byte[] pmk) { diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java index 8529a89a9914..26a6c08bee29 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.net.ConnectivityManager; @@ -57,11 +58,7 @@ import java.util.List; * {@link WifiAwareSession#subscribe(SubscribeConfig, DiscoverySessionCallback, Handler)}. * <li>Create a Aware network specifier to be used with * {@link ConnectivityManager#requestNetwork(NetworkRequest, ConnectivityManager.NetworkCallback)} - * to set-up a Aware connection with a peer. Refer to - * {@link DiscoverySession#createNetworkSpecifierOpen(PeerHandle)}, - * {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)}, - * {@link WifiAwareSession#createNetworkSpecifierOpen(int, byte[])}, and - * {@link WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)}. + * to set-up a Aware connection with a peer. Refer to {@link NetworkSpecifierBuilder}. * </ul> * <p> * Aware may not be usable when Wi-Fi is disabled (and other conditions). To validate that @@ -110,10 +107,7 @@ import java.util.List; * <li>{@link NetworkRequest.Builder#addTransportType(int)} of * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}. * <li>{@link NetworkRequest.Builder#setNetworkSpecifier(String)} using - * {@link WifiAwareSession#createNetworkSpecifierOpen(int, byte[])}, - * {@link WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)}, - * {@link DiscoverySession#createNetworkSpecifierOpen(PeerHandle)}, or - * {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)}. + * {@link NetworkSpecifierBuilder}. * </ul> */ @SystemService(Context.WIFI_AWARE_SERVICE) @@ -145,8 +139,6 @@ public class WifiAwareManager { * Connection creation role is that of INITIATOR. Used to create a network specifier string * when requesting a Aware network. * - * @see DiscoverySession#createNetworkSpecifierOpen(PeerHandle) - * @see DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String) * @see WifiAwareSession#createNetworkSpecifierOpen(int, byte[]) * @see WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String) */ @@ -156,8 +148,6 @@ public class WifiAwareManager { * Connection creation role is that of RESPONDER. Used to create a network specifier string * when requesting a Aware network. * - * @see DiscoverySession#createNetworkSpecifierOpen(PeerHandle) - * @see DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String) * @see WifiAwareSession#createNetworkSpecifierOpen(int, byte[]) * @see WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String) */ @@ -415,6 +405,11 @@ public class WifiAwareManager { + ", passphrase=" + ((passphrase == null) ? "null" : "non-null")); } + if (!WifiAwareUtils.isLegacyVersion(mContext, Build.VERSION_CODES.Q)) { + throw new UnsupportedOperationException( + "API not deprecated - use WifiAwareManager.NetworkSpecifierBuilder"); + } + if (role != WIFI_AWARE_DATA_PATH_ROLE_INITIATOR && role != WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) { throw new IllegalArgumentException( @@ -813,4 +808,135 @@ public class WifiAwareManager { mOriginalCallback.onSessionTerminated(); } } + + /** + * A builder class for a Wi-Fi Aware network specifier to set up an Aware connection with a + * peer. + * <p> + * Note that all Wi-Fi Aware connection specifier objects must call the + * {@link NetworkSpecifierBuilder#setDiscoverySession(DiscoverySession)} to specify the context + * within which the connection is created, and + * {@link NetworkSpecifierBuilder#setPeerHandle(PeerHandle)} to specify the peer to which the + * connection is created. + */ + public static class NetworkSpecifierBuilder { + private DiscoverySession mDiscoverySession; + private PeerHandle mPeerHandle; + private String mPskPassphrase; + private byte[] mPmk; + + /** + * Configure the {@link PublishDiscoverySession} or {@link SubscribeDiscoverySession} + * discovery session in whose context the connection is created. + * <p> + * Note: this method must be called for any connection request! + * + * @param discoverySession A Wi-Fi Aware discovery session. + * @return the current {@link NetworkSpecifierBuilder} builder, enabling chaining of builder + * methods. + */ + public @NonNull NetworkSpecifierBuilder setDiscoverySession( + @NonNull DiscoverySession discoverySession) { + if (discoverySession == null) { + throw new IllegalArgumentException("Non-null discoverySession required"); + } + mDiscoverySession = discoverySession; + return this; + } + + /** + * Configure the {@link PeerHandle} of the peer to which the Wi-Fi Aware connection is + * requested. The peer is discovered through Wi-Fi Aware discovery, + * <p> + * Note: this method must be called for any connection request! + * + * @param peerHandle The peer's handle obtained through + * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], java.util.List)} + * or + * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle, byte[])}. + * @return the current {@link NetworkSpecifierBuilder} builder, enabling chaining of builder + * methods. + */ + public @NonNull NetworkSpecifierBuilder setPeerHandle(@NonNull PeerHandle peerHandle) { + if (peerHandle == null) { + throw new IllegalArgumentException("Non-null peerHandle required"); + } + mPeerHandle = peerHandle; + return this; + } + + /** + * Configure the PSK Passphrase for the Wi-Fi Aware connection being requested. This method + * is optional - if not called, then an Open (unencrypted) connection will be created. + * + * @param pskPassphrase The (optional) passphrase to be used to encrypt the link. + * @return the current {@link NetworkSpecifierBuilder} builder, enabling chaining of builder + * methods. + */ + public @NonNull NetworkSpecifierBuilder setPskPassphrase(@NonNull String pskPassphrase) { + if (!WifiAwareUtils.validatePassphrase(pskPassphrase)) { + throw new IllegalArgumentException("Passphrase must meet length requirements"); + } + mPskPassphrase = pskPassphrase; + return this; + } + + /** + * Configure the PMK for the Wi-Fi Aware connection being requested. This method + * is optional - if not called, then an Open (unencrypted) connection will be created. + * + * @param pmk A PMK (pairwise master key, see IEEE 802.11i) specifying the key to use for + * encrypting the data-path. Use the {@link #setPskPassphrase(String)} to + * specify a Passphrase. + * @return the current {@link NetworkSpecifierBuilder} builder, enabling chaining of builder + * methods. + * @hide + */ + @SystemApi + public @NonNull NetworkSpecifierBuilder setPmk(@NonNull byte[] pmk) { + if (!WifiAwareUtils.validatePmk(pmk)) { + throw new IllegalArgumentException("PMK must 32 bytes"); + } + mPmk = pmk; + return this; + } + + /** + * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} + * for a WiFi Aware connection (link) to the specified peer. The + * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to + * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}. + * <p> The default builder constructor will initialize a NetworkSpecifier which requests an + * open (non-encrypted) link. To request an encrypted link use the + * {@link #setPskPassphrase(String)} builder method. + * + * @return A {@link NetworkSpecifier} to be used to construct + * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass + * to {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, + * android.net.ConnectivityManager.NetworkCallback)} + * [or other varieties of that API]. + */ + public @NonNull NetworkSpecifier build() { + if (mDiscoverySession == null) { + throw new IllegalStateException("Null discovery session!?"); + } + if (mPskPassphrase != null & mPmk != null) { + throw new IllegalStateException( + "Can only specify a Passphrase or a PMK - not both!"); + } + + int role = mDiscoverySession instanceof SubscribeDiscoverySession + ? WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR + : WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER; + + if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR && mPeerHandle == null) { + throw new IllegalStateException("Null peerHandle!?"); + } + + return new WifiAwareNetworkSpecifier( + WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB, role, + mDiscoverySession.mClientId, mDiscoverySession.mSessionId, mPeerHandle.peerId, + null, mPmk, mPskPassphrase, Process.myUid()); + } + } } diff --git a/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java b/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java new file mode 100644 index 000000000000..0f29e081e2a0 --- /dev/null +++ b/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.aware; + +import android.annotation.Nullable; +import android.net.NetworkCapabilities; +import android.net.TransportInfo; +import android.os.Parcel; +import android.os.Parcelable; + +import java.net.Inet6Address; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Objects; + +/** + * Wi-Fi Aware-specific network information. The information can be extracted from the + * {@link android.net.NetworkCapabilities} of the network using + * {@link NetworkCapabilities#getTransportInfo()}. + * The {@link NetworkCapabilities} is provided by the connectivity service to apps, e.g. received + * through the + * {@link android.net.ConnectivityManager.NetworkCallback#onCapabilitiesChanged(android.net.Network, + * android.net.NetworkCapabilities)} callback. + * <p> + * The Wi-Fi Aware-specific network information include the peer's scoped link-local IPv6 address + * for the Wi-Fi Aware link. The scoped link-local IPv6 can then be used to create a + * {@link java.net.Socket} connection to the peer. + */ +public final class WifiAwareNetworkInfo implements TransportInfo, Parcelable { + private Inet6Address mIpv6Addr; + + /** @hide */ + public WifiAwareNetworkInfo(Inet6Address ipv6Addr) { + mIpv6Addr = ipv6Addr; + } + + /** + * Get the scoped link-local IPv6 address of the Wi-Fi Aware peer (not of the local device!). + * + * @return An IPv6 address. + */ + @Nullable + public Inet6Address getPeerIpv6Addr() { + return mIpv6Addr; + } + + // parcelable methods + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeByteArray(mIpv6Addr.getAddress()); + NetworkInterface ni = mIpv6Addr.getScopedInterface(); + dest.writeString(ni == null ? null : ni.getName()); + } + + public static final Creator<WifiAwareNetworkInfo> CREATOR = + new Creator<WifiAwareNetworkInfo>() { + @Override + public WifiAwareNetworkInfo createFromParcel(Parcel in) { + Inet6Address ipv6Addr; + try { + byte[] addr = in.createByteArray(); + String interfaceName = in.readString(); + NetworkInterface ni = null; + if (interfaceName != null) { + try { + ni = NetworkInterface.getByName(interfaceName); + } catch (SocketException e) { + e.printStackTrace(); + } + } + ipv6Addr = Inet6Address.getByAddress(null, addr, ni); + } catch (UnknownHostException e) { + e.printStackTrace(); + return null; + } + + return new WifiAwareNetworkInfo(ipv6Addr); + } + + @Override + public WifiAwareNetworkInfo[] newArray(int size) { + return new WifiAwareNetworkInfo[size]; + } + }; + + + // object methods + + @Override + public String toString() { + return new StringBuilder("AwareNetworkInfo: IPv6=").append(mIpv6Addr).toString(); + } + + /** @hide */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof WifiAwareNetworkInfo)) { + return false; + } + + WifiAwareNetworkInfo lhs = (WifiAwareNetworkInfo) obj; + return Objects.equals(mIpv6Addr, lhs.mIpv6Addr); + } + + /** @hide */ + @Override + public int hashCode() { + return Objects.hash(mIpv6Addr); + } +} diff --git a/wifi/java/android/net/wifi/aware/WifiAwareSession.java b/wifi/java/android/net/wifi/aware/WifiAwareSession.java index 321965330e0b..5f8841cb0148 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareSession.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareSession.java @@ -213,7 +213,7 @@ public class WifiAwareSession implements AutoCloseable { * This API is targeted for applications which can obtain the peer MAC address using OOB * (out-of-band) discovery. Aware discovery does not provide the MAC address of the peer - * when using Aware discovery use the alternative network specifier method - - * {@link DiscoverySession#createNetworkSpecifierOpen(PeerHandle)}. + * {@link android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder}. * <p> * To set up an encrypted link use the * {@link #createNetworkSpecifierPassphrase(int, byte[], String)} API. @@ -254,7 +254,7 @@ public class WifiAwareSession implements AutoCloseable { * This API is targeted for applications which can obtain the peer MAC address using OOB * (out-of-band) discovery. Aware discovery does not provide the MAC address of the peer - * when using Aware discovery use the alternative network specifier method - - * {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)}. + * {@link android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder}. * * @param role The role of this device: * {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or @@ -300,7 +300,7 @@ public class WifiAwareSession implements AutoCloseable { * This API is targeted for applications which can obtain the peer MAC address using OOB * (out-of-band) discovery. Aware discovery does not provide the MAC address of the peer - * when using Aware discovery use the alternative network specifier method - - * {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)}. + * {@link android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder}. * * @param role The role of this device: * {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java index 272f72721747..45e17201bd1f 100644 --- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java +++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.net.MacAddress; import android.net.wifi.RttManager; import android.os.Build; import android.os.Handler; @@ -50,6 +51,8 @@ import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.net.Inet6Address; +import java.net.UnknownHostException; import java.util.List; /** @@ -105,7 +108,7 @@ public class WifiAwareManagerTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.P; + mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.Q; when(mockPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn( mockApplicationInfo); when(mockContext.getOpPackageName()).thenReturn("XXX"); @@ -915,6 +918,8 @@ public class WifiAwareManagerTest { final ConfigRequest configRequest = new ConfigRequest.Builder().build(); final PublishConfig publishConfig = new PublishConfig.Builder().build(); + mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.P; + ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass( WifiAwareSession.class); ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor @@ -948,6 +953,9 @@ public class WifiAwareManagerTest { WifiAwareNetworkSpecifier ns = (WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierOpen( peerHandle); + WifiAwareNetworkSpecifier nsb = (WifiAwareNetworkSpecifier) new WifiAwareManager + .NetworkSpecifierBuilder().setDiscoverySession(publishSession.getValue()) + .setPeerHandle(peerHandle).build(); // validate format collector.checkThat("role", role, equalTo(ns.role)); @@ -955,9 +963,18 @@ public class WifiAwareManagerTest { collector.checkThat("session_id", sessionId, equalTo(ns.sessionId)); collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId)); + collector.checkThat("role", role, equalTo(nsb.role)); + collector.checkThat("client_id", clientId, equalTo(nsb.clientId)); + collector.checkThat("session_id", sessionId, equalTo(nsb.sessionId)); + collector.checkThat("peer_id", peerHandle.peerId, equalTo(nsb.peerId)); + // (4) request an encrypted (PMK) network specifier from the session ns = (WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierPmk( peerHandle, pmk); + nsb = + (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder() + .setDiscoverySession( + publishSession.getValue()).setPeerHandle(peerHandle).setPmk(pmk).build(); // validate format collector.checkThat("role", role, equalTo(ns.role)); @@ -966,9 +983,18 @@ public class WifiAwareManagerTest { collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId)); collector.checkThat("pmk", pmk , equalTo(ns.pmk)); + collector.checkThat("role", role, equalTo(nsb.role)); + collector.checkThat("client_id", clientId, equalTo(nsb.clientId)); + collector.checkThat("session_id", sessionId, equalTo(nsb.sessionId)); + collector.checkThat("peer_id", peerHandle.peerId, equalTo(nsb.peerId)); + collector.checkThat("pmk", pmk , equalTo(nsb.pmk)); + // (5) request an encrypted (Passphrase) network specifier from the session ns = (WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierPassphrase( peerHandle, passphrase); + nsb = (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder() + .setDiscoverySession(publishSession.getValue()).setPeerHandle(peerHandle) + .setPskPassphrase(passphrase).build(); // validate format collector.checkThat("role", role, equalTo(ns.role)); @@ -977,6 +1003,12 @@ public class WifiAwareManagerTest { collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId)); collector.checkThat("passphrase", passphrase, equalTo(ns.passphrase)); + collector.checkThat("role", role, equalTo(nsb.role)); + collector.checkThat("client_id", clientId, equalTo(nsb.clientId)); + collector.checkThat("session_id", sessionId, equalTo(nsb.sessionId)); + collector.checkThat("peer_id", peerHandle.peerId, equalTo(nsb.peerId)); + collector.checkThat("passphrase", passphrase, equalTo(nsb.passphrase)); + verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService, mockPublishSession, mockRttListener); } @@ -1048,7 +1080,7 @@ public class WifiAwareManagerTest { */ @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierWithClientNullPmk() throws Exception { - executeNetworkSpecifierWithClient(new PeerHandle(123412), true, null, null); + executeNetworkSpecifierWithClient(new PeerHandle(123412), true, null, null, false); } /** @@ -1056,7 +1088,7 @@ public class WifiAwareManagerTest { */ @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierWithClientIncorrectLengthPmk() throws Exception { - executeNetworkSpecifierWithClient(new PeerHandle(123412), true, PMK_INVALID, null); + executeNetworkSpecifierWithClient(new PeerHandle(123412), true, PMK_INVALID, null, false); } /** @@ -1064,7 +1096,7 @@ public class WifiAwareManagerTest { */ @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierWithClientNullPassphrase() throws Exception { - executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, null); + executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, null, false); } /** @@ -1073,7 +1105,7 @@ public class WifiAwareManagerTest { @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierWithClientTooShortPassphrase() throws Exception { executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, - PASSPHRASE_TOO_SHORT); + PASSPHRASE_TOO_SHORT, false); } /** @@ -1081,7 +1113,8 @@ public class WifiAwareManagerTest { */ @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierWithClientTooLongPassphrase() throws Exception { - executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, PASSPHRASE_TOO_LONG); + executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, PASSPHRASE_TOO_LONG, + false); } /** @@ -1089,7 +1122,8 @@ public class WifiAwareManagerTest { */ @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierWithClientNullPeer() throws Exception { - executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID); + mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.P; + executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID, false); } /** @@ -1098,11 +1132,75 @@ public class WifiAwareManagerTest { @Test public void testNetworkSpecifierWithClientNullPeerLegacyApi() throws Exception { mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.O; - executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID); + executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID, false); + } + + /** + * Validate that a null PMK triggers an exception. + */ + @Test(expected = IllegalArgumentException.class) + public void testNetworkSpecifierWithClientNullPmkBuilder() throws Exception { + executeNetworkSpecifierWithClient(new PeerHandle(123412), true, null, null, true); + } + + /** + * Validate that a non-32-bytes PMK triggers an exception. + */ + @Test(expected = IllegalArgumentException.class) + public void testNetworkSpecifierWithClientIncorrectLengthPmkBuilder() throws Exception { + executeNetworkSpecifierWithClient(new PeerHandle(123412), true, PMK_INVALID, null, true); + } + + /** + * Validate that a null Passphrase triggers an exception. + */ + @Test(expected = IllegalArgumentException.class) + public void testNetworkSpecifierWithClientNullPassphraseBuilder() throws Exception { + executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, null, true); + } + + /** + * Validate that a too short Passphrase triggers an exception. + */ + @Test(expected = IllegalArgumentException.class) + public void testNetworkSpecifierWithClientTooShortPassphraseBuilder() throws Exception { + executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, + PASSPHRASE_TOO_SHORT, true); + } + + /** + * Validate that a too long Passphrase triggers an exception. + */ + @Test(expected = IllegalArgumentException.class) + public void testNetworkSpecifierWithClientTooLongPassphraseBuilder() throws Exception { + executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, PASSPHRASE_TOO_LONG, + true); + } + + /** + * Validate that a null PeerHandle triggers an exception. + */ + @Test(expected = IllegalArgumentException.class) + public void testNetworkSpecifierWithClientNullPeerBuilder() throws Exception { + executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID, true); + } + + /** + * Validate that a null PeerHandle does not trigger an exception for legacy API. + */ + @Test + public void testNetworkSpecifierWithClientNullPeerLegacyApiBuilder() throws Exception { + mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.O; + executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID, false); + } + + @Test(expected = UnsupportedOperationException.class) + public void testNetworkSpecifierDeprecatedOnNewApi() throws Exception { + executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID, false); } private void executeNetworkSpecifierWithClient(PeerHandle peerHandle, boolean doPmk, byte[] pmk, - String passphrase) throws Exception { + String passphrase, boolean useBuilder) throws Exception { final int clientId = 4565; final int sessionId = 123; final ConfigRequest configRequest = new ConfigRequest.Builder().build(); @@ -1139,9 +1237,20 @@ public class WifiAwareManagerTest { // (3) create network specifier if (doPmk) { - publishSession.getValue().createNetworkSpecifierPmk(peerHandle, pmk); + if (useBuilder) { + new WifiAwareManager.NetworkSpecifierBuilder().setDiscoverySession( + publishSession.getValue()).setPeerHandle(peerHandle).setPmk(pmk).build(); + } else { + publishSession.getValue().createNetworkSpecifierPmk(peerHandle, pmk); + } } else { - publishSession.getValue().createNetworkSpecifierPassphrase(peerHandle, passphrase); + if (useBuilder) { + new WifiAwareManager.NetworkSpecifierBuilder().setDiscoverySession( + publishSession.getValue()).setPeerHandle(peerHandle).setPskPassphrase( + passphrase).build(); + } else { + publishSession.getValue().createNetworkSpecifierPassphrase(peerHandle, passphrase); + } } } @@ -1245,4 +1354,31 @@ public class WifiAwareManagerTest { sessionCaptor.getValue().createNetworkSpecifierPassphrase(role, someMac, passphrase); } } + + // WifiAwareNetworkInfo tests + + @Test + public void testWifiAwareNetworkCapabilitiesParcel() throws UnknownHostException { + final Inet6Address inet6 = MacAddress.fromString( + "11:22:33:44:55:66").getLinkLocalIpv6FromEui48Mac(); + // note: dummy scope = 5 + final Inet6Address inet6Scoped = Inet6Address.getByAddress(null, inet6.getAddress(), 5); + + assertEquals(inet6Scoped.toString(), "/fe80::1322:33ff:fe44:5566%5"); + WifiAwareNetworkInfo cap = new WifiAwareNetworkInfo(inet6Scoped); + + Parcel parcelW = Parcel.obtain(); + cap.writeToParcel(parcelW, 0); + byte[] bytes = parcelW.marshall(); + parcelW.recycle(); + + Parcel parcelR = Parcel.obtain(); + parcelR.unmarshall(bytes, 0, bytes.length); + parcelR.setDataPosition(0); + WifiAwareNetworkInfo rereadCap = + WifiAwareNetworkInfo.CREATOR.createFromParcel(parcelR); + + assertEquals(cap.getPeerIpv6Addr().toString(), "/fe80::1322:33ff:fe44:5566%5"); + assertEquals(cap.hashCode(), rereadCap.hashCode()); + } } |