diff options
224 files changed, 8450 insertions, 2630 deletions
diff --git a/Android.bp b/Android.bp index 36c2fa8762a4..23168232b7a3 100644 --- a/Android.bp +++ b/Android.bp @@ -295,6 +295,7 @@ filegroup { srcs: [ // Java/AIDL sources under frameworks/base ":framework-blobstore-sources", + ":framework-connectivity-sources", // framework-connectivity is not yet a module ":framework-core-sources", ":framework-drm-sources", ":framework-graphics-sources", @@ -481,6 +482,7 @@ java_library { "android.hardware.vibrator-V1.1-java", "android.hardware.vibrator-V1.2-java", "android.hardware.vibrator-V1.3-java", + "android.security.apc-java", "android.system.keystore2-java", "android.system.suspend.control.internal-java", "devicepolicyprotosnano", diff --git a/MULTIUSER_OWNERS b/MULTIUSER_OWNERS new file mode 100644 index 000000000000..fbc611a39d7d --- /dev/null +++ b/MULTIUSER_OWNERS @@ -0,0 +1,4 @@ +# OWNERS of Multiuser related files +bookatz@google.com +omakoto@google.com +yamasani@google.com @@ -9,6 +9,7 @@ hackbod@google.com jjaggi@google.com jsharkey@android.com jsharkey@google.com +lorenzo@google.com michaelwr@google.com nandana@google.com narayan@google.com diff --git a/apct-tests/perftests/multiuser/OWNERS b/apct-tests/perftests/multiuser/OWNERS new file mode 100644 index 000000000000..1a206cb27c3b --- /dev/null +++ b/apct-tests/perftests/multiuser/OWNERS @@ -0,0 +1 @@ +include /MULTIUSER_OWNERS
\ No newline at end of file diff --git a/core/api/current.txt b/core/api/current.txt index 8681d419f160..252c33c4efda 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8909,6 +8909,8 @@ package android.bluetooth { method public int getConnectionState(android.bluetooth.BluetoothDevice); method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]); method public boolean isAudioConnected(android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isNoiseReductionSupported(@NonNull android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isVoiceRecognitionSupported(@NonNull android.bluetooth.BluetoothDevice); method public boolean sendVendorSpecificResultCode(android.bluetooth.BluetoothDevice, String, String); method public boolean startVoiceRecognition(android.bluetooth.BluetoothDevice); method public boolean stopVoiceRecognition(android.bluetooth.BluetoothDevice); @@ -23898,7 +23900,9 @@ package android.media.tv { field public static final String COLUMN_APP_LINK_INTENT_URI = "app_link_intent_uri"; field public static final String COLUMN_APP_LINK_POSTER_ART_URI = "app_link_poster_art_uri"; field public static final String COLUMN_APP_LINK_TEXT = "app_link_text"; + field public static final String COLUMN_BROADCAST_GENRE = "broadcast_genre"; field public static final String COLUMN_BROWSABLE = "browsable"; + field public static final String COLUMN_CHANNEL_LIST_ID = "channel_list_id"; field public static final String COLUMN_DESCRIPTION = "description"; field public static final String COLUMN_DISPLAY_NAME = "display_name"; field public static final String COLUMN_DISPLAY_NUMBER = "display_number"; @@ -23913,6 +23917,8 @@ package android.media.tv { field public static final String COLUMN_LOCKED = "locked"; field public static final String COLUMN_NETWORK_AFFILIATION = "network_affiliation"; field public static final String COLUMN_ORIGINAL_NETWORK_ID = "original_network_id"; + field public static final String COLUMN_REMOTE_CONTROL_KEY_PRESET_NUMBER = "remote_control_key_preset_number"; + field public static final String COLUMN_SCRAMBLED = "scrambled"; field public static final String COLUMN_SEARCHABLE = "searchable"; field public static final String COLUMN_SERVICE_ID = "service_id"; field public static final String COLUMN_SERVICE_TYPE = "service_type"; @@ -23921,6 +23927,7 @@ package android.media.tv { field public static final String COLUMN_TYPE = "type"; field public static final String COLUMN_VERSION_NUMBER = "version_number"; field public static final String COLUMN_VIDEO_FORMAT = "video_format"; + field public static final String COLUMN_VIDEO_RESOLUTION = "video_resolution"; field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/channel"; field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/channel"; field public static final android.net.Uri CONTENT_URI; @@ -38773,6 +38780,7 @@ package android.telecom { method public android.telecom.PhoneAccount.Builder toBuilder(); method public void writeToParcel(android.os.Parcel, int); field public static final int CAPABILITY_ADHOC_CONFERENCE_CALLING = 16384; // 0x4000 + field public static final int CAPABILITY_CALL_COMPOSER = 32768; // 0x8000 field public static final int CAPABILITY_CALL_PROVIDER = 2; // 0x2 field public static final int CAPABILITY_CALL_SUBJECT = 64; // 0x40 field public static final int CAPABILITY_CONNECTION_MANAGER = 1; // 0x1 @@ -39030,14 +39038,19 @@ package android.telecom { field public static final String EXTRA_DEFAULT_CALL_SCREENING_APP_COMPONENT_NAME = "android.telecom.extra.DEFAULT_CALL_SCREENING_APP_COMPONENT_NAME"; field public static final String EXTRA_DISCONNECT_CAUSE = "android.telecom.extra.DISCONNECT_CAUSE"; field public static final String EXTRA_HANDLE = "android.telecom.extra.HANDLE"; + field public static final String EXTRA_HAS_PICTURE = "android.telecom.extra.HAS_PICTURE"; field public static final String EXTRA_INCOMING_CALL_ADDRESS = "android.telecom.extra.INCOMING_CALL_ADDRESS"; field public static final String EXTRA_INCOMING_CALL_EXTRAS = "android.telecom.extra.INCOMING_CALL_EXTRAS"; + field public static final String EXTRA_INCOMING_PICTURE = "android.telecom.extra.INCOMING_PICTURE"; field public static final String EXTRA_INCOMING_VIDEO_STATE = "android.telecom.extra.INCOMING_VIDEO_STATE"; field public static final String EXTRA_IS_DEFAULT_CALL_SCREENING_APP = "android.telecom.extra.IS_DEFAULT_CALL_SCREENING_APP"; + field public static final String EXTRA_LOCATION = "android.telecom.extra.LOCATION"; field public static final String EXTRA_NOTIFICATION_COUNT = "android.telecom.extra.NOTIFICATION_COUNT"; field public static final String EXTRA_NOTIFICATION_PHONE_NUMBER = "android.telecom.extra.NOTIFICATION_PHONE_NUMBER"; field public static final String EXTRA_OUTGOING_CALL_EXTRAS = "android.telecom.extra.OUTGOING_CALL_EXTRAS"; + field public static final String EXTRA_OUTGOING_PICTURE = "android.telecom.extra.OUTGOING_PICTURE"; field public static final String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telecom.extra.PHONE_ACCOUNT_HANDLE"; + field public static final String EXTRA_PRIORITY = "android.telecom.extra.PRIORITY"; field public static final String EXTRA_START_CALL_WITH_RTT = "android.telecom.extra.START_CALL_WITH_RTT"; field public static final String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE"; field public static final String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.telecom.extra.START_CALL_WITH_VIDEO_STATE"; @@ -39053,6 +39066,8 @@ package android.telecom { field public static final int PRESENTATION_PAYPHONE = 4; // 0x4 field public static final int PRESENTATION_RESTRICTED = 2; // 0x2 field public static final int PRESENTATION_UNKNOWN = 3; // 0x3 + field public static final int PRIORITY_NORMAL = 0; // 0x0 + field public static final int PRIORITY_URGENT = 1; // 0x1 } public class VideoProfile implements android.os.Parcelable { @@ -39356,6 +39371,7 @@ package android.telephony { field public static final String KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL = "call_barring_supports_deactivate_all_bool"; field public static final String KEY_CALL_BARRING_SUPPORTS_PASSWORD_CHANGE_BOOL = "call_barring_supports_password_change_bool"; field public static final String KEY_CALL_BARRING_VISIBILITY_BOOL = "call_barring_visibility_bool"; + field public static final String KEY_CALL_COMPOSER_PICTURE_SERVER_URL_STRING = "call_composer_picture_server_url_string"; field public static final String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY = "call_forwarding_blocks_while_roaming_string_array"; field public static final String KEY_CALL_REDIRECTION_SERVICE_COMPONENT_NAME_STRING = "call_redirection_service_component_name_string"; field public static final String KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL = "carrier_allow_deflect_ims_call_bool"; @@ -39545,6 +39561,7 @@ package android.telephony { field public static final String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool"; field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool"; field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool"; + field public static final String KEY_SUPPORTS_CALL_COMPOSER_BOOL = "supports_call_composer_bool"; field public static final String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool"; field public static final String KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL = "support_add_conference_participants_bool"; field public static final String KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL = "support_adhoc_conference_calls_bool"; @@ -41000,6 +41017,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean doesSwitchMultiSimConfigTriggerReboot(); method public int getActiveModemCount(); method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public java.util.List<android.telephony.CellInfo> getAllCellInfo(); + method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public int getCallComposerStatus(); method public int getCallState(); method public int getCardIdForDefaultEuicc(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @WorkerThread public android.os.PersistableBundle getCarrierConfig(); @@ -41094,6 +41112,7 @@ package android.telephony { method @Deprecated public String sendEnvelopeWithStatus(String); method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler); method public void sendVisualVoicemailSms(String, int, String, android.app.PendingIntent); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallComposerStatus(int); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabledForReason(int, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setForbiddenPlmns(@NonNull java.util.List<java.lang.String>); @@ -41128,6 +41147,8 @@ package android.telephony { field public static final int APPTYPE_USIM = 2; // 0x2 field public static final int AUTHTYPE_EAP_AKA = 129; // 0x81 field public static final int AUTHTYPE_EAP_SIM = 128; // 0x80 + field public static final int CALL_COMPOSER_STATUS_OFF = 0; // 0x0 + field public static final int CALL_COMPOSER_STATUS_ON = 1; // 0x1 field public static final int CALL_STATE_IDLE = 0; // 0x0 field public static final int CALL_STATE_OFFHOOK = 2; // 0x2 field public static final int CALL_STATE_RINGING = 1; // 0x1 @@ -41880,6 +41901,7 @@ package android.telephony.ims.feature { public static class MmTelFeature.MmTelCapabilities { method public final boolean isCapable(int); + field public static final int CAPABILITY_TYPE_CALL_COMPOSER = 16; // 0x10 field public static final int CAPABILITY_TYPE_SMS = 8; // 0x8 field public static final int CAPABILITY_TYPE_UT = 4; // 0x4 field public static final int CAPABILITY_TYPE_VIDEO = 2; // 0x2 @@ -44631,7 +44653,7 @@ package android.util { field public static final java.util.regex.Pattern DOMAIN_NAME; field public static final java.util.regex.Pattern EMAIL_ADDRESS; field @Deprecated public static final String GOOD_IRI_CHAR = "a-zA-Z0-9\u00a0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef"; - field public static final java.util.regex.Pattern IP_ADDRESS; + field @Deprecated public static final java.util.regex.Pattern IP_ADDRESS; field public static final java.util.regex.Pattern PHONE; field @Deprecated public static final java.util.regex.Pattern TOP_LEVEL_DOMAIN; field @Deprecated public static final String TOP_LEVEL_DOMAIN_STR = "((aero|arpa|asia|a[cdefgilmnoqrstuwxz])|(biz|b[abdefghijmnorstvwyz])|(cat|com|coop|c[acdfghiklmnoruvxyz])|d[ejkmoz]|(edu|e[cegrstu])|f[ijkmor]|(gov|g[abdefghilmnpqrstuwy])|h[kmnrtu]|(info|int|i[delmnoqrst])|(jobs|j[emop])|k[eghimnprwyz]|l[abcikrstuvy]|(mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])|(name|net|n[acefgilopruz])|(org|om)|(pro|p[aefghklmnrstwy])|qa|r[eosuw]|s[abcdeghijklmnortuvyz]|(tel|travel|t[cdfghjklmnoprtvwz])|u[agksyz]|v[aceginu]|w[fs]|(\u03b4\u03bf\u03ba\u03b9\u03bc\u03ae|\u0438\u0441\u043f\u044b\u0442\u0430\u043d\u0438\u0435|\u0440\u0444|\u0441\u0440\u0431|\u05d8\u05e2\u05e1\u05d8|\u0622\u0632\u0645\u0627\u06cc\u0634\u06cc|\u0625\u062e\u062a\u0628\u0627\u0631|\u0627\u0644\u0627\u0631\u062f\u0646|\u0627\u0644\u062c\u0632\u0627\u0626\u0631|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633|\u0633\u0648\u0631\u064a\u0629|\u0641\u0644\u0633\u0637\u064a\u0646|\u0642\u0637\u0631|\u0645\u0635\u0631|\u092a\u0930\u0940\u0915\u094d\u0937\u093e|\u092d\u093e\u0930\u0924|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24|\u0aad\u0abe\u0ab0\u0aa4|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd|\u0baa\u0bb0\u0bbf\u0b9f\u0bcd\u0b9a\u0bc8|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e44\u0e17\u0e22|\u30c6\u30b9\u30c8|\u4e2d\u56fd|\u4e2d\u570b|\u53f0\u6e7e|\u53f0\u7063|\u65b0\u52a0\u5761|\u6d4b\u8bd5|\u6e2c\u8a66|\u9999\u6e2f|\ud14c\uc2a4\ud2b8|\ud55c\uad6d|xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-3e0b707e|xn\\-\\-45brj9c|xn\\-\\-80akhbyknj4f|xn\\-\\-90a3ac|xn\\-\\-9t4b11yi5a|xn\\-\\-clchc0ea0b2g2a9gcd|xn\\-\\-deba0ad|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-g6w251d|xn\\-\\-gecrj9c|xn\\-\\-h2brj9c|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-j6w193g|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-kprw13d|xn\\-\\-kpry57d|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1ai|xn\\-\\-pgbs0dh|xn\\-\\-s9brj9c|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xkc2al3hye2a|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-yfro4i67o|xn\\-\\-ygbi2ammx|xn\\-\\-zckzah|xxx)|y[et]|z[amw])"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index cc819fa2e078..6af21502f6c5 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1572,6 +1572,7 @@ package android.bluetooth { field @NonNull public static final android.os.ParcelUuid AVRCP_TARGET; field @NonNull public static final android.os.ParcelUuid BASE_UUID; field @NonNull public static final android.os.ParcelUuid BNEP; + field @NonNull public static final android.os.ParcelUuid DIP; field @NonNull public static final android.os.ParcelUuid HEARING_AID; field @NonNull public static final android.os.ParcelUuid HFP; field @NonNull public static final android.os.ParcelUuid HFP_AG; @@ -5964,6 +5965,7 @@ package android.net { method public long getExpiryTimeMillis(); method public long getRefreshTimeMillis(); method @Nullable public android.net.Uri getUserPortalUrl(); + method @Nullable public String getVenueFriendlyName(); method @Nullable public android.net.Uri getVenueInfoUrl(); method public boolean isCaptive(); method public boolean isSessionExtendable(); @@ -5981,6 +5983,7 @@ package android.net { method @NonNull public android.net.CaptivePortalData.Builder setRefreshTime(long); method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean); method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri); + method @NonNull public android.net.CaptivePortalData.Builder setVenueFriendlyName(@Nullable String); method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri); } @@ -6236,9 +6239,11 @@ package android.net { method @Nullable public String getSsid(); method @NonNull public int[] getTransportTypes(); method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities); + field public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; // 0x1c field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16 field public static final int NET_CAPABILITY_OEM_PRIVATE = 26; // 0x1a field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18 + field public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27; // 0x1b } public static final class NetworkCapabilities.Builder { @@ -10494,6 +10499,35 @@ package android.telephony.cdma { package android.telephony.data { + public final class ApnThrottleStatus implements android.os.Parcelable { + method public int describeContents(); + method public int getApnType(); + method public int getRetryType(); + method public int getSlotIndex(); + method public long getThrottleExpiryTimeMillis(); + method public int getThrottleType(); + method public int getTransportType(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.ApnThrottleStatus> CREATOR; + field public static final int RETRY_TYPE_HANDOVER = 3; // 0x3 + field public static final int RETRY_TYPE_NEW_CONNECTION = 2; // 0x2 + field public static final int RETRY_TYPE_NONE = 1; // 0x1 + field public static final int THROTTLE_TYPE_ELAPSED_TIME = 2; // 0x2 + field public static final int THROTTLE_TYPE_NONE = 1; // 0x1 + } + + public static final class ApnThrottleStatus.Builder { + ctor public ApnThrottleStatus.Builder(); + method @NonNull public android.telephony.data.ApnThrottleStatus build(); + method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setApnType(int); + method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setNoThrottle(); + method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setRetryType(int); + method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setSlotIndex(int); + method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setThrottleExpiryTimeMillis(long); + method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setTransportType(int); + field public static final long NO_THROTTLE_EXPIRY_TIME = -1L; // 0xffffffffffffffffL + } + public final class DataCallResponse implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.List<android.net.LinkAddress> getAddresses(); @@ -10510,7 +10544,7 @@ package android.telephony.data { method @NonNull public java.util.List<java.net.InetAddress> getPcscfAddresses(); method public int getPduSessionId(); method public int getProtocolType(); - method public long getRetryIntervalMillis(); + method public long getRetryDurationMillis(); method @Deprecated public int getSuggestedRetryTime(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.DataCallResponse> CREATOR; @@ -10524,7 +10558,7 @@ package android.telephony.data { field public static final int LINK_STATUS_INACTIVE = 0; // 0x0 field public static final int LINK_STATUS_UNKNOWN = -1; // 0xffffffff field public static final int PDU_SESSION_ID_NOT_SET = 0; // 0x0 - field public static final int RETRY_INTERVAL_UNDEFINED = -1; // 0xffffffff + field public static final int RETRY_DURATION_UNDEFINED = -1; // 0xffffffff } public static final class DataCallResponse.Builder { @@ -10544,7 +10578,7 @@ package android.telephony.data { method @NonNull public android.telephony.data.DataCallResponse.Builder setPcscfAddresses(@NonNull java.util.List<java.net.InetAddress>); method @NonNull public android.telephony.data.DataCallResponse.Builder setPduSessionId(int); method @NonNull public android.telephony.data.DataCallResponse.Builder setProtocolType(int); - method @NonNull public android.telephony.data.DataCallResponse.Builder setRetryIntervalMillis(long); + method @NonNull public android.telephony.data.DataCallResponse.Builder setRetryDurationMillis(long); method @Deprecated @NonNull public android.telephony.data.DataCallResponse.Builder setSuggestedRetryTime(int); } @@ -10611,6 +10645,7 @@ package android.telephony.data { method public abstract void close(); method public void deactivateDataCall(int, int, @Nullable android.telephony.data.DataServiceCallback); method public final int getSlotIndex(); + method public final void notifyApnUnthrottled(@NonNull String); method public final void notifyDataCallListChanged(java.util.List<android.telephony.data.DataCallResponse>); method public void requestDataCallList(@NonNull android.telephony.data.DataServiceCallback); method public void setDataProfile(@NonNull java.util.List<android.telephony.data.DataProfile>, boolean, @NonNull android.telephony.data.DataServiceCallback); @@ -10621,6 +10656,7 @@ package android.telephony.data { } public class DataServiceCallback { + method public void onApnUnthrottled(@NonNull String); method public void onDataCallListChanged(@NonNull java.util.List<android.telephony.data.DataCallResponse>); method public void onDeactivateDataCallComplete(int); method public void onHandoverCancelled(int); @@ -10646,6 +10682,7 @@ package android.telephony.data { ctor public QualifiedNetworksService.NetworkAvailabilityProvider(int); method public abstract void close(); method public final int getSlotIndex(); + method public void reportApnThrottleStatusChanged(@NonNull java.util.List<android.telephony.data.ApnThrottleStatus>); method public final void updateQualifiedNetworkTypes(int, @NonNull java.util.List<java.lang.Integer>); } @@ -11031,6 +11068,7 @@ package android.telephony.ims { method public boolean getCallExtraBoolean(String, boolean); method public int getCallExtraInt(String); method public int getCallExtraInt(String, int); + method @Nullable public <T extends android.os.Parcelable> T getCallExtraParcelable(@Nullable String); method public android.os.Bundle getCallExtras(); method public int getCallType(); method public static int getCallTypeFromVideoState(int); @@ -11053,6 +11091,7 @@ package android.telephony.ims { method public void setCallExtra(String, String); method public void setCallExtraBoolean(String, boolean); method public void setCallExtraInt(String, int); + method public void setCallExtraParcelable(@NonNull String, @NonNull android.os.Parcelable); method public void setCallRestrictCause(int); method public void setCallerNumberVerificationStatus(int); method public void setEmergencyCallRouting(int); @@ -11087,6 +11126,7 @@ package android.telephony.ims { field public static final String EXTRA_CALL_DISCONNECT_CAUSE = "android.telephony.ims.extra.CALL_DISCONNECT_CAUSE"; field public static final String EXTRA_CALL_NETWORK_TYPE = "android.telephony.ims.extra.CALL_NETWORK_TYPE"; field @Deprecated public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech"; + field public static final String EXTRA_CALL_SUBJECT = "android.telephony.ims.extra.CALL_SUBJECT"; field public static final String EXTRA_CHILD_NUMBER = "ChildNum"; field public static final String EXTRA_CNA = "cna"; field public static final String EXTRA_CNAP = "cnap"; @@ -11096,8 +11136,11 @@ package android.telephony.ims { field public static final String EXTRA_EMERGENCY_CALL = "e_call"; field public static final String EXTRA_FORWARDED_NUMBER = "android.telephony.ims.extra.FORWARDED_NUMBER"; field public static final String EXTRA_IS_CALL_PULL = "CallPull"; + field public static final String EXTRA_LOCATION = "android.telephony.ims.extra.LOCATION"; field public static final String EXTRA_OI = "oi"; field public static final String EXTRA_OIR = "oir"; + field public static final String EXTRA_PICTURE_URL = "android.telephony.ims.extra.PICTURE_URL"; + field public static final String EXTRA_PRIORITY = "android.telephony.ims.extra.PRIORITY"; field public static final String EXTRA_REMOTE_URI = "remote_uri"; field public static final String EXTRA_USSD = "ussd"; field public static final int OIR_DEFAULT = 0; // 0x0 @@ -11105,6 +11148,8 @@ package android.telephony.ims { field public static final int OIR_PRESENTATION_PAYPHONE = 4; // 0x4 field public static final int OIR_PRESENTATION_RESTRICTED = 1; // 0x1 field public static final int OIR_PRESENTATION_UNKNOWN = 3; // 0x3 + field public static final int PRIORITY_NORMAL = 0; // 0x0 + field public static final int PRIORITY_URGENT = 1; // 0x1 field public static final int SERVICE_TYPE_EMERGENCY = 2; // 0x2 field public static final int SERVICE_TYPE_NONE = 0; // 0x0 field public static final int SERVICE_TYPE_NORMAL = 1; // 0x1 @@ -11126,7 +11171,9 @@ package android.telephony.ims { method public void callSessionHoldFailed(android.telephony.ims.ImsReasonInfo); method public void callSessionHoldReceived(android.telephony.ims.ImsCallProfile); method public void callSessionInitiated(android.telephony.ims.ImsCallProfile); - method public void callSessionInitiatedFailed(android.telephony.ims.ImsReasonInfo); + method @Deprecated public void callSessionInitiatedFailed(android.telephony.ims.ImsReasonInfo); + method public void callSessionInitiating(@NonNull android.telephony.ims.ImsCallProfile); + method public void callSessionInitiatingFailed(@NonNull android.telephony.ims.ImsReasonInfo); method public void callSessionInviteParticipantsRequestDelivered(); method public void callSessionInviteParticipantsRequestFailed(android.telephony.ims.ImsReasonInfo); method @Deprecated public void callSessionMayHandover(int, int); @@ -11526,7 +11573,32 @@ package android.telephony.ims { } public class RcsUceAdapter { + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void addOnPublishStateChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.OnPublishStateChangedListener) throws android.telephony.ims.ImsException; + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getUcePublishState() throws android.telephony.ims.ImsException; + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void removeOnPublishStateChangedListener(@NonNull android.telephony.ims.RcsUceAdapter.OnPublishStateChangedListener) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUceSettingEnabled(boolean) throws android.telephony.ims.ImsException; + field public static final int CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED = 1; // 0x1 + field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_2G = 7; // 0x7 + field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_3G = 6; // 0x6 + field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_EHRPD = 4; // 0x4 + field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_HSPAPLUS = 5; // 0x5 + field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_IWLAN = 9; // 0x9 + field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_DISABLED = 2; // 0x2 + field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_ENABLED = 3; // 0x3 + field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_DISABLED = 10; // 0xa + field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED = 11; // 0xb + field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN = 8; // 0x8 + field public static final int CAPABILITY_UPDATE_TRIGGER_UNKNOWN = 0; // 0x0 + field public static final int PUBLISH_STATE_NOT_PUBLISHED = 2; // 0x2 + field public static final int PUBLISH_STATE_OK = 1; // 0x1 + field public static final int PUBLISH_STATE_OTHER_ERROR = 6; // 0x6 + field public static final int PUBLISH_STATE_RCS_PROVISION_ERROR = 4; // 0x4 + field public static final int PUBLISH_STATE_REQUEST_TIMEOUT = 5; // 0x5 + field public static final int PUBLISH_STATE_VOICE_PROVISION_ERROR = 3; // 0x3 + } + + public static interface RcsUceAdapter.OnPublishStateChangedListener { + method public void onPublishStateChange(int); } public final class RtpHeaderExtension implements android.os.Parcelable { @@ -11733,16 +11805,24 @@ package android.telephony.ims.feature { } public class RcsFeature extends android.telephony.ims.feature.ImsFeature { - ctor public RcsFeature(); + ctor @Deprecated public RcsFeature(); + ctor public RcsFeature(@NonNull java.util.concurrent.Executor); method public void changeEnabledCapabilities(@NonNull android.telephony.ims.feature.CapabilityChangeRequest, @NonNull android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy); + method @NonNull public android.telephony.ims.stub.RcsCapabilityExchangeImplBase createCapabilityExchangeImpl(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.stub.CapabilityExchangeEventListener); method public void onFeatureReady(); method public void onFeatureRemoved(); + method public void removeCapabilityExchangeImpl(@NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase); } } package android.telephony.ims.stub { + public interface CapabilityExchangeEventListener { + method public void onRequestPublishCapabilities(int) throws android.telephony.ims.ImsException; + method public void onUnpublish() throws android.telephony.ims.ImsException; + } + public interface DelegateConnectionMessageCallback { method public void onMessageReceived(@NonNull android.telephony.ims.SipMessage); method public void onMessageSendFailure(@NonNull String, int); @@ -11923,6 +12003,27 @@ package android.telephony.ims.stub { method public int updateColr(int); } + public class RcsCapabilityExchangeImplBase { + ctor public RcsCapabilityExchangeImplBase(@NonNull java.util.concurrent.Executor); + method public void publishCapabilities(@NonNull String, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.PublishResponseCallback); + field public static final int COMMAND_CODE_FETCH_ERROR = 3; // 0x3 + field public static final int COMMAND_CODE_GENERIC_FAILURE = 1; // 0x1 + field public static final int COMMAND_CODE_INSUFFICIENT_MEMORY = 5; // 0x5 + field public static final int COMMAND_CODE_INVALID_PARAM = 2; // 0x2 + field public static final int COMMAND_CODE_LOST_NETWORK_CONNECTION = 6; // 0x6 + field public static final int COMMAND_CODE_NOT_FOUND = 8; // 0x8 + field public static final int COMMAND_CODE_NOT_SUPPORTED = 7; // 0x7 + field public static final int COMMAND_CODE_NO_CHANGE = 10; // 0xa + field public static final int COMMAND_CODE_REQUEST_TIMEOUT = 4; // 0x4 + field public static final int COMMAND_CODE_SERVICE_UNAVAILABLE = 9; // 0x9 + field public static final int COMMAND_CODE_SERVICE_UNKNOWN = 0; // 0x0 + } + + public static interface RcsCapabilityExchangeImplBase.PublishResponseCallback { + method public void onCommandError(int) throws android.telephony.ims.ImsException; + method public void onNetworkResponse(@IntRange(from=100, to=699) int, @NonNull String) throws android.telephony.ims.ImsException; + } + public interface SipDelegate { method public void closeDialog(@NonNull String); method public void notifyMessageReceiveError(@NonNull String, int); diff --git a/core/java/android/accounts/GrantCredentialsPermissionActivity.java b/core/java/android/accounts/GrantCredentialsPermissionActivity.java index af74b036a796..5dc6e602e5d6 100644 --- a/core/java/android/accounts/GrantCredentialsPermissionActivity.java +++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java @@ -16,16 +16,23 @@ package android.accounts; import android.app.Activity; -import android.content.res.Resources; -import android.os.Bundle; -import android.widget.TextView; -import android.widget.LinearLayout; -import android.view.View; -import android.view.LayoutInflater; +import android.app.ActivityTaskManager; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + import com.android.internal.R; import java.io.IOException; @@ -42,11 +49,15 @@ public class GrantCredentialsPermissionActivity extends Activity implements View private Account mAccount; private String mAuthTokenType; private int mUid; + private int mCallingUid; private Bundle mResultBundle = null; protected LayoutInflater mInflater; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + getWindow().addSystemFlags( + android.view.WindowManager.LayoutParams + .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); setContentView(R.layout.grant_credentials_permission); setTitle(R.string.grant_permissions_header_text); @@ -74,6 +85,20 @@ public class GrantCredentialsPermissionActivity extends Activity implements View return; } + try { + IBinder activityToken = getActivityToken(); + mCallingUid = ActivityTaskManager.getService().getLaunchedFromUid(activityToken); + } catch (RemoteException re) { + // Couldn't figure out caller details + Log.w(getClass().getSimpleName(), "Unable to get caller identity \n" + re); + } + + if (!UserHandle.isSameApp(mCallingUid, Process.SYSTEM_UID) && mCallingUid != mUid) { + setResult(Activity.RESULT_CANCELED); + finish(); + return; + } + String accountTypeLabel; try { accountTypeLabel = getAccountLabel(mAccount); diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index 06ad9c99c301..6d79e2d3c166 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -2,6 +2,33 @@ # Remain no owner because multiple modules may touch this file. per-file ContextImpl.java = * +# ActivityManager +per-file ActivityManager* = file:/services/core/java/com/android/server/am/OWNERS +per-file ApplicationErrorReport* = file:/services/core/java/com/android/server/am/OWNERS +per-file ApplicationExitInfo* = file:/services/core/java/com/android/server/am/OWNERS +per-file Application.java = file:/services/core/java/com/android/server/am/OWNERS +per-file ApplicationLoaders.java = file:/services/core/java/com/android/server/am/OWNERS +per-file ApplicationThreadConstants.java = file:/services/core/java/com/android/server/am/OWNERS +per-file BroadcastOptions.java = file:/services/core/java/com/android/server/am/OWNERS +per-file ContentProviderHolder* = file:/services/core/java/com/android/server/am/OWNERS +per-file IActivityController.aidl = file:/services/core/java/com/android/server/am/OWNERS +per-file IActivityManager.aidl = file:/services/core/java/com/android/server/am/OWNERS +per-file IApplicationThread.aidl = file:/services/core/java/com/android/server/am/OWNERS +per-file IAppTraceRetriever.aidl = file:/services/core/java/com/android/server/am/OWNERS +per-file IInstrumentationWatcher.aidl = file:/services/core/java/com/android/server/am/OWNERS +per-file IntentService.aidl = file:/services/core/java/com/android/server/am/OWNERS +per-file IServiceConnection.aidl = file:/services/core/java/com/android/server/am/OWNERS +per-file IStopUserCallback.aidl = file:/services/core/java/com/android/server/am/OWNERS +per-file IUidObserver.aidl = file:/services/core/java/com/android/server/am/OWNERS +per-file LoadedApk.java = file:/services/core/java/com/android/server/am/OWNERS +per-file LocalActivityManager.java = file:/services/core/java/com/android/server/am/OWNERS +per-file PendingIntent* = file:/services/core/java/com/android/server/am/OWNERS +per-file *Process* = file:/services/core/java/com/android/server/am/OWNERS +per-file ProfilerInfo* = file:/services/core/java/com/android/server/am/OWNERS +per-file Service* = file:/services/core/java/com/android/server/am/OWNERS +per-file SystemServiceRegistry.java = file:/services/core/java/com/android/server/am/OWNERS +per-file *UserSwitchObserver* = file:/services/core/java/com/android/server/am/OWNERS + # ActivityThread per-file ActivityThread.java = file:/services/core/java/com/android/server/am/OWNERS per-file ActivityThread.java = file:/services/core/java/com/android/server/wm/OWNERS @@ -12,6 +39,9 @@ per-file *Alarm* = file:/apex/jobscheduler/OWNERS # AppOps per-file *AppOp* = file:/core/java/android/permission/OWNERS +# Multiuser +per-file *User* = file:/MULTIUSER_OWNERS + # Notification per-file *Notification* = file:/packages/SystemUI/OWNERS diff --git a/core/java/android/app/people/OWNERS b/core/java/android/app/people/OWNERS new file mode 100644 index 000000000000..7371a88df237 --- /dev/null +++ b/core/java/android/app/people/OWNERS @@ -0,0 +1,4 @@ +# Bug component: 978868 + +danningc@google.com +juliacr@google.com
\ No newline at end of file diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java index bd1eea51f8af..46be54814dc9 100644 --- a/core/java/android/app/slice/SliceProvider.java +++ b/core/java/android/app/slice/SliceProvider.java @@ -153,6 +153,7 @@ public abstract class SliceProvider extends ContentProvider { */ public static final String EXTRA_PKG = "pkg"; /** + * @Deprecated provider pkg is now being extracted in SlicePermissionActivity * @hide */ public static final String EXTRA_PROVIDER_PKG = "provider_pkg"; diff --git a/core/java/android/apphibernation/OWNERS b/core/java/android/apphibernation/OWNERS new file mode 100644 index 000000000000..587c719f5587 --- /dev/null +++ b/core/java/android/apphibernation/OWNERS @@ -0,0 +1,2 @@ +kevhan@google.com +rajekumar@google.com diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java index 7a6ff79623af..381318b26dad 100644 --- a/core/java/android/bluetooth/BluetoothGatt.java +++ b/core/java/android/bluetooth/BluetoothGatt.java @@ -824,6 +824,25 @@ public final class BluetoothGatt implements BluetoothProfile { * error */ private boolean registerApp(BluetoothGattCallback callback, Handler handler) { + return registerApp(callback, handler, false); + } + + /** + * Register an application callback to start using GATT. + * + * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered} + * is used to notify success or failure if the function returns true. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * @param callback GATT callback handler that will receive asynchronous callbacks. + * @param eatt_support indicate to allow for eatt support + * @return If true, the callback will be called to notify success or failure, false on immediate + * error + * @hide + */ + private boolean registerApp(BluetoothGattCallback callback, Handler handler, + boolean eatt_support) { if (DBG) Log.d(TAG, "registerApp()"); if (mService == null) return false; @@ -833,7 +852,7 @@ public final class BluetoothGatt implements BluetoothProfile { if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid); try { - mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback); + mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback, eatt_support); } catch (RemoteException e) { Log.e(TAG, "", e); return false; diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java index 13b1b4f93cf0..088b0169b631 100644 --- a/core/java/android/bluetooth/BluetoothGattServer.java +++ b/core/java/android/bluetooth/BluetoothGattServer.java @@ -443,6 +443,25 @@ public final class BluetoothGattServer implements BluetoothProfile { * error */ /*package*/ boolean registerCallback(BluetoothGattServerCallback callback) { + return registerCallback(callback, false); + } + + /** + * Register an application callback to start using GattServer. + * + * <p>This is an asynchronous call. The callback is used to notify + * success or failure if the function returns true. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * @param callback GATT callback handler that will receive asynchronous callbacks. + * @param eatt_support indicates if server can use eatt + * @return true, the callback will be called to notify success or failure, false on immediate + * error + * @hide + */ + /*package*/ boolean registerCallback(BluetoothGattServerCallback callback, + boolean eatt_support) { if (DBG) Log.d(TAG, "registerCallback()"); if (mService == null) { Log.e(TAG, "GATT service not available"); @@ -459,7 +478,7 @@ public final class BluetoothGattServer implements BluetoothProfile { mCallback = callback; try { - mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback); + mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback, eatt_support); } catch (RemoteException e) { Log.e(TAG, "", e); mCallback = null; diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index fdfc6beca3fd..36076da91cc5 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -682,6 +682,48 @@ public final class BluetoothHeadset implements BluetoothProfile { } /** + * Checks whether the headset supports some form of noise reduction + * + * @param device Bluetooth device + * @return true if echo cancellation and/or noise reduction is supported, false otherwise + */ + @RequiresPermission(Manifest.permission.BLUETOOTH) + public boolean isNoiseReductionSupported(@NonNull BluetoothDevice device) { + if (DBG) log("isNoiseReductionSupported()"); + final IBluetoothHeadset service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { + try { + return service.isNoiseReductionSupported(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Checks whether the headset supports voice recognition + * + * @param device Bluetooth device + * @return true if voice recognition is supported, false otherwise + */ + @RequiresPermission(Manifest.permission.BLUETOOTH) + public boolean isVoiceRecognitionSupported(@NonNull BluetoothDevice device) { + if (DBG) log("isVoiceRecognitionSupported()"); + final IBluetoothHeadset service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { + try { + return service.isVoiceRecognitionSupported(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** * Start Bluetooth voice recognition. This methods sends the voice * recognition AT command to the headset and establishes the * audio connection. diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java index 3b4fe0a30b80..d5c1c3e2d61e 100644 --- a/core/java/android/bluetooth/BluetoothManager.java +++ b/core/java/android/bluetooth/BluetoothManager.java @@ -225,6 +225,24 @@ public final class BluetoothManager { * * @param context App context * @param callback GATT server callback handler that will receive asynchronous callbacks. + * @param eatt_support idicates if server should use eatt channel for notifications. + * @return BluetoothGattServer instance + * @hide + */ + public BluetoothGattServer openGattServer(Context context, + BluetoothGattServerCallback callback, boolean eatt_support) { + return (openGattServer(context, callback, BluetoothDevice.TRANSPORT_AUTO, eatt_support)); + } + + /** + * Open a GATT Server + * The callback is used to deliver results to Caller, such as connection status as well + * as the results of any other GATT server operations. + * The method returns a BluetoothGattServer instance. You can use BluetoothGattServer + * to conduct GATT server operations. + * + * @param context App context + * @param callback GATT server callback handler that will receive asynchronous callbacks. * @param transport preferred transport for GATT connections to remote dual-mode devices {@link * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link * BluetoothDevice#TRANSPORT_LE} @@ -233,6 +251,27 @@ public final class BluetoothManager { */ public BluetoothGattServer openGattServer(Context context, BluetoothGattServerCallback callback, int transport) { + return (openGattServer(context, callback, transport, false)); + } + + /** + * Open a GATT Server + * The callback is used to deliver results to Caller, such as connection status as well + * as the results of any other GATT server operations. + * The method returns a BluetoothGattServer instance. You can use BluetoothGattServer + * to conduct GATT server operations. + * + * @param context App context + * @param callback GATT server callback handler that will receive asynchronous callbacks. + * @param transport preferred transport for GATT connections to remote dual-mode devices {@link + * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link + * BluetoothDevice#TRANSPORT_LE} + * @param eatt_support idicates if server should use eatt channel for notifications. + * @return BluetoothGattServer instance + * @hide + */ + public BluetoothGattServer openGattServer(Context context, + BluetoothGattServerCallback callback, int transport, boolean eatt_support) { if (context == null || callback == null) { throw new IllegalArgumentException("null parameter: " + context + " " + callback); } @@ -248,7 +287,7 @@ public final class BluetoothManager { return null; } BluetoothGattServer mGattServer = new BluetoothGattServer(iGatt, transport); - Boolean regStatus = mGattServer.registerCallback(callback); + Boolean regStatus = mGattServer.registerCallback(callback, eatt_support); return regStatus ? mGattServer : null; } catch (RemoteException e) { Log.e(TAG, "", e); diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java index 56c482471003..c0736a6b7bba 100644 --- a/core/java/android/bluetooth/BluetoothUuid.java +++ b/core/java/android/bluetooth/BluetoothUuid.java @@ -162,6 +162,11 @@ public final class BluetoothUuid { /** @hide */ @NonNull @SystemApi + public static final ParcelUuid DIP = + ParcelUuid.fromString("00001200-0000-1000-8000-00805F9B34FB"); + /** @hide */ + @NonNull + @SystemApi public static final ParcelUuid BASE_UUID = ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB"); diff --git a/core/java/android/bluetooth/SdpDipRecord.java b/core/java/android/bluetooth/SdpDipRecord.java new file mode 100644 index 000000000000..84b0eef0593e --- /dev/null +++ b/core/java/android/bluetooth/SdpDipRecord.java @@ -0,0 +1,104 @@ +/* +* Copyright (C) 2015 Samsung System LSI +* 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.bluetooth; + +import java.util.Arrays; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Data representation of a Object Push Profile Server side SDP record. + */ +/** @hide */ +public class SdpDipRecord implements Parcelable { + private final int mSpecificationId; + private final int mVendorId; + private final int mVendorIdSource; + private final int mProductId; + private final int mVersion; + private final boolean mPrimaryRecord; + + public SdpDipRecord(int specificationId, + int vendorId, int vendorIdSource, + int productId, int version, + boolean primaryRecord) { + super(); + this.mSpecificationId = specificationId; + this.mVendorId = vendorId; + this.mVendorIdSource = vendorIdSource; + this.mProductId = productId; + this.mVersion = version; + this.mPrimaryRecord = primaryRecord; + } + + public SdpDipRecord(Parcel in) { + this.mSpecificationId = in.readInt(); + this.mVendorId = in.readInt(); + this.mVendorIdSource = in.readInt(); + this.mProductId = in.readInt(); + this.mVersion = in.readInt(); + this.mPrimaryRecord = in.readBoolean(); + } + + public int getSpecificationId() { + return mSpecificationId; + } + + public int getVendorId() { + return mVendorId; + } + + public int getVendorIdSource() { + return mVendorIdSource; + } + + public int getProductId() { + return mProductId; + } + + public int getVersion() { + return mVersion; + } + + public boolean getPrimaryRecord() { + return mPrimaryRecord; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mSpecificationId); + dest.writeInt(mVendorId); + dest.writeInt(mVendorIdSource); + dest.writeInt(mProductId); + dest.writeInt(mVersion); + dest.writeBoolean(mPrimaryRecord); + } + + @Override + public int describeContents() { + /* No special objects */ + return 0; + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public SdpDipRecord createFromParcel(Parcel in) { + return new SdpDipRecord(in); + } + public SdpDipRecord[] newArray(int size) { + return new SdpDipRecord[size]; + } + }; +} diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 3d724f0a2d48..5bdd521e92dd 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -943,7 +943,7 @@ public class ContextWrapper extends Context { /** @hide */ @Override - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage(trackingBug = 175981568) public Context createApplicationContext(ApplicationInfo application, int flags) throws PackageManager.NameNotFoundException { return mBase.createApplicationContext(application, flags); diff --git a/core/java/android/content/pm/LAUNCHER_OWNERS b/core/java/android/content/pm/LAUNCHER_OWNERS new file mode 100644 index 000000000000..400836f55ceb --- /dev/null +++ b/core/java/android/content/pm/LAUNCHER_OWNERS @@ -0,0 +1,7 @@ +set noparent + +omakoto@google.com +sunnygoyal@google.com +mett@google.com +jonmiranda@google.com +pinyaoting@google.com diff --git a/core/java/android/content/pm/OWNERS b/core/java/android/content/pm/OWNERS index 24872e8c3c1d..fd32efccbcec 100644 --- a/core/java/android/content/pm/OWNERS +++ b/core/java/android/content/pm/OWNERS @@ -5,4 +5,6 @@ toddke@google.com patb@google.com per-file PackageParser.java = chiuwinson@google.com -per-file *Shortcut* = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com +per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS +per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS +per-file UserInfo* = file:/MULTIUSER_OWNERS diff --git a/core/java/android/content/pm/SHORTCUT_OWNERS b/core/java/android/content/pm/SHORTCUT_OWNERS new file mode 100644 index 000000000000..3688d5a3a4c7 --- /dev/null +++ b/core/java/android/content/pm/SHORTCUT_OWNERS @@ -0,0 +1,7 @@ +set noparent + +omakoto@google.com +yamasani@google.com +sunnygoyal@google.com +mett@google.com +pinyaoting@google.com diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 6c911f68dbe2..63c58d2b2987 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -2057,7 +2057,7 @@ public class Resources { } /** @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage(trackingBug = 176190631) public DisplayAdjustments getDisplayAdjustments() { final DisplayAdjustments overrideDisplayAdjustments = mOverrideDisplayAdjustments; if (overrideDisplayAdjustments != null) { diff --git a/core/java/android/hardware/biometrics/OWNERS b/core/java/android/hardware/biometrics/OWNERS index 33527f824827..2065ffacca7c 100644 --- a/core/java/android/hardware/biometrics/OWNERS +++ b/core/java/android/hardware/biometrics/OWNERS @@ -1,3 +1,8 @@ # Bug component: 879035 +curtislb@google.com +ilyamaty@google.com jaggies@google.com +joshmccloskey@google.com +kchyn@google.com + diff --git a/core/java/android/hardware/face/OWNERS b/core/java/android/hardware/face/OWNERS index 33527f824827..be10df1099ed 100644 --- a/core/java/android/hardware/face/OWNERS +++ b/core/java/android/hardware/face/OWNERS @@ -1,3 +1,7 @@ # Bug component: 879035 +curtislb@google.com +ilyamaty@google.com jaggies@google.com +joshmccloskey@google.com +kchyn@google.com diff --git a/core/java/android/hardware/fingerprint/OWNERS b/core/java/android/hardware/fingerprint/OWNERS index dcead40d482d..e55b8c564ddb 100644 --- a/core/java/android/hardware/fingerprint/OWNERS +++ b/core/java/android/hardware/fingerprint/OWNERS @@ -1,3 +1,8 @@ # Bug component: 114777 +curtislb@google.com +ilyamaty@google.com jaggies@google.com +joshmccloskey@google.com +kchyn@google.com + diff --git a/core/java/android/net/CaptivePortalData.java b/core/java/android/net/CaptivePortalData.java index c443c7500f1d..18467fad8ec4 100644 --- a/core/java/android/net/CaptivePortalData.java +++ b/core/java/android/net/CaptivePortalData.java @@ -39,9 +39,11 @@ public final class CaptivePortalData implements Parcelable { private final long mByteLimit; private final long mExpiryTimeMillis; private final boolean mCaptive; + private final String mVenueFriendlyName; private CaptivePortalData(long refreshTimeMillis, Uri userPortalUrl, Uri venueInfoUrl, - boolean isSessionExtendable, long byteLimit, long expiryTimeMillis, boolean captive) { + boolean isSessionExtendable, long byteLimit, long expiryTimeMillis, boolean captive, + String venueFriendlyName) { mRefreshTimeMillis = refreshTimeMillis; mUserPortalUrl = userPortalUrl; mVenueInfoUrl = venueInfoUrl; @@ -49,11 +51,12 @@ public final class CaptivePortalData implements Parcelable { mByteLimit = byteLimit; mExpiryTimeMillis = expiryTimeMillis; mCaptive = captive; + mVenueFriendlyName = venueFriendlyName; } private CaptivePortalData(Parcel p) { this(p.readLong(), p.readParcelable(null), p.readParcelable(null), p.readBoolean(), - p.readLong(), p.readLong(), p.readBoolean()); + p.readLong(), p.readLong(), p.readBoolean(), p.readString()); } @Override @@ -70,6 +73,7 @@ public final class CaptivePortalData implements Parcelable { dest.writeLong(mByteLimit); dest.writeLong(mExpiryTimeMillis); dest.writeBoolean(mCaptive); + dest.writeString(mVenueFriendlyName); } /** @@ -83,6 +87,7 @@ public final class CaptivePortalData implements Parcelable { private long mBytesRemaining = -1; private long mExpiryTime = -1; private boolean mCaptive; + private String mVenueFriendlyName; /** * Create an empty builder. @@ -100,7 +105,8 @@ public final class CaptivePortalData implements Parcelable { .setSessionExtendable(data.mIsSessionExtendable) .setBytesRemaining(data.mByteLimit) .setExpiryTime(data.mExpiryTimeMillis) - .setCaptive(data.mCaptive); + .setCaptive(data.mCaptive) + .setVenueFriendlyName(data.mVenueFriendlyName); } /** @@ -167,12 +173,22 @@ public final class CaptivePortalData implements Parcelable { } /** + * Set the venue friendly name. + */ + @NonNull + public Builder setVenueFriendlyName(@Nullable String venueFriendlyName) { + mVenueFriendlyName = venueFriendlyName; + return this; + } + + /** * Create a new {@link CaptivePortalData}. */ @NonNull public CaptivePortalData build() { return new CaptivePortalData(mRefreshTime, mUserPortalUrl, mVenueInfoUrl, - mIsSessionExtendable, mBytesRemaining, mExpiryTime, mCaptive); + mIsSessionExtendable, mBytesRemaining, mExpiryTime, mCaptive, + mVenueFriendlyName); } } @@ -232,6 +248,14 @@ public final class CaptivePortalData implements Parcelable { return mCaptive; } + /** + * Get the venue friendly name + */ + @Nullable + public String getVenueFriendlyName() { + return mVenueFriendlyName; + } + @NonNull public static final Creator<CaptivePortalData> CREATOR = new Creator<CaptivePortalData>() { @Override @@ -248,7 +272,7 @@ public final class CaptivePortalData implements Parcelable { @Override public int hashCode() { return Objects.hash(mRefreshTimeMillis, mUserPortalUrl, mVenueInfoUrl, - mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive); + mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive, mVenueFriendlyName); } @Override @@ -261,7 +285,8 @@ public final class CaptivePortalData implements Parcelable { && mIsSessionExtendable == other.mIsSessionExtendable && mByteLimit == other.mByteLimit && mExpiryTimeMillis == other.mExpiryTimeMillis - && mCaptive == other.mCaptive; + && mCaptive == other.mCaptive + && Objects.equals(mVenueFriendlyName, other.mVenueFriendlyName); } @Override @@ -274,6 +299,7 @@ public final class CaptivePortalData implements Parcelable { + ", byteLimit: " + mByteLimit + ", expiryTime: " + mExpiryTimeMillis + ", captive: " + mCaptive + + ", venueFriendlyName: " + mVenueFriendlyName + "}"; } } diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java index 704f31d7f773..523449497345 100644 --- a/core/java/android/net/ConnectivityDiagnosticsManager.java +++ b/core/java/android/net/ConnectivityDiagnosticsManager.java @@ -623,32 +623,41 @@ public class ConnectivityDiagnosticsManager { /** @hide */ @VisibleForTesting public void onConnectivityReportAvailable(@NonNull ConnectivityReport report) { - Binder.withCleanCallingIdentity(() -> { + final long token = Binder.clearCallingIdentity(); + try { mExecutor.execute(() -> { mCb.onConnectivityReportAvailable(report); }); - }); + } finally { + Binder.restoreCallingIdentity(token); + } } /** @hide */ @VisibleForTesting public void onDataStallSuspected(@NonNull DataStallReport report) { - Binder.withCleanCallingIdentity(() -> { + final long token = Binder.clearCallingIdentity(); + try { mExecutor.execute(() -> { mCb.onDataStallSuspected(report); }); - }); + } finally { + Binder.restoreCallingIdentity(token); + } } /** @hide */ @VisibleForTesting public void onNetworkConnectivityReported( @NonNull Network network, boolean hasConnectivity) { - Binder.withCleanCallingIdentity(() -> { + final long token = Binder.clearCallingIdentity(); + try { mExecutor.execute(() -> { mCb.onNetworkConnectivityReported(network, hasConnectivity); }); - }); + } finally { + Binder.restoreCallingIdentity(token); + } } } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 540ea5c159cc..06c159804a45 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -59,8 +59,10 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.Log; +import android.util.Range; import android.util.SparseIntArray; +import com.android.connectivity.aidl.INetworkAgent; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import com.android.internal.util.Protocol; @@ -72,10 +74,12 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.net.DatagramSocket; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -1162,6 +1166,55 @@ public class ConnectivityManager { } /** + * Adds or removes a requirement for given UID ranges to use the VPN. + * + * If set to {@code true}, informs the system that the UIDs in the specified ranges must not + * have any connectivity except if a VPN is connected and applies to the UIDs, or if the UIDs + * otherwise have permission to bypass the VPN (e.g., because they have the + * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission, or when + * using a socket protected by a method such as {@link VpnService#protect(DatagramSocket)}. If + * set to {@code false}, a previously-added restriction is removed. + * <p> + * Each of the UID ranges specified by this method is added and removed as is, and no processing + * is performed on the ranges to de-duplicate, merge, split, or intersect them. In order to + * remove a previously-added range, the exact range must be removed as is. + * <p> + * The changes are applied asynchronously and may not have been applied by the time the method + * returns. Apps will be notified about any changes that apply to them via + * {@link NetworkCallback#onBlockedStatusChanged} callbacks called after the changes take + * effect. + * <p> + * This method should be called only by the VPN code. + * + * @param ranges the UID ranges to restrict + * @param requireVpn whether the specified UID ranges must use a VPN + * + * TODO: expose as @SystemApi. + * @hide + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) + public void setRequireVpnForUids(boolean requireVpn, + @NonNull Collection<Range<Integer>> ranges) { + Objects.requireNonNull(ranges); + // The Range class is not parcelable. Convert to UidRange, which is what is used internally. + // This method is not necessarily expected to be used outside the system server, so + // parceling may not be necessary, but it could be used out-of-process, e.g., by the network + // stack process, or by tests. + UidRange[] rangesArray = new UidRange[ranges.size()]; + int index = 0; + for (Range<Integer> range : ranges) { + rangesArray[index++] = new UidRange(range.getLower(), range.getUpper()); + } + try { + mService.setRequireVpnForUids(requireVpn, rangesArray); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns details about the currently active default data network * for a given uid. This is for internal use only to avoid spying * other apps. @@ -1832,30 +1885,42 @@ public class ConnectivityManager { mCallback = new ISocketKeepaliveCallback.Stub() { @Override public void onStarted(int slot) { - Binder.withCleanCallingIdentity(() -> - mExecutor.execute(() -> { - mSlot = slot; - callback.onStarted(); - })); + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mSlot = slot; + callback.onStarted(); + }); + } finally { + Binder.restoreCallingIdentity(token); + } } @Override public void onStopped() { - Binder.withCleanCallingIdentity(() -> - mExecutor.execute(() -> { - mSlot = null; - callback.onStopped(); - })); + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mSlot = null; + callback.onStopped(); + }); + } finally { + Binder.restoreCallingIdentity(token); + } mExecutor.shutdown(); } @Override public void onError(int error) { - Binder.withCleanCallingIdentity(() -> - mExecutor.execute(() -> { - mSlot = null; - callback.onError(error); - })); + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mSlot = null; + callback.onError(error); + }); + } finally { + Binder.restoreCallingIdentity(token); + } mExecutor.shutdown(); } @@ -3119,39 +3184,6 @@ public class ConnectivityManager { } /** - * Check mobile provisioning. - * - * @param suggestedTimeOutMs, timeout in milliseconds - * - * @return time out that will be used, maybe less that suggestedTimeOutMs - * -1 if an error. - * - * {@hide} - */ - public int checkMobileProvisioning(int suggestedTimeOutMs) { - int timeOutMs = -1; - try { - timeOutMs = mService.checkMobileProvisioning(suggestedTimeOutMs); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - return timeOutMs; - } - - /** - * Get the mobile provisioning url. - * {@hide} - */ - @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) - public String getMobileProvisioningUrl() { - try { - return mService.getMobileProvisioningUrl(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** * Set sign in error notification to visible or invisible * * @hide @@ -3287,9 +3319,9 @@ public class ConnectivityManager { @RequiresPermission(anyOf = { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) - public Network registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp, + public Network registerNetworkAgent(INetworkAgent na, NetworkInfo ni, LinkProperties lp, NetworkCapabilities nc, int score, NetworkAgentConfig config) { - return registerNetworkAgent(messenger, ni, lp, nc, score, config, NetworkProvider.ID_NONE); + return registerNetworkAgent(na, ni, lp, nc, score, config, NetworkProvider.ID_NONE); } /** @@ -3300,10 +3332,10 @@ public class ConnectivityManager { @RequiresPermission(anyOf = { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) - public Network registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp, + public Network registerNetworkAgent(INetworkAgent na, NetworkInfo ni, LinkProperties lp, NetworkCapabilities nc, int score, NetworkAgentConfig config, int providerId) { try { - return mService.registerNetworkAgent(messenger, ni, lp, nc, score, config, providerId); + return mService.registerNetworkAgent(na, ni, lp, nc, score, config, providerId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 95a2f2efeb7d..b32c98b49cfc 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -29,6 +29,7 @@ import android.net.NetworkRequest; import android.net.NetworkState; import android.net.ISocketKeepaliveCallback; import android.net.ProxyInfo; +import android.net.UidRange; import android.os.Bundle; import android.os.IBinder; import android.os.INetworkActivityListener; @@ -37,6 +38,7 @@ import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.ResultReceiver; +import com.android.connectivity.aidl.INetworkAgent; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnInfo; @@ -145,10 +147,7 @@ interface IConnectivityManager String getAlwaysOnVpnPackage(int userId); boolean isVpnLockdownEnabled(int userId); List<String> getVpnLockdownWhitelist(int userId); - - int checkMobileProvisioning(int suggestedTimeOutMs); - - String getMobileProvisioningUrl(); + void setRequireVpnForUids(boolean requireVpn, in UidRange[] ranges); void setProvisioningNotificationVisible(boolean visible, int networkType, in String action); @@ -164,7 +163,7 @@ interface IConnectivityManager void declareNetworkRequestUnfulfillable(in NetworkRequest request); - Network registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp, + Network registerNetworkAgent(in INetworkAgent na, in NetworkInfo ni, in LinkProperties lp, in NetworkCapabilities nc, int score, in NetworkAgentConfig config, in int factorySerialNumber); diff --git a/core/java/android/net/IIpConnectivityMetrics.aidl b/core/java/android/net/IIpConnectivityMetrics.aidl index aeaf09d8fafe..aa3682d92105 100644 --- a/core/java/android/net/IIpConnectivityMetrics.aidl +++ b/core/java/android/net/IIpConnectivityMetrics.aidl @@ -19,6 +19,9 @@ package android.net; import android.os.Parcelable; import android.net.ConnectivityMetricsEvent; import android.net.INetdEventCallback; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; /** {@hide} */ interface IIpConnectivityMetrics { @@ -29,6 +32,11 @@ interface IIpConnectivityMetrics { */ int logEvent(in ConnectivityMetricsEvent event); + void logDefaultNetworkValidity(boolean valid); + void logDefaultNetworkEvent(in Network defaultNetwork, int score, boolean validated, + in LinkProperties lp, in NetworkCapabilities nc, in Network previousDefaultNetwork, + int previousScore, in LinkProperties previousLp, in NetworkCapabilities previousNc); + /** * Callback can be registered by DevicePolicyManager or NetworkWatchlistService only. * @return status {@code true} if registering/unregistering of the callback was successful, diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl index 37813ce11a5f..0a6be20226b8 100644 --- a/core/java/android/net/INetworkManagementEventObserver.aidl +++ b/core/java/android/net/INetworkManagementEventObserver.aidl @@ -85,14 +85,14 @@ oneway interface INetworkManagementEventObserver { /** * Interface data activity status is changed. * - * @param networkType The legacy network type of the data activity change. + * @param transportType The transport type of the data activity change. * @param active True if the interface is actively transmitting data, false if it is idle. * @param tsNanos Elapsed realtime in nanos when the state of the network interface changed. * @param uid Uid of this event. It represents the uid that was responsible for waking the * radio. For those events that are reported by system itself, not from specific uid, * use -1 for the events which means no uid. */ - void interfaceClassDataActivityChanged(int networkType, boolean active, long tsNanos, int uid); + void interfaceClassDataActivityChanged(int transportType, boolean active, long tsNanos, int uid); /** * Information about available DNS servers has been received. diff --git a/core/java/android/net/NattKeepalivePacketData.aidl b/core/java/android/net/NattKeepalivePacketData.aidl new file mode 100644 index 000000000000..af644b54827c --- /dev/null +++ b/core/java/android/net/NattKeepalivePacketData.aidl @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing perNmissions and + * limitations under the License. + */ +package android.net; + +@JavaOnlyStableParcelable parcelable NattKeepalivePacketData; diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 6780167fa63e..4166c2c4f244 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -29,11 +29,12 @@ import android.os.ConditionVariable; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.os.Messenger; +import android.os.RemoteException; import android.util.Log; +import com.android.connectivity.aidl.INetworkAgent; +import com.android.connectivity.aidl.INetworkAgentRegistry; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; import java.lang.annotation.Retention; @@ -94,12 +95,18 @@ public abstract class NetworkAgent { @Nullable private volatile Network mNetwork; + @Nullable + private volatile INetworkAgentRegistry mRegistry; + + private interface RegistryAction { + void execute(@NonNull INetworkAgentRegistry registry) throws RemoteException; + } + private final Handler mHandler; - private volatile AsyncChannel mAsyncChannel; private final String LOG_TAG; private static final boolean DBG = true; private static final boolean VDBG = false; - private final ArrayList<Message> mPreConnectedQueue = new ArrayList<Message>(); + private final ArrayList<RegistryAction> mPreConnectedQueue = new ArrayList<>(); private volatile long mLastBwRefreshTime = 0; private static final long BW_REFRESH_MIN_WIN_MS = 500; private boolean mBandwidthUpdateScheduled = false; @@ -329,6 +336,17 @@ public abstract class NetworkAgent { */ public static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER = BASE + 17; + /** + * Sent by ConnectivityService to the NetworkAgent to complete the bidirectional connection. + * obj = INetworkAgentRegistry + */ + private static final int EVENT_AGENT_CONNECTED = BASE + 18; + + /** + * Sent by ConnectivityService to the NetworkAgent to inform the agent that it was disconnected. + */ + private static final int EVENT_AGENT_DISCONNECTED = BASE + 19; + private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) { // The subtype can be changed with (TODO) setLegacySubtype, but it starts // with 0 (TelephonyManager.NETWORK_TYPE_UNKNOWN) and an empty description. @@ -402,36 +420,33 @@ public abstract class NetworkAgent { @Override public void handleMessage(Message msg) { switch (msg.what) { - case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: { - if (mAsyncChannel != null) { + case EVENT_AGENT_CONNECTED: { + if (mRegistry != null) { log("Received new connection while already connected!"); } else { if (VDBG) log("NetworkAgent fully connected"); - AsyncChannel ac = new AsyncChannel(); - ac.connected(null, this, msg.replyTo); - ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, - AsyncChannel.STATUS_SUCCESSFUL); synchronized (mPreConnectedQueue) { - mAsyncChannel = ac; - for (Message m : mPreConnectedQueue) { - ac.sendMessage(m); + final INetworkAgentRegistry registry = (INetworkAgentRegistry) msg.obj; + mRegistry = registry; + for (RegistryAction a : mPreConnectedQueue) { + try { + a.execute(registry); + } catch (RemoteException e) { + Log.wtf(LOG_TAG, "Communication error with registry", e); + // Fall through + } } mPreConnectedQueue.clear(); } } break; } - case AsyncChannel.CMD_CHANNEL_DISCONNECT: { - if (VDBG) log("CMD_CHANNEL_DISCONNECT"); - if (mAsyncChannel != null) mAsyncChannel.disconnect(); - break; - } - case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { + case EVENT_AGENT_DISCONNECTED: { if (DBG) log("NetworkAgent channel lost"); // let the client know CS is done with us. onNetworkUnwanted(); synchronized (mPreConnectedQueue) { - mAsyncChannel = null; + mRegistry = null; } break; } @@ -494,15 +509,7 @@ public abstract class NetworkAgent { } case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: { - ArrayList<Integer> thresholds = - ((Bundle) msg.obj).getIntegerArrayList("thresholds"); - // TODO: Change signal strength thresholds API to use an ArrayList<Integer> - // rather than convert to int[]. - int[] intThresholds = new int[(thresholds != null) ? thresholds.size() : 0]; - for (int i = 0; i < intThresholds.length; i++) { - intThresholds[i] = thresholds.get(i); - } - onSignalStrengthThresholdsUpdated(intThresholds); + onSignalStrengthThresholdsUpdated((int[]) msg.obj); break; } case CMD_PREVENT_AUTOMATIC_RECONNECT: { @@ -541,7 +548,7 @@ public abstract class NetworkAgent { } final ConnectivityManager cm = (ConnectivityManager) mInitialConfiguration.context .getSystemService(Context.CONNECTIVITY_SERVICE); - mNetwork = cm.registerNetworkAgent(new Messenger(mHandler), + mNetwork = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler), new NetworkInfo(mInitialConfiguration.info), mInitialConfiguration.properties, mInitialConfiguration.capabilities, mInitialConfiguration.score, mInitialConfiguration.config, providerId); @@ -550,6 +557,95 @@ public abstract class NetworkAgent { return mNetwork; } + private static class NetworkAgentBinder extends INetworkAgent.Stub { + private final Handler mHandler; + + private NetworkAgentBinder(Handler handler) { + mHandler = handler; + } + + @Override + public void onRegistered(@NonNull INetworkAgentRegistry registry) { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_AGENT_CONNECTED, registry)); + } + + @Override + public void onDisconnected() { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_AGENT_DISCONNECTED)); + } + + @Override + public void onBandwidthUpdateRequested() { + mHandler.sendMessage(mHandler.obtainMessage(CMD_REQUEST_BANDWIDTH_UPDATE)); + } + + @Override + public void onValidationStatusChanged( + int validationStatus, @Nullable String captivePortalUrl) { + // TODO: consider using a parcelable as argument when the interface is structured + Bundle redirectUrlBundle = new Bundle(); + redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, captivePortalUrl); + mHandler.sendMessage(mHandler.obtainMessage(CMD_REPORT_NETWORK_STATUS, + validationStatus, 0, redirectUrlBundle)); + } + + @Override + public void onSaveAcceptUnvalidated(boolean acceptUnvalidated) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_SAVE_ACCEPT_UNVALIDATED, + acceptUnvalidated ? 1 : 0, 0)); + } + + @Override + public void onStartNattSocketKeepalive(int slot, int intervalDurationMs, + @NonNull NattKeepalivePacketData packetData) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, + slot, intervalDurationMs, packetData)); + } + + @Override + public void onStartTcpSocketKeepalive(int slot, int intervalDurationMs, + @NonNull TcpKeepalivePacketData packetData) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, + slot, intervalDurationMs, packetData)); + } + + @Override + public void onStopSocketKeepalive(int slot) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_STOP_SOCKET_KEEPALIVE, slot, 0)); + } + + @Override + public void onSignalStrengthThresholdsUpdated(@NonNull int[] thresholds) { + mHandler.sendMessage(mHandler.obtainMessage( + CMD_SET_SIGNAL_STRENGTH_THRESHOLDS, thresholds)); + } + + @Override + public void onPreventAutomaticReconnect() { + mHandler.sendMessage(mHandler.obtainMessage(CMD_PREVENT_AUTOMATIC_RECONNECT)); + } + + @Override + public void onAddNattKeepalivePacketFilter(int slot, + @NonNull NattKeepalivePacketData packetData) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER, + slot, 0, packetData)); + } + + @Override + public void onAddTcpKeepalivePacketFilter(int slot, + @NonNull TcpKeepalivePacketData packetData) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER, + slot, 0, packetData)); + } + + @Override + public void onRemoveKeepalivePacketFilter(int slot) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, + slot, 0)); + } + } + /** * Register this network agent with a testing harness. * @@ -559,13 +655,13 @@ public abstract class NetworkAgent { * * @hide */ - public Messenger registerForTest(final Network network) { + public INetworkAgent registerForTest(final Network network) { log("Registering NetworkAgent for test"); synchronized (mRegisterLock) { mNetwork = network; mInitialConfiguration = null; } - return new Messenger(mHandler); + return new NetworkAgentBinder(mHandler); } /** @@ -589,29 +685,17 @@ public abstract class NetworkAgent { return mNetwork; } - private void queueOrSendMessage(int what, Object obj) { - queueOrSendMessage(what, 0, 0, obj); - } - - private void queueOrSendMessage(int what, int arg1, int arg2) { - queueOrSendMessage(what, arg1, arg2, null); - } - - private void queueOrSendMessage(int what, int arg1, int arg2, Object obj) { - Message msg = Message.obtain(); - msg.what = what; - msg.arg1 = arg1; - msg.arg2 = arg2; - msg.obj = obj; - queueOrSendMessage(msg); - } - - private void queueOrSendMessage(Message msg) { + private void queueOrSendMessage(@NonNull RegistryAction action) { synchronized (mPreConnectedQueue) { - if (mAsyncChannel != null) { - mAsyncChannel.sendMessage(msg); + if (mRegistry != null) { + try { + action.execute(mRegistry); + } catch (RemoteException e) { + Log.wtf(LOG_TAG, "Error executing registry action", e); + // Fall through: the channel is asynchronous and does not report errors back + } } else { - mPreConnectedQueue.add(msg); + mPreConnectedQueue.add(action); } } } @@ -622,7 +706,8 @@ public abstract class NetworkAgent { */ public final void sendLinkProperties(@NonNull LinkProperties linkProperties) { Objects.requireNonNull(linkProperties); - queueOrSendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, new LinkProperties(linkProperties)); + final LinkProperties lp = new LinkProperties(linkProperties); + queueOrSendMessage(reg -> reg.sendLinkProperties(lp)); } /** @@ -647,9 +732,7 @@ public abstract class NetworkAgent { public final void setUnderlyingNetworks(@Nullable List<Network> underlyingNetworks) { final ArrayList<Network> underlyingArray = (underlyingNetworks != null) ? new ArrayList<>(underlyingNetworks) : null; - final Bundle bundle = new Bundle(); - bundle.putParcelableArrayList(UNDERLYING_NETWORKS_KEY, underlyingArray); - queueOrSendMessage(EVENT_UNDERLYING_NETWORKS_CHANGED, bundle); + queueOrSendMessage(reg -> reg.sendUnderlyingNetworks(underlyingArray)); } /** @@ -659,7 +742,7 @@ public abstract class NetworkAgent { public void markConnected() { mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null /* reason */, mNetworkInfo.getExtraInfo()); - queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, mNetworkInfo); + queueOrSendNetworkInfo(mNetworkInfo); } /** @@ -672,7 +755,7 @@ public abstract class NetworkAgent { // When unregistering an agent nobody should use the extrainfo (or reason) any more. mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null /* reason */, null /* extraInfo */); - queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, mNetworkInfo); + queueOrSendNetworkInfo(mNetworkInfo); } /** @@ -689,7 +772,7 @@ public abstract class NetworkAgent { @Deprecated public void setLegacySubtype(final int legacySubtype, @NonNull final String legacySubtypeName) { mNetworkInfo.setSubtype(legacySubtype, legacySubtypeName); - queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, mNetworkInfo); + queueOrSendNetworkInfo(mNetworkInfo); } /** @@ -711,7 +794,7 @@ public abstract class NetworkAgent { @Deprecated public void setLegacyExtraInfo(@Nullable final String extraInfo) { mNetworkInfo.setExtraInfo(extraInfo); - queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, mNetworkInfo); + queueOrSendNetworkInfo(mNetworkInfo); } /** @@ -720,7 +803,11 @@ public abstract class NetworkAgent { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public final void sendNetworkInfo(NetworkInfo networkInfo) { - queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, new NetworkInfo(networkInfo)); + queueOrSendNetworkInfo(new NetworkInfo(networkInfo)); + } + + private void queueOrSendNetworkInfo(NetworkInfo networkInfo) { + queueOrSendMessage(reg -> reg.sendNetworkInfo(networkInfo)); } /** @@ -731,8 +818,8 @@ public abstract class NetworkAgent { Objects.requireNonNull(networkCapabilities); mBandwidthUpdatePending.set(false); mLastBwRefreshTime = System.currentTimeMillis(); - queueOrSendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED, - new NetworkCapabilities(networkCapabilities)); + final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities); + queueOrSendMessage(reg -> reg.sendNetworkCapabilities(nc)); } /** @@ -744,7 +831,7 @@ public abstract class NetworkAgent { if (score < 0) { throw new IllegalArgumentException("Score must be >= 0"); } - queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, score, 0); + queueOrSendMessage(reg -> reg.sendScore(score)); } /** @@ -784,9 +871,8 @@ public abstract class NetworkAgent { * @hide should move to NetworkAgentConfig. */ public void explicitlySelected(boolean explicitlySelected, boolean acceptUnvalidated) { - queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED, - explicitlySelected ? 1 : 0, - acceptUnvalidated ? 1 : 0); + queueOrSendMessage(reg -> reg.sendExplicitlySelected( + explicitlySelected, acceptUnvalidated)); } /** @@ -909,7 +995,7 @@ public abstract class NetworkAgent { */ public final void sendSocketKeepaliveEvent(int slot, @SocketKeepalive.KeepaliveEvent int event) { - queueOrSendMessage(EVENT_SOCKET_KEEPALIVE, slot, event); + queueOrSendMessage(reg -> reg.sendSocketKeepaliveEvent(slot, event)); } /** @hide TODO delete once callers have moved to sendSocketKeepaliveEvent */ public void onSocketKeepaliveEvent(int slot, int reason) { diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 1a37fb9fb690..85973293e63f 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -171,6 +171,8 @@ public final class NetworkCapabilities implements Parcelable { NET_CAPABILITY_PARTIAL_CONNECTIVITY, NET_CAPABILITY_TEMPORARILY_NOT_METERED, NET_CAPABILITY_OEM_PRIVATE, + NET_CAPABILITY_VEHICLE_INTERNAL, + NET_CAPABILITY_NOT_VCN_MANAGED, }) public @interface NetCapability { } @@ -357,8 +359,26 @@ public final class NetworkCapabilities implements Parcelable { @SystemApi public static final int NET_CAPABILITY_OEM_PRIVATE = 26; + /** + * Indicates this is an internal vehicle network, meant to communicate with other + * automotive systems. + * + * @hide + */ + @SystemApi + public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27; + + /** + * Indicates that this network is not managed by a Virtual Carrier Network (VCN). + * + * TODO(b/177299683): Add additional clarifying javadoc. + * @hide + */ + @SystemApi + public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; + private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS; - private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_OEM_PRIVATE; + private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_VCN_MANAGED; /** * Network capabilities that are expected to be mutable, i.e., can change while a particular @@ -375,7 +395,8 @@ public final class NetworkCapabilities implements Parcelable { | (1 << NET_CAPABILITY_NOT_CONGESTED) | (1 << NET_CAPABILITY_NOT_SUSPENDED) | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY) - | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED); + | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED) + | (1 << NET_CAPABILITY_NOT_VCN_MANAGED); /** * Network capabilities that are not allowed in NetworkRequests. This exists because the @@ -384,16 +405,22 @@ public final class NetworkCapabilities implements Parcelable { * can get into a cycle where the NetworkFactory endlessly churns out NetworkAgents that then * get immediately torn down because they do not have the requested capability. */ + // Note that as a historical exception, the TRUSTED and NOT_VCN_MANAGED capabilities + // are mutable but requestable. Factories are responsible for not getting + // in an infinite loop about these. private static final long NON_REQUESTABLE_CAPABILITIES = - MUTABLE_CAPABILITIES & ~(1 << NET_CAPABILITY_TRUSTED); + MUTABLE_CAPABILITIES + & ~(1 << NET_CAPABILITY_TRUSTED) + & ~(1 << NET_CAPABILITY_NOT_VCN_MANAGED); /** * Capabilities that are set by default when the object is constructed. */ private static final long DEFAULT_CAPABILITIES = - (1 << NET_CAPABILITY_NOT_RESTRICTED) | - (1 << NET_CAPABILITY_TRUSTED) | - (1 << NET_CAPABILITY_NOT_VPN); + (1 << NET_CAPABILITY_NOT_RESTRICTED) + | (1 << NET_CAPABILITY_TRUSTED) + | (1 << NET_CAPABILITY_NOT_VPN) + | (1 << NET_CAPABILITY_NOT_VCN_MANAGED); /** * Capabilities that suggest that a network is restricted. @@ -401,15 +428,16 @@ public final class NetworkCapabilities implements Parcelable { */ @VisibleForTesting /* package */ static final long RESTRICTED_CAPABILITIES = - (1 << NET_CAPABILITY_CBS) | - (1 << NET_CAPABILITY_DUN) | - (1 << NET_CAPABILITY_EIMS) | - (1 << NET_CAPABILITY_FOTA) | - (1 << NET_CAPABILITY_IA) | - (1 << NET_CAPABILITY_IMS) | - (1 << NET_CAPABILITY_RCS) | - (1 << NET_CAPABILITY_XCAP) | - (1 << NET_CAPABILITY_MCX); + (1 << NET_CAPABILITY_CBS) + | (1 << NET_CAPABILITY_DUN) + | (1 << NET_CAPABILITY_EIMS) + | (1 << NET_CAPABILITY_FOTA) + | (1 << NET_CAPABILITY_IA) + | (1 << NET_CAPABILITY_IMS) + | (1 << NET_CAPABILITY_MCX) + | (1 << NET_CAPABILITY_RCS) + | (1 << NET_CAPABILITY_VEHICLE_INTERNAL) + | (1 << NET_CAPABILITY_XCAP); /** * Capabilities that force network to be restricted. @@ -452,7 +480,8 @@ public final class NetworkCapabilities implements Parcelable { | (1 << NET_CAPABILITY_NOT_VPN) | (1 << NET_CAPABILITY_NOT_ROAMING) | (1 << NET_CAPABILITY_NOT_CONGESTED) - | (1 << NET_CAPABILITY_NOT_SUSPENDED); + | (1 << NET_CAPABILITY_NOT_SUSPENDED) + | (1 << NET_CAPABILITY_NOT_VCN_MANAGED); /** * Adds the given capability to this {@code NetworkCapability} instance. @@ -1939,6 +1968,8 @@ public final class NetworkCapabilities implements Parcelable { case NET_CAPABILITY_PARTIAL_CONNECTIVITY: return "PARTIAL_CONNECTIVITY"; case NET_CAPABILITY_TEMPORARILY_NOT_METERED: return "TEMPORARILY_NOT_METERED"; case NET_CAPABILITY_OEM_PRIVATE: return "OEM_PRIVATE"; + case NET_CAPABILITY_VEHICLE_INTERNAL: return "NET_CAPABILITY_VEHICLE_INTERNAL"; + case NET_CAPABILITY_NOT_VCN_MANAGED: return "NOT_VCN_MANAGED"; default: return Integer.toString(capability); } } diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index ce16a7835179..c029deae09df 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -32,8 +32,8 @@ import android.content.pm.Signature; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.os.Build; +import android.os.Process; import android.os.RemoteException; -import android.os.UserHandle; import android.telephony.SubscriptionPlan; import android.util.DebugUtils; import android.util.Pair; @@ -122,17 +122,26 @@ public class NetworkPolicyManager { * @hide */ public static final int RULE_REJECT_ALL = 1 << 6; + /** + * Reject traffic on all networks for restricted networking mode. + */ + public static final int RULE_REJECT_RESTRICTED_MODE = 1 << 10; /** * Mask used to get the {@code RULE_xxx_METERED} rules * @hide */ - public static final int MASK_METERED_NETWORKS = 0b00001111; + public static final int MASK_METERED_NETWORKS = 0b000000001111; /** * Mask used to get the {@code RULE_xxx_ALL} rules * @hide */ - public static final int MASK_ALL_NETWORKS = 0b11110000; + public static final int MASK_ALL_NETWORKS = 0b000011110000; + /** + * Mask used to get the {@code RULE_xxx_RESTRICTED_MODE} rules + * @hide + */ + public static final int MASK_RESTRICTED_MODE_NETWORKS = 0b111100000000; /** @hide */ public static final int FIREWALL_RULE_DEFAULT = 0; @@ -433,6 +442,24 @@ public class NetworkPolicyManager { } /** + * Check that networking is blocked for the given uid. + * + * @param uid The target uid. + * @param meteredNetwork True if the network is metered. + * @return true if networking is blocked for the given uid according to current networking + * policies. + * + * @hide + */ + public boolean isUidNetworkingBlocked(int uid, boolean meteredNetwork) { + try { + return mService.isUidNetworkingBlocked(uid, meteredNetwork); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Get multipath preference for the given network. */ public int getMultipathPreference(Network network) { @@ -473,7 +500,7 @@ public class NetworkPolicyManager { @Deprecated public static boolean isUidValidForPolicy(Context context, int uid) { // first, quick-reject non-applications - if (!UserHandle.isApp(uid)) { + if (!Process.isApplicationUid(uid)) { return false; } diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java index dc16d7422038..f0c637c76ec5 100644 --- a/core/java/android/net/NetworkRequest.java +++ b/core/java/android/net/NetworkRequest.java @@ -40,6 +40,18 @@ import java.util.Set; */ public class NetworkRequest implements Parcelable { /** + * The first requestId value that will be allocated. + * @hide only used by ConnectivityService. + */ + public static final int FIRST_REQUEST_ID = 1; + + /** + * The requestId value that represents the absence of a request. + * @hide only used by ConnectivityService. + */ + public static final int REQUEST_ID_NONE = -1; + + /** * The {@link NetworkCapabilities} that define this request. * @hide */ diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java index bf25602041cf..f41306301d42 100644 --- a/core/java/android/net/NetworkStatsHistory.java +++ b/core/java/android/net/NetworkStatsHistory.java @@ -45,8 +45,8 @@ import com.android.internal.util.IndentingPrintWriter; import libcore.util.EmptyArray; import java.io.CharArrayWriter; -import java.io.DataInputStream; -import java.io.DataOutputStream; +import java.io.DataInput; +import java.io.DataOutput; import java.io.IOException; import java.io.PrintWriter; import java.net.ProtocolException; @@ -162,7 +162,7 @@ public class NetworkStatsHistory implements Parcelable { out.writeLong(totalBytes); } - public NetworkStatsHistory(DataInputStream in) throws IOException { + public NetworkStatsHistory(DataInput in) throws IOException { final int version = in.readInt(); switch (version) { case VERSION_INIT: { @@ -204,7 +204,7 @@ public class NetworkStatsHistory implements Parcelable { } } - public void writeToStream(DataOutputStream out) throws IOException { + public void writeToStream(DataOutput out) throws IOException { out.writeInt(VERSION_ADD_ACTIVE); out.writeLong(bucketDuration); writeVarLongArray(out, bucketStart, bucketCount); @@ -768,7 +768,7 @@ public class NetworkStatsHistory implements Parcelable { */ public static class DataStreamUtils { @Deprecated - public static long[] readFullLongArray(DataInputStream in) throws IOException { + public static long[] readFullLongArray(DataInput in) throws IOException { final int size = in.readInt(); if (size < 0) throw new ProtocolException("negative array size"); final long[] values = new long[size]; @@ -781,7 +781,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Read variable-length {@link Long} using protobuf-style approach. */ - public static long readVarLong(DataInputStream in) throws IOException { + public static long readVarLong(DataInput in) throws IOException { int shift = 0; long result = 0; while (shift < 64) { @@ -797,7 +797,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Write variable-length {@link Long} using protobuf-style approach. */ - public static void writeVarLong(DataOutputStream out, long value) throws IOException { + public static void writeVarLong(DataOutput out, long value) throws IOException { while (true) { if ((value & ~0x7FL) == 0) { out.writeByte((int) value); @@ -809,7 +809,7 @@ public class NetworkStatsHistory implements Parcelable { } } - public static long[] readVarLongArray(DataInputStream in) throws IOException { + public static long[] readVarLongArray(DataInput in) throws IOException { final int size = in.readInt(); if (size == -1) return null; if (size < 0) throw new ProtocolException("negative array size"); @@ -820,7 +820,7 @@ public class NetworkStatsHistory implements Parcelable { return values; } - public static void writeVarLongArray(DataOutputStream out, long[] values, int size) + public static void writeVarLongArray(DataOutput out, long[] values, int size) throws IOException { if (values == null) { out.writeInt(-1); diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java index a7dce18a4ff9..d007a9520cb5 100644 --- a/core/java/android/net/SocketKeepalive.java +++ b/core/java/android/net/SocketKeepalive.java @@ -187,38 +187,54 @@ public abstract class SocketKeepalive implements AutoCloseable { mCallback = new ISocketKeepaliveCallback.Stub() { @Override public void onStarted(int slot) { - Binder.withCleanCallingIdentity(() -> - mExecutor.execute(() -> { - mSlot = slot; - callback.onStarted(); - })); + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mSlot = slot; + callback.onStarted(); + }); + } finally { + Binder.restoreCallingIdentity(token); + } } @Override public void onStopped() { - Binder.withCleanCallingIdentity(() -> - executor.execute(() -> { - mSlot = null; - callback.onStopped(); - })); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> { + mSlot = null; + callback.onStopped(); + }); + } finally { + Binder.restoreCallingIdentity(token); + } } @Override public void onError(int error) { - Binder.withCleanCallingIdentity(() -> - executor.execute(() -> { - mSlot = null; - callback.onError(error); - })); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> { + mSlot = null; + callback.onError(error); + }); + } finally { + Binder.restoreCallingIdentity(token); + } } @Override public void onDataReceived() { - Binder.withCleanCallingIdentity(() -> - executor.execute(() -> { - mSlot = null; - callback.onDataReceived(); - })); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> { + mSlot = null; + callback.onDataReceived(); + }); + } finally { + Binder.restoreCallingIdentity(token); + } } }; } diff --git a/core/java/android/net/TcpKeepalivePacketData.aidl b/core/java/android/net/TcpKeepalivePacketData.aidl new file mode 100644 index 000000000000..fdc7af9fed5c --- /dev/null +++ b/core/java/android/net/TcpKeepalivePacketData.aidl @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing perNmissions and + * limitations under the License. + */ +package android.net; + +@JavaOnlyStableParcelable parcelable TcpKeepalivePacketData; diff --git a/core/java/android/net/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java index a008d855025a..58ea91573775 100644 --- a/core/java/android/net/metrics/IpConnectivityLog.java +++ b/core/java/android/net/metrics/IpConnectivityLog.java @@ -17,10 +17,13 @@ package android.net.metrics; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.net.ConnectivityMetricsEvent; import android.net.IIpConnectivityMetrics; +import android.net.LinkProperties; import android.net.Network; +import android.net.NetworkCapabilities; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; @@ -66,6 +69,9 @@ public class IpConnectivityLog { final IIpConnectivityMetrics service = IIpConnectivityMetrics.Stub.asInterface(ServiceManager.getService(SERVICE_NAME)); if (service == null) { + if (DBG) { + Log.d(TAG, SERVICE_NAME + " service was not ready"); + } return false; } // Two threads racing here will write the same pointer because getService @@ -83,9 +89,6 @@ public class IpConnectivityLog { */ public boolean log(@NonNull ConnectivityMetricsEvent ev) { if (!checkLoggerService()) { - if (DBG) { - Log.d(TAG, SERVICE_NAME + " service was not ready"); - } return false; } if (ev.timestamp == 0) { @@ -161,6 +164,56 @@ public class IpConnectivityLog { return log(makeEv(data)); } + /** + * Logs the validation status of the default network. + * @param valid whether the current default network was validated (i.e., whether it had + * {@link NetworkCapabilities.NET_CAPABILITY_VALIDATED} + * @return true if the event was successfully logged. + * @hide + */ + public boolean logDefaultNetworkValidity(boolean valid) { + if (!checkLoggerService()) { + return false; + } + try { + mService.logDefaultNetworkValidity(valid); + } catch (RemoteException ignored) { + // Only called within the system server. + } + return true; + } + + /** + * Logs a change in the default network. + * + * @param defaultNetwork the current default network + * @param score the current score of {@code defaultNetwork} + * @param lp the {@link LinkProperties} of {@code defaultNetwork} + * @param nc the {@link NetworkCapabilities} of the {@code defaultNetwork} + * @param validated whether {@code defaultNetwork} network is validated + * @param previousDefaultNetwork the previous default network + * @param previousScore the score of {@code previousDefaultNetwork} + * @param previousLp the {@link LinkProperties} of {@code previousDefaultNetwork} + * @param previousNc the {@link NetworkCapabilities} of {@code previousDefaultNetwork} + * @return true if the event was successfully logged. + * @hide + */ + public boolean logDefaultNetworkEvent(@Nullable Network defaultNetwork, int score, + boolean validated, @Nullable LinkProperties lp, @Nullable NetworkCapabilities nc, + @Nullable Network previousDefaultNetwork, int previousScore, + @Nullable LinkProperties previousLp, @Nullable NetworkCapabilities previousNc) { + if (!checkLoggerService()) { + return false; + } + try { + mService.logDefaultNetworkEvent(defaultNetwork, score, validated, lp, nc, + previousDefaultNetwork, previousScore, previousLp, previousNc); + } catch (RemoteException ignored) { + // Only called within the system server. + } + return true; + } + private static ConnectivityMetricsEvent makeEv(Event data) { ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent(); ev.data = data; diff --git a/core/java/android/net/vcn/IVcnManagementService.aidl b/core/java/android/net/vcn/IVcnManagementService.aidl index 9dd01140b413..04b585cdf420 100644 --- a/core/java/android/net/vcn/IVcnManagementService.aidl +++ b/core/java/android/net/vcn/IVcnManagementService.aidl @@ -23,6 +23,6 @@ import android.os.ParcelUuid; * @hide */ interface IVcnManagementService { - void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config); + void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config, in String opPkgName); void clearVcnConfig(in ParcelUuid subscriptionGroup); } diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java index d4a3fa7411b1..ede8faaaf261 100644 --- a/core/java/android/net/vcn/VcnConfig.java +++ b/core/java/android/net/vcn/VcnConfig.java @@ -19,6 +19,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Context; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; @@ -45,11 +46,17 @@ import java.util.Set; public final class VcnConfig implements Parcelable { @NonNull private static final String TAG = VcnConfig.class.getSimpleName(); + private static final String PACKAGE_NAME_KEY = "mPackageName"; + @NonNull private final String mPackageName; + private static final String GATEWAY_CONNECTION_CONFIGS_KEY = "mGatewayConnectionConfigs"; @NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs; - private VcnConfig(@NonNull Set<VcnGatewayConnectionConfig> tunnelConfigs) { - mGatewayConnectionConfigs = Collections.unmodifiableSet(tunnelConfigs); + private VcnConfig( + @NonNull String packageName, + @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs) { + mPackageName = packageName; + mGatewayConnectionConfigs = Collections.unmodifiableSet(gatewayConnectionConfigs); validate(); } @@ -61,6 +68,8 @@ public final class VcnConfig implements Parcelable { */ @VisibleForTesting(visibility = Visibility.PRIVATE) public VcnConfig(@NonNull PersistableBundle in) { + mPackageName = in.getString(PACKAGE_NAME_KEY); + final PersistableBundle gatewayConnectionConfigsBundle = in.getPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY); mGatewayConnectionConfigs = @@ -72,8 +81,19 @@ public final class VcnConfig implements Parcelable { } private void validate() { + Objects.requireNonNull(mPackageName, "packageName was null"); Preconditions.checkCollectionNotEmpty( - mGatewayConnectionConfigs, "gatewayConnectionConfigs"); + mGatewayConnectionConfigs, "gatewayConnectionConfigs was empty"); + } + + /** + * Retrieve the package name of the provisioning app. + * + * @hide + */ + @NonNull + public String getProvisioningPackageName() { + return mPackageName; } /** Retrieves the set of configured tunnels. */ @@ -91,6 +111,8 @@ public final class VcnConfig implements Parcelable { public PersistableBundle toPersistableBundle() { final PersistableBundle result = new PersistableBundle(); + result.putString(PACKAGE_NAME_KEY, mPackageName); + final PersistableBundle gatewayConnectionConfigsBundle = PersistableBundleUtils.fromList( new ArrayList<>(mGatewayConnectionConfigs), @@ -102,7 +124,7 @@ public final class VcnConfig implements Parcelable { @Override public int hashCode() { - return Objects.hash(mGatewayConnectionConfigs); + return Objects.hash(mPackageName, mGatewayConnectionConfigs); } @Override @@ -112,7 +134,8 @@ public final class VcnConfig implements Parcelable { } final VcnConfig rhs = (VcnConfig) other; - return mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs); + return mPackageName.equals(rhs.mPackageName) + && mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs); } // Parcelable methods @@ -143,9 +166,17 @@ public final class VcnConfig implements Parcelable { /** This class is used to incrementally build {@link VcnConfig} objects. */ public static class Builder { + @NonNull private final String mPackageName; + @NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs = new ArraySet<>(); + public Builder(@NonNull Context context) { + Objects.requireNonNull(context, "context was null"); + + mPackageName = context.getOpPackageName(); + } + /** * Adds a configuration for an individual gateway connection. * @@ -168,7 +199,7 @@ public final class VcnConfig implements Parcelable { */ @NonNull public VcnConfig build() { - return new VcnConfig(mGatewayConnectionConfigs); + return new VcnConfig(mPackageName, mGatewayConnectionConfigs); } } } diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index 19c183f9fe9c..b881a339535b 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -101,7 +101,7 @@ public final class VcnManager { requireNonNull(config, "config was null"); try { - mService.setVcnConfig(subscriptionGroup, config); + mService.setVcnConfig(subscriptionGroup, config, mContext.getOpPackageName()); } catch (ServiceSpecificException e) { throw new IOException(e); } catch (RemoteException e) { diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS index 8c77a2099975..6c49b365c4f3 100644 --- a/core/java/android/os/OWNERS +++ b/core/java/android/os/OWNERS @@ -22,6 +22,10 @@ per-file BatteryStats* = file:/BATTERY_STATS_OWNERS per-file BatteryUsageStats* = file:/BATTERY_STATS_OWNERS per-file PowerComponents.java = file:/BATTERY_STATS_OWNERS +# Multiuser +per-file IUser* = file:/MULTIUSER_OWNERS +per-file User* = file:/MULTIUSER_OWNERS + # Binder per-file BadParcelableException.java = file:platform/frameworks/native:/libs/binder/OWNERS per-file Binder.java = file:platform/frameworks/native:/libs/binder/OWNERS diff --git a/core/java/android/os/TransactionTooLargeException.java b/core/java/android/os/TransactionTooLargeException.java index 10abf26420f1..4d5b2a10f3fe 100644 --- a/core/java/android/os/TransactionTooLargeException.java +++ b/core/java/android/os/TransactionTooLargeException.java @@ -23,9 +23,11 @@ import android.os.RemoteException; * During a remote procedure call, the arguments and the return value of the call * are transferred as {@link Parcel} objects stored in the Binder transaction buffer. * If the arguments or the return value are too large to fit in the transaction buffer, - * then the call will fail and {@link TransactionTooLargeException} will be thrown. + * then the call will fail. {@link TransactionTooLargeException} is thrown as a + * heuristic when a transaction is large, and it fails, since these are the transactions + * which are most likely to overfill the transaction buffer. * </p><p> - * The Binder transaction buffer has a limited fixed size, currently 1Mb, which + * The Binder transaction buffer has a limited fixed size, currently 1MB, which * is shared by all transactions in progress for the process. Consequently this * exception can be thrown when there are many transactions in progress even when * most of the individual transactions are of moderate size. diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java index 58268e2fc914..5aa4e27fc2f3 100644 --- a/core/java/android/os/image/DynamicSystemClient.java +++ b/core/java/android/os/image/DynamicSystemClient.java @@ -35,8 +35,6 @@ import android.os.Message; import android.os.Messenger; import android.os.ParcelableException; import android.os.RemoteException; -import android.os.SystemProperties; -import android.util.FeatureFlagUtils; import android.util.Slog; import java.lang.annotation.Retention; @@ -68,6 +66,8 @@ import java.util.concurrent.Executor; */ @SystemApi public class DynamicSystemClient { + private static final String TAG = "DynamicSystemClient"; + /** @hide */ @IntDef(prefix = { "STATUS_" }, value = { STATUS_UNKNOWN, @@ -92,8 +92,6 @@ public class DynamicSystemClient { @Retention(RetentionPolicy.SOURCE) public @interface StatusChangedCause {} - private static final String TAG = "DynSystemClient"; - /** Listener for installation status updates. */ public interface OnStatusChangedListener { /** @@ -240,7 +238,7 @@ public class DynamicSystemClient { private class DynSystemServiceConnection implements ServiceConnection { public void onServiceConnected(ComponentName className, IBinder service) { - Slog.v(TAG, "DynSystemService connected"); + Slog.v(TAG, "onServiceConnected: " + className); mService = new Messenger(service); @@ -251,18 +249,12 @@ public class DynamicSystemClient { mService.send(msg); } catch (RemoteException e) { Slog.e(TAG, "Unable to get status from installation service"); - if (mExecutor != null) { - mExecutor.execute(() -> { - mListener.onStatusChanged(STATUS_UNKNOWN, CAUSE_ERROR_IPC, 0, e); - }); - } else { - mListener.onStatusChanged(STATUS_UNKNOWN, CAUSE_ERROR_IPC, 0, e); - } + notifyOnStatusChangedListener(STATUS_UNKNOWN, CAUSE_ERROR_IPC, 0, e); } } public void onServiceDisconnected(ComponentName className) { - Slog.v(TAG, "DynSystemService disconnected"); + Slog.v(TAG, "onServiceDisconnected: " + className); mService = null; } } @@ -311,6 +303,20 @@ public class DynamicSystemClient { mExecutor = null; } + private void notifyOnStatusChangedListener( + int status, int cause, long progress, Throwable detail) { + if (mListener != null) { + if (mExecutor != null) { + mExecutor.execute( + () -> { + mListener.onStatusChanged(status, cause, progress, detail); + }); + } else { + mListener.onStatusChanged(status, cause, progress, detail); + } + } + } + /** * Bind to {@code DynamicSystem} installation service. Binding to the installation service * allows it to send status updates to {@link #OnStatusChangedListener}. It is recommanded @@ -320,11 +326,6 @@ public class DynamicSystemClient { @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM) @SystemApi public void bind() { - if (!featureFlagEnabled()) { - Slog.w(TAG, FeatureFlagUtils.DYNAMIC_SYSTEM + " not enabled; bind() aborted."); - return; - } - Intent intent = new Intent(); intent.setClassName("com.android.dynsystem", "com.android.dynsystem.DynamicSystemInstallationService"); @@ -395,11 +396,6 @@ public class DynamicSystemClient { @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM) public void start(@NonNull Uri systemUrl, @BytesLong long systemSize, @BytesLong long userdataSize) { - if (!featureFlagEnabled()) { - Slog.w(TAG, FeatureFlagUtils.DYNAMIC_SYSTEM + " not enabled; start() aborted."); - return; - } - Intent intent = new Intent(); intent.setClassName("com.android.dynsystem", @@ -407,6 +403,7 @@ public class DynamicSystemClient { intent.setData(systemUrl); intent.setAction(ACTION_START_INSTALL); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(KEY_SYSTEM_SIZE, systemSize); intent.putExtra(KEY_USERDATA_SIZE, userdataSize); @@ -414,11 +411,6 @@ public class DynamicSystemClient { mContext.startActivity(intent); } - private boolean featureFlagEnabled() { - return SystemProperties.getBoolean( - FeatureFlagUtils.PERSIST_PREFIX + FeatureFlagUtils.DYNAMIC_SYSTEM, false); - } - private void handleMessage(Message msg) { switch (msg.what) { case MSG_POST_STATUS: @@ -432,13 +424,7 @@ public class DynamicSystemClient { Throwable detail = t == null ? null : t.getCause(); - if (mExecutor != null) { - mExecutor.execute(() -> { - mListener.onStatusChanged(status, cause, progress, detail); - }); - } else { - mListener.onStatusChanged(status, cause, progress, detail); - } + notifyOnStatusChangedListener(status, cause, progress, detail); break; default: // do nothing diff --git a/core/java/android/os/image/DynamicSystemManager.java b/core/java/android/os/image/DynamicSystemManager.java index 7f01cad940ec..e8e47857ecba 100644 --- a/core/java/android/os/image/DynamicSystemManager.java +++ b/core/java/android/os/image/DynamicSystemManager.java @@ -269,4 +269,16 @@ public class DynamicSystemManager { throw new RuntimeException(e.toString()); } } + + /** + * Returns the suggested scratch partition size for overlayFS. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) + public long suggestScratchSize() { + try { + return mService.suggestScratchSize(); + } catch (RemoteException e) { + throw new RuntimeException(e.toString()); + } + } } diff --git a/core/java/android/os/image/IDynamicSystemService.aidl b/core/java/android/os/image/IDynamicSystemService.aidl index df0a69b47225..a5a40ad55853 100644 --- a/core/java/android/os/image/IDynamicSystemService.aidl +++ b/core/java/android/os/image/IDynamicSystemService.aidl @@ -125,4 +125,9 @@ interface IDynamicSystemService * valid VBMeta block to retrieve the AVB key from. */ boolean getAvbPublicKey(out AvbPublicKey dst); + + /** + * Returns the suggested scratch partition size for overlayFS. + */ + long suggestScratchSize(); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 300bb7603722..4086161603a4 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -13283,6 +13283,16 @@ public final class Settings { @TestApi public static final String HIDDEN_API_POLICY = "hidden_api_policy"; + /** + * Flag for forcing {@link com.android.server.compat.OverrideValidatorImpl} + * to consider this a non-debuggable build. + * + * @hide + */ + public static final String FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT = + "force_non_debuggable_final_build_for_compat"; + + /** * Current version of signed configuration applied. * @@ -14396,6 +14406,17 @@ public final class Settings { */ public static final String NR_NSA_TRACKING_SCREEN_OFF_MODE = "nr_nsa_tracking_screen_off_mode"; + + /** + * Used to enable / disable the Restricted Networking Mode in which network access is + * restricted to apps holding the CONNECTIVITY_USE_RESTRICTED_NETWORKS permission. + * + * Values are: + * 0: disabled + * 1: enabled + * @hide + */ + public static final String RESTRICTED_NETWORKING_MODE = "restricted_networking_mode"; } /** diff --git a/core/java/android/security/ConfirmationPrompt.java b/core/java/android/security/ConfirmationPrompt.java index f67af85d00e3..232903724d82 100644 --- a/core/java/android/security/ConfirmationPrompt.java +++ b/core/java/android/security/ConfirmationPrompt.java @@ -21,6 +21,7 @@ import android.content.ContentResolver; import android.content.Context; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; +import android.security.keystore.AndroidKeyStoreProvider; import android.text.TextUtils; import android.util.Log; @@ -36,15 +37,15 @@ import java.util.concurrent.Executor; * compromised. Implementing confirmation prompts with these guarantees requires dedicated * hardware-support and may not always be available. * - * <p>Confirmation prompts are typically used with an external entitity - the <i>Relying Party</i> - + * <p>Confirmation prompts are typically used with an external entity - the <i>Relying Party</i> - * in the following way. The setup steps are as follows: * <ul> * <li> Before first use, the application generates a key-pair with the * {@link android.security.keystore.KeyGenParameterSpec.Builder#setUserConfirmationRequired - * CONFIRMATION tag} set. Device attestation, - * e.g. {@link java.security.KeyStore#getCertificateChain getCertificateChain()}, is used to - * generate a certificate chain that includes the public key (<code>Kpub</code> in the following) - * of the newly generated key. + * CONFIRMATION tag} set. AndroidKeyStore key attestation, e.g., + * {@link android.security.keystore.KeyGenParameterSpec.Builder#setAttestationChallenge(byte[])} + * is used to generate a certificate chain that includes the public key (<code>Kpub</code> in the + * following) of the newly generated key. * <li> The application sends <code>Kpub</code> and the certificate chain resulting from device * attestation to the <i>Relying Party</i>. * <li> The <i>Relying Party</i> validates the certificate chain which involves checking the root @@ -78,9 +79,10 @@ import java.util.concurrent.Executor; * previously created nonce. If all checks passes, the transaction is executed. * </ul> * - * <p>A common way of implementing the "<code>promptText</code> is what is expected" check in the - * last bullet, is to have the <i>Relying Party</i> generate <code>promptText</code> and store it - * along the nonce in the <code>extraData</code> blob. + * <p>Note: It is vital to check the <code>promptText</code> because this is the only part that + * the user has approved. To avoid writing parsers for all of the possible locales, it is + * recommended that the <i>Relying Party</i> uses the same string generator as used on the device + * and performs a simple string comparison. */ public class ConfirmationPrompt { private static final String TAG = "ConfirmationPrompt"; @@ -92,6 +94,14 @@ public class ConfirmationPrompt { private Context mContext; private final KeyStore mKeyStore = KeyStore.getInstance(); + private AndroidProtectedConfirmation mProtectedConfirmation; + + private AndroidProtectedConfirmation getService() { + if (mProtectedConfirmation == null) { + mProtectedConfirmation = new AndroidProtectedConfirmation(); + } + return mProtectedConfirmation; + } private void doCallback(int responseCode, byte[] dataThatWasConfirmed, ConfirmationCallback callback) { @@ -119,6 +129,32 @@ public class ConfirmationPrompt { } } + private void doCallback2(int responseCode, byte[] dataThatWasConfirmed, + ConfirmationCallback callback) { + switch (responseCode) { + case AndroidProtectedConfirmation.ERROR_OK: + callback.onConfirmed(dataThatWasConfirmed); + break; + + case AndroidProtectedConfirmation.ERROR_CANCELED: + callback.onDismissed(); + break; + + case AndroidProtectedConfirmation.ERROR_ABORTED: + callback.onCanceled(); + break; + + case AndroidProtectedConfirmation.ERROR_SYSTEM_ERROR: + callback.onError(new Exception("System error returned by ConfirmationUI.")); + break; + + default: + callback.onError(new Exception("Unexpected responseCode=" + responseCode + + " from onConfirmtionPromptCompleted() callback.")); + break; + } + } + private final android.os.IBinder mCallbackBinder = new android.security.IConfirmationPromptCallback.Stub() { @Override @@ -144,6 +180,29 @@ public class ConfirmationPrompt { } }; + private final android.security.apc.IConfirmationCallback mConfirmationCallback = + new android.security.apc.IConfirmationCallback.Stub() { + @Override + public void onCompleted(int result, byte[] dataThatWasConfirmed) + throws android.os.RemoteException { + if (mCallback != null) { + ConfirmationCallback callback = mCallback; + Executor executor = mExecutor; + mCallback = null; + mExecutor = null; + if (executor == null) { + doCallback2(result, dataThatWasConfirmed, callback); + } else { + executor.execute(new Runnable() { + @Override public void run() { + doCallback2(result, dataThatWasConfirmed, callback); + } + }); + } + } + } + }; + /** * A builder that collects arguments, to be shown on the system-provided confirmation prompt. */ @@ -211,6 +270,9 @@ public class ConfirmationPrompt { private static final int UI_OPTION_ACCESSIBILITY_MAGNIFIED_FLAG = 1 << 1; private int getUiOptionsAsFlags() { + if (AndroidKeyStoreProvider.isKeystore2Enabled()) { + return getUiOptionsAsFlags2(); + } int uiOptionsAsFlags = 0; ContentResolver contentResolver = mContext.getContentResolver(); int inversionEnabled = Settings.Secure.getInt(contentResolver, @@ -226,6 +288,22 @@ public class ConfirmationPrompt { return uiOptionsAsFlags; } + private int getUiOptionsAsFlags2() { + int uiOptionsAsFlags = 0; + ContentResolver contentResolver = mContext.getContentResolver(); + int inversionEnabled = Settings.Secure.getInt(contentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0); + if (inversionEnabled == 1) { + uiOptionsAsFlags |= AndroidProtectedConfirmation.FLAG_UI_OPTION_INVERTED; + } + float fontScale = Settings.System.getFloat(contentResolver, + Settings.System.FONT_SCALE, (float) 1.0); + if (fontScale > 1.0) { + uiOptionsAsFlags |= AndroidProtectedConfirmation.FLAG_UI_OPTION_MAGNIFIED; + } + return uiOptionsAsFlags; + } + private static boolean isAccessibilityServiceRunning(Context context) { boolean serviceRunning = false; try { @@ -270,29 +348,53 @@ public class ConfirmationPrompt { mCallback = callback; mExecutor = executor; - int uiOptionsAsFlags = getUiOptionsAsFlags(); String locale = Locale.getDefault().toLanguageTag(); - int responseCode = mKeyStore.presentConfirmationPrompt( - mCallbackBinder, mPromptText.toString(), mExtraData, locale, uiOptionsAsFlags); - switch (responseCode) { - case KeyStore.CONFIRMATIONUI_OK: - return; + if (AndroidKeyStoreProvider.isKeystore2Enabled()) { + int uiOptionsAsFlags = getUiOptionsAsFlags2(); + int responseCode = getService().presentConfirmationPrompt( + mConfirmationCallback, mPromptText.toString(), mExtraData, locale, + uiOptionsAsFlags); + switch (responseCode) { + case AndroidProtectedConfirmation.ERROR_OK: + return; + + case AndroidProtectedConfirmation.ERROR_OPERATION_PENDING: + throw new ConfirmationAlreadyPresentingException(); + + case AndroidProtectedConfirmation.ERROR_UNIMPLEMENTED: + throw new ConfirmationNotAvailableException(); - case KeyStore.CONFIRMATIONUI_OPERATION_PENDING: - throw new ConfirmationAlreadyPresentingException(); + default: + // Unexpected error code. + Log.w(TAG, + "Unexpected responseCode=" + responseCode + + " from presentConfirmationPrompt() call."); + throw new IllegalArgumentException(); + } + } else { + int uiOptionsAsFlags = getUiOptionsAsFlags(); + int responseCode = mKeyStore.presentConfirmationPrompt( + mCallbackBinder, mPromptText.toString(), mExtraData, locale, uiOptionsAsFlags); + switch (responseCode) { + case KeyStore.CONFIRMATIONUI_OK: + return; - case KeyStore.CONFIRMATIONUI_UNIMPLEMENTED: - throw new ConfirmationNotAvailableException(); + case KeyStore.CONFIRMATIONUI_OPERATION_PENDING: + throw new ConfirmationAlreadyPresentingException(); - case KeyStore.CONFIRMATIONUI_UIERROR: - throw new IllegalArgumentException(); + case KeyStore.CONFIRMATIONUI_UNIMPLEMENTED: + throw new ConfirmationNotAvailableException(); - default: - // Unexpected error code. - Log.w(TAG, - "Unexpected responseCode=" + responseCode - + " from presentConfirmationPrompt() call."); - throw new IllegalArgumentException(); + case KeyStore.CONFIRMATIONUI_UIERROR: + throw new IllegalArgumentException(); + + default: + // Unexpected error code. + Log.w(TAG, + "Unexpected responseCode=" + responseCode + + " from presentConfirmationPrompt() call."); + throw new IllegalArgumentException(); + } } } @@ -306,17 +408,33 @@ public class ConfirmationPrompt { * @throws IllegalStateException if no prompt is currently being presented. */ public void cancelPrompt() { - int responseCode = mKeyStore.cancelConfirmationPrompt(mCallbackBinder); - if (responseCode == KeyStore.CONFIRMATIONUI_OK) { - return; - } else if (responseCode == KeyStore.CONFIRMATIONUI_OPERATION_PENDING) { - throw new IllegalStateException(); + if (AndroidKeyStoreProvider.isKeystore2Enabled()) { + int responseCode = + getService().cancelConfirmationPrompt(mConfirmationCallback); + if (responseCode == AndroidProtectedConfirmation.ERROR_OK) { + return; + } else if (responseCode == AndroidProtectedConfirmation.ERROR_OPERATION_PENDING) { + throw new IllegalStateException(); + } else { + // Unexpected error code. + Log.w(TAG, + "Unexpected responseCode=" + responseCode + + " from cancelConfirmationPrompt() call."); + throw new IllegalStateException(); + } } else { - // Unexpected error code. - Log.w(TAG, - "Unexpected responseCode=" + responseCode - + " from cancelConfirmationPrompt() call."); - throw new IllegalStateException(); + int responseCode = mKeyStore.cancelConfirmationPrompt(mCallbackBinder); + if (responseCode == KeyStore.CONFIRMATIONUI_OK) { + return; + } else if (responseCode == KeyStore.CONFIRMATIONUI_OPERATION_PENDING) { + throw new IllegalStateException(); + } else { + // Unexpected error code. + Log.w(TAG, + "Unexpected responseCode=" + responseCode + + " from cancelConfirmationPrompt() call."); + throw new IllegalStateException(); + } } } @@ -330,6 +448,9 @@ public class ConfirmationPrompt { if (isAccessibilityServiceRunning(context)) { return false; } + if (AndroidKeyStoreProvider.isKeystore2Enabled()) { + return new AndroidProtectedConfirmation().isConfirmationPromptSupported(); + } return KeyStore.getInstance().isConfirmationPromptSupported(); } } diff --git a/core/java/android/service/timezone/OWNERS b/core/java/android/service/timezone/OWNERS new file mode 100644 index 000000000000..28aff188dbd8 --- /dev/null +++ b/core/java/android/service/timezone/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 847766 +nfuller@google.com +include /core/java/android/app/timedetector/OWNERS diff --git a/core/java/android/util/Patterns.java b/core/java/android/util/Patterns.java index 7ad16ff3dc23..ece069fa0873 100644 --- a/core/java/android/util/Patterns.java +++ b/core/java/android/util/Patterns.java @@ -248,6 +248,13 @@ public class Patterns { + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]" + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}" + "|[1-9][0-9]|[0-9]))"; + + /** + * Kept for backward compatibility reasons. It does not match IPv6 addresses. + * + * @deprecated Please use {@link android.net.InetAddresses#isNumericAddress(String)} instead. + */ + @Deprecated public static final Pattern IP_ADDRESS = Pattern.compile(IP_ADDRESS_STRING); /** diff --git a/core/java/android/uwb/IUwbAdapter.aidl b/core/java/android/uwb/IUwbAdapter.aidl index 2c8b2e462510..b9c55081a103 100644 --- a/core/java/android/uwb/IUwbAdapter.aidl +++ b/core/java/android/uwb/IUwbAdapter.aidl @@ -119,42 +119,96 @@ interface IUwbAdapter { PersistableBundle getSpecificationInfo(); /** - * Request to start a new ranging session + * Request to open a new ranging session * - * This function must return before calling IUwbAdapterCallbacks - * #onRangingStarted, #onRangingClosed, or #onRangingResult. + * This function must return before calling any functions in + * IUwbAdapterCallbacks. * - * A ranging session does not need to be started before returning. + * This function does not start the ranging session, but all necessary + * components must be initialized and ready to start a new ranging + * session prior to calling IUwbAdapterCallback#onRangingOpened. * - * IUwbAdapterCallbacks#onRangingStarted must be called within - * RANGING_SESSION_START_THRESHOLD_MS milliseconds of #startRanging being called - * if the ranging session is scheduled to start successfully. + * IUwbAdapterCallbacks#onRangingOpened must be called within + * RANGING_SESSION_OPEN_THRESHOLD_MS milliseconds of #openRanging being + * called if the ranging session is opened successfully. * - * IUwbAdapterCallbacks#onRangingStartFailed must be called within - * RANGING_SESSION_START_THRESHOLD_MS milliseconds of #startRanging being called - * if the ranging session fails to be scheduled to start successfully. + * IUwbAdapterCallbacks#onRangingOpenFailed must be called within + * RANGING_SESSION_OPEN_THRESHOLD_MS milliseconds of #openRanging being called + * if the ranging session fails to be opened. * * @param rangingCallbacks the callbacks used to deliver ranging information * @param parameters the configuration to use for ranging * @return a SessionHandle used to identify this ranging request */ - SessionHandle startRanging(in IUwbRangingCallbacks rangingCallbacks, - in PersistableBundle parameters); + SessionHandle openRanging(in IUwbRangingCallbacks rangingCallbacks, + in PersistableBundle parameters); + + /** + * Request to start ranging + * + * IUwbAdapterCallbacks#onRangingStarted must be called within + * RANGING_SESSION_START_THRESHOLD_MS milliseconds of #startRanging being + * called if the ranging session starts successfully. + * + * IUwbAdapterCallbacks#onRangingStartFailed must be called within + * RANGING_SESSION_START_THRESHOLD_MS milliseconds of #startRanging being + * called if the ranging session fails to be started. + * + * @param sessionHandle the session handle to start ranging for + * @param parameters additional configuration required to start ranging + */ + void startRanging(in SessionHandle sessionHandle, + in PersistableBundle parameters); + + /** + * Request to reconfigure ranging + * + * IUwbAdapterCallbacks#onRangingReconfigured must be called after + * successfully reconfiguring the session. + * + * IUwbAdapterCallbacks#onRangingReconfigureFailed must be called after + * failing to reconfigure the session. + * + * A session must not be modified by a failed call to #reconfigureRanging. + * + * @param sessionHandle the session handle to start ranging for + * @param parameters the parameters to reconfigure and their new values + */ + void reconfigureRanging(in SessionHandle sessionHandle, + in PersistableBundle parameters); + + /** + * Request to stop ranging + * + * IUwbAdapterCallbacks#onRangingStopped must be called after + * successfully stopping the session. + * + * IUwbAdapterCallbacks#onRangingStopFailed must be called after failing + * to stop the session. + * + * @param sessionHandle the session handle to stop ranging for + */ + void stopRanging(in SessionHandle sessionHandle); /** - * Stop and close ranging for the session associated with the given handle + * Close ranging for the session associated with the given handle * * Calling with an invalid handle or a handle that has already been closed * is a no-op. * * IUwbAdapterCallbacks#onRangingClosed must be called within - * RANGING_SESSION_CLOSE_THRESHOLD_MS of #stopRanging being called. + * RANGING_SESSION_CLOSE_THRESHOLD_MS of #closeRanging being called. * - * @param sessionHandle the session handle to stop ranging for + * @param sessionHandle the session handle to close ranging for */ void closeRanging(in SessionHandle sessionHandle); /** + * The maximum allowed time to open a ranging session. + */ + const int RANGING_SESSION_OPEN_THRESHOLD_MS = 3000; // Value TBD + + /** * The maximum allowed time to start a ranging session. */ const int RANGING_SESSION_START_THRESHOLD_MS = 3000; // Value TBD diff --git a/core/java/android/uwb/IUwbRangingCallbacks.aidl b/core/java/android/uwb/IUwbRangingCallbacks.aidl index 1fc3bfd818c3..f71f3ff7ad44 100644 --- a/core/java/android/uwb/IUwbRangingCallbacks.aidl +++ b/core/java/android/uwb/IUwbRangingCallbacks.aidl @@ -17,16 +17,33 @@ package android.uwb; import android.os.PersistableBundle; -import android.uwb.CloseReason; +import android.uwb.RangingChangeReason; import android.uwb.RangingReport; import android.uwb.SessionHandle; -import android.uwb.StartFailureReason; /** * @hide */ interface IUwbRangingCallbacks { /** + * Called when the ranging session has been opened + * + * @param sessionHandle the session the callback is being invoked for + */ + void onRangingOpened(in SessionHandle sessionHandle); + + /** + * Called when a ranging session fails to start + * + * @param sessionHandle the session the callback is being invoked for + * @param reason the reason the session failed to start + * @param parameters protocol specific parameters + */ + void onRangingOpenFailed(in SessionHandle sessionHandle, + RangingChangeReason reason, + in PersistableBundle parameters); + + /** * Called when ranging has started * * May output parameters generated by the lower layers that must be sent to the @@ -47,8 +64,49 @@ interface IUwbRangingCallbacks { * @param reason the reason the session failed to start * @param parameters protocol specific parameters */ - void onRangingStartFailed(in SessionHandle sessionHandle, StartFailureReason reason, + void onRangingStartFailed(in SessionHandle sessionHandle, + RangingChangeReason reason, in PersistableBundle parameters); + + /** + * Called when ranging has been reconfigured + * + * @param sessionHandle the session the callback is being invoked for + * @param parameters the updated ranging configuration + */ + void onRangingReconfigured(in SessionHandle sessionHandle, + in PersistableBundle parameters); + + /** + * Called when a ranging session fails to be reconfigured + * + * @param sessionHandle the session the callback is being invoked for + * @param reason the reason the session failed to reconfigure + * @param parameters protocol specific parameters + */ + void onRangingReconfigureFailed(in SessionHandle sessionHandle, + RangingChangeReason reason, + in PersistableBundle parameters); + + /** + * Called when the ranging session has been stopped + * + * @param sessionHandle the session the callback is being invoked for + */ + + void onRangingStopped(in SessionHandle sessionHandle); + + /** + * Called when a ranging session fails to stop + * + * @param sessionHandle the session the callback is being invoked for + * @param reason the reason the session failed to stop + * @param parameters protocol specific parameters + */ + void onRangingStopFailed(in SessionHandle sessionHandle, + RangingChangeReason reason, + in PersistableBundle parameters); + /** * Called when a ranging session is closed * @@ -56,7 +114,8 @@ interface IUwbRangingCallbacks { * @param reason the reason the session was closed * @param parameters protocol specific parameters */ - void onRangingClosed(in SessionHandle sessionHandle, CloseReason reason, + void onRangingClosed(in SessionHandle sessionHandle, + RangingChangeReason reason, in PersistableBundle parameters); /** diff --git a/core/java/android/uwb/CloseReason.aidl b/core/java/android/uwb/RangingChangeReason.aidl index bef129e2c1c7..19d4b3949d07 100644 --- a/core/java/android/uwb/CloseReason.aidl +++ b/core/java/android/uwb/RangingChangeReason.aidl @@ -20,39 +20,44 @@ package android.uwb; * @hide */ @Backing(type="int") -enum CloseReason { +enum RangingChangeReason { /** * Unknown reason */ UNKNOWN, /** - * A local API call triggered the close, such as a call to - * IUwbAdapter.stopRanging. + * A local API call triggered the change, such as a call to + * IUwbAdapter.closeRanging. */ LOCAL_API, /** - * The maximum number of sessions has been reached. This error may be generated - * for an active session if a higher priority session begins. + * The maximum number of sessions has been reached. This may be generated for + * an active session if a higher priority session begins. */ MAX_SESSIONS_REACHED, /** - * The system state has changed resulting in the session ending (e.g. the user - * disables UWB, or the user's locale changes and an active channel is no longer - * permitted to be used). + * The system state has changed resulting in the session changing (e.g. the + * user disables UWB, or the user's locale changes and an active channel is no + * longer permitted to be used). */ SYSTEM_POLICY, /** - * The remote device has requested to terminate the session + * The remote device has requested to change the session */ REMOTE_REQUEST, /** - * The session was closed for a protocol specific reason + * The session changed for a protocol specific reason */ PROTOCOL_SPECIFIC, + + /** + * The provided parameters were invalid + */ + BAD_PARAMETERS, } diff --git a/core/java/android/uwb/RangingManager.java b/core/java/android/uwb/RangingManager.java index a9bf4abe566a..5ac95d49c1bb 100644 --- a/core/java/android/uwb/RangingManager.java +++ b/core/java/android/uwb/RangingManager.java @@ -50,7 +50,7 @@ public class RangingManager extends android.uwb.IUwbRangingCallbacks.Stub { @NonNull RangingSession.Callback callbacks) { SessionHandle sessionHandle; try { - sessionHandle = mAdapter.startRanging(this, params); + sessionHandle = mAdapter.openRanging(this, params); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -59,7 +59,7 @@ public class RangingManager extends android.uwb.IUwbRangingCallbacks.Stub { if (hasSession(sessionHandle)) { Log.w(TAG, "Newly created session unexpectedly reuses an active SessionHandle"); executor.execute(() -> callbacks.onClosed( - RangingSession.Callback.CLOSE_REASON_LOCAL_GENERIC_ERROR, + RangingSession.Callback.REASON_GENERIC_ERROR, new PersistableBundle())); } @@ -75,6 +75,67 @@ public class RangingManager extends android.uwb.IUwbRangingCallbacks.Stub { } @Override + public void onRangingOpened(SessionHandle sessionHandle) { + synchronized (this) { + if (!hasSession(sessionHandle)) { + Log.w(TAG, + "onRangingOpened - received unexpected SessionHandle: " + sessionHandle); + return; + } + + RangingSession session = mRangingSessionTable.get(sessionHandle); + session.onRangingOpened(); + } + } + + @Override + public void onRangingOpenFailed(SessionHandle sessionHandle, @RangingChangeReason int reason, + PersistableBundle parameters) { + synchronized (this) { + if (!hasSession(sessionHandle)) { + Log.w(TAG, + "onRangingOpened - received unexpected SessionHandle: " + sessionHandle); + return; + } + + RangingSession session = mRangingSessionTable.get(sessionHandle); + session.onRangingOpenFailed(convertToReason(reason), parameters); + mRangingSessionTable.remove(sessionHandle); + } + } + + @Override + public void onRangingReconfigured(SessionHandle sessionHandle, PersistableBundle parameters) { + synchronized (this) { + if (!hasSession(sessionHandle)) { + Log.w(TAG, + "onRangingReconfigured - received unexpected SessionHandle: " + + sessionHandle); + return; + } + + RangingSession session = mRangingSessionTable.get(sessionHandle); + session.onRangingReconfigured(parameters); + } + } + + @Override + public void onRangingReconfigureFailed(SessionHandle sessionHandle, + @RangingChangeReason int reason, PersistableBundle params) { + synchronized (this) { + if (!hasSession(sessionHandle)) { + Log.w(TAG, "onRangingStartFailed - received unexpected SessionHandle: " + + sessionHandle); + return; + } + + RangingSession session = mRangingSessionTable.get(sessionHandle); + session.onRangingReconfigureFailed(convertToReason(reason), params); + } + } + + + @Override public void onRangingStarted(SessionHandle sessionHandle, PersistableBundle parameters) { synchronized (this) { if (!hasSession(sessionHandle)) { @@ -89,7 +150,7 @@ public class RangingManager extends android.uwb.IUwbRangingCallbacks.Stub { } @Override - public void onRangingStartFailed(SessionHandle sessionHandle, int reason, + public void onRangingStartFailed(SessionHandle sessionHandle, @RangingChangeReason int reason, PersistableBundle params) { synchronized (this) { if (!hasSession(sessionHandle)) { @@ -99,13 +160,42 @@ public class RangingManager extends android.uwb.IUwbRangingCallbacks.Stub { } RangingSession session = mRangingSessionTable.get(sessionHandle); - session.onRangingClosed(convertStartFailureToCloseReason(reason), params); - mRangingSessionTable.remove(sessionHandle); + session.onRangingStartFailed(convertToReason(reason), params); + } + } + + @Override + public void onRangingStopped(SessionHandle sessionHandle) { + synchronized (this) { + if (!hasSession(sessionHandle)) { + Log.w(TAG, "onRangingStopped - received unexpected SessionHandle: " + + sessionHandle); + return; + } + + RangingSession session = mRangingSessionTable.get(sessionHandle); + session.onRangingStopped(); } } @Override - public void onRangingClosed(SessionHandle sessionHandle, int reason, PersistableBundle params) { + public void onRangingStopFailed(SessionHandle sessionHandle, @RangingChangeReason int reason, + PersistableBundle parameters) { + synchronized (this) { + if (!hasSession(sessionHandle)) { + Log.w(TAG, "onRangingStopFailed - received unexpected SessionHandle: " + + sessionHandle); + return; + } + + RangingSession session = mRangingSessionTable.get(sessionHandle); + session.onRangingStopFailed(convertToReason(reason), parameters); + } + } + + @Override + public void onRangingClosed(SessionHandle sessionHandle, @RangingChangeReason int reason, + PersistableBundle params) { synchronized (this) { if (!hasSession(sessionHandle)) { Log.w(TAG, "onRangingClosed - received unexpected SessionHandle: " + sessionHandle); @@ -113,7 +203,7 @@ public class RangingManager extends android.uwb.IUwbRangingCallbacks.Stub { } RangingSession session = mRangingSessionTable.get(sessionHandle); - session.onRangingClosed(convertToCloseReason(reason), params); + session.onRangingClosed(convertToReason(reason), params); mRangingSessionTable.remove(sessionHandle); } } @@ -131,48 +221,30 @@ public class RangingManager extends android.uwb.IUwbRangingCallbacks.Stub { } } - @RangingSession.Callback.CloseReason - private static int convertToCloseReason(@CloseReason int reason) { + @RangingSession.Callback.Reason + private static int convertToReason(@RangingChangeReason int reason) { switch (reason) { - case CloseReason.LOCAL_API: - return RangingSession.Callback.CLOSE_REASON_LOCAL_CLOSE_API; - - case CloseReason.MAX_SESSIONS_REACHED: - return RangingSession.Callback.CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED; - - case CloseReason.SYSTEM_POLICY: - return RangingSession.Callback.CLOSE_REASON_LOCAL_SYSTEM_POLICY; + case RangingChangeReason.LOCAL_API: + return RangingSession.Callback.REASON_LOCAL_REQUEST; - case CloseReason.REMOTE_REQUEST: - return RangingSession.Callback.CLOSE_REASON_REMOTE_REQUEST; + case RangingChangeReason.MAX_SESSIONS_REACHED: + return RangingSession.Callback.REASON_MAX_SESSIONS_REACHED; - case CloseReason.PROTOCOL_SPECIFIC: - return RangingSession.Callback.CLOSE_REASON_PROTOCOL_SPECIFIC; - - case CloseReason.UNKNOWN: - default: - return RangingSession.Callback.CLOSE_REASON_UNKNOWN; - } - } - - @RangingSession.Callback.CloseReason - private static int convertStartFailureToCloseReason(@StartFailureReason int reason) { - switch (reason) { - case StartFailureReason.BAD_PARAMETERS: - return RangingSession.Callback.CLOSE_REASON_LOCAL_BAD_PARAMETERS; + case RangingChangeReason.SYSTEM_POLICY: + return RangingSession.Callback.REASON_SYSTEM_POLICY; - case StartFailureReason.MAX_SESSIONS_REACHED: - return RangingSession.Callback.CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED; + case RangingChangeReason.REMOTE_REQUEST: + return RangingSession.Callback.REASON_REMOTE_REQUEST; - case StartFailureReason.SYSTEM_POLICY: - return RangingSession.Callback.CLOSE_REASON_LOCAL_SYSTEM_POLICY; + case RangingChangeReason.PROTOCOL_SPECIFIC: + return RangingSession.Callback.REASON_PROTOCOL_SPECIFIC_ERROR; - case StartFailureReason.PROTOCOL_SPECIFIC: - return RangingSession.Callback.CLOSE_REASON_PROTOCOL_SPECIFIC; + case RangingChangeReason.BAD_PARAMETERS: + return RangingSession.Callback.REASON_BAD_PARAMETERS; - case StartFailureReason.UNKNOWN: + case RangingChangeReason.UNKNOWN: default: - return RangingSession.Callback.CLOSE_REASON_UNKNOWN; + return RangingSession.Callback.REASON_UNKNOWN; } } } diff --git a/core/java/android/uwb/RangingSession.java b/core/java/android/uwb/RangingSession.java index b0dbd85c0812..0f87af415825 100644 --- a/core/java/android/uwb/RangingSession.java +++ b/core/java/android/uwb/RangingSession.java @@ -36,9 +36,9 @@ import java.util.concurrent.Executor; * <p>To get an instance of {@link RangingSession}, first use * {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)} to request to open a * session. Once the session is opened, a {@link RangingSession} object is provided through - * {@link RangingSession.Callback#onOpenSuccess(RangingSession, PersistableBundle)}. If opening a - * session fails, the failure is reported through - * {@link RangingSession.Callback#onClosed(int, PersistableBundle)} with the failure reason. + * {@link RangingSession.Callback#onOpened(RangingSession)}. If opening a session fails, the failure + * is reported through {@link RangingSession.Callback#onOpenFailed(int, PersistableBundle)} with the + * failure reason. * * @hide */ @@ -50,103 +50,162 @@ public final class RangingSession implements AutoCloseable { private final Callback mCallback; private enum State { + /** + * The state of the {@link RangingSession} until + * {@link RangingSession.Callback#onOpened(RangingSession)} is invoked + */ INIT, - OPEN, - CLOSED, + + /** + * The {@link RangingSession} is initialized and ready to begin ranging + */ + IDLE, + + /** + * The {@link RangingSession} is actively ranging + */ + ACTIVE, + + /** + * The {@link RangingSession} is closed and may not be used for ranging. + */ + CLOSED } - private State mState; + private State mState = State.INIT; /** * Interface for receiving {@link RangingSession} events */ public interface Callback { /** - * Invoked when {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)} - * is successful - * - * @param session the newly opened {@link RangingSession} - * @param sessionInfo session specific parameters from lower layers - */ - void onOpenSuccess(@NonNull RangingSession session, @NonNull PersistableBundle sessionInfo); - - /** * @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(value = { - CLOSE_REASON_UNKNOWN, - CLOSE_REASON_LOCAL_CLOSE_API, - CLOSE_REASON_LOCAL_BAD_PARAMETERS, - CLOSE_REASON_LOCAL_GENERIC_ERROR, - CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED, - CLOSE_REASON_LOCAL_SYSTEM_POLICY, - CLOSE_REASON_REMOTE_GENERIC_ERROR, - CLOSE_REASON_REMOTE_REQUEST}) - @interface CloseReason {} + REASON_UNKNOWN, + REASON_LOCAL_REQUEST, + REASON_REMOTE_REQUEST, + REASON_BAD_PARAMETERS, + REASON_GENERIC_ERROR, + REASON_MAX_SESSIONS_REACHED, + REASON_SYSTEM_POLICY, + REASON_PROTOCOL_SPECIFIC_ERROR}) + @interface Reason {} /** * Indicates that the session was closed or failed to open due to an unknown reason */ - int CLOSE_REASON_UNKNOWN = 0; + int REASON_UNKNOWN = 0; /** * Indicates that the session was closed or failed to open because * {@link AutoCloseable#close()} or {@link RangingSession#close()} was called */ - int CLOSE_REASON_LOCAL_CLOSE_API = 1; + int REASON_LOCAL_REQUEST = 1; /** - * Indicates that the session failed to open due to erroneous parameters passed - * to {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)} + * Indicates that the session was closed or failed to open due to an explicit request from + * the remote device. */ - int CLOSE_REASON_LOCAL_BAD_PARAMETERS = 2; + int REASON_REMOTE_REQUEST = 2; /** - * Indicates that the session was closed due to some local error on this device besides the - * error code already listed + * Indicates that the session was closed or failed to open due to erroneous parameters */ - int CLOSE_REASON_LOCAL_GENERIC_ERROR = 3; + int REASON_BAD_PARAMETERS = 3; /** - * Indicates that the session failed to open because the number of currently open sessions - * is equal to {@link UwbManager#getMaxSimultaneousSessions()} + * Indicates an error on this device besides the error code already listed */ - int CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED = 4; + int REASON_GENERIC_ERROR = 4; /** - * Indicates that the session was closed or failed to open due to local system policy, such + * Indicates that the number of currently open sessions is equal to + * {@link UwbManager#getMaxSimultaneousSessions()} and additional sessions may not be + * opened. + */ + int REASON_MAX_SESSIONS_REACHED = 5; + + /** + * Indicates that the local system policy caused the change, such * as privacy policy, power management policy, permissions, and more. */ - int CLOSE_REASON_LOCAL_SYSTEM_POLICY = 5; + int REASON_SYSTEM_POLICY = 6; /** - * Indicates that the session was closed or failed to open due to an error with the remote - * device besides error codes already listed. + * Indicates a protocol specific error. The associated {@link PersistableBundle} should be + * consulted for additional information. */ - int CLOSE_REASON_REMOTE_GENERIC_ERROR = 6; + int REASON_PROTOCOL_SPECIFIC_ERROR = 7; /** - * Indicates that the session was closed or failed to open due to an explicit request from - * the remote device. + * Invoked when {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)} + * is successful + * + * @param session the newly opened {@link RangingSession} + */ + void onOpened(@NonNull RangingSession session); + + /** + * Invoked if {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)}} + * fails + * + * @param reason the failure reason + * @param params protocol specific parameters + */ + void onOpenFailed(@Reason int reason, @NonNull PersistableBundle params); + + /** + * Invoked when {@link RangingSession#start(PersistableBundle)} is successful + * @param sessionInfo session specific parameters from the lower layers + */ + void onStarted(@NonNull PersistableBundle sessionInfo); + + /** + * Invoked when {@link RangingSession#start(PersistableBundle)} fails + * + * @param reason the failure reason + * @param params protocol specific parameters + */ + void onStartFailed(@Reason int reason, @NonNull PersistableBundle params); + + /** + * Invoked when a request to reconfigure the session succeeds + * + * @param params the updated ranging configuration + */ + void onReconfigured(@NonNull PersistableBundle params); + + /** + * Invoked when a request to reconfigure the session fails + * + * @param reason reason the session failed to be reconfigured + * @param params protocol specific failure reasons */ - int CLOSE_REASON_REMOTE_REQUEST = 7; + void onReconfigureFailed(@Reason int reason, @NonNull PersistableBundle params); /** - * Indicates that the session was closed for a protocol specific reason. The associated - * {@link PersistableBundle} should be consulted for additional information. + * Invoked when a request to stop the session succeeds */ - int CLOSE_REASON_PROTOCOL_SPECIFIC = 8; + void onStopped(); /** + * Invoked when a request to stop the session fails + * + * @param reason reason the session failed to be stopped + * @param params protocol specific failure reasons + */ + void onStopFailed(@Reason int reason, @NonNull PersistableBundle params); + + /** * Invoked when session is either closed spontaneously, or per user request via - * {@link RangingSession#close()} or {@link AutoCloseable#close()}, or when session failed - * to open. + * {@link RangingSession#close()} or {@link AutoCloseable#close()}. * * @param reason reason for the session closure * @param parameters protocol specific parameters related to the close reason */ - void onClosed(@CloseReason int reason, @NonNull PersistableBundle parameters); + void onClosed(@Reason int reason, @NonNull PersistableBundle parameters); /** * Called once per ranging interval even when a ranging measurement fails @@ -172,12 +231,95 @@ public final class RangingSession implements AutoCloseable { * @hide */ public boolean isOpen() { - return mState == State.OPEN; + return mState == State.IDLE || mState == State.ACTIVE; + } + + /** + * Begins ranging for the session. + * + * <p>On successfully starting a ranging session, + * {@link RangingSession.Callback#onStarted(PersistableBundle)} is invoked. + * + * <p>On failure to start the session, + * {@link RangingSession.Callback#onStartFailed(int, PersistableBundle)} is invoked. + * + * @param params configuration parameters for starting the session + */ + public void start(@NonNull PersistableBundle params) { + if (mState != State.IDLE) { + throw new IllegalStateException(); + } + + try { + mAdapter.startRanging(mSessionHandle, params); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Attempts to reconfigure the session with the given parameters + * <p>This call may be made when the session is open. + * + * <p>On successfully reconfiguring the session + * {@link RangingSession.Callback#onReconfigured(PersistableBundle)} is invoked. + * + * <p>On failure to reconfigure the session, + * {@link RangingSession.Callback#onReconfigureFailed(int, PersistableBundle)} is invoked. + * + * @param params the parameters to reconfigure and their new values + */ + public void reconfigure(@NonNull PersistableBundle params) { + if (mState != State.ACTIVE && mState != State.IDLE) { + throw new IllegalStateException(); + } + + try { + mAdapter.reconfigureRanging(mSessionHandle, params); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Stops actively ranging + * + * <p>A session that has been stopped may be resumed by calling + * {@link RangingSession#start(PersistableBundle)} without the need to open a new session. + * + * <p>Stopping a {@link RangingSession} is useful when the lower layers should not discard + * the parameters of the session, or when a session needs to be able to be resumed quickly. + * + * <p>If the {@link RangingSession} is no longer needed, use {@link RangingSession#close()} to + * completely close the session and allow lower layers of the stack to perform necessarily + * cleanup. + * + * <p>Stopped sessions may be closed by the system at any time. In such a case, + * {@link RangingSession.Callback#onClosed(int, PersistableBundle)} is invoked. + * + * <p>On failure to stop the session, + * {@link RangingSession.Callback#onStopFailed(int, PersistableBundle)} is invoked. + */ + public void stop() { + if (mState != State.ACTIVE) { + throw new IllegalStateException(); + } + + try { + mAdapter.stopRanging(mSessionHandle); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** * Close the ranging session - * <p>If this session is currently open, it will close and stop the session. + * + * <p>After calling this function, in order resume ranging, a new {@link RangingSession} must + * be opened by calling + * {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)}. + * + * <p>If this session is currently ranging, it will stop and close the session. * <p>If the session is in the process of being opened, it will attempt to stop the session from * being opened. * <p>If the session is already closed, the registered @@ -192,7 +334,7 @@ public final class RangingSession implements AutoCloseable { public void close() { if (mState == State.CLOSED) { mExecutor.execute(() -> mCallback.onClosed( - Callback.CLOSE_REASON_LOCAL_CLOSE_API, new PersistableBundle())); + Callback.REASON_LOCAL_REQUEST, new PersistableBundle())); return; } @@ -206,32 +348,114 @@ public final class RangingSession implements AutoCloseable { /** * @hide */ + public void onRangingOpened() { + if (mState == State.CLOSED) { + Log.w(TAG, "onRangingOpened invoked for a closed session"); + return; + } + + mState = State.IDLE; + executeCallback(() -> mCallback.onOpened(this)); + } + + /** + * @hide + */ + public void onRangingOpenFailed(@Callback.Reason int reason, + @NonNull PersistableBundle params) { + if (mState == State.CLOSED) { + Log.w(TAG, "onRangingOpenFailed invoked for a closed session"); + return; + } + + mState = State.CLOSED; + executeCallback(() -> mCallback.onOpenFailed(reason, params)); + } + + /** + * @hide + */ public void onRangingStarted(@NonNull PersistableBundle parameters) { if (mState == State.CLOSED) { Log.w(TAG, "onRangingStarted invoked for a closed session"); return; } - mState = State.OPEN; - final long identity = Binder.clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onOpenSuccess(this, parameters)); - } finally { - Binder.restoreCallingIdentity(identity); + mState = State.ACTIVE; + executeCallback(() -> mCallback.onStarted(parameters)); + } + + /** + * @hide + */ + public void onRangingStartFailed(@Callback.Reason int reason, + @NonNull PersistableBundle params) { + if (mState == State.CLOSED) { + Log.w(TAG, "onRangingStartFailed invoked for a closed session"); + return; } + + executeCallback(() -> mCallback.onStartFailed(reason, params)); } /** * @hide */ - public void onRangingClosed(@Callback.CloseReason int reason, PersistableBundle parameters) { - mState = State.CLOSED; - final long identity = Binder.clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onClosed(reason, parameters)); - } finally { - Binder.restoreCallingIdentity(identity); + public void onRangingReconfigured(@NonNull PersistableBundle params) { + if (mState == State.CLOSED) { + Log.w(TAG, "onRangingReconfigured invoked for a closed session"); + return; + } + + executeCallback(() -> mCallback.onReconfigured(params)); + } + + /** + * @hide + */ + public void onRangingReconfigureFailed(@Callback.Reason int reason, + @NonNull PersistableBundle params) { + if (mState == State.CLOSED) { + Log.w(TAG, "onRangingReconfigureFailed invoked for a closed session"); + return; + } + + executeCallback(() -> mCallback.onReconfigureFailed(reason, params)); + } + + /** + * @hide + */ + public void onRangingStopped() { + if (mState == State.CLOSED) { + Log.w(TAG, "onRangingStopped invoked for a closed session"); + return; } + + mState = State.IDLE; + executeCallback(() -> mCallback.onStopped()); + } + + /** + * @hide + */ + public void onRangingStopFailed(@Callback.Reason int reason, + @NonNull PersistableBundle params) { + if (mState == State.CLOSED) { + Log.w(TAG, "onRangingStopFailed invoked for a closed session"); + return; + } + + executeCallback(() -> mCallback.onStopFailed(reason, params)); + } + + /** + * @hide + */ + public void onRangingClosed(@Callback.Reason int reason, + @NonNull PersistableBundle parameters) { + mState = State.CLOSED; + executeCallback(() -> mCallback.onClosed(reason, parameters)); } /** @@ -243,9 +467,16 @@ public final class RangingSession implements AutoCloseable { return; } + executeCallback(() -> mCallback.onReportReceived(report)); + } + + /** + * @hide + */ + private void executeCallback(@NonNull Runnable runnable) { final long identity = Binder.clearCallingIdentity(); try { - mExecutor.execute(() -> mCallback.onReportReceived(report)); + mExecutor.execute(runnable); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/core/java/android/uwb/StartFailureReason.aidl b/core/java/android/uwb/StartFailureReason.aidl deleted file mode 100644 index 4d9c962f529b..000000000000 --- a/core/java/android/uwb/StartFailureReason.aidl +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.uwb; - -/** - * @hide - */ -@Backing(type="int") -enum StartFailureReason { - /** - * Unknown start failure reason - */ - UNKNOWN, - - /** - * The provided parameters were invalid and ranging could not start - */ - BAD_PARAMETERS, - - /** - * The maximum number of sessions has been reached. This error may be generated - * for an active session if a higher priority session begins. - */ - MAX_SESSIONS_REACHED, - - /** - * The system state has changed resulting in the session ending (e.g. the user - * disables UWB, or the user's locale changes and an active channel is no longer - * permitted to be used). - */ - SYSTEM_POLICY, - - /** - * The session could not start because of a protocol specific reason. - */ - PROTOCOL_SPECIFIC, -} - diff --git a/core/java/android/uwb/UwbManager.java b/core/java/android/uwb/UwbManager.java index f4d801868e18..15ee5b5f22eb 100644 --- a/core/java/android/uwb/UwbManager.java +++ b/core/java/android/uwb/UwbManager.java @@ -369,13 +369,13 @@ public final class UwbManager { /** * Open a {@link RangingSession} with the given parameters - * <p>This function is asynchronous and will return before ranging begins. The - * {@link RangingSession.Callback#onOpenSuccess(RangingSession, PersistableBundle)} function is - * called with a {@link RangingSession} object used to control ranging when the session is - * successfully opened. + * <p>The {@link RangingSession.Callback#onOpened(RangingSession)} function is called with a + * {@link RangingSession} object used to control ranging when the session is successfully + * opened. * - * <p>If a session cannot be opened, then {@link RangingSession.Callback#onClosed(int)} will be - * invoked with the appropriate {@link RangingSession.Callback.CloseReason}. + * <p>If a session cannot be opened, then + * {@link RangingSession.Callback#onClosed(int, PersistableBundle)} will be invoked with the + * appropriate {@link RangingSession.Callback.Reason}. * * <p>An open {@link RangingSession} will be automatically closed if client application process * dies. @@ -391,7 +391,7 @@ public final class UwbManager { * @return an {@link AutoCloseable} that is able to be used to close or cancel the opening of a * {@link RangingSession} that has been requested through {@link #openRangingSession} * but has not yet been made available by - * {@link RangingSession.Callback#onOpenSuccess}. + * {@link RangingSession.Callback#onOpened(RangingSession)}. */ @NonNull public AutoCloseable openRangingSession(@NonNull PersistableBundle parameters, diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS index bae6ee85c064..e66b17aa4426 100644 --- a/core/java/android/view/OWNERS +++ b/core/java/android/view/OWNERS @@ -33,6 +33,9 @@ per-file SimulatedDpad.java = file:/services/core/java/com/android/server/input/ per-file InputWindowHandle.java = file:/services/core/java/com/android/server/input/OWNERS per-file InputWindowHandle.java = file:/services/core/java/com/android/server/wm/OWNERS +# Notifications +per-file Notification*.java = file:/services/core/java/com/android/server/notification/OWNERS + # Surface per-file Surface.java = file:/graphics/java/android/graphics/OWNERS per-file Surface.java = file:/services/core/java/com/android/server/wm/OWNERS diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index cf0e0d1f1842..5e74381dce22 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -73,7 +73,7 @@ import java.util.function.Predicate; /** * <p>Displays a vertically-scrollable collection of views, where each view is positioned * immediatelybelow the previous view in the list. For a more modern, flexible, and performant - * approach to displaying lists, use {@link android.support.v7.widget.RecyclerView}.</p> + * approach to displaying lists, use {@link androidx.recyclerview.widget.RecyclerView}.</p> * * <p>To display a list, you can include a list view in your layout XML file:</p> * diff --git a/core/java/com/android/internal/app/OWNERS b/core/java/com/android/internal/app/OWNERS index 382b49e68e6f..c5a956a81d8d 100644 --- a/core/java/com/android/internal/app/OWNERS +++ b/core/java/com/android/internal/app/OWNERS @@ -2,3 +2,4 @@ per-file *AppOp* = file:/core/java/android/permission/OWNERS per-file *Resolver* = file:/packages/SystemUI/OWNERS per-file *Chooser* = file:/packages/SystemUI/OWNERS per-file SimpleIconFactory.java = file:/packages/SystemUI/OWNERS +per-file NetInitiatedActivity.java = file:/location/java/android/location/OWNERS diff --git a/core/java/com/android/internal/compat/CompatibilityChangeInfo.java b/core/java/com/android/internal/compat/CompatibilityChangeInfo.java index 670ca9f6091e..03fe4551c249 100644 --- a/core/java/com/android/internal/compat/CompatibilityChangeInfo.java +++ b/core/java/com/android/internal/compat/CompatibilityChangeInfo.java @@ -32,6 +32,7 @@ public class CompatibilityChangeInfo implements Parcelable { private final boolean mDisabled; private final boolean mLoggingOnly; private final @Nullable String mDescription; + private final boolean mOverridable; public long getId() { return mChangeId; @@ -58,9 +59,13 @@ public class CompatibilityChangeInfo implements Parcelable { return mDescription; } + public boolean getOverridable() { + return mOverridable; + } + public CompatibilityChangeInfo( Long changeId, String name, int enableAfterTargetSdk, int enableSinceTargetSdk, - boolean disabled, boolean loggingOnly, String description) { + boolean disabled, boolean loggingOnly, String description, boolean overridable) { this.mChangeId = changeId; this.mName = name; if (enableAfterTargetSdk > 0) { @@ -75,6 +80,7 @@ public class CompatibilityChangeInfo implements Parcelable { this.mDisabled = disabled; this.mLoggingOnly = loggingOnly; this.mDescription = description; + this.mOverridable = overridable; } public CompatibilityChangeInfo(CompatibilityChangeInfo other) { @@ -84,6 +90,7 @@ public class CompatibilityChangeInfo implements Parcelable { this.mDisabled = other.mDisabled; this.mLoggingOnly = other.mLoggingOnly; this.mDescription = other.mDescription; + this.mOverridable = other.mOverridable; } private CompatibilityChangeInfo(Parcel in) { @@ -93,6 +100,7 @@ public class CompatibilityChangeInfo implements Parcelable { mDisabled = in.readBoolean(); mLoggingOnly = in.readBoolean(); mDescription = in.readString(); + mOverridable = in.readBoolean(); } @Override @@ -108,6 +116,7 @@ public class CompatibilityChangeInfo implements Parcelable { dest.writeBoolean(mDisabled); dest.writeBoolean(mLoggingOnly); dest.writeString(mDescription); + dest.writeBoolean(mOverridable); } @Override @@ -126,6 +135,9 @@ public class CompatibilityChangeInfo implements Parcelable { if (getLoggingOnly()) { sb.append("; loggingOnly"); } + if (getOverridable()) { + sb.append("; overridable"); + } return sb.append(")").toString(); } @@ -143,8 +155,8 @@ public class CompatibilityChangeInfo implements Parcelable { && this.mEnableSinceTargetSdk == that.mEnableSinceTargetSdk && this.mDisabled == that.mDisabled && this.mLoggingOnly == that.mLoggingOnly - && this.mDescription.equals(that.mDescription); - + && this.mDescription.equals(that.mDescription) + && this.mOverridable == that.mOverridable; } public static final Parcelable.Creator<CompatibilityChangeInfo> CREATOR = diff --git a/core/java/com/android/internal/compat/OverrideAllowedState.java b/core/java/com/android/internal/compat/OverrideAllowedState.java index 9a78ad2011cf..c0bbe5082131 100644 --- a/core/java/com/android/internal/compat/OverrideAllowedState.java +++ b/core/java/com/android/internal/compat/OverrideAllowedState.java @@ -33,7 +33,7 @@ public final class OverrideAllowedState implements Parcelable { DISABLED_NOT_DEBUGGABLE, DISABLED_NON_TARGET_SDK, DISABLED_TARGET_SDK_TOO_HIGH, - PACKAGE_DOES_NOT_EXIST, + DEFERRED_VERIFICATION, LOGGING_ONLY_CHANGE }) @Retention(RetentionPolicy.SOURCE) @@ -57,10 +57,10 @@ public final class OverrideAllowedState implements Parcelable { * Change cannot be overridden, due to the app's targetSdk being above the change's targetSdk. */ public static final int DISABLED_TARGET_SDK_TOO_HIGH = 3; - /** - * Package does not exist. + /** + * Change override decision is currently being deferred, due to the app not being installed yet. */ - public static final int PACKAGE_DOES_NOT_EXIST = 4; + public static final int DEFERRED_VERIFICATION = 4; /** * Change is marked as logging only, and cannot be toggled. */ @@ -106,6 +106,7 @@ public final class OverrideAllowedState implements Parcelable { throws SecurityException { switch (state) { case ALLOWED: + case DEFERRED_VERIFICATION: return; case DISABLED_NOT_DEBUGGABLE: throw new SecurityException( @@ -118,11 +119,6 @@ public final class OverrideAllowedState implements Parcelable { "Cannot override %1$d for %2$s because the app's targetSdk (%3$d) is " + "above the change's targetSdk threshold (%4$d)", changeId, packageName, appTargetSdk, changeIdTargetSdk)); - case PACKAGE_DOES_NOT_EXIST: - throw new SecurityException(String.format( - "Cannot override %1$d for %2$s because the package does not exist, and " - + "the change is targetSdk gated.", - changeId, packageName)); case LOGGING_ONLY_CHANGE: throw new SecurityException(String.format( "Cannot override %1$d because it is marked as a logging-only change.", @@ -170,8 +166,8 @@ public final class OverrideAllowedState implements Parcelable { return "DISABLED_NON_TARGET_SDK"; case DISABLED_TARGET_SDK_TOO_HIGH: return "DISABLED_TARGET_SDK_TOO_HIGH"; - case PACKAGE_DOES_NOT_EXIST: - return "PACKAGE_DOES_NOT_EXIST"; + case DEFERRED_VERIFICATION: + return "DEFERRED_VERIFICATION"; case LOGGING_ONLY_CHANGE: return "LOGGING_ONLY_CHANGE"; } diff --git a/core/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS index 8f78b2a3a5ea..1b07aa0cf0b7 100644 --- a/core/java/com/android/internal/os/OWNERS +++ b/core/java/com/android/internal/os/OWNERS @@ -6,3 +6,5 @@ per-file BatterySipper.java = file:/BATTERY_STATS_OWNERS per-file BatteryStats* = file:/BATTERY_STATS_OWNERS per-file BatteryUsageStats* = file:/BATTERY_STATS_OWNERS per-file *PowerCalculator* = file:/BATTERY_STATS_OWNERS +per-file *PowerEstimator* = file:/BATTERY_STATS_OWNERS + diff --git a/core/java/com/android/server/net/BaseNetworkObserver.java b/core/java/com/android/server/net/BaseNetworkObserver.java index 93f89b5db820..139b88b108c5 100644 --- a/core/java/com/android/server/net/BaseNetworkObserver.java +++ b/core/java/com/android/server/net/BaseNetworkObserver.java @@ -64,7 +64,7 @@ public class BaseNetworkObserver extends INetworkManagementEventObserver.Stub { } @Override - public void interfaceClassDataActivityChanged(int networkType, boolean active, long tsNanos, + public void interfaceClassDataActivityChanged(int transportType, boolean active, long tsNanos, int uid) { // default no-op } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index b4572fda6cca..79a0dfd61e9f 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -311,12 +311,6 @@ cc_library_shared { }, }, - product_variables: { - experimental_mte: { - cflags: ["-DANDROID_EXPERIMENTAL_MTE"], - }, - }, - // Workaround Clang LTO crash. lto: { never: true, diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 6381cf59380a..22dd765f2526 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -14,15 +14,6 @@ * limitations under the License. */ -/* - * Disable optimization of this file if we are compiling with the address - * sanitizer. This is a mitigation for b/122921367 and can be removed once the - * bug is fixed. - */ -#if __has_feature(address_sanitizer) -#pragma clang optimize off -#endif - #define LOG_TAG "Zygote" #define ATRACE_TAG ATRACE_TAG_DALVIK diff --git a/core/proto/android/app/OWNERS b/core/proto/android/app/OWNERS new file mode 100644 index 000000000000..296abd18aadc --- /dev/null +++ b/core/proto/android/app/OWNERS @@ -0,0 +1 @@ +per-file location_time_zone_manager.proto = nfuller@google.com, mingaleev@google.com diff --git a/core/proto/android/app/location_time_zone_manager.proto b/core/proto/android/app/location_time_zone_manager.proto new file mode 100644 index 000000000000..f44d5495f132 --- /dev/null +++ b/core/proto/android/app/location_time_zone_manager.proto @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; +package android.app.time; + +import "frameworks/base/core/proto/android/privacy.proto"; + +option java_multiple_files = true; +option java_outer_classname = "LocationTimeZoneManagerProto"; + +// Represents the state of the LocationTimeZoneManagerService for use in tests. +message LocationTimeZoneManagerServiceStateProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional GeolocationTimeZoneSuggestionProto last_suggestion = 1; + repeated TimeZoneProviderStateProto primary_provider_states = 2; + repeated TimeZoneProviderStateProto secondary_provider_states = 3; +} + +// Represents a GeolocationTimeZoneSuggestion that can be / has been passed to the time zone +// detector. +message GeolocationTimeZoneSuggestionProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + repeated string zone_ids = 1; + repeated string debug_info = 2; +} + +// The state tracked for a LocationTimeZoneProvider. +message TimeZoneProviderStateProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional TimeZoneProviderStateEnum state = 1; +} + +// The state enum for LocationTimeZoneProviders. +enum TimeZoneProviderStateEnum { + TIME_ZONE_PROVIDER_STATE_UNKNOWN = 0; + TIME_ZONE_PROVIDER_STATE_INITIALIZING = 1; + TIME_ZONE_PROVIDER_STATE_CERTAIN = 2; + TIME_ZONE_PROVIDER_STATE_UNCERTAIN = 3; + TIME_ZONE_PROVIDER_STATE_DISABLED = 4; + TIME_ZONE_PROVIDER_STATE_PERM_FAILED = 5; + TIME_ZONE_PROVIDER_STATE_DESTROYED = 6; +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index a9fe5d58af05..714a09d02264 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2206,7 +2206,9 @@ <!-- Allows to query ongoing call details and manage ongoing calls <p>Protection level: signature|appop --> <permission android:name="android.permission.MANAGE_ONGOING_CALLS" - android:protectionLevel="signature|appop" /> + android:protectionLevel="signature|appop" + android:label="@string/permlab_manageOngoingCalls" + android:description="@string/permdesc_manageOngoingCalls" /> <!-- Allows the app to request network scans from telephony. <p>Not for use by third-party applications. diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 42a658e50073..d30efa95edff 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -455,6 +455,10 @@ --> </string-array> + <!-- Whether the internal vehicle network should remain active even when no + apps requested it. --> + <bool name="config_vehicleInternalNetworkAlwaysRequested">false</bool> + <!-- Configuration of network interfaces that support WakeOnLAN --> <string-array translatable="false" name="config_wakeonlan_supported_interfaces"> <!-- diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index c1f3028c9931..1143467425af 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -914,6 +914,15 @@ interfere with the performance or operation of your device when an emergency cell broadcast is received.</string> + <!-- Title for an application which grants an app the ability to see and manage calls on + the user's device. Usually reserved for apps associated with wearable devices that + can show information about calls. --> + <string name="permlab_manageOngoingCalls">Manage ongoing calls</string> + <!-- Description of an application permission, listed so the user can choose whether they + want to allow the application to do this. --> + <string name="permdesc_manageOngoingCalls">Allows an app to see details about ongoing calls + on your device and to control these calls.</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_readCellBroadcasts">read cell broadcast messages</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c505afe0509e..937716dbcf74 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -684,6 +684,7 @@ <java-symbol type="string" name="config_ethernet_iface_regex" /> <java-symbol type="string" name="not_checked" /> <java-symbol type="array" name="config_ethernet_interfaces" /> + <java-symbol type="bool" name="config_vehicleInternalNetworkAlwaysRequested" /> <java-symbol type="array" name="config_wakeonlan_supported_interfaces" /> <java-symbol type="string" name="config_forceVoiceInteractionServicePackage" /> <java-symbol type="string" name="config_mms_user_agent" /> diff --git a/core/tests/coretests/src/android/app/assist/OWNERS b/core/tests/coretests/src/android/app/assist/OWNERS new file mode 100644 index 000000000000..43ad1085a28f --- /dev/null +++ b/core/tests/coretests/src/android/app/assist/OWNERS @@ -0,0 +1 @@ +file:/core/java/android/app/assist/OWNERS diff --git a/core/tests/coretests/src/android/content/OWNERS b/core/tests/coretests/src/android/content/OWNERS index 911efb2562e7..912db1e835dc 100644 --- a/core/tests/coretests/src/android/content/OWNERS +++ b/core/tests/coretests/src/android/content/OWNERS @@ -1 +1,3 @@ per-file ContextTest.java = file:/services/core/java/com/android/server/wm/OWNERS +per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS +per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS diff --git a/core/tests/coretests/src/android/graphics/OWNERS b/core/tests/coretests/src/android/graphics/OWNERS new file mode 100644 index 000000000000..1e8478eeb141 --- /dev/null +++ b/core/tests/coretests/src/android/graphics/OWNERS @@ -0,0 +1,6 @@ +# Bug component: 24939 + +include /graphics/java/android/graphics/OWNERS + +per-file Font* = file:/graphics/java/android/graphics/fonts/OWNERS +per-file Typeface* = file:/graphics/java/android/graphics/fonts/OWNERS diff --git a/core/tests/coretests/src/android/service/notification/OWNERS b/core/tests/coretests/src/android/service/notification/OWNERS new file mode 100644 index 000000000000..1502b6071b80 --- /dev/null +++ b/core/tests/coretests/src/android/service/notification/OWNERS @@ -0,0 +1,2 @@ +include platform/frameworks/base:/services/core/java/com/android/server/notification/OWNERS + diff --git a/core/tests/coretests/src/android/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS index a3a3e7cfc4af..5031ff913e6d 100644 --- a/core/tests/coretests/src/android/view/OWNERS +++ b/core/tests/coretests/src/android/view/OWNERS @@ -2,3 +2,10 @@ per-file *MotionEventTest.* = michaelwr@google.com, svv@google.com per-file *KeyEventTest.* = michaelwr@google.com, svv@google.com per-file VelocityTest.java = michaelwr@google.com, svv@google.com + +# WindowManager +per-file *Display* = file:/services/core/java/com/android/server/wm/OWNERS +per-file *Focus* = file:/services/core/java/com/android/server/wm/OWNERS +per-file *Insets* = file:/services/core/java/com/android/server/wm/OWNERS +per-file *View* = file:/services/core/java/com/android/server/wm/OWNERS +per-file *Visibility* = file:/services/core/java/com/android/server/wm/OWNERS diff --git a/core/tests/coretests/src/android/view/inputmethod/OWNERS b/core/tests/coretests/src/android/view/inputmethod/OWNERS new file mode 100644 index 000000000000..eb06b78a435b --- /dev/null +++ b/core/tests/coretests/src/android/view/inputmethod/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 34867 + +include /core/java/android/view/inputmethod/OWNERS diff --git a/core/tests/overlaytests/device/TEST_MAPPING b/core/tests/overlaytests/device/TEST_MAPPING new file mode 100644 index 000000000000..43ee00f47e84 --- /dev/null +++ b/core/tests/overlaytests/device/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name" : "OverlayDeviceTests" + } + ] +} diff --git a/core/tests/uwbtests/src/android/uwb/RangingManagerTest.java b/core/tests/uwbtests/src/android/uwb/RangingManagerTest.java index 6df1c3ed220f..c01bb75c32aa 100644 --- a/core/tests/uwbtests/src/android/uwb/RangingManagerTest.java +++ b/core/tests/uwbtests/src/android/uwb/RangingManagerTest.java @@ -45,14 +45,14 @@ public class RangingManagerTest { private static final IUwbAdapter ADAPTER = mock(IUwbAdapter.class); private static final Executor EXECUTOR = UwbTestUtils.getExecutor(); private static final PersistableBundle PARAMS = new PersistableBundle(); - private static final @CloseReason int CLOSE_REASON = CloseReason.UNKNOWN; + private static final @RangingChangeReason int REASON = RangingChangeReason.UNKNOWN; @Test - public void testOpenSession_StartRangingInvoked() throws RemoteException { + public void testOpenSession_OpenRangingInvoked() throws RemoteException { RangingManager rangingManager = new RangingManager(ADAPTER); RangingSession.Callback callback = mock(RangingSession.Callback.class); rangingManager.openSession(PARAMS, EXECUTOR, callback); - verify(ADAPTER, times(1)).startRanging(eq(rangingManager), eq(PARAMS)); + verify(ADAPTER, times(1)).openRanging(eq(rangingManager), eq(PARAMS)); } @Test @@ -60,7 +60,7 @@ public class RangingManagerTest { RangingManager rangingManager = new RangingManager(ADAPTER); RangingSession.Callback callback = mock(RangingSession.Callback.class); SessionHandle handle = new SessionHandle(1); - when(ADAPTER.startRanging(any(), any())).thenReturn(handle); + when(ADAPTER.openRanging(any(), any())).thenReturn(handle); rangingManager.openSession(PARAMS, EXECUTOR, callback); @@ -73,34 +73,34 @@ public class RangingManagerTest { } @Test - public void testOnRangingStarted_ValidSessionHandle() throws RemoteException { + public void testOnRangingOpened_ValidSessionHandle() throws RemoteException { RangingManager rangingManager = new RangingManager(ADAPTER); RangingSession.Callback callback = mock(RangingSession.Callback.class); SessionHandle handle = new SessionHandle(1); - when(ADAPTER.startRanging(any(), any())).thenReturn(handle); + when(ADAPTER.openRanging(any(), any())).thenReturn(handle); rangingManager.openSession(PARAMS, EXECUTOR, callback); - rangingManager.onRangingStarted(handle, PARAMS); - verify(callback, times(1)).onOpenSuccess(any(), any()); + rangingManager.onRangingOpened(handle); + verify(callback, times(1)).onOpened(any()); } @Test - public void testOnRangingStarted_InvalidSessionHandle() throws RemoteException { + public void testOnRangingOpened_InvalidSessionHandle() throws RemoteException { RangingManager rangingManager = new RangingManager(ADAPTER); RangingSession.Callback callback = mock(RangingSession.Callback.class); - rangingManager.onRangingStarted(new SessionHandle(2), PARAMS); - verify(callback, times(0)).onOpenSuccess(any(), any()); + rangingManager.onRangingOpened(new SessionHandle(2)); + verify(callback, times(0)).onOpened(any()); } @Test - public void testOnRangingStarted_MultipleSessionsRegistered() throws RemoteException { + public void testOnRangingOpened_MultipleSessionsRegistered() throws RemoteException { SessionHandle sessionHandle1 = new SessionHandle(1); SessionHandle sessionHandle2 = new SessionHandle(2); RangingSession.Callback callback1 = mock(RangingSession.Callback.class); RangingSession.Callback callback2 = mock(RangingSession.Callback.class); - when(ADAPTER.startRanging(any(), any())) + when(ADAPTER.openRanging(any(), any())) .thenReturn(sessionHandle1) .thenReturn(sessionHandle2); @@ -108,25 +108,50 @@ public class RangingManagerTest { rangingManager.openSession(PARAMS, EXECUTOR, callback1); rangingManager.openSession(PARAMS, EXECUTOR, callback2); - rangingManager.onRangingStarted(sessionHandle1, PARAMS); - verify(callback1, times(1)).onOpenSuccess(any(), any()); - verify(callback2, times(0)).onOpenSuccess(any(), any()); + rangingManager.onRangingOpened(sessionHandle1); + verify(callback1, times(1)).onOpened(any()); + verify(callback2, times(0)).onOpened(any()); - rangingManager.onRangingStarted(sessionHandle2, PARAMS); - verify(callback1, times(1)).onOpenSuccess(any(), any()); - verify(callback2, times(1)).onOpenSuccess(any(), any()); + rangingManager.onRangingOpened(sessionHandle2); + verify(callback1, times(1)).onOpened(any()); + verify(callback2, times(1)).onOpened(any()); } @Test - public void testOnRangingClosed_OnRangingClosedCalled() throws RemoteException { + public void testCorrectCallbackInvoked() throws RemoteException { RangingManager rangingManager = new RangingManager(ADAPTER); RangingSession.Callback callback = mock(RangingSession.Callback.class); SessionHandle handle = new SessionHandle(1); - when(ADAPTER.startRanging(any(), any())).thenReturn(handle); + when(ADAPTER.openRanging(any(), any())).thenReturn(handle); + rangingManager.openSession(PARAMS, EXECUTOR, callback); + rangingManager.onRangingOpened(handle); + verify(callback, times(1)).onOpened(any()); + + rangingManager.onRangingStarted(handle, PARAMS); + verify(callback, times(1)).onStarted(eq(PARAMS)); + + rangingManager.onRangingStartFailed(handle, REASON, PARAMS); + verify(callback, times(1)).onStartFailed(eq(REASON), eq(PARAMS)); + + RangingReport report = UwbTestUtils.getRangingReports(1); + rangingManager.onRangingResult(handle, report); + verify(callback, times(1)).onReportReceived(eq(report)); - rangingManager.onRangingClosed(handle, CLOSE_REASON, PARAMS); - verify(callback, times(1)).onClosed(anyInt(), any()); + rangingManager.onRangingReconfigured(handle, PARAMS); + verify(callback, times(1)).onReconfigured(eq(PARAMS)); + + rangingManager.onRangingReconfigureFailed(handle, REASON, PARAMS); + verify(callback, times(1)).onReconfigureFailed(eq(REASON), eq(PARAMS)); + + rangingManager.onRangingStopped(handle); + verify(callback, times(1)).onStopped(); + + rangingManager.onRangingStopFailed(handle, REASON, PARAMS); + verify(callback, times(1)).onStopFailed(eq(REASON), eq(PARAMS)); + + rangingManager.onRangingClosed(handle, REASON, PARAMS); + verify(callback, times(1)).onClosed(eq(REASON), eq(PARAMS)); } @Test @@ -138,7 +163,7 @@ public class RangingManagerTest { RangingSession.Callback callback1 = mock(RangingSession.Callback.class); RangingSession.Callback callback2 = mock(RangingSession.Callback.class); - when(ADAPTER.startRanging(any(), any())) + when(ADAPTER.openRanging(any(), any())) .thenReturn(sessionHandle1) .thenReturn(sessionHandle2); @@ -146,37 +171,23 @@ public class RangingManagerTest { rangingManager.openSession(PARAMS, EXECUTOR, callback1); rangingManager.openSession(PARAMS, EXECUTOR, callback2); - rangingManager.onRangingClosed(sessionHandle1, CLOSE_REASON, PARAMS); + rangingManager.onRangingClosed(sessionHandle1, REASON, PARAMS); verify(callback1, times(1)).onClosed(anyInt(), any()); verify(callback2, times(0)).onClosed(anyInt(), any()); - rangingManager.onRangingClosed(sessionHandle2, CLOSE_REASON, PARAMS); + rangingManager.onRangingClosed(sessionHandle2, REASON, PARAMS); verify(callback1, times(1)).onClosed(anyInt(), any()); verify(callback2, times(1)).onClosed(anyInt(), any()); } @Test - public void testOnRangingReport_OnReportReceived() throws RemoteException { - RangingManager rangingManager = new RangingManager(ADAPTER); - RangingSession.Callback callback = mock(RangingSession.Callback.class); - SessionHandle handle = new SessionHandle(1); - when(ADAPTER.startRanging(any(), any())).thenReturn(handle); - rangingManager.openSession(PARAMS, EXECUTOR, callback); - rangingManager.onRangingStarted(handle, PARAMS); - - RangingReport report = UwbTestUtils.getRangingReports(1); - rangingManager.onRangingResult(handle, report); - verify(callback, times(1)).onReportReceived(eq(report)); - } - - @Test public void testOnRangingReport_MultipleSessionsRegistered() throws RemoteException { SessionHandle sessionHandle1 = new SessionHandle(1); SessionHandle sessionHandle2 = new SessionHandle(2); RangingSession.Callback callback1 = mock(RangingSession.Callback.class); RangingSession.Callback callback2 = mock(RangingSession.Callback.class); - when(ADAPTER.startRanging(any(), any())) + when(ADAPTER.openRanging(any(), any())) .thenReturn(sessionHandle1) .thenReturn(sessionHandle2); @@ -196,65 +207,54 @@ public class RangingManagerTest { } @Test - public void testOnClose_Reasons() throws RemoteException { - runOnClose_Reason(CloseReason.LOCAL_API, - RangingSession.Callback.CLOSE_REASON_LOCAL_CLOSE_API); + public void testReasons() throws RemoteException { + runReason(RangingChangeReason.LOCAL_API, + RangingSession.Callback.REASON_LOCAL_REQUEST); + + runReason(RangingChangeReason.MAX_SESSIONS_REACHED, + RangingSession.Callback.REASON_MAX_SESSIONS_REACHED); - runOnClose_Reason(CloseReason.MAX_SESSIONS_REACHED, - RangingSession.Callback.CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED); + runReason(RangingChangeReason.PROTOCOL_SPECIFIC, + RangingSession.Callback.REASON_PROTOCOL_SPECIFIC_ERROR); - runOnClose_Reason(CloseReason.PROTOCOL_SPECIFIC, - RangingSession.Callback.CLOSE_REASON_PROTOCOL_SPECIFIC); + runReason(RangingChangeReason.REMOTE_REQUEST, + RangingSession.Callback.REASON_REMOTE_REQUEST); - runOnClose_Reason(CloseReason.REMOTE_REQUEST, - RangingSession.Callback.CLOSE_REASON_REMOTE_REQUEST); + runReason(RangingChangeReason.SYSTEM_POLICY, + RangingSession.Callback.REASON_SYSTEM_POLICY); - runOnClose_Reason(CloseReason.SYSTEM_POLICY, - RangingSession.Callback.CLOSE_REASON_LOCAL_SYSTEM_POLICY); + runReason(RangingChangeReason.BAD_PARAMETERS, + RangingSession.Callback.REASON_BAD_PARAMETERS); - runOnClose_Reason(CloseReason.UNKNOWN, - RangingSession.Callback.CLOSE_REASON_UNKNOWN); + runReason(RangingChangeReason.UNKNOWN, + RangingSession.Callback.REASON_UNKNOWN); } - private void runOnClose_Reason(@CloseReason int reasonIn, - @RangingSession.Callback.CloseReason int reasonOut) throws RemoteException { + private void runReason(@RangingChangeReason int reasonIn, + @RangingSession.Callback.Reason int reasonOut) throws RemoteException { RangingManager rangingManager = new RangingManager(ADAPTER); RangingSession.Callback callback = mock(RangingSession.Callback.class); SessionHandle handle = new SessionHandle(1); - when(ADAPTER.startRanging(any(), any())).thenReturn(handle); + when(ADAPTER.openRanging(any(), any())).thenReturn(handle); rangingManager.openSession(PARAMS, EXECUTOR, callback); - rangingManager.onRangingClosed(handle, reasonIn, PARAMS); - verify(callback, times(1)).onClosed(eq(reasonOut), eq(PARAMS)); - } - - @Test - public void testStartFailureReasons() throws RemoteException { - runOnRangingStartFailed_Reason(StartFailureReason.BAD_PARAMETERS, - RangingSession.Callback.CLOSE_REASON_LOCAL_BAD_PARAMETERS); + rangingManager.onRangingOpenFailed(handle, reasonIn, PARAMS); + verify(callback, times(1)).onOpenFailed(eq(reasonOut), eq(PARAMS)); - runOnRangingStartFailed_Reason(StartFailureReason.MAX_SESSIONS_REACHED, - RangingSession.Callback.CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED); + // Open a new session + rangingManager.openSession(PARAMS, EXECUTOR, callback); + rangingManager.onRangingOpened(handle); - runOnRangingStartFailed_Reason(StartFailureReason.PROTOCOL_SPECIFIC, - RangingSession.Callback.CLOSE_REASON_PROTOCOL_SPECIFIC); + rangingManager.onRangingStartFailed(handle, reasonIn, PARAMS); + verify(callback, times(1)).onStartFailed(eq(reasonOut), eq(PARAMS)); - runOnRangingStartFailed_Reason(StartFailureReason.SYSTEM_POLICY, - RangingSession.Callback.CLOSE_REASON_LOCAL_SYSTEM_POLICY); + rangingManager.onRangingReconfigureFailed(handle, reasonIn, PARAMS); + verify(callback, times(1)).onReconfigureFailed(eq(reasonOut), eq(PARAMS)); - runOnRangingStartFailed_Reason(StartFailureReason.UNKNOWN, - RangingSession.Callback.CLOSE_REASON_UNKNOWN); - } - - private void runOnRangingStartFailed_Reason(@StartFailureReason int reasonIn, - @RangingSession.Callback.CloseReason int reasonOut) throws RemoteException { - RangingManager rangingManager = new RangingManager(ADAPTER); - RangingSession.Callback callback = mock(RangingSession.Callback.class); - SessionHandle handle = new SessionHandle(1); - when(ADAPTER.startRanging(any(), any())).thenReturn(handle); - rangingManager.openSession(PARAMS, EXECUTOR, callback); + rangingManager.onRangingStopFailed(handle, reasonIn, PARAMS); + verify(callback, times(1)).onStopFailed(eq(reasonOut), eq(PARAMS)); - rangingManager.onRangingStartFailed(handle, reasonIn, PARAMS); + rangingManager.onRangingClosed(handle, reasonIn, PARAMS); verify(callback, times(1)).onClosed(eq(reasonOut), eq(PARAMS)); } } diff --git a/core/tests/uwbtests/src/android/uwb/RangingSessionTest.java b/core/tests/uwbtests/src/android/uwb/RangingSessionTest.java index 702c68ebc9de..e5eea26f5d11 100644 --- a/core/tests/uwbtests/src/android/uwb/RangingSessionTest.java +++ b/core/tests/uwbtests/src/android/uwb/RangingSessionTest.java @@ -19,9 +19,11 @@ package android.uwb; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -34,6 +36,8 @@ import androidx.test.filters.SmallTest; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import java.util.concurrent.Executor; @@ -43,47 +47,48 @@ import java.util.concurrent.Executor; @SmallTest @RunWith(AndroidJUnit4.class) public class RangingSessionTest { - private static final IUwbAdapter ADAPTER = mock(IUwbAdapter.class); private static final Executor EXECUTOR = UwbTestUtils.getExecutor(); private static final PersistableBundle PARAMS = new PersistableBundle(); - private static final @RangingSession.Callback.CloseReason int CLOSE_REASON = - RangingSession.Callback.CLOSE_REASON_LOCAL_GENERIC_ERROR; + private static final @RangingSession.Callback.Reason int REASON = + RangingSession.Callback.REASON_GENERIC_ERROR; @Test - public void testOnRangingStarted_OnOpenSuccessCalled() { + public void testOnRangingOpened_OnOpenSuccessCalled() { SessionHandle handle = new SessionHandle(123); RangingSession.Callback callback = mock(RangingSession.Callback.class); - RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); verifyOpenState(session, false); - session.onRangingStarted(PARAMS); + session.onRangingOpened(); verifyOpenState(session, true); // Verify that the onOpenSuccess callback was invoked - verify(callback, times(1)).onOpenSuccess(eq(session), any()); + verify(callback, times(1)).onOpened(eq(session)); verify(callback, times(0)).onClosed(anyInt(), any()); } @Test - public void testOnRangingStarted_CannotOpenClosedSession() { + public void testOnRangingOpened_CannotOpenClosedSession() { SessionHandle handle = new SessionHandle(123); RangingSession.Callback callback = mock(RangingSession.Callback.class); - RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); - session.onRangingStarted(PARAMS); + session.onRangingOpened(); verifyOpenState(session, true); - verify(callback, times(1)).onOpenSuccess(eq(session), any()); + verify(callback, times(1)).onOpened(eq(session)); verify(callback, times(0)).onClosed(anyInt(), any()); - session.onRangingClosed(CLOSE_REASON, PARAMS); + session.onRangingClosed(REASON, PARAMS); verifyOpenState(session, false); - verify(callback, times(1)).onOpenSuccess(eq(session), any()); + verify(callback, times(1)).onOpened(eq(session)); verify(callback, times(1)).onClosed(anyInt(), any()); // Now invoke the ranging started callback and ensure the session remains closed - session.onRangingStarted(PARAMS); + session.onRangingOpened(); verifyOpenState(session, false); - verify(callback, times(1)).onOpenSuccess(eq(session), any()); + verify(callback, times(1)).onOpened(eq(session)); verify(callback, times(1)).onClosed(anyInt(), any()); } @@ -91,27 +96,30 @@ public class RangingSessionTest { public void testOnRangingClosed_OnClosedCalledWhenSessionNotOpen() { SessionHandle handle = new SessionHandle(123); RangingSession.Callback callback = mock(RangingSession.Callback.class); - RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); verifyOpenState(session, false); - session.onRangingClosed(CLOSE_REASON, PARAMS); + session.onRangingClosed(REASON, PARAMS); verifyOpenState(session, false); // Verify that the onOpenSuccess callback was invoked - verify(callback, times(0)).onOpenSuccess(eq(session), any()); + verify(callback, times(0)).onOpened(eq(session)); verify(callback, times(1)).onClosed(anyInt(), any()); } - @Test public void testOnRangingClosed_OnClosedCalled() { + @Test + public void testOnRangingClosed_OnClosedCalled() { SessionHandle handle = new SessionHandle(123); RangingSession.Callback callback = mock(RangingSession.Callback.class); - RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); session.onRangingStarted(PARAMS); - session.onRangingClosed(CLOSE_REASON, PARAMS); + session.onRangingClosed(REASON, PARAMS); verify(callback, times(1)).onClosed(anyInt(), any()); verifyOpenState(session, false); - session.onRangingClosed(CLOSE_REASON, PARAMS); + session.onRangingClosed(REASON, PARAMS); verify(callback, times(2)).onClosed(anyInt(), any()); } @@ -119,7 +127,8 @@ public class RangingSessionTest { public void testOnRangingResult_OnReportReceivedCalled() { SessionHandle handle = new SessionHandle(123); RangingSession.Callback callback = mock(RangingSession.Callback.class); - RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); verifyOpenState(session, false); session.onRangingStarted(PARAMS); @@ -131,11 +140,83 @@ public class RangingSessionTest { } @Test - public void testClose() throws RemoteException { + public void testStart_CannotStartIfAlreadyStarted() throws RemoteException { SessionHandle handle = new SessionHandle(123); RangingSession.Callback callback = mock(RangingSession.Callback.class); - RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); + doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any()); + session.onRangingOpened(); + + session.start(PARAMS); + verify(callback, times(1)).onStarted(any()); + + // Calling start again should throw an illegal state + verifyThrowIllegalState(() -> session.start(PARAMS)); + verify(callback, times(1)).onStarted(any()); + } + + @Test + public void testStop_CannotStopIfAlreadyStopped() throws RemoteException { + SessionHandle handle = new SessionHandle(123); + RangingSession.Callback callback = mock(RangingSession.Callback.class); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); + doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any()); + doAnswer(new StopAnswer(session)).when(adapter).stopRanging(any()); + session.onRangingOpened(); + session.start(PARAMS); + + verifyNoThrowIllegalState(session::stop); + verify(callback, times(1)).onStopped(); + + // Calling stop again should throw an illegal state + verifyThrowIllegalState(session::stop); + verify(callback, times(1)).onStopped(); + } + + @Test + public void testReconfigure_OnlyWhenOpened() throws RemoteException { + SessionHandle handle = new SessionHandle(123); + RangingSession.Callback callback = mock(RangingSession.Callback.class); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); + doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any()); + doAnswer(new ReconfigureAnswer(session)).when(adapter).reconfigureRanging(any(), any()); + + verifyThrowIllegalState(() -> session.reconfigure(PARAMS)); + verify(callback, times(0)).onReconfigured(any()); + verifyOpenState(session, false); + + session.onRangingOpened(); + verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS)); + verify(callback, times(1)).onReconfigured(any()); + verifyOpenState(session, true); + session.onRangingStarted(PARAMS); + verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS)); + verify(callback, times(2)).onReconfigured(any()); + verifyOpenState(session, true); + + session.onRangingStopped(); + verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS)); + verify(callback, times(3)).onReconfigured(any()); + verifyOpenState(session, true); + + + session.onRangingClosed(REASON, PARAMS); + verifyThrowIllegalState(() -> session.reconfigure(PARAMS)); + verify(callback, times(3)).onReconfigured(any()); + verifyOpenState(session, false); + } + + @Test + public void testClose_NoCallbackUntilInvoked() throws RemoteException { + SessionHandle handle = new SessionHandle(123); + RangingSession.Callback callback = mock(RangingSession.Callback.class); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); + session.onRangingOpened(); // Calling close multiple times should invoke closeRanging until the session receives // the onClosed callback. @@ -143,7 +224,7 @@ public class RangingSessionTest { for (int i = 1; i <= totalCallsBeforeOnRangingClosed; i++) { session.close(); verifyOpenState(session, true); - verify(ADAPTER, times(i)).closeRanging(handle); + verify(adapter, times(i)).closeRanging(handle); verify(callback, times(0)).onClosed(anyInt(), any()); } @@ -151,18 +232,47 @@ public class RangingSessionTest { // the session's close. final int totalCallsAfterOnRangingClosed = 2; for (int i = 1; i <= totalCallsAfterOnRangingClosed; i++) { - session.onRangingClosed(CLOSE_REASON, PARAMS); + session.onRangingClosed(REASON, PARAMS); verifyOpenState(session, false); - verify(ADAPTER, times(totalCallsBeforeOnRangingClosed)).closeRanging(handle); + verify(adapter, times(totalCallsBeforeOnRangingClosed)).closeRanging(handle); verify(callback, times(i)).onClosed(anyInt(), any()); } } @Test + public void testClose_OnClosedCalled() throws RemoteException { + SessionHandle handle = new SessionHandle(123); + RangingSession.Callback callback = mock(RangingSession.Callback.class); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); + doAnswer(new CloseAnswer(session)).when(adapter).closeRanging(any()); + session.onRangingOpened(); + + session.close(); + verify(callback, times(1)).onClosed(anyInt(), any()); + } + + @Test + public void testClose_CannotInteractFurther() throws RemoteException { + SessionHandle handle = new SessionHandle(123); + RangingSession.Callback callback = mock(RangingSession.Callback.class); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); + doAnswer(new CloseAnswer(session)).when(adapter).closeRanging(any()); + session.close(); + + verifyThrowIllegalState(() -> session.start(PARAMS)); + verifyThrowIllegalState(() -> session.reconfigure(PARAMS)); + verifyThrowIllegalState(() -> session.stop()); + verifyNoThrowIllegalState(() -> session.close()); + } + + @Test public void testOnRangingResult_OnReportReceivedCalledWhenOpen() { SessionHandle handle = new SessionHandle(123); RangingSession.Callback callback = mock(RangingSession.Callback.class); - RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); assertFalse(session.isOpen()); session.onRangingStarted(PARAMS); @@ -178,7 +288,8 @@ public class RangingSessionTest { public void testOnRangingResult_OnReportReceivedNotCalledWhenNotOpen() { SessionHandle handle = new SessionHandle(123); RangingSession.Callback callback = mock(RangingSession.Callback.class); - RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); assertFalse(session.isOpen()); @@ -191,4 +302,77 @@ public class RangingSessionTest { private void verifyOpenState(RangingSession session, boolean expected) { assertEquals(expected, session.isOpen()); } + + private void verifyThrowIllegalState(Runnable runnable) { + try { + runnable.run(); + fail(); + } catch (IllegalStateException e) { + // Pass + } + } + + private void verifyNoThrowIllegalState(Runnable runnable) { + try { + runnable.run(); + } catch (IllegalStateException e) { + fail(); + } + } + + abstract class AdapterAnswer implements Answer { + protected RangingSession mSession; + + protected AdapterAnswer(RangingSession session) { + mSession = session; + } + } + + class StartAnswer extends AdapterAnswer { + StartAnswer(RangingSession session) { + super(session); + } + + @Override + public Object answer(InvocationOnMock invocation) { + mSession.onRangingStarted(PARAMS); + return null; + } + } + + class ReconfigureAnswer extends AdapterAnswer { + ReconfigureAnswer(RangingSession session) { + super(session); + } + + @Override + public Object answer(InvocationOnMock invocation) { + mSession.onRangingReconfigured(PARAMS); + return null; + } + } + + class StopAnswer extends AdapterAnswer { + StopAnswer(RangingSession session) { + super(session); + } + + @Override + public Object answer(InvocationOnMock invocation) { + mSession.onRangingStopped(); + return null; + } + } + + class CloseAnswer extends AdapterAnswer { + CloseAnswer(RangingSession session) { + super(session); + } + + @Override + public Object answer(InvocationOnMock invocation) { + mSession.onRangingClosed(REASON, PARAMS); + return null; + } + } } diff --git a/data/etc/OWNERS b/data/etc/OWNERS index 5efd0bd06b74..9867d810dba2 100644 --- a/data/etc/OWNERS +++ b/data/etc/OWNERS @@ -11,3 +11,5 @@ svetoslavganov@google.com toddke@android.com toddke@google.com yamasani@google.com + +per-file preinstalled-packages* = file:/MULTIUSER_OWNERS
\ No newline at end of file diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 057c0120b685..a185da19e71b 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -343,6 +343,8 @@ applications that come with the platform <permission name="android.permission.MOUNT_FORMAT_FILESYSTEMS"/> <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <permission name="android.permission.MOVE_PACKAGE"/> + <!-- Needed for test only --> + <permission name="android.permission.NETWORK_AIRPLANE_MODE"/> <permission name="android.permission.OBSERVE_APP_USAGE"/> <permission name="android.permission.NETWORK_SCAN"/> <permission name="android.permission.PACKAGE_USAGE_STATS" /> @@ -438,6 +440,9 @@ applications that come with the platform <permission name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION" /> <!-- Permissions required for CTS test - CtsHdmiCecHostTestCases --> <permission name="android.permission.HDMI_CEC"/> + <!-- Permission needed for CTS test - WifiManagerTest --> + <permission name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS" /> + <permission name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS" /> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> @@ -447,6 +452,8 @@ applications that come with the platform <privapp-permissions package="com.android.traceur"> <!-- Permissions required to receive BUGREPORT_STARTED intent --> <permission name="android.permission.DUMP"/> + <!-- Permissions required to start/stop tracing --> + <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"/> <!-- Permissions required for quick settings tile --> <permission name="android.permission.STATUS_BAR"/> </privapp-permissions> diff --git a/data/keyboards/Vendor_0957_Product_0001.idc b/data/keyboards/Vendor_0957_Product_0001.idc new file mode 100644 index 000000000000..e1f4346369f3 --- /dev/null +++ b/data/keyboards/Vendor_0957_Product_0001.idc @@ -0,0 +1,23 @@ +# Copyright (C) 2021 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. + +# +# Input Device Configuration file for Google Reference RCU Remote. +# +# + +# Basic Parameters +keyboard.layout = Vendor_0957_Product_0001 +keyboard.characterMap = Vendor_0957_Product_0001 +audio.mic = 1
\ No newline at end of file diff --git a/data/keyboards/Vendor_0957_Product_0001.kl b/data/keyboards/Vendor_0957_Product_0001.kl new file mode 100644 index 000000000000..e9f4f2880c91 --- /dev/null +++ b/data/keyboards/Vendor_0957_Product_0001.kl @@ -0,0 +1,72 @@ +# Copyright (C) 2021 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. + +# +# Key Layout file for Google Reference RCU Remote. +# + +key 116 POWER WAKE +key 217 ASSIST WAKE + +key 103 DPAD_UP +key 108 DPAD_DOWN +key 105 DPAD_LEFT +key 106 DPAD_RIGHT +key 353 DPAD_CENTER + +key 158 BACK +key 172 HOME WAKE + +key 113 VOLUME_MUTE +key 114 VOLUME_DOWN +key 115 VOLUME_UP + +key 2 1 +key 3 2 +key 4 3 +key 5 4 +key 6 5 +key 7 6 +key 8 7 +key 9 8 +key 10 9 +key 11 0 + +# custom keys +key usage 0x000c01BB TV_INPUT +key usage 0x000c022A BOOKMARK +key usage 0x000c0096 SETTINGS +key usage 0x000c0097 NOTIFICATION +key usage 0x000c008D GUIDE +key usage 0x000c0089 TV +key usage 0x000c009C CHANNEL_UP +key usage 0x000c009D CHANNEL_DOWN +key usage 0x000c00CD MEDIA_PLAY_PAUSE +key usage 0x000c00B4 MEDIA_SKIP_BACKWARD +key usage 0x000c00B3 MEDIA_SKIP_FORWARD +key usage 0x000c0226 MEDIA_STOP + +key usage 0x000c0077 BUTTON_3 WAKE #YouTube +key usage 0x000c0078 BUTTON_4 WAKE #Netflix +key usage 0x000c0079 BUTTON_6 WAKE #Disney+ +key usage 0x000c007A BUTTON_7 WAKE #HBOmax + +key usage 0x000c01BD INFO +key usage 0x000c0061 CAPTIONS +key usage 0x000c0185 TV_TELETEXT + +key usage 0x000c0069 PROG_RED +key usage 0x000c006A PROG_GREEN +key usage 0x000c006B PROG_BLUE +key usage 0x000c006C PROG_YELLOW
\ No newline at end of file diff --git a/framework-jarjar-rules.txt b/framework-jarjar-rules.txt index d8af726ffa72..52ee63a15a63 100644 --- a/framework-jarjar-rules.txt +++ b/framework-jarjar-rules.txt @@ -1,2 +1,3 @@ rule android.hidl.** android.internal.hidl.@1 rule android.net.wifi.WifiAnnotations* android.internal.wifi.WifiAnnotations@1 +rule com.android.server.vcn.util.** com.android.server.vcn.repackaged.util.@1 diff --git a/graphics/OWNERS b/graphics/OWNERS index a6d1bc37d27d..5851cbbdf33c 100644 --- a/graphics/OWNERS +++ b/graphics/OWNERS @@ -1 +1 @@ -include /core/java/android/graphics/OWNERS +include /graphics/java/android/graphics/OWNERS
\ No newline at end of file diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java index 7a2e5843ffc7..9b2effcde1d3 100644 --- a/graphics/java/android/graphics/SurfaceTexture.java +++ b/graphics/java/android/graphics/SurfaceTexture.java @@ -76,9 +76,9 @@ public class SurfaceTexture { /** * These fields are used by native code, do not access or modify. */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage(trackingBug = 176388660) private long mSurfaceTexture; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage(trackingBug = 176388660) private long mProducer; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private long mFrameAvailableListener; diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index 635f6c66985c..b3c33554b5aa 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -122,7 +122,7 @@ public class RippleDrawable extends LayerDrawable { private final Rect mDirtyBounds = new Rect(); /** Mirrors mLayerState with some extra information. */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage(trackingBug = 175939224) private RippleState mState; /** The masking layer, e.g. the layer with id R.id.mask. */ diff --git a/keystore/java/android/security/AndroidProtectedConfirmation.java b/keystore/java/android/security/AndroidProtectedConfirmation.java new file mode 100644 index 000000000000..dfe485ac8274 --- /dev/null +++ b/keystore/java/android/security/AndroidProtectedConfirmation.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceSpecificException; +import android.security.apc.IConfirmationCallback; +import android.security.apc.IProtectedConfirmation; +import android.security.apc.ResponseCode; +import android.util.Log; + +/** + * @hide + */ +public class AndroidProtectedConfirmation { + private static final String TAG = "AndroidProtectedConfirmation"; + + public static final int ERROR_OK = ResponseCode.OK; + public static final int ERROR_CANCELED = ResponseCode.CANCELLED; + public static final int ERROR_ABORTED = ResponseCode.ABORTED; + public static final int ERROR_OPERATION_PENDING = ResponseCode.OPERATION_PENDING; + public static final int ERROR_IGNORED = ResponseCode.IGNORED; + public static final int ERROR_SYSTEM_ERROR = ResponseCode.SYSTEM_ERROR; + public static final int ERROR_UNIMPLEMENTED = ResponseCode.UNIMPLEMENTED; + + public static final int FLAG_UI_OPTION_INVERTED = + IProtectedConfirmation.FLAG_UI_OPTION_INVERTED; + public static final int FLAG_UI_OPTION_MAGNIFIED = + IProtectedConfirmation.FLAG_UI_OPTION_MAGNIFIED; + + private IProtectedConfirmation mProtectedConfirmation; + + public AndroidProtectedConfirmation() { + mProtectedConfirmation = null; + } + + private synchronized IProtectedConfirmation getService() { + if (mProtectedConfirmation == null) { + mProtectedConfirmation = IProtectedConfirmation.Stub.asInterface(ServiceManager + .getService("android.security.apc")); + } + return mProtectedConfirmation; + } + + /** + * Requests keystore call into the confirmationui HAL to display a prompt. + * + * @param listener the binder to use for callbacks. + * @param promptText the prompt to display. + * @param extraData extra data / nonce from application. + * @param locale the locale as a BCP 47 language tag. + * @param uiOptionsAsFlags the UI options to use, as flags. + * @return one of the {@code CONFIRMATIONUI_*} constants, for + * example {@code KeyStore.CONFIRMATIONUI_OK}. + */ + public int presentConfirmationPrompt(IConfirmationCallback listener, String promptText, + byte[] extraData, String locale, int uiOptionsAsFlags) { + try { + getService().presentPrompt(listener, promptText, extraData, locale, + uiOptionsAsFlags); + return ERROR_OK; + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return ERROR_SYSTEM_ERROR; + } catch (ServiceSpecificException e) { + return e.errorCode; + } + } + + /** + * Requests keystore call into the confirmationui HAL to cancel displaying a prompt. + * + * @param listener the binder passed to the {@link #presentConfirmationPrompt} method. + * @return one of the {@code CONFIRMATIONUI_*} constants, for + * example {@code KeyStore.CONFIRMATIONUI_OK}. + */ + public int cancelConfirmationPrompt(IConfirmationCallback listener) { + try { + getService().cancelPrompt(listener); + return ERROR_OK; + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return ERROR_SYSTEM_ERROR; + } catch (ServiceSpecificException e) { + return e.errorCode; + } + } + + /** + * Requests keystore to check if the confirmationui HAL is available. + * + * @return whether the confirmationUI HAL is available. + */ + public boolean isConfirmationPromptSupported() { + try { + return getService().isSupported(); + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return false; + } + } + +} diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java index f87a3d25f90c..992454285738 100644 --- a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java +++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java @@ -120,6 +120,7 @@ abstract class KeyStoreCryptoOperationUtils { return new KeyPermanentlyInvalidatedException(); case ResponseCode.LOCKED: case ResponseCode.UNINITIALIZED: + case KeymasterDefs.KM_ERROR_KEY_USER_NOT_AUTHENTICATED: // TODO b/173111727 remove response codes LOCKED and UNINITIALIZED return new UserNotAuthenticatedException(); default: diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index cf643dd113f5..1a367d9b1734 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -44,6 +44,7 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import android.util.SparseIntArray; import android.view.Display; import java.lang.annotation.Retention; @@ -94,6 +95,7 @@ public class MediaRouter { RouteInfo mDefaultAudioVideo; RouteInfo mBluetoothA2dpRoute; + boolean mIsBluetoothA2dpOn; RouteInfo mSelectedRoute; @@ -108,9 +110,16 @@ public class MediaRouter { IMediaRouterClient mClient; MediaRouterClientState mClientState; + SparseIntArray mStreamVolume = new SparseIntArray(); + final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() { @Override public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) { + try { + mIsBluetoothA2dpOn = mAudioService.isBluetoothA2dpOn(); + } catch (RemoteException e) { + Log.e(TAG, "Error querying Bluetooth A2DP state", e); + } mHandler.post(new Runnable() { @Override public void run() { updateAudioRoutes(newRoutes); @@ -259,13 +268,24 @@ public class MediaRouter { mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName; } - boolean isBluetoothA2dpOn() { - try { - return mBluetoothA2dpRoute != null && mAudioService.isBluetoothA2dpOn(); - } catch (RemoteException e) { - Log.e(TAG, "Error querying Bluetooth A2DP state", e); - return false; + int getStreamVolume(int streamType) { + int idx = mStreamVolume.indexOfKey(streamType); + if (idx < 0) { + int volume = 0; + try { + volume = mAudioService.getStreamVolume(streamType); + mStreamVolume.put(streamType, volume); + } catch (RemoteException e) { + Log.e(TAG, "Error getting local stream volume", e); + } finally { + return volume; + } } + return mStreamVolume.valueAt(idx); + } + + boolean isBluetoothA2dpOn() { + return mBluetoothA2dpRoute != null && mIsBluetoothA2dpOn; } void updateDiscoveryRequest() { @@ -1426,12 +1446,8 @@ public class MediaRouter { selectedRoute == sStatic.mDefaultAudioVideo) { dispatchRouteVolumeChanged(selectedRoute); } else if (sStatic.mBluetoothA2dpRoute != null) { - try { - dispatchRouteVolumeChanged(sStatic.mAudioService.isBluetoothA2dpOn() ? - sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo); - } catch (RemoteException e) { - Log.e(TAG, "Error checking Bluetooth A2DP state to report volume change", e); - } + dispatchRouteVolumeChanged(sStatic.mIsBluetoothA2dpOn + ? sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo); } else { dispatchRouteVolumeChanged(sStatic.mDefaultAudioVideo); } @@ -1956,13 +1972,7 @@ public class MediaRouter { */ public int getVolume() { if (mPlaybackType == PLAYBACK_TYPE_LOCAL) { - int vol = 0; - try { - vol = sStatic.mAudioService.getStreamVolume(mPlaybackStream); - } catch (RemoteException e) { - Log.e(TAG, "Error getting local stream volume", e); - } - return vol; + return sStatic.getStreamVolume(mPlaybackStream); } else { return mVolume; } @@ -3077,11 +3087,12 @@ public class MediaRouter { if (intent.getAction().equals(AudioManager.VOLUME_CHANGED_ACTION)) { final int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); + final int newVolume = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0); + sStatic.mStreamVolume.put(streamType, newVolume); if (streamType != AudioManager.STREAM_MUSIC) { return; } - final int newVolume = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0); final int oldVolume = intent.getIntExtra( AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0); if (newVolume != oldVolume) { diff --git a/media/java/android/media/musicrecognition/OWNERS b/media/java/android/media/musicrecognition/OWNERS new file mode 100644 index 000000000000..58f5d40dd8c3 --- /dev/null +++ b/media/java/android/media/musicrecognition/OWNERS @@ -0,0 +1,6 @@ +# Bug component: 830636 + +joannechung@google.com +oni@google.com +volnov@google.com + diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java index 433c6227cd5f..30a14c84b72e 100644 --- a/media/java/android/media/tv/TvContract.java +++ b/media/java/android/media/tv/TvContract.java @@ -2450,6 +2450,71 @@ public final class TvContract { */ public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id"; + /** + * The remote control key preset number that is assigned to this channel. + * + * <p> This can be used for one-touch-tuning, tuning to the channel with + * pressing the preset button. + * + * <p> Type: INTEGER (remote control key preset number) + */ + public static final String COLUMN_REMOTE_CONTROL_KEY_PRESET_NUMBER = + "remote_control_key_preset_number"; + + /** + * The flag indicating whether this TV channel is scrambled or not. + * + * <p>Use the same coding for scrambled in the underlying broadcast standard + * if {@code free_ca_mode} in SDT is defined there (e.g. ETSI EN 300 468). + * + * <p>Type: INTEGER (boolean) + */ + public static final String COLUMN_SCRAMBLED = "scrambled"; + + /** + * The typical video resolution. + * + * <p>This is primarily used to filter out channels based on video resolution + * by applications. The value is from SDT if defined there. (e.g. ETSI EN 300 468) + * The value should match one of the followings: {@link #VIDEO_RESOLUTION_SD}, + * {@link #VIDEO_RESOLUTION_HD}, {@link #VIDEO_RESOLUTION_UHD}. + * + * <p>Type: TEXT + * + */ + public static final String COLUMN_VIDEO_RESOLUTION = "video_resolution"; + + /** + * The channel list ID of this TV channel. + * + * <p>It is used to identify the channel list constructed from broadcast SI based on the + * underlying broadcast standard or country/operator profile, if applicable. Otherwise, + * leave empty. + * + * <p>The ID can be defined by individual TV input services. For example, one may assign a + * service operator name for the service operator channel list constructed from broadcast + * SI or one may assign the {@code profile_name} of the operator_info() APDU defined in CI + * Plus 1.3 for the dedicated CICAM operator profile channel list constructed + * from CICAM NIT. + * + * <p>Type: TEXT + */ + public static final String COLUMN_CHANNEL_LIST_ID = "channel_list_id"; + + /** + * The comma-separated genre string of this TV channel. + * + * <p>Use the same language appeared in the underlying broadcast standard, if applicable. + * Otherwise, leave empty. Use + * {@link Genres#encode Genres.encode()} to create a text that can be stored in this column. + * Use {@link Genres#decode Genres.decode()} to get the broadcast genre strings from the + * text stored in the column. + * + * <p>Type: TEXT + * @see Programs#COLUMN_BROADCAST_GENRE + */ + public static final String COLUMN_BROADCAST_GENRE = Programs.COLUMN_BROADCAST_GENRE; + private Channels() {} /** diff --git a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java index 334900ba705a..766d6032955f 100644 --- a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java @@ -43,6 +43,10 @@ public class FrontendInfo { FrontendCapabilities frontendCap) { mId = id; mType = type; + // if max Frequency is negative, we set it as max value of the Integer. + if (maxFrequency < 0) { + maxFrequency = Integer.MAX_VALUE; + } mFrequencyRange = new Range<>(minFrequency, maxFrequency); mSymbolRateRange = new Range<>(minSymbolRate, maxSymbolRate); mAcquireRange = acquireRange; diff --git a/media/java/android/mtp/MtpStorageManager.java b/media/java/android/mtp/MtpStorageManager.java index c0eb5e8bbea9..0bede0dccbed 100644 --- a/media/java/android/mtp/MtpStorageManager.java +++ b/media/java/android/mtp/MtpStorageManager.java @@ -958,7 +958,7 @@ public class MtpStorageManager { MtpObject parent = obj.getParent(); MtpObject oldObj = parent.getChild(oldName); if (!success) { - // If the rename failed, we want oldObj to be the original and obj to be the dummy. + // If the rename failed, we want oldObj to be the original and obj to be the stand-in. // Switch the objects, except for their name and state. MtpObject temp = oldObj; MtpObjectState oldState = oldObj.getState(); @@ -1034,7 +1034,7 @@ public class MtpStorageManager { return generalBeginRemoveObject(obj, MtpOperation.RENAME) && generalBeginCopyObject(newObj, false); } - // Move obj to new parent, create a dummy object in the old parent. + // Move obj to new parent, create a fake object in the old parent. MtpObject oldObj = obj.copy(false); obj.setParent(newParent); oldObj.getParent().addChild(oldObj); @@ -1063,7 +1063,7 @@ public class MtpStorageManager { return generalEndCopyObject(newObj, success, true) && ret; } if (!success) { - // If the rename failed, we want oldObj to be the original and obj to be the dummy. + // If the rename failed, we want oldObj to be the original and obj to be the stand-in. // Switch the objects, except for their parent and state. MtpObject temp = oldObj; MtpObjectState oldState = oldObj.getState(); diff --git a/native/graphics/OWNERS b/native/graphics/OWNERS index a6d1bc37d27d..d81ea2cd538a 100644 --- a/native/graphics/OWNERS +++ b/native/graphics/OWNERS @@ -1 +1 @@ -include /core/java/android/graphics/OWNERS +include /graphics/java/android/graphics/OWNERS diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp index a26f715280a1..c8f3bd3666e4 100644 --- a/packages/Connectivity/service/Android.bp +++ b/packages/Connectivity/service/Android.bp @@ -14,8 +14,8 @@ // limitations under the License. // -cc_defaults { - name: "libservice-connectivity-defaults", +cc_library_shared { + name: "libservice-connectivity", // TODO: build against the NDK (sdk_version: "30" for example) cflags: [ "-Wall", @@ -26,6 +26,7 @@ cc_defaults { srcs: [ "jni/com_android_server_TestNetworkService.cpp", "jni/com_android_server_connectivity_Vpn.cpp", + "jni/onload.cpp", ], shared_libs: [ "libbase", @@ -35,27 +36,11 @@ cc_defaults { // addresses, and remove dependency on libnetutils. "libnetutils", ], -} - -cc_library_shared { - name: "libservice-connectivity", - defaults: ["libservice-connectivity-defaults"], - srcs: [ - "jni/onload.cpp", - ], apex_available: [ - // TODO: move this library to the tethering APEX and remove libservice-connectivity-static - // "com.android.tethering", + "com.android.tethering", ], } -// Static library linked into libservices.core until libservice-connectivity can be loaded from -// the tethering APEX instead. -cc_library_static { - name: "libservice-connectivity-static", - defaults: ["libservice-connectivity-defaults"], -} - java_library { name: "service-connectivity", srcs: [ @@ -75,5 +60,6 @@ java_library { ], apex_available: [ "//apex_available:platform", + "com.android.tethering", ], } diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java index a4896cb2d8dc..7f19662c6961 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java @@ -74,7 +74,7 @@ import java.util.ArrayList; public class DynamicSystemInstallationService extends Service implements InstallationAsyncTask.ProgressListener { - private static final String TAG = "DynSystemInstallationService"; + private static final String TAG = "DynamicSystemInstallationService"; // TODO (b/131866826): This is currently for test only. Will move this to System API. static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED"; diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java index ac73f35517d6..4ef5e2b4f090 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java @@ -20,6 +20,7 @@ import android.content.Context; import android.gsi.AvbPublicKey; import android.net.Uri; import android.os.AsyncTask; +import android.os.Build; import android.os.MemoryFile; import android.os.ParcelFileDescriptor; import android.os.image.DynamicSystemManager; @@ -51,7 +52,8 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog private static final long MIN_PROGRESS_TO_PUBLISH = 1 << 27; private static final List<String> UNSUPPORTED_PARTITIONS = - Arrays.asList("vbmeta", "boot", "userdata", "dtbo", "super_empty", "system_other"); + Arrays.asList( + "vbmeta", "boot", "userdata", "dtbo", "super_empty", "system_other", "scratch"); private class UnsupportedUrlException extends Exception { private UnsupportedUrlException(String message) { @@ -196,6 +198,22 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog return null; } + if (Build.IS_DEBUGGABLE) { + // If host is debuggable, then install a scratch partition so that we can do + // adb remount in the guest system. + try { + installScratch(); + } catch (IOException e) { + // Failing to install overlayFS scratch shouldn't be fatal. + // Just ignore the error and skip installing the scratch partition. + Log.w(TAG, e.toString(), e); + } + if (isCancelled()) { + mDynSystem.remove(); + return null; + } + } + mDynSystem.finishInstallation(); } catch (Exception e) { Log.e(TAG, e.toString(), e); @@ -302,12 +320,53 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog } } - private void installUserdata() throws Exception { + private void installScratch() throws IOException, InterruptedException { + final long scratchSize = mDynSystem.suggestScratchSize(); + Thread thread = new Thread() { + @Override + public void run() { + mInstallationSession = + mDynSystem.createPartition("scratch", scratchSize, /* readOnly= */ false); + } + }; + + Log.d(TAG, "Creating partition: scratch, size = " + scratchSize); + thread.start(); + + Progress progress = new Progress("scratch", scratchSize, mNumInstalledPartitions++); + + while (thread.isAlive()) { + if (isCancelled()) { + return; + } + + final long installedSize = mDynSystem.getInstallationProgress().bytes_processed; + + if (installedSize > progress.installedSize + MIN_PROGRESS_TO_PUBLISH) { + progress.installedSize = installedSize; + publishProgress(progress); + } + + Thread.sleep(100); + } + + if (mInstallationSession == null) { + throw new IOException( + "Failed to start installation with requested size: " + scratchSize); + } + // Reset installation session and verify that installation completes successfully. + mInstallationSession = null; + if (!mDynSystem.closePartition()) { + throw new IOException("Failed to complete partition installation: scratch"); + } + } + + private void installUserdata() throws IOException, InterruptedException { Thread thread = new Thread(() -> { mInstallationSession = mDynSystem.createPartition("userdata", mUserdataSize, false); }); - Log.d(TAG, "Creating partition: userdata"); + Log.d(TAG, "Creating partition: userdata, size = " + mUserdataSize); thread.start(); Progress progress = new Progress("userdata", mUserdataSize, mNumInstalledPartitions++); @@ -324,7 +383,7 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog publishProgress(progress); } - Thread.sleep(10); + Thread.sleep(100); } if (mInstallationSession == null) { @@ -445,7 +504,7 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog return; } - Thread.sleep(10); + Thread.sleep(100); } if (mInstallationSession == null) { diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java index be778e92787e..94829b506f95 100755 --- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java @@ -17,6 +17,7 @@ package com.android.packageinstaller; import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid; @@ -87,6 +88,8 @@ public class UninstallerActivity extends Activity { @Override public void onCreate(Bundle icicle) { + getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + // Never restore any state, esp. never create any fragments. The data in the fragment might // be stale, if e.g. the app was uninstalled while the activity was destroyed. super.onCreate(null); diff --git a/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS b/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS new file mode 100644 index 000000000000..e7a20b3f73a4 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS @@ -0,0 +1,8 @@ +# Default reviewers for this and subdirectories. +andychou@google.com +arcwang@google.com +goldmanj@google.com +qal@google.com +wengsu@google.com + +# Emergency approvers in case the above are not available diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index dd94d2eb8fe0..c559678d4005 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -149,5 +149,6 @@ public class GlobalSettingsValidators { VALIDATORS.put(Global.CUSTOM_BUGREPORT_HANDLER_APP, ANY_STRING_VALIDATOR); VALIDATORS.put(Global.CUSTOM_BUGREPORT_HANDLER_USER, ANY_INTEGER_VALIDATOR); VALIDATORS.put(Global.DEVELOPMENT_SETTINGS_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Global.RESTRICTED_NETWORKING_MODE, BOOLEAN_VALIDATOR); } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index e90bb36214a8..1345c3f071c7 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -300,6 +300,7 @@ public class SettingsBackupTest { Settings.Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, Settings.Global.HIDDEN_API_POLICY, + Settings.Global.FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT, Settings.Global.HIDE_ERROR_DIALOGS, Settings.Global.HTTP_PROXY, HYBRID_SYSUI_BATTERY_WARNING_FLAGS, @@ -419,6 +420,7 @@ public class SettingsBackupTest { Settings.Global.RADIO_WIMAX, Settings.Global.RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS, Settings.Global.READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT, + Settings.Global.RESTRICTED_NETWORKING_MODE, Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT, Settings.Global.SAFE_BOOT_DISALLOWED, Settings.Global.SELINUX_STATUS, diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 0fdb282edc23..741a6803a1e3 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -120,6 +120,7 @@ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.CREATE_USERS" /> <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" /> + <uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" /> <uses-permission android:name="android.permission.ACCESS_LOWPAN_STATE"/> <uses-permission android:name="android.permission.CHANGE_LOWPAN_STATE"/> <uses-permission android:name="android.permission.READ_LOWPAN_CREDENTIAL"/> @@ -291,6 +292,9 @@ <uses-permission android:name="android.permission.UPGRADE_RUNTIME_PERMISSIONS" /> <!-- Permission needed to read wifi network credentials for CtsNetTestCases --> + <uses-permission android:name="android.permission.NETWORK_AIRPLANE_MODE" /> + + <!-- Permission needed to read wifi network credentials for CtsNetTestCases --> <uses-permission android:name="android.permission.READ_WIFI_CREDENTIAL" /> <!-- Permission needed to use wifi usability API's for CtsNetTestCases --> @@ -337,6 +341,10 @@ <!-- Permission needed for CTS test - CtsHdmiCecHostTestCases --> <uses-permission android:name="android.permission.HDMI_CEC" /> + <!-- Permission needed for CTS test - WifiManagerTest --> + <uses-permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS" /> + <uses-permission android:name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 63b9bb39cba0..7b4cf0f748d3 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -47,6 +47,7 @@ import android.content.res.Configuration; import android.graphics.Bitmap; import android.net.Uri; import android.os.AsyncTask; +import android.os.Binder; import android.os.BugreportManager; import android.os.BugreportManager.BugreportCallback; import android.os.BugreportManager.BugreportCallback.BugreportErrorCode; @@ -186,7 +187,7 @@ public class BugreportProgressService extends Service { static final int SCREENSHOT_DELAY_SECONDS = 3; /** System property where dumpstate stores last triggered bugreport id */ - private static final String PROPERTY_LAST_ID = "dumpstate.last_id"; + static final String PROPERTY_LAST_ID = "dumpstate.last_id"; private static final String BUGREPORT_SERVICE = "bugreport"; @@ -233,7 +234,7 @@ public class BugreportProgressService extends Service { private File mBugreportsDir; - private BugreportManager mBugreportManager; + @VisibleForTesting BugreportManager mBugreportManager; /** * id of the notification used to set service on foreground. @@ -248,6 +249,11 @@ public class BugreportProgressService extends Service { */ private boolean mTakingScreenshot; + /** + * The delay timeout before taking a screenshot. + */ + @VisibleForTesting int mScreenshotDelaySec = SCREENSHOT_DELAY_SECONDS; + @GuardedBy("sNotificationBundle") private static final Bundle sNotificationBundle = new Bundle(); @@ -282,6 +288,7 @@ public class BugreportProgressService extends Service { mContext.getString(R.string.bugreport_notification_channel), isTv(this) ? NotificationManager.IMPORTANCE_DEFAULT : NotificationManager.IMPORTANCE_LOW)); + mBugreportManager = mContext.getSystemService(BugreportManager.class); } @Override @@ -305,7 +312,7 @@ public class BugreportProgressService extends Service { @Override public IBinder onBind(Intent intent) { - return null; + return new LocalBinder(); } @Override @@ -373,8 +380,14 @@ public class BugreportProgressService extends Service { public void onFinished() { mInfo.renameBugreportFile(); mInfo.renameScreenshots(); + if (mInfo.bugreportFile.length() == 0) { + Log.e(TAG, "Bugreport file empty. File path = " + mInfo.bugreportFile); + onError(BUGREPORT_ERROR_RUNTIME); + return; + } synchronized (mLock) { sendBugreportFinishedBroadcastLocked(); + mMainThreadHandler.post(() -> mInfoDialog.onBugreportFinished(mInfo)); } } @@ -400,10 +413,6 @@ public class BugreportProgressService extends Service { @GuardedBy("mLock") private void sendBugreportFinishedBroadcastLocked() { final String bugreportFilePath = mInfo.bugreportFile.getAbsolutePath(); - if (mInfo.bugreportFile.length() == 0) { - Log.e(TAG, "Bugreport file empty. File path = " + bugreportFilePath); - return; - } if (mInfo.type == BugreportParams.BUGREPORT_MODE_REMOTE) { sendRemoteBugreportFinishedBroadcast(mContext, bugreportFilePath, mInfo.bugreportFile); @@ -609,12 +618,21 @@ public class BugreportProgressService extends Service { BugreportInfo info = new BugreportInfo(mContext, baseName, name, shareTitle, shareDescription, bugreportType, mBugreportsDir); + synchronized (mLock) { + if (info.bugreportFile.exists()) { + Log.e(TAG, "Failed to start bugreport generation, the requested bugreport file " + + info.bugreportFile + " already exists"); + return; + } + info.createBugreportFile(); + } ParcelFileDescriptor bugreportFd = info.getBugreportFd(); if (bugreportFd == null) { Log.e(TAG, "Failed to start bugreport generation as " + " bugreport parcel file descriptor is null."); return; } + info.createScreenshotFile(mBugreportsDir); ParcelFileDescriptor screenshotFd = null; if (isDefaultScreenshotRequired(bugreportType, /* hasScreenshotButton= */ !mIsTv)) { screenshotFd = info.getDefaultScreenshotFd(); @@ -627,8 +645,6 @@ public class BugreportProgressService extends Service { } } - mBugreportManager = (BugreportManager) mContext.getSystemService( - Context.BUGREPORT_SERVICE); final Executor executor = ActivityThread.currentActivityThread().getExecutor(); Log.i(TAG, "bugreport type = " + bugreportType @@ -888,12 +904,12 @@ public class BugreportProgressService extends Service { collapseNotificationBar(); final String msg = mContext.getResources() .getQuantityString(com.android.internal.R.plurals.bugreport_countdown, - SCREENSHOT_DELAY_SECONDS, SCREENSHOT_DELAY_SECONDS); + mScreenshotDelaySec, mScreenshotDelaySec); Log.i(TAG, msg); // Show a toast just once, otherwise it might be captured in the screenshot. Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show(); - takeScreenshot(id, SCREENSHOT_DELAY_SECONDS); + takeScreenshot(id, mScreenshotDelaySec); } /** @@ -1248,6 +1264,7 @@ public class BugreportProgressService extends Service { .setContentText(content) .setContentIntent(PendingIntent.getService(mContext, info.id, shareIntent, PendingIntent.FLAG_UPDATE_CURRENT)) + .setOnlyAlertOnce(false) .setDeleteIntent(newCancelIntent(mContext, info)); if (!TextUtils.isEmpty(info.getName())) { @@ -1287,6 +1304,7 @@ public class BugreportProgressService extends Service { .setLocalOnly(true) .setColor(context.getColor( com.android.internal.R.color.system_notification_accent_color)) + .setOnlyAlertOnce(true) .extend(new Notification.TvExtender()); } @@ -1621,6 +1639,16 @@ public class BugreportProgressService extends Service { } /** + * A local binder with interface to return an instance of BugreportProgressService for the + * purpose of testing. + */ + final class LocalBinder extends Binder { + @VisibleForTesting BugreportProgressService getService() { + return BugreportProgressService.this; + } + } + + /** * Helper class encapsulating the UI elements and logic used to display a dialog where user * can change the details of a bugreport. */ @@ -1749,6 +1777,22 @@ public class BugreportProgressService extends Service { } } + /** + * Notifies the dialog that the bugreport has finished so it disables the {@code name} + * field. + * <p>Once the bugreport is finished dumpstate has already generated the final files, so + * changing the name would have no effect. + */ + void onBugreportFinished(BugreportInfo info) { + if (mId == info.id && mInfoName != null) { + mInfoName.setEnabled(false); + mInfoName.setText(null); + if (!TextUtils.isEmpty(info.getName())) { + mInfoName.setText(info.getName()); + } + } + } + void cancel() { if (mDialog != null) { mDialog.cancel(); @@ -1883,12 +1927,10 @@ public class BugreportProgressService extends Service { this.shareDescription = shareDescription == null ? "" : shareDescription; this.type = type; this.baseName = baseName; - createBugreportFile(bugreportsDir); - createScreenshotFile(bugreportsDir); + this.bugreportFile = new File(bugreportsDir, getFileName(this, ".zip")); } - void createBugreportFile(File bugreportsDir) { - bugreportFile = new File(bugreportsDir, getFileName(this, ".zip")); + void createBugreportFile() { createReadWriteFile(bugreportFile); } @@ -1993,12 +2035,21 @@ public class BugreportProgressService extends Service { Log.i(TAG, "Deleting empty bugreport file: " + bugreportFile); bugreportFile.delete(); } - for (File file : screenshotFiles) { - if (file.length() == 0) { + deleteEmptyScreenshots(); + } + + /** + * Deletes empty screenshot files. + */ + private void deleteEmptyScreenshots() { + screenshotFiles.removeIf(file -> { + final long length = file.length(); + if (length == 0) { Log.i(TAG, "Deleting empty screenshot file: " + file); file.delete(); } - } + return length == 0; + }); } /** @@ -2006,7 +2057,8 @@ public class BugreportProgressService extends Service { * {@code initialName} if user has changed it. */ void renameScreenshots() { - if (TextUtils.isEmpty(name)) { + deleteEmptyScreenshots(); + if (TextUtils.isEmpty(name) || screenshotFiles.isEmpty()) { return; } final List<File> renamedFiles = new ArrayList<>(screenshotFiles.size()); @@ -2025,7 +2077,7 @@ public class BugreportProgressService extends Service { if (newFile.length() > 0) { renamedFiles.add(newFile); } else if (newFile.delete()) { - Log.d(TAG, "screenshot file: " + newFile + "deleted successfully."); + Log.d(TAG, "screenshot file: " + newFile + " deleted successfully."); } } screenshotFiles = renamedFiles; @@ -2094,8 +2146,10 @@ public class BugreportProgressService extends Service { name = in.readString(); initialName = in.readString(); title = in.readString(); + shareTitle = in.readString(); description = in.readString(); progress.set(in.readInt()); + lastProgress.set(in.readInt()); lastUpdate.set(in.readLong()); formattedLastUpdate = in.readString(); bugreportFile = readFile(in); @@ -2106,9 +2160,10 @@ public class BugreportProgressService extends Service { } finished.set(in.readInt() == 1); + addingDetailsToZip = in.readBoolean(); + addedDetailsToZip = in.readBoolean(); screenshotCounter = in.readInt(); shareDescription = in.readString(); - shareTitle = in.readString(); type = in.readInt(); } @@ -2119,8 +2174,10 @@ public class BugreportProgressService extends Service { dest.writeString(name); dest.writeString(initialName); dest.writeString(title); + dest.writeString(shareTitle); dest.writeString(description); dest.writeInt(progress.intValue()); + dest.writeInt(lastProgress.intValue()); dest.writeLong(lastUpdate.longValue()); dest.writeString(getFormattedLastUpdate()); writeFile(dest, bugreportFile); @@ -2131,9 +2188,10 @@ public class BugreportProgressService extends Service { } dest.writeInt(finished.get() ? 1 : 0); + dest.writeBoolean(addingDetailsToZip); + dest.writeBoolean(addedDetailsToZip); dest.writeInt(screenshotCounter); dest.writeString(shareDescription); - dest.writeString(shareTitle); dest.writeInt(type); } diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java index b8cfa1e80043..947691206741 100644 --- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java +++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java @@ -25,19 +25,23 @@ import static com.android.shell.BugreportPrefs.STATE_SHOW; import static com.android.shell.BugreportPrefs.STATE_UNKNOWN; import static com.android.shell.BugreportPrefs.getWarningState; import static com.android.shell.BugreportPrefs.setWarningState; -import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT; -import static com.android.shell.BugreportProgressService.EXTRA_ID; -import static com.android.shell.BugreportProgressService.EXTRA_NAME; -import static com.android.shell.BugreportProgressService.EXTRA_SCREENSHOT; -import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_FINISHED; +import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_REQUESTED; +import static com.android.shell.BugreportProgressService.PROPERTY_LAST_ID; import static com.android.shell.BugreportProgressService.SCREENSHOT_DELAY_SECONDS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import android.app.ActivityManager; import android.app.ActivityManager.RunningServiceInfo; @@ -46,13 +50,18 @@ import android.app.NotificationManager; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.os.BugreportManager; import android.os.Build; import android.os.Bundle; +import android.os.IDumpstate; +import android.os.IDumpstateListener; +import android.os.ParcelFileDescriptor; import android.os.SystemClock; import android.os.SystemProperties; import android.service.notification.StatusBarNotification; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject; +import android.support.test.uiautomator.UiObject2; import android.support.test.uiautomator.UiObjectNotFoundException; import android.text.TextUtils; import android.text.format.DateUtils; @@ -60,10 +69,12 @@ import android.util.Log; import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; +import androidx.test.rule.ServiceTestRule; import androidx.test.runner.AndroidJUnit4; import com.android.shell.ActionSendMultipleConsumerActivity.CustomActionSendMultipleListener; +import libcore.io.IoUtils; import libcore.io.Streams; import org.junit.After; @@ -72,17 +83,19 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; -import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.Writer; -import java.text.NumberFormat; import java.util.ArrayList; import java.util.List; import java.util.SortedSet; @@ -92,10 +105,10 @@ import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; /** - * Integration tests for {@link BugreportReceiver}. + * Integration tests for {@link BugreportProgressService}. * <p> - * These tests don't mock any component and rely on external UI components (like the notification - * bar and activity chooser), which can make them unreliable and slow. + * These tests rely on external UI components (like the notificatio bar and activity chooser), + * which can make them unreliable and slow. * <p> * The general workflow is: * <ul> @@ -115,63 +128,48 @@ public class BugreportReceiverTest { // Timeout for UI operations, in milliseconds. private static final int TIMEOUT = (int) (5 * DateUtils.SECOND_IN_MILLIS); + // The default timeout is too short to verify the notification button state. Using a longer + // timeout in the tests. + private static final int SCREENSHOT_DELAY_SECONDS = 5; + // Timeout for when waiting for a screenshot to finish. private static final int SAFE_SCREENSHOT_DELAY = SCREENSHOT_DELAY_SECONDS + 10; - private static final String BUGREPORTS_DIR = "bugreports"; private static final String BUGREPORT_FILE = "test_bugreport.txt"; - private static final String ZIP_FILE = "test_bugreport.zip"; - private static final String ZIP_FILE2 = "test_bugreport2.zip"; private static final String SCREENSHOT_FILE = "test_screenshot.png"; - private static final String BUGREPORT_CONTENT = "Dump, might as well dump!\n"; private static final String SCREENSHOT_CONTENT = "A picture is worth a thousand words!\n"; - private static final int PID = 42; - private static final int PID2 = 24; - private static final int ID = 108; - private static final int ID2 = 801; - private static final String PROGRESS_PROPERTY = "dumpstate." + PID + ".progress"; - private static final String MAX_PROPERTY = "dumpstate." + PID + ".max"; - private static final String NAME_PROPERTY = "dumpstate." + PID + ".name"; private static final String NAME = "BUG, Y U NO REPORT?"; - private static final String NAME2 = "A bugreport's life"; private static final String NEW_NAME = "Bug_Forrest_Bug"; - private static final String NEW_NAME2 = "BugsyReportsy"; private static final String TITLE = "Wimbugdom Champion 2015"; - private static final String TITLE2 = "Master of the Universe"; - private static final String DESCRIPTION = "One's description..."; - private static final String DESCRIPTION2 = "...is another's treasure."; - // TODO(b/143130523): Fix (update) tests and add to presubmit - private static final String EXTRA_MAX = "android.intent.extra.MAX"; - private static final String EXTRA_PID = "android.intent.extra.PID"; - private static final String INTENT_BUGREPORT_STARTED = - "com.android.internal.intent.action.BUGREPORT_STARTED"; private static final String NO_DESCRIPTION = null; private static final String NO_NAME = null; private static final String NO_SCREENSHOT = null; private static final String NO_TITLE = null; - private static final int NO_ID = 0; - private static final boolean RENAMED_SCREENSHOTS = true; - private static final boolean DIDNT_RENAME_SCREENSHOTS = false; private String mDescription; - - private String mPlainTextPath; - private String mZipPath; - private String mZipPath2; - private String mScreenshotPath; + private String mProgressTitle; + private int mBugreportId; private Context mContext; private UiBot mUiBot; private CustomActionSendMultipleListener mListener; + private BugreportProgressService mService; + private IDumpstateListener mIDumpstateListener; + private ParcelFileDescriptor mBugreportFd; + private ParcelFileDescriptor mScreenshotFd; + + @Mock private IDumpstate mMockIDumpstate; @Rule public TestName mName = new TestName(); + @Rule public ServiceTestRule mServiceRule = new ServiceTestRule(); @Before public void setUp() throws Exception { Log.i(TAG, getName() + ".setup()"); + MockitoAnnotations.initMocks(this); Instrumentation instrumentation = getInstrumentation(); mContext = instrumentation.getTargetContext(); mUiBot = new UiBot(instrumentation, TIMEOUT); @@ -179,15 +177,8 @@ public class BugreportReceiverTest { cancelExistingNotifications(); - mPlainTextPath = getPath(BUGREPORT_FILE); - mZipPath = getPath(ZIP_FILE); - mZipPath2 = getPath(ZIP_FILE2); - mScreenshotPath = getPath(SCREENSHOT_FILE); - createTextFile(mPlainTextPath, BUGREPORT_CONTENT); - createTextFile(mScreenshotPath, SCREENSHOT_CONTENT); - createZipFile(mZipPath, BUGREPORT_FILE, BUGREPORT_CONTENT); - createZipFile(mZipPath2, BUGREPORT_FILE, BUGREPORT_CONTENT); - + mBugreportId = getBugreportId(); + mProgressTitle = getBugreportInProgress(mBugreportId); // Creates a multi-line description. StringBuilder sb = new StringBuilder(); for (int i = 1; i <= 20; i++) { @@ -195,6 +186,22 @@ public class BugreportReceiverTest { } mDescription = sb.toString(); + // Mocks BugreportManager and updates tests value to the service + mService = ((BugreportProgressService.LocalBinder) mServiceRule.bindService( + new Intent(mContext, BugreportProgressService.class))).getService(); + mService.mBugreportManager = new BugreportManager(mContext, mMockIDumpstate); + mService.mScreenshotDelaySec = SCREENSHOT_DELAY_SECONDS; + // Dup the fds which are passing to startBugreport function. + Mockito.doAnswer(invocation -> { + final boolean isScreenshotRequested = invocation.getArgument(6); + if (isScreenshotRequested) { + mScreenshotFd = ParcelFileDescriptor.dup(invocation.getArgument(3)); + } + mBugreportFd = ParcelFileDescriptor.dup(invocation.getArgument(2)); + return null; + }).when(mMockIDumpstate).startBugreport(anyInt(), any(), any(), any(), anyInt(), any(), + anyBoolean()); + setWarningState(mContext, STATE_HIDE); mUiBot.turnScreenOn(); @@ -203,6 +210,13 @@ public class BugreportReceiverTest { @After public void tearDown() throws Exception { Log.i(TAG, getName() + ".tearDown()"); + if (mBugreportFd != null) { + IoUtils.closeQuietly(mBugreportFd); + } + if (mScreenshotFd != null) { + IoUtils.closeQuietly(mScreenshotFd); + } + mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); try { cancelExistingNotifications(); } finally { @@ -219,131 +233,90 @@ public class BugreportReceiverTest { */ @Test public void testProgress() throws Exception { - resetProperties(); - sendBugreportStarted(1000); + sendBugreportStarted(); waitForScreenshotButtonEnabled(true); + assertProgressNotification(mProgressTitle, 0f); - assertProgressNotification(NAME, 0f); - - SystemProperties.set(PROGRESS_PROPERTY, "108"); - assertProgressNotification(NAME, 10.80f); - - assertProgressNotification(NAME, 50.00f); - - SystemProperties.set(PROGRESS_PROPERTY, "950"); - assertProgressNotification(NAME, 95.00f); - - // Make sure progress never goes back... - SystemProperties.set(MAX_PROPERTY, "2000"); - assertProgressNotification(NAME, 95.00f); - - SystemProperties.set(PROGRESS_PROPERTY, "1000"); - assertProgressNotification(NAME, 95.00f); + mIDumpstateListener.onProgress(10); + assertProgressNotification(mProgressTitle, 10); - // ...only forward... - SystemProperties.set(PROGRESS_PROPERTY, "1902"); - assertProgressNotification(NAME, 95.10f); - - SystemProperties.set(PROGRESS_PROPERTY, "1960"); - assertProgressNotification(NAME, 98.00f); + mIDumpstateListener.onProgress(95); + assertProgressNotification(mProgressTitle, 95.00f); // ...but never more than the capped value. - SystemProperties.set(PROGRESS_PROPERTY, "2000"); - assertProgressNotification(NAME, 99.00f); + mIDumpstateListener.onProgress(200); + assertProgressNotification(mProgressTitle, 99); - SystemProperties.set(PROGRESS_PROPERTY, "3000"); - assertProgressNotification(NAME, 99.00f); + mIDumpstateListener.onProgress(300); + assertProgressNotification(mProgressTitle, 99); - Bundle extras = - sendBugreportFinishedAndGetSharedIntent(ID, mPlainTextPath, mScreenshotPath); - assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, ID, PID, ZIP_FILE, - NAME, NO_TITLE, NO_DESCRIPTION, 0, RENAMED_SCREENSHOTS); + Bundle extras = sendBugreportFinishedAndGetSharedIntent(mBugreportId); + assertActionSendMultiple(extras); assertServiceNotRunning(); } @Test public void testProgress_cancel() throws Exception { - resetProperties(); - sendBugreportStarted(1000); + sendBugreportStarted(); waitForScreenshotButtonEnabled(true); - final NumberFormat nf = NumberFormat.getPercentInstance(); - nf.setMinimumFractionDigits(2); - nf.setMaximumFractionDigits(2); - - assertProgressNotification(NAME, 00.00f); + assertProgressNotification(mProgressTitle, 00.00f); - cancelFromNotification(); + cancelFromNotification(mProgressTitle); - waitForService(false); + assertServiceNotRunning(); } @Test public void testProgress_takeExtraScreenshot() throws Exception { - resetProperties(); - sendBugreportStarted(1000); + sendBugreportStarted(); waitForScreenshotButtonEnabled(true); takeScreenshot(); assertScreenshotButtonEnabled(false); waitForScreenshotButtonEnabled(true); - sendBugreportFinished(ID, mPlainTextPath, mScreenshotPath); - - Bundle extras = acceptBugreportAndGetSharedIntent(ID); - assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, ID, PID, ZIP_FILE, - NAME, NO_TITLE, NO_DESCRIPTION, 1, RENAMED_SCREENSHOTS); + Bundle extras = sendBugreportFinishedAndGetSharedIntent(mBugreportId); + assertActionSendMultiple(extras, NO_NAME, NO_TITLE, NO_DESCRIPTION, 1); assertServiceNotRunning(); } @Test public void testScreenshotFinishesAfterBugreport() throws Exception { - resetProperties(); - - sendBugreportStarted(1000); + sendBugreportStarted(); waitForScreenshotButtonEnabled(true); takeScreenshot(); - sendBugreportFinished(ID, mPlainTextPath, NO_SCREENSHOT); - waitShareNotification(ID); + sendBugreportFinished(); + waitShareNotification(mBugreportId); // There's no indication in the UI about the screenshot finish, so just sleep like a baby... sleep(SAFE_SCREENSHOT_DELAY * DateUtils.SECOND_IN_MILLIS); - Bundle extras = acceptBugreportAndGetSharedIntent(ID); - assertActionSendMultiple(extras, BUGREPORT_CONTENT, NO_SCREENSHOT, ID, PID, ZIP_FILE, - NAME, NO_TITLE, NO_DESCRIPTION, 1, RENAMED_SCREENSHOTS); + Bundle extras = acceptBugreportAndGetSharedIntent(mBugreportId); + assertActionSendMultiple(extras, NO_NAME, NO_TITLE, NO_DESCRIPTION, 1); assertServiceNotRunning(); } @Test public void testProgress_changeDetailsInvalidInput() throws Exception { - resetProperties(); - sendBugreportStarted(1000); + sendBugreportStarted(); waitForScreenshotButtonEnabled(true); - DetailsUi detailsUi = new DetailsUi(mUiBot, ID, NAME); - - // Check initial name. - detailsUi.assertName(NAME); + DetailsUi detailsUi = new DetailsUi(mBugreportId); - // Change name - it should have changed system property once focus is changed. + // Change name detailsUi.focusOnName(); detailsUi.nameField.setText(NEW_NAME); detailsUi.focusAwayFromName(); - assertPropertyValue(NAME_PROPERTY, NEW_NAME); - - // Cancel the dialog to make sure property was restored. - detailsUi.clickCancel(); - assertPropertyValue(NAME_PROPERTY, NAME); + detailsUi.clickOk(); // Now try to set an invalid name. - detailsUi.reOpen(NAME); + detailsUi.reOpen(NEW_NAME); detailsUi.nameField.setText("/etc/passwd"); detailsUi.clickOk(); - assertPropertyValue(NAME_PROPERTY, "_etc_passwd"); // Finally, make the real changes. detailsUi.reOpen("_etc_passwd"); @@ -353,27 +326,20 @@ public class BugreportReceiverTest { detailsUi.clickOk(); - assertPropertyValue(NAME_PROPERTY, NEW_NAME); assertProgressNotification(NEW_NAME, 00.00f); - Bundle extras = sendBugreportFinishedAndGetSharedIntent(ID, mPlainTextPath, - mScreenshotPath, TITLE); - assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, ID, PID, TITLE, - NEW_NAME, TITLE, mDescription, 0, RENAMED_SCREENSHOTS); + Bundle extras = sendBugreportFinishedAndGetSharedIntent(TITLE); + assertActionSendMultiple(extras, NEW_NAME, TITLE, mDescription, 0); assertServiceNotRunning(); } @Test public void testProgress_cancelBugClosesDetailsDialog() throws Exception { - resetProperties(); - sendBugreportStarted(1000); + sendBugreportStarted(); waitForScreenshotButtonEnabled(true); - DetailsUi detailsUi = new DetailsUi(mUiBot, ID, NAME); - detailsUi.assertName(NAME); // Sanity check - - cancelFromNotification(); + cancelFromNotification(mProgressTitle); mUiBot.collapseStatusBar(); assertDetailsUiClosed(); @@ -381,40 +347,24 @@ public class BugreportReceiverTest { } @Test - public void testProgress_changeDetailsPlainBugreport() throws Exception { - changeDetailsTest(true); - } - - @Test - public void testProgress_changeDetailsZippedBugreport() throws Exception { - changeDetailsTest(false); - } - - private void changeDetailsTest(boolean plainText) throws Exception { - resetProperties(); - sendBugreportStarted(1000); + public void testProgress_changeDetailsTest() throws Exception { + sendBugreportStarted(); waitForScreenshotButtonEnabled(true); - DetailsUi detailsUi = new DetailsUi(mUiBot, ID, NAME); - - // Check initial name. - detailsUi.assertName(NAME); + DetailsUi detailsUi = new DetailsUi(mBugreportId); // Change fields. - detailsUi.reOpen(NAME); + detailsUi.reOpen(mProgressTitle); detailsUi.nameField.setText(NEW_NAME); detailsUi.titleField.setText(TITLE); detailsUi.descField.setText(mDescription); detailsUi.clickOk(); - assertPropertyValue(NAME_PROPERTY, NEW_NAME); assertProgressNotification(NEW_NAME, 00.00f); - Bundle extras = sendBugreportFinishedAndGetSharedIntent(ID, - plainText? mPlainTextPath : mZipPath, mScreenshotPath, TITLE); - assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, ID, PID, TITLE, - NEW_NAME, TITLE, mDescription, 0, RENAMED_SCREENSHOTS); + Bundle extras = sendBugreportFinishedAndGetSharedIntent(TITLE); + assertActionSendMultiple(extras, NEW_NAME, TITLE, mDescription, 0); assertServiceNotRunning(); } @@ -430,60 +380,18 @@ public class BugreportReceiverTest { } private void changeJustDetailsTest(boolean touchDetails) throws Exception { - resetProperties(); - sendBugreportStarted(1000); + sendBugreportStarted(); waitForScreenshotButtonEnabled(true); - DetailsUi detailsUi = new DetailsUi(mUiBot, ID, NAME, touchDetails); + DetailsUi detailsUi = new DetailsUi(mBugreportId, touchDetails); detailsUi.nameField.setText(""); detailsUi.titleField.setText(""); detailsUi.descField.setText(mDescription); detailsUi.clickOk(); - Bundle extras = sendBugreportFinishedAndGetSharedIntent(ID, mZipPath, mScreenshotPath); - assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, ID, PID, ZIP_FILE, - NO_NAME, NO_TITLE, mDescription, 0, DIDNT_RENAME_SCREENSHOTS); - - assertServiceNotRunning(); - } - - @Test - public void testProgress_changeJustDetailsIsClearedOnSecondBugreport() throws Exception { - resetProperties(); - sendBugreportStarted(ID, PID, NAME, 1000); - waitForScreenshotButtonEnabled(true); - - DetailsUi detailsUi = new DetailsUi(mUiBot, ID, NAME); - detailsUi.assertName(NAME); - detailsUi.assertTitle(""); - detailsUi.assertDescription(""); - assertTrue("didn't enable name on UI", detailsUi.nameField.isEnabled()); - detailsUi.nameField.setText(NEW_NAME); - detailsUi.titleField.setText(TITLE); - detailsUi.descField.setText(DESCRIPTION); - detailsUi.clickOk(); - - sendBugreportStarted(ID2, PID2, NAME2, 1000); - - sendBugreportFinished(ID, mZipPath, mScreenshotPath); - Bundle extras = acceptBugreportAndGetSharedIntent(TITLE); - - detailsUi = new DetailsUi(mUiBot, ID2, NAME2); - detailsUi.assertName(NAME2); - detailsUi.assertTitle(""); - detailsUi.assertDescription(""); - assertTrue("didn't enable name on UI", detailsUi.nameField.isEnabled()); - detailsUi.nameField.setText(NEW_NAME2); - detailsUi.titleField.setText(TITLE2); - detailsUi.descField.setText(DESCRIPTION2); - detailsUi.clickOk(); - - // Must use a different zip file otherwise it will fail because zip already contains - // title.txt and description.txt entries. - extras = sendBugreportFinishedAndGetSharedIntent(ID2, mZipPath2, NO_SCREENSHOT, TITLE2); - assertActionSendMultiple(extras, BUGREPORT_CONTENT, NO_SCREENSHOT, ID2, PID2, TITLE2, - NEW_NAME2, TITLE2, DESCRIPTION2, 0, RENAMED_SCREENSHOTS); + Bundle extras = sendBugreportFinishedAndGetSharedIntent(mBugreportId); + assertActionSendMultiple(extras, NO_NAME, NO_TITLE, mDescription, 0); assertServiceNotRunning(); } @@ -507,26 +415,25 @@ public class BugreportReceiverTest { } private void bugreportFinishedWhileChangingDetailsTest(boolean waitScreenshot) throws Exception { - resetProperties(); - sendBugreportStarted(1000); + sendBugreportStarted(); if (waitScreenshot) { waitForScreenshotButtonEnabled(true); } - DetailsUi detailsUi = new DetailsUi(mUiBot, ID, NAME); + DetailsUi detailsUi = new DetailsUi(mBugreportId); // Finish the bugreport while user's still typing the name. detailsUi.nameField.setText(NEW_NAME); - sendBugreportFinished(ID, mPlainTextPath, mScreenshotPath); + sendBugreportFinished(); // Wait until the share notification is received... - waitShareNotification(ID); + waitShareNotification(mBugreportId); // ...then close notification bar. mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); // Make sure UI was updated properly. assertFalse("didn't disable name on UI", detailsUi.nameField.isEnabled()); - assertEquals("didn't revert name on UI", NAME, detailsUi.nameField.getText().toString()); + assertNotEquals("didn't revert name on UI", NAME, detailsUi.nameField.getText()); // Finish changing other fields. detailsUi.titleField.setText(TITLE); @@ -534,9 +441,8 @@ public class BugreportReceiverTest { detailsUi.clickOk(); // Finally, share bugreport. - Bundle extras = acceptBugreportAndGetSharedIntent(ID); - assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, ID, PID, TITLE, - NAME, TITLE, mDescription, 0, RENAMED_SCREENSHOTS); + Bundle extras = acceptBugreportAndGetSharedIntent(mBugreportId); + assertActionSendMultiple(extras, NO_NAME, TITLE, mDescription, 0); assertServiceNotRunning(); } @@ -569,11 +475,14 @@ public class BugreportReceiverTest { } // Send notification and click on share. - sendBugreportFinished(NO_ID, mPlainTextPath, null); - mUiBot.clickOnNotification(mContext.getString(R.string.bugreport_finished_title, NO_ID)); + sendBugreportStarted(); + waitForScreenshotButtonEnabled(true); + sendBugreportFinished(); + mUiBot.clickOnNotification(mContext.getString( + R.string.bugreport_finished_title, mBugreportId)); // Handle the warning - mUiBot.getVisibleObject(mContext.getString(R.string.bugreport_confirm)); + mUiBot.getObject(mContext.getString(R.string.bugreport_confirm)); // TODO: get ok and dontShowAgain from the dialog reference above UiObject dontShowAgain = mUiBot.getVisibleObject(mContext.getString(R.string.bugreport_confirm_dont_repeat)); @@ -597,7 +506,7 @@ public class BugreportReceiverTest { // Share the bugreport. mUiBot.chooseActivity(UI_NAME); Bundle extras = mListener.getExtras(); - assertActionSendMultiple(extras, BUGREPORT_CONTENT, NO_SCREENSHOT); + assertActionSendMultiple(extras); // Make sure it's hidden now. int newState = getWarningState(mContext, STATE_UNKNOWN); @@ -605,35 +514,37 @@ public class BugreportReceiverTest { } @Test - public void testShareBugreportAfterServiceDies() throws Exception { - sendBugreportFinished(NO_ID, mPlainTextPath, NO_SCREENSHOT); - waitForService(false); - Bundle extras = acceptBugreportAndGetSharedIntent(NO_ID); - assertActionSendMultiple(extras, BUGREPORT_CONTENT, NO_SCREENSHOT); - } + public void testBugreportFinished_withEmptyBugreportFile() throws Exception { + sendBugreportStarted(); - @Test - public void testBugreportFinished_plainBugreportAndScreenshot() throws Exception { - Bundle extras = sendBugreportFinishedAndGetSharedIntent(mPlainTextPath, mScreenshotPath); - assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT); - } + IoUtils.closeQuietly(mBugreportFd); + mBugreportFd = null; + sendBugreportFinished(); - @Test - public void testBugreportFinished_zippedBugreportAndScreenshot() throws Exception { - Bundle extras = sendBugreportFinishedAndGetSharedIntent(mZipPath, mScreenshotPath); - assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT); + assertServiceNotRunning(); } @Test - public void testBugreportFinished_plainBugreportAndNoScreenshot() throws Exception { - Bundle extras = sendBugreportFinishedAndGetSharedIntent(mPlainTextPath, NO_SCREENSHOT); - assertActionSendMultiple(extras, BUGREPORT_CONTENT, NO_SCREENSHOT); + public void testShareBugreportAfterServiceDies() throws Exception { + sendBugreportStarted(); + waitForScreenshotButtonEnabled(true); + sendBugreportFinished(); + killService(); + assertServiceNotRunning(); + Bundle extras = acceptBugreportAndGetSharedIntent(mBugreportId); + assertActionSendMultiple(extras); } @Test - public void testBugreportFinished_zippedBugreportAndNoScreenshot() throws Exception { - Bundle extras = sendBugreportFinishedAndGetSharedIntent(mZipPath, NO_SCREENSHOT); - assertActionSendMultiple(extras, BUGREPORT_CONTENT, NO_SCREENSHOT); + public void testBugreportRequestTwice_oneStartBugreportInvoked() throws Exception { + sendBugreportStarted(); + new BugreportRequestedReceiver().onReceive(mContext, + new Intent(INTENT_BUGREPORT_REQUESTED)); + getInstrumentation().waitForIdleSync(); + + verify(mMockIDumpstate, times(1)).startBugreport(anyInt(), any(), any(), any(), + anyInt(), any(), anyBoolean()); + sendBugreportFinished(); } private void cancelExistingNotifications() { @@ -664,10 +575,10 @@ public class BugreportReceiverTest { assertEquals("old notifications were not cancelled", 0, nm.getActiveNotifications().length); } - private void cancelFromNotification() { - openProgressNotification(NAME); - UiObject cancelButton = mUiBot.getVisibleObject(mContext.getString( - com.android.internal.R.string.cancel).toUpperCase()); + private void cancelFromNotification(String name) { + openProgressNotification(name); + UiObject cancelButton = mUiBot.getObject(mContext.getString( + com.android.internal.R.string.cancel)); mUiBot.click(cancelButton, "cancel_button"); } @@ -676,67 +587,60 @@ public class BugreportReceiverTest { // TODO: need a way to get the ProgresBar from the "android:id/progress" UIObject... } - private UiObject openProgressNotification(String bugreportName) { - Log.v(TAG, "Looking for progress notification for '" + bugreportName + "'"); - return mUiBot.getNotification(bugreportName); - } - - void resetProperties() { - // TODO: call method to remove property instead - SystemProperties.set(PROGRESS_PROPERTY, "Reset"); - SystemProperties.set(MAX_PROPERTY, "Reset"); - SystemProperties.set(NAME_PROPERTY, "Reset"); + private void openProgressNotification(String title) { + Log.v(TAG, "Looking for progress notification for '" + title + "'"); + UiObject2 notification = mUiBot.getNotification2(title); + if (notification != null) { + mUiBot.expandNotification(notification); + } } /** - * Sends a "bugreport started" intent with the default values. + * Sends a "bugreport requested" intent with the default values. */ - private void sendBugreportStarted(int max) throws Exception { - sendBugreportStarted(ID, PID, NAME, max); - } - - private void sendBugreportStarted(int id, int pid, String name, int max) throws Exception { - Intent intent = new Intent(INTENT_BUGREPORT_STARTED); - intent.setPackage("com.android.shell"); - intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); - intent.putExtra(EXTRA_ID, id); - intent.putExtra(EXTRA_PID, pid); - intent.putExtra(EXTRA_NAME, name); - intent.putExtra(EXTRA_MAX, max); - mContext.sendBroadcast(intent); + private void sendBugreportStarted() throws Exception { + Intent intent = new Intent(INTENT_BUGREPORT_REQUESTED); + // Ideally, we should invoke BugreportRequestedReceiver by sending + // INTENT_BUGREPORT_REQUESTED. But the intent has been protected broadcast by the system + // starting from S. + new BugreportRequestedReceiver().onReceive(mContext, intent); + + ArgumentCaptor<IDumpstateListener> listenerCap = ArgumentCaptor.forClass( + IDumpstateListener.class); + verify(mMockIDumpstate, timeout(TIMEOUT)).startBugreport(anyInt(), any(), any(), any(), + anyInt(), listenerCap.capture(), anyBoolean()); + mIDumpstateListener = listenerCap.getValue(); + assertNotNull("Dumpstate listener should not be null", mIDumpstateListener); + mIDumpstateListener.onProgress(0); } /** - * Sends a "bugreport finished" intent and waits for the result. + * Sends a "bugreport finished" event and waits for the result. * + * @param id The bugreport id for finished notification string title substitution. * @return extras sent in the shared intent. */ - private Bundle sendBugreportFinishedAndGetSharedIntent(String bugreportPath, - String screenshotPath) { - return sendBugreportFinishedAndGetSharedIntent(NO_ID, bugreportPath, screenshotPath); + private Bundle sendBugreportFinishedAndGetSharedIntent(int id) throws Exception { + sendBugreportFinished(); + return acceptBugreportAndGetSharedIntent(id); } /** - * Sends a "bugreport finished" intent and waits for the result. + * Sends a "bugreport finished" event and waits for the result. * + * @param notificationTitle The title of finished notification. * @return extras sent in the shared intent. */ - private Bundle sendBugreportFinishedAndGetSharedIntent(int id, String bugreportPath, - String screenshotPath) { - sendBugreportFinished(id, bugreportPath, screenshotPath); - return acceptBugreportAndGetSharedIntent(id); - } - - // TODO: document / merge these 3 sendBugreportFinishedAndGetSharedIntent methods - private Bundle sendBugreportFinishedAndGetSharedIntent(int id, String bugreportPath, - String screenshotPath, String notificationTitle) { - sendBugreportFinished(id, bugreportPath, screenshotPath); + private Bundle sendBugreportFinishedAndGetSharedIntent(String notificationTitle) + throws Exception { + sendBugreportFinished(); return acceptBugreportAndGetSharedIntent(notificationTitle); } /** * Accepts the notification to share the finished bugreport and waits for the result. * + * @param id The bugreport id for finished notification string title substitution. * @return extras sent in the shared intent. */ private Bundle acceptBugreportAndGetSharedIntent(int id) { @@ -744,7 +648,12 @@ public class BugreportReceiverTest { return acceptBugreportAndGetSharedIntent(notificationTitle); } - // TODO: document and/or merge these 2 acceptBugreportAndGetSharedIntent methods + /** + * Accepts the notification to share the finished bugreport and waits for the result. + * + * @param notificationTitle The title of finished notification. + * @return extras sent in the shared intent. + */ private Bundle acceptBugreportAndGetSharedIntent(String notificationTitle) { mUiBot.clickOnNotification(notificationTitle); mUiBot.chooseActivity(UI_NAME); @@ -759,53 +668,38 @@ public class BugreportReceiverTest { } /** - * Sends a "bugreport finished" intent. + * Callbacks to service to finish the bugreport. */ - private void sendBugreportFinished(int id, String bugreportPath, String screenshotPath) { - Intent intent = new Intent(INTENT_BUGREPORT_FINISHED); - intent.setPackage("com.android.shell"); - intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); - if (id != NO_ID) { - intent.putExtra(EXTRA_ID, id); + private void sendBugreportFinished() throws Exception { + if (mBugreportFd != null) { + writeZipFile(mBugreportFd, BUGREPORT_FILE, BUGREPORT_CONTENT); } - if (bugreportPath != null) { - intent.putExtra(EXTRA_BUGREPORT, bugreportPath); + if (mScreenshotFd != null) { + writeScreenshotFile(mScreenshotFd, SCREENSHOT_CONTENT); } - if (screenshotPath != null) { - intent.putExtra(EXTRA_SCREENSHOT, screenshotPath); - } - - mContext.sendBroadcast(intent); + mIDumpstateListener.onFinished(); + getInstrumentation().waitForIdleSync(); } /** * Asserts the proper {@link Intent#ACTION_SEND_MULTIPLE} intent was sent. */ - private void assertActionSendMultiple(Bundle extras, String bugreportContent, - String screenshotContent) throws IOException { - assertActionSendMultiple(extras, bugreportContent, screenshotContent, ID, PID, ZIP_FILE, - NO_NAME, NO_TITLE, NO_DESCRIPTION, 0, DIDNT_RENAME_SCREENSHOTS); + private void assertActionSendMultiple(Bundle extras) throws IOException { + assertActionSendMultiple(extras, NO_NAME, NO_TITLE, NO_DESCRIPTION, 0); } /** * Asserts the proper {@link Intent#ACTION_SEND_MULTIPLE} intent was sent. * * @param extras extras received in the intent - * @param bugreportContent expected content in the bugreport file - * @param screenshotContent expected content in the screenshot file (sent by dumpstate), if any - * @param id emulated dumpstate id - * @param pid emulated dumpstate pid - * @param name expected subject * @param name bugreport name as provided by the user (or received by dumpstate) * @param title bugreport name as provided by the user * @param description bugreport description as provided by the user * @param numberScreenshots expected number of screenshots taken by Shell. - * @param renamedScreenshots whether the screenshots are expected to be renamed */ - private void assertActionSendMultiple(Bundle extras, String bugreportContent, - String screenshotContent, int id, int pid, String subject, - String name, String title, String description, - int numberScreenshots, boolean renamedScreenshots) throws IOException { + private void assertActionSendMultiple(Bundle extras, String name, String title, + String description, int numberScreenshots) + throws IOException { String body = extras.getString(Intent.EXTRA_TEXT); assertContainsRegex("missing build info", SystemProperties.get("ro.build.description"), body); @@ -815,11 +709,21 @@ public class BugreportReceiverTest { assertContainsRegex("missing description", description, body); } - assertEquals("wrong subject", subject, extras.getString(Intent.EXTRA_SUBJECT)); + final String extrasSubject = extras.getString(Intent.EXTRA_SUBJECT); + if (title != null) { + assertEquals("wrong subject", title, extrasSubject); + } else { + if (name != null) { + assertEquals("wrong subject", getBugreportName(name), extrasSubject); + } else { + assertTrue("wrong subject", extrasSubject.startsWith( + getBugreportPrefixName())); + } + } List<Uri> attachments = extras.getParcelableArrayList(Intent.EXTRA_STREAM); int expectedNumberScreenshots = numberScreenshots; - if (screenshotContent != null) { + if (getScreenshotContent() != null) { expectedNumberScreenshots ++; // Add screenshot received by dumpstate } int expectedSize = expectedNumberScreenshots + 1; // All screenshots plus the bugreport file @@ -858,7 +762,7 @@ public class BugreportReceiverTest { } } // Check external screenshot - if (screenshotContent != null) { + if (getScreenshotContent() != null) { assertNotNull("did not get .png attachment for external screenshot", externalScreenshotUri); assertContent(externalScreenshotUri, SCREENSHOT_CONTENT); @@ -866,17 +770,18 @@ public class BugreportReceiverTest { assertNull("should not have .png attachment for external screenshot", externalScreenshotUri); } - // Check internal screenshots. - SortedSet<String> expectedNames = new TreeSet<>(); - for (int i = 1 ; i <= numberScreenshots; i++) { - String prefix = renamedScreenshots ? name : Integer.toString(pid); - String expectedName = "screenshot-" + prefix + "-" + i + ".png"; - expectedNames.add(expectedName); + // Check internal screenshots' file names. + if (name != null) { + SortedSet<String> expectedNames = new TreeSet<>(); + for (int i = 1; i <= numberScreenshots; i++) { + String expectedName = "screenshot-" + name + "-" + i + ".png"; + expectedNames.add(expectedName); + } + // Ideally we should use MoreAsserts, but the error message in case of failure is not + // really useful. + assertEquals("wrong names for internal screenshots", + expectedNames, internalScreenshotNames); } - // Ideally we should use MoreAsserts, but the error message in case of failure is not - // really useful. - assertEquals("wrong names for internal screenshots", - expectedNames, internalScreenshotNames); } private void assertContent(Uri uri, String expectedContent) throws IOException { @@ -909,28 +814,9 @@ public class BugreportReceiverTest { fail("Did not find entry '" + entryName + "' on file '" + uri + "'"); } - private void assertPropertyValue(String key, String expectedValue) { - // Since the property is set in a different thread by BugreportProgressService, we need to - // poll it a couple times... - - for (int i = 1; i <= 5; i++) { - String actualValue = SystemProperties.get(key); - if (expectedValue.equals(actualValue)) { - return; - } - Log.d(TAG, "Value of property " + key + " (" + actualValue - + ") does not match expected value (" + expectedValue - + ") on attempt " + i + ". Sleeping before next attempt..."); - sleep(1000); - } - // Final try... - String actualValue = SystemProperties.get(key); - assertEquals("Wrong value for property '" + key + "'", expectedValue, actualValue); - } - private void assertServiceNotRunning() { - String service = BugreportProgressService.class.getName(); - assertFalse("Service '" + service + "' is still running", isServiceRunning(service)); + mServiceRule.unbindService(); + waitForService(false); } private boolean isServiceRunning(String name) { @@ -962,7 +848,7 @@ public class BugreportReceiverTest { private void killService() { String service = BugreportProgressService.class.getName(); - + mServiceRule.unbindService(); if (!isServiceRunning(service)) return; Log.w(TAG, "Service '" + service + "' is still running, killing it"); @@ -980,18 +866,19 @@ public class BugreportReceiverTest { } } - private void createTextFile(String path, String content) throws IOException { - Log.v(TAG, "createFile(" + path + ")"); + private void writeScreenshotFile(ParcelFileDescriptor fd, String content) throws IOException { + Log.v(TAG, "writeScreenshotFile(" + fd + ")"); try (Writer writer = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(path)))) { + new FileOutputStream(fd.getFileDescriptor())))) { writer.write(content); } } - private void createZipFile(String path, String entryName, String content) throws IOException { - Log.v(TAG, "createZipFile(" + path + ", " + entryName + ")"); + private void writeZipFile(ParcelFileDescriptor fd, String entryName, String content) + throws IOException { + Log.v(TAG, "writeZipFile(" + fd + ", " + entryName + ")"); try (ZipOutputStream zos = new ZipOutputStream( - new BufferedOutputStream(new FileOutputStream(path)))) { + new BufferedOutputStream(new FileOutputStream(fd.getFileDescriptor())))) { ZipEntry entry = new ZipEntry(entryName); zos.putNextEntry(entry); byte[] data = content.getBytes(); @@ -1000,25 +887,13 @@ public class BugreportReceiverTest { } } - private String getPath(String file) { - final File rootDir = mContext.getFilesDir(); - final File dir = new File(rootDir, BUGREPORTS_DIR); - if (!dir.exists()) { - Log.i(TAG, "Creating directory " + dir); - assertTrue("Could not create directory " + dir, dir.mkdir()); - } - String path = new File(dir, file).getAbsolutePath(); - Log.v(TAG, "Path for '" + file + "': " + path); - return path; - } - /** * Gets the notification button used to take a screenshot. */ private UiObject getScreenshotButton() { - openProgressNotification(NAME); - return mUiBot.getVisibleObject( - mContext.getString(R.string.bugreport_screenshot_action).toUpperCase()); + openProgressNotification(mProgressTitle); + return mUiBot.getObject( + mContext.getString(R.string.bugreport_screenshot_action)); } /** @@ -1072,12 +947,36 @@ public class BugreportReceiverTest { Log.d(TAG, "woke up"); } + private int getBugreportId() { + return SystemProperties.getInt(PROPERTY_LAST_ID, 1); + } + + private String getBugreportInProgress(int bugreportId) { + return mContext.getString(R.string.bugreport_in_progress_title, bugreportId); + } + + private String getBugreportPrefixName() { + String buildId = SystemProperties.get("ro.build.id", "UNKNOWN_BUILD"); + String deviceName = SystemProperties.get("ro.product.name", "UNKNOWN_DEVICE"); + return String.format("bugreport-%s-%s", deviceName, buildId); + } + + private String getBugreportName(String name) { + return String.format("%s-%s.zip", getBugreportPrefixName(), name); + } + + private String getScreenshotContent() { + if (mScreenshotFd == null) { + return NO_SCREENSHOT; + } + return SCREENSHOT_CONTENT; + } + /** * Helper class containing the UiObjects present in the bugreport info dialog. */ private final class DetailsUi { - final UiObject detailsButton; final UiObject nameField; final UiObject titleField; final UiObject descField; @@ -1088,10 +987,9 @@ public class BugreportReceiverTest { * Gets the UI objects by opening the progress notification and clicking on DETAILS. * * @param id bugreport id - * @param id bugreport name */ - DetailsUi(UiBot uiBot, int id, String name) throws UiObjectNotFoundException { - this(uiBot, id, name, true); + DetailsUi(int id) throws UiObjectNotFoundException { + this(id, true); } /** @@ -1099,13 +997,12 @@ public class BugreportReceiverTest { * the notification itself. * * @param id bugreport id - * @param id bugreport name */ - DetailsUi(UiBot uiBot, int id, String name, boolean clickDetails) - throws UiObjectNotFoundException { - final UiObject notification = openProgressNotification(name); - detailsButton = mUiBot.getVisibleObject(mContext.getString( - R.string.bugreport_info_action).toUpperCase()); + DetailsUi(int id, boolean clickDetails) throws UiObjectNotFoundException { + openProgressNotification(mProgressTitle); + final UiObject notification = mUiBot.getObject(mProgressTitle); + final UiObject detailsButton = mUiBot.getObject(mContext.getString( + R.string.bugreport_info_action)); if (clickDetails) { mUiBot.click(detailsButton, "details_button"); @@ -1123,24 +1020,6 @@ public class BugreportReceiverTest { cancelButton = mUiBot.getObjectById("android:id/button2"); } - private void assertField(String name, UiObject field, String expected) - throws UiObjectNotFoundException { - String actual = field.getText().toString(); - assertEquals("Wrong value on field '" + name + "'", expected, actual); - } - - void assertName(String expected) throws UiObjectNotFoundException { - assertField("name", nameField, expected); - } - - void assertTitle(String expected) throws UiObjectNotFoundException { - assertField("title", titleField, expected); - } - - void assertDescription(String expected) throws UiObjectNotFoundException { - assertField("description", descField, expected); - } - /** * Set focus on the name field so it can be validated once focus is lost. */ @@ -1159,6 +1038,8 @@ public class BugreportReceiverTest { void reOpen(String name) { openProgressNotification(name); + final UiObject detailsButton = mUiBot.getObject(mContext.getString( + R.string.bugreport_info_action)); mUiBot.click(detailsButton, "details_button"); } diff --git a/packages/Shell/tests/src/com/android/shell/UiBot.java b/packages/Shell/tests/src/com/android/shell/UiBot.java index e8397659e692..53b124ff9c4c 100644 --- a/packages/Shell/tests/src/com/android/shell/UiBot.java +++ b/packages/Shell/tests/src/com/android/shell/UiBot.java @@ -18,17 +18,23 @@ package com.android.shell; import android.app.Instrumentation; import android.app.StatusBarManager; +import android.os.SystemClock; import android.support.test.uiautomator.By; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject; +import android.support.test.uiautomator.UiObject2; import android.support.test.uiautomator.UiObjectNotFoundException; import android.support.test.uiautomator.UiSelector; import android.support.test.uiautomator.Until; +import android.text.format.DateUtils; import android.util.Log; import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; +import java.util.List; + /** * A helper class for UI-related testing tasks. */ @@ -36,6 +42,9 @@ final class UiBot { private static final String TAG = "UiBot"; private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; + private static final String ANDROID_PACKAGE = "android"; + + private static final long SHORT_UI_TIMEOUT_MS = (3 * DateUtils.SECOND_IN_MILLIS); private final Instrumentation mInstrumentation; private final UiDevice mDevice; @@ -48,9 +57,9 @@ final class UiBot { } /** - * Opens the system notification and gets a given notification. + * Opens the system notification and gets a UiObject with the text. * - * @param text Notificaton's text as displayed by the UI. + * @param text Notification's text as displayed by the UI. * @return notification object. */ public UiObject getNotification(String text) { @@ -62,6 +71,43 @@ final class UiBot { return getObject(text); } + /** + * Opens the system notification and gets a notification containing the text. + * + * @param text Notification's text as displayed by the UI. + * @return notification object. + */ + public UiObject2 getNotification2(String text) { + boolean opened = mDevice.openNotification(); + Log.v(TAG, "openNotification(): " + opened); + final UiObject2 notificationScroller = mDevice.wait(Until.findObject( + By.res(SYSTEMUI_PACKAGE, "notification_stack_scroller")), mTimeout); + assertNotNull("could not get notification stack scroller", notificationScroller); + final List<UiObject2> notificationList = notificationScroller.getChildren(); + for (UiObject2 notification: notificationList) { + final UiObject2 notificationText = notification.findObject(By.textContains(text)); + if (notificationText != null) { + return notification; + } + } + return null; + } + + /** + * Expands the notification. + * + * @param notification The notification object returned by {@link #getNotification2(String)}. + */ + public void expandNotification(UiObject2 notification) { + final UiObject2 expandBtn = notification.findObject( + By.res(ANDROID_PACKAGE, "expand_button")); + if (expandBtn.getContentDescription().equals("Collapse")) { + return; + } + expandBtn.click(); + mDevice.waitForIdle(); + } + public void collapseStatusBar() throws Exception { // TODO: mDevice should provide such method.. StatusBarManager sbm = @@ -162,6 +208,12 @@ final class UiBot { */ public void chooseActivity(String name) { // It uses an intent chooser now, so just getting the activity by text is enough... + final String share = mInstrumentation.getContext().getString( + com.android.internal.R.string.share); + boolean gotIt = mDevice.wait(Until.hasObject(By.text(share)), mTimeout); + assertTrue("could not get share activity (" + share + ")", gotIt); + swipeUp(); + SystemClock.sleep(SHORT_UI_TIMEOUT_MS); UiObject activity = getObject(name); click(activity, name); } @@ -173,6 +225,11 @@ final class UiBot { public void turnScreenOn() throws Exception { mDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP"); mDevice.executeShellCommand("wm dismiss-keyguard"); + mDevice.waitForIdle(); } + public void swipeUp() { + mDevice.swipe(mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight() * 3 / 4, + mDevice.getDisplayWidth() / 2, 0, 30); + } } diff --git a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java index 449ed8c3bcdb..57e656827f1c 100644 --- a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java @@ -16,6 +16,7 @@ package com.android.systemui; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; +import android.annotation.Nullable; import android.app.Activity; import android.app.AlertDialog; import android.app.slice.SliceManager; @@ -29,6 +30,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.net.Uri; import android.os.Bundle; import android.text.BidiFormatter; +import android.util.EventLog; import android.util.Log; import android.widget.CheckBox; import android.widget.TextView; @@ -50,10 +52,12 @@ public class SlicePermissionActivity extends Activity implements OnClickListener mUri = getIntent().getParcelableExtra(SliceProvider.EXTRA_BIND_URI); mCallingPkg = getIntent().getStringExtra(SliceProvider.EXTRA_PKG); - mProviderPkg = getIntent().getStringExtra(SliceProvider.EXTRA_PROVIDER_PKG); try { PackageManager pm = getPackageManager(); + mProviderPkg = pm.resolveContentProvider(mUri.getAuthority(), + PackageManager.GET_META_DATA).applicationInfo.packageName; + verifyCallingPkg(); CharSequence app1 = BidiFormatter.getInstance().unicodeWrap(pm.getApplicationInfo( mCallingPkg, 0).loadSafeLabel(pm, PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX, PackageItemInfo.SAFE_LABEL_FLAG_TRIM @@ -97,4 +101,27 @@ public class SlicePermissionActivity extends Activity implements OnClickListener public void onDismiss(DialogInterface dialog) { finish(); } + + private void verifyCallingPkg() { + final String providerPkg = getIntent().getStringExtra(SliceProvider.EXTRA_PROVIDER_PKG); + if (providerPkg == null || mProviderPkg.equals(providerPkg)) return; + final String callingPkg = getCallingPkg(); + EventLog.writeEvent(0x534e4554, "159145361", getUid(callingPkg)); + } + + @Nullable + private String getCallingPkg() { + final Uri referrer = getReferrer(); + if (referrer == null) return null; + return referrer.getHost(); + } + + private int getUid(@Nullable final String pkg) { + if (pkg == null) return -1; + try { + return getPackageManager().getApplicationInfo(pkg, 0).uid; + } catch (NameNotFoundException e) { + } + return -1; + } } diff --git a/proto/src/OWNERS b/proto/src/OWNERS index e7ddf8691463..b456ba60d086 100644 --- a/proto/src/OWNERS +++ b/proto/src/OWNERS @@ -1,2 +1,3 @@ per-file gnss.proto = file:/services/core/java/com/android/server/location/OWNERS per-file wifi.proto = file:/wifi/OWNERS +per-file camera.proto = file:/services/core/java/com/android/server/camera/OWNERS diff --git a/services/Android.bp b/services/Android.bp index f40f7cfa7321..ef52c2aff002 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -83,7 +83,6 @@ java_library { "services.voiceinteraction", "services.wifi", "service-blobstore", - "service-connectivity", "service-jobscheduler", "android.hidl.base-V1.0-java", ], diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index a704c58a9b70..66bbf66e88db 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -372,7 +372,10 @@ public class CompanionDeviceManagerService extends SystemService implements Bind checkArgument(getCallingUserId() == userId, "Must be called by either same user or system"); - mAppOpsManager.checkPackage(Binder.getCallingUid(), pkg); + int callingUid = Binder.getCallingUid(); + if (mAppOpsManager.checkPackage(callingUid, pkg) != AppOpsManager.MODE_ALLOWED) { + throw new SecurityException(pkg + " doesn't belong to uid " + callingUid); + } } @Override diff --git a/services/core/Android.bp b/services/core/Android.bp index 6adf66c01b5e..307d344bffa7 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -130,7 +130,7 @@ java_library_static { "capture_state_listener-aidl-java", "dnsresolver_aidl_interface-java", "icu4j_calendar_astronomer", - "netd_aidl_interfaces-platform-java", + "netd-client", "overlayable_policy_aidl-java", "SurfaceFlingerProperties", "com.android.sysprop.watchdog", @@ -189,15 +189,11 @@ filegroup { "java/com/android/server/connectivity/AutodestructReference.java", "java/com/android/server/connectivity/ConnectivityConstants.java", "java/com/android/server/connectivity/DataConnectionStats.java", - "java/com/android/server/connectivity/DefaultNetworkMetrics.java", "java/com/android/server/connectivity/DnsManager.java", - "java/com/android/server/connectivity/IpConnectivityEventBuilder.java", - "java/com/android/server/connectivity/IpConnectivityMetrics.java", "java/com/android/server/connectivity/KeepaliveTracker.java", "java/com/android/server/connectivity/LingerMonitor.java", "java/com/android/server/connectivity/MockableSystemProperties.java", "java/com/android/server/connectivity/Nat464Xlat.java", - "java/com/android/server/connectivity/NetdEventListenerService.java", "java/com/android/server/connectivity/NetworkAgentInfo.java", "java/com/android/server/connectivity/NetworkDiagnostics.java", "java/com/android/server/connectivity/NetworkNotificationManager.java", diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 26c0e59cb885..a330a748d30a 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -56,12 +56,14 @@ import static android.net.NetworkPolicyManager.RULE_NONE; import static android.net.NetworkPolicyManager.uidRulesToString; import static android.net.shared.NetworkMonitorUtils.isPrivateDnsValidationRequired; import static android.os.Process.INVALID_UID; +import static android.os.Process.VPN_UID; import static android.system.OsConstants.IPPROTO_TCP; import static android.system.OsConstants.IPPROTO_UDP; import static java.util.Map.Entry; import android.Manifest; +import android.annotation.BoolRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; @@ -74,7 +76,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; -import android.content.res.Configuration; import android.database.ContentObserver; import android.net.CaptivePortal; import android.net.CaptivePortalData; @@ -88,14 +89,11 @@ import android.net.ICaptivePortal; import android.net.IConnectivityDiagnosticsCallback; import android.net.IConnectivityManager; import android.net.IDnsResolver; -import android.net.IIpConnectivityMetrics; import android.net.INetd; -import android.net.INetdEventCallback; import android.net.INetworkManagementEventObserver; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; -import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; import android.net.ISocketKeepaliveCallback; import android.net.InetAddresses; @@ -133,6 +131,7 @@ import android.net.UidRangeParcel; import android.net.Uri; import android.net.VpnManager; import android.net.VpnService; +import android.net.metrics.INetdEventListener; import android.net.metrics.IpConnectivityLog; import android.net.metrics.NetworkEvent; import android.net.netlink.InetDiagMessage; @@ -156,7 +155,6 @@ import android.os.PersistableBundle; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.ServiceSpecificException; import android.os.SystemClock; import android.os.SystemProperties; @@ -173,8 +171,8 @@ import android.util.Log; import android.util.Pair; import android.util.SparseArray; import android.util.SparseIntArray; -import android.util.Xml; +import com.android.connectivity.aidl.INetworkAgent; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -189,7 +187,6 @@ import com.android.internal.util.AsyncChannel; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.LocationPermissionChecker; import com.android.internal.util.MessageUtils; -import com.android.internal.util.XmlUtils; import com.android.modules.utils.BasicShellCommandHandler; import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult; import com.android.net.module.util.LinkPropertiesUtils.CompareResult; @@ -210,7 +207,6 @@ import com.android.server.connectivity.NetworkRanker; import com.android.server.connectivity.PermissionMonitor; import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.Vpn; -import com.android.server.net.BaseNetdEventCallback; import com.android.server.net.BaseNetworkObserver; import com.android.server.net.LockdownVpnTracker; import com.android.server.net.NetworkPolicyManagerInternal; @@ -220,14 +216,7 @@ import com.google.android.collect.Lists; import libcore.io.IoUtils; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.File; import java.io.FileDescriptor; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; import java.io.PrintWriter; import java.net.Inet4Address; import java.net.InetAddress; @@ -336,7 +325,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @VisibleForTesting protected INetd mNetd; private INetworkStatsService mStatsService; - private INetworkPolicyManager mPolicyManager; + private NetworkPolicyManager mPolicyManager; private NetworkPolicyManagerInternal mPolicyManagerInternal; /** @@ -557,6 +546,13 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int EVENT_CAPPORT_DATA_CHANGED = 46; /** + * Used by setRequireVpnForUids. + * arg1 = whether the specified UID ranges are required to use a VPN. + * obj = Array of UidRange objects. + */ + private static final int EVENT_SET_REQUIRE_VPN_FOR_UIDS = 47; + + /** * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification * should be shown. */ @@ -624,7 +620,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private LingerMonitor mLingerMonitor; // sequence number of NetworkRequests - private int mNextNetworkRequestId = 1; + private int mNextNetworkRequestId = NetworkRequest.FIRST_REQUEST_ID; // Sequence number for NetworkProvider IDs. private final AtomicInteger mNextNetworkProviderId = new AtomicInteger( @@ -931,29 +927,21 @@ public class ConnectivityService extends IConnectivityManager.Stub "no IpConnectivityMetrics service"); } - /** - * @see IpConnectivityMetrics - */ - public IIpConnectivityMetrics getIpConnectivityMetrics() { - return IIpConnectivityMetrics.Stub.asInterface( - ServiceManager.getService(IpConnectivityLog.SERVICE_NAME)); - } - public IBatteryStats getBatteryStatsService() { return BatteryStatsService.getService(); } } public ConnectivityService(Context context, INetworkManagementService netManager, - INetworkStatsService statsService, INetworkPolicyManager policyManager) { - this(context, netManager, statsService, policyManager, getDnsResolver(context), - new IpConnectivityLog(), NetdService.getInstance(), new Dependencies()); + INetworkStatsService statsService) { + this(context, netManager, statsService, getDnsResolver(context), new IpConnectivityLog(), + NetdService.getInstance(), new Dependencies()); } @VisibleForTesting protected ConnectivityService(Context context, INetworkManagementService netManager, - INetworkStatsService statsService, INetworkPolicyManager policyManager, - IDnsResolver dnsresolver, IpConnectivityLog logger, INetd netd, Dependencies deps) { + INetworkStatsService statsService, IDnsResolver dnsresolver, IpConnectivityLog logger, + INetd netd, Dependencies deps) { if (DBG) log("ConnectivityService starting up"); mDeps = Objects.requireNonNull(deps, "missing Dependencies"); @@ -977,6 +965,10 @@ public class ConnectivityService extends IConnectivityManager.Stub mDefaultWifiRequest = createDefaultInternetRequestForTransport( NetworkCapabilities.TRANSPORT_WIFI, NetworkRequest.Type.BACKGROUND_REQUEST); + mDefaultVehicleRequest = createAlwaysOnRequestForCapability( + NetworkCapabilities.NET_CAPABILITY_VEHICLE_INTERNAL, + NetworkRequest.Type.BACKGROUND_REQUEST); + mHandlerThread = mDeps.makeHandlerThread(); mHandlerThread.start(); mHandler = new InternalHandler(mHandlerThread.getLooper()); @@ -991,7 +983,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mNMS = Objects.requireNonNull(netManager, "missing INetworkManagementService"); mStatsService = Objects.requireNonNull(statsService, "missing INetworkStatsService"); - mPolicyManager = Objects.requireNonNull(policyManager, "missing INetworkPolicyManager"); + mPolicyManager = mContext.getSystemService(NetworkPolicyManager.class); mPolicyManagerInternal = Objects.requireNonNull( LocalServices.getService(NetworkPolicyManagerInternal.class), "missing NetworkPolicyManagerInternal"); @@ -1007,12 +999,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // To ensure uid rules are synchronized with Network Policy, register for // NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService // reading existing policy from disk. - try { - mPolicyManager.registerListener(mPolicyListener); - } catch (RemoteException e) { - // ouch, no rules updates means some processes may never get network - loge("unable to register INetworkPolicyListener" + e); - } + mPolicyManager.registerListener(mPolicyListener); final PowerManager powerManager = (PowerManager) context.getSystemService( Context.POWER_SERVICE); @@ -1181,6 +1168,15 @@ public class ConnectivityService extends IConnectivityManager.Stub return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId(), type); } + private NetworkRequest createAlwaysOnRequestForCapability(int capability, + NetworkRequest.Type type) { + final NetworkCapabilities netCap = new NetworkCapabilities(); + netCap.clearAll(); + netCap.addCapability(capability); + netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName()); + return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId(), type); + } + // Used only for testing. // TODO: Delete this and either: // 1. Give FakeSettingsProvider the ability to send settings change notifications (requires @@ -1198,10 +1194,19 @@ public class ConnectivityService extends IConnectivityManager.Stub mHandler.sendEmptyMessage(EVENT_PRIVATE_DNS_SETTINGS_CHANGED); } + private void handleAlwaysOnNetworkRequest(NetworkRequest networkRequest, @BoolRes int id) { + final boolean enable = mContext.getResources().getBoolean(id); + handleAlwaysOnNetworkRequest(networkRequest, enable); + } + private void handleAlwaysOnNetworkRequest( NetworkRequest networkRequest, String settingName, boolean defaultValue) { final boolean enable = toBool(Settings.Global.getInt( mContext.getContentResolver(), settingName, encodeBool(defaultValue))); + handleAlwaysOnNetworkRequest(networkRequest, enable); + } + + private void handleAlwaysOnNetworkRequest(NetworkRequest networkRequest, boolean enable) { final boolean isEnabled = (mNetworkRequests.get(networkRequest) != null); if (enable == isEnabled) { return; // Nothing to do. @@ -1218,9 +1223,11 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleConfigureAlwaysOnNetworks() { handleAlwaysOnNetworkRequest( - mDefaultMobileDataRequest,Settings.Global.MOBILE_DATA_ALWAYS_ON, true); + mDefaultMobileDataRequest, Settings.Global.MOBILE_DATA_ALWAYS_ON, true); handleAlwaysOnNetworkRequest(mDefaultWifiRequest, Settings.Global.WIFI_ALWAYS_REQUESTED, false); + handleAlwaysOnNetworkRequest(mDefaultVehicleRequest, + com.android.internal.R.bool.config_vehicleInternalNetworkAlwaysRequested); } private void registerSettingsCallbacks() { @@ -1247,6 +1254,8 @@ public class ConnectivityService extends IConnectivityManager.Stub } private synchronized int nextNetworkRequestId() { + // TODO: Consider handle wrapping and exclude {@link NetworkRequest#REQUEST_ID_NONE} if + // doing that. return mNextNetworkRequestId++; } @@ -1289,19 +1298,28 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private Network[] getVpnUnderlyingNetworks(int uid) { - synchronized (mVpns) { - if (!mLockdownEnabled) { - int user = UserHandle.getUserId(uid); - Vpn vpn = mVpns.get(user); - if (vpn != null && vpn.appliesToUid(uid)) { - return vpn.getUnderlyingNetworks(); + // TODO: determine what to do when more than one VPN applies to |uid|. + private NetworkAgentInfo getVpnForUid(int uid) { + synchronized (mNetworkForNetId) { + for (int i = 0; i < mNetworkForNetId.size(); i++) { + final NetworkAgentInfo nai = mNetworkForNetId.valueAt(i); + if (nai.isVPN() && nai.everConnected && nai.networkCapabilities.appliesToUid(uid)) { + return nai; } } } return null; } + private Network[] getVpnUnderlyingNetworks(int uid) { + synchronized (mVpns) { + if (mLockdownEnabled) return null; + } + final NetworkAgentInfo nai = getVpnForUid(uid); + if (nai != null) return nai.declaredUnderlyingNetworks; + return null; + } + private NetworkState getUnfilteredActiveNetworkState(int uid) { NetworkAgentInfo nai = getDefaultNetwork(); @@ -1327,22 +1345,22 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * Check if UID should be blocked from using the network with the given LinkProperties. + * Check if UID should be blocked from using the specified network. */ - private boolean isNetworkWithLinkPropertiesBlocked(LinkProperties lp, int uid, - boolean ignoreBlocked) { + private boolean isNetworkWithCapabilitiesBlocked(@Nullable final NetworkCapabilities nc, + final int uid, final boolean ignoreBlocked) { // Networks aren't blocked when ignoring blocked status if (ignoreBlocked) { return false; } - synchronized (mVpns) { - final Vpn vpn = mVpns.get(UserHandle.getUserId(uid)); - if (vpn != null && vpn.getLockdown() && vpn.isBlockingUid(uid)) { - return true; - } + if (isUidBlockedByVpn(uid, mVpnBlockedUidRanges)) return true; + final long ident = Binder.clearCallingIdentity(); + try { + final boolean metered = nc == null ? true : nc.isMetered(); + return mPolicyManager.isUidNetworkingBlocked(uid, metered); + } finally { + Binder.restoreCallingIdentity(ident); } - final String iface = (lp == null ? "" : lp.getInterfaceName()); - return mPolicyManagerInternal.isUidNetworkingBlocked(uid, iface); } private void maybeLogBlockedNetworkInfo(NetworkInfo ni, int uid) { @@ -1380,12 +1398,13 @@ public class ConnectivityService extends IConnectivityManager.Stub /** * Apply any relevant filters to {@link NetworkState} for the given UID. For * example, this may mark the network as {@link DetailedState#BLOCKED} based - * on {@link #isNetworkWithLinkPropertiesBlocked}. + * on {@link #isNetworkWithCapabilitiesBlocked}. */ private void filterNetworkStateForUid(NetworkState state, int uid, boolean ignoreBlocked) { if (state == null || state.networkInfo == null || state.linkProperties == null) return; - if (isNetworkWithLinkPropertiesBlocked(state.linkProperties, uid, ignoreBlocked)) { + if (isNetworkWithCapabilitiesBlocked(state.networkCapabilities, uid, + ignoreBlocked)) { state.networkInfo.setDetailedState(DetailedState.BLOCKED, null, null); } synchronized (mVpns) { @@ -1445,8 +1464,8 @@ public class ConnectivityService extends IConnectivityManager.Stub } } nai = getDefaultNetwork(); - if (nai != null - && isNetworkWithLinkPropertiesBlocked(nai.linkProperties, uid, ignoreBlocked)) { + if (nai != null && isNetworkWithCapabilitiesBlocked( + nai.networkCapabilities, uid, ignoreBlocked)) { nai = null; } return nai != null ? nai.network : null; @@ -1518,7 +1537,7 @@ public class ConnectivityService extends IConnectivityManager.Stub enforceAccessPermission(); final int uid = mDeps.getCallingUid(); NetworkState state = getFilteredNetworkState(networkType, uid); - if (!isNetworkWithLinkPropertiesBlocked(state.linkProperties, uid, false)) { + if (!isNetworkWithCapabilitiesBlocked(state.networkCapabilities, uid, false)) { return state.network; } return null; @@ -1566,22 +1585,14 @@ public class ConnectivityService extends IConnectivityManager.Stub nc, mDeps.getCallingUid(), callingPackageName)); } - synchronized (mVpns) { - if (!mLockdownEnabled) { - Vpn vpn = mVpns.get(userId); - if (vpn != null) { - Network[] networks = vpn.getUnderlyingNetworks(); - if (networks != null) { - for (Network network : networks) { - nc = getNetworkCapabilitiesInternal(network); - if (nc != null) { - result.put( - network, - maybeSanitizeLocationInfoForCaller( - nc, mDeps.getCallingUid(), callingPackageName)); - } - } - } + // No need to check mLockdownEnabled. If it's true, getVpnUnderlyingNetworks returns null. + final Network[] networks = getVpnUnderlyingNetworks(Binder.getCallingUid()); + if (networks != null) { + for (Network network : networks) { + nc = getNetworkCapabilitiesInternal(network); + if (nc != null) { + result.put(network, maybeSanitizeLocationInfoForCaller( + nc, mDeps.getCallingUid(), callingPackageName)); } } } @@ -1792,12 +1803,28 @@ public class ConnectivityService extends IConnectivityManager.Stub private INetworkManagementEventObserver mDataActivityObserver = new BaseNetworkObserver() { @Override - public void interfaceClassDataActivityChanged(int networkType, boolean active, long tsNanos, - int uid) { - sendDataActivityBroadcast(networkType, active, tsNanos); + public void interfaceClassDataActivityChanged(int transportType, boolean active, + long tsNanos, int uid) { + sendDataActivityBroadcast(transportTypeToLegacyType(transportType), active, tsNanos); } }; + // This is deprecated and only to support legacy use cases. + private int transportTypeToLegacyType(int type) { + switch (type) { + case NetworkCapabilities.TRANSPORT_CELLULAR: + return ConnectivityManager.TYPE_MOBILE; + case NetworkCapabilities.TRANSPORT_WIFI: + return ConnectivityManager.TYPE_WIFI; + case NetworkCapabilities.TRANSPORT_BLUETOOTH: + return ConnectivityManager.TYPE_BLUETOOTH; + case NetworkCapabilities.TRANSPORT_ETHERNET: + return ConnectivityManager.TYPE_ETHERNET; + default: + loge("Unexpected transport in transportTypeToLegacyType: " + type); + } + return ConnectivityManager.TYPE_NONE; + } /** * Ensures that the system cannot call a particular method. */ @@ -1919,8 +1946,7 @@ public class ConnectivityService extends IConnectivityManager.Stub return true; } - @VisibleForTesting - protected final INetdEventCallback mNetdEventCallback = new BaseNetdEventCallback() { + private class NetdEventCallback extends INetdEventListener.Stub { @Override public void onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname, boolean validated) { @@ -1936,8 +1962,8 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override - public void onDnsEvent(int netId, int eventType, int returnCode, String hostname, - String[] ipAddresses, int ipAddressesCount, long timestamp, int uid) { + public void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs, + String hostname, String[] ipAddresses, int ipAddressesCount, int uid) { NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId); // Netd event only allow registrants from system. Each NetworkMonitor thread is under // the caller thread of registerNetworkAgent. Thus, it's not allowed to register netd @@ -1956,21 +1982,42 @@ public class ConnectivityService extends IConnectivityManager.Stub String prefixString, int prefixLength) { mHandler.post(() -> handleNat64PrefixEvent(netId, added, prefixString, prefixLength)); } - }; - private void registerNetdEventCallback() { - final IIpConnectivityMetrics ipConnectivityMetrics = mDeps.getIpConnectivityMetrics(); - if (ipConnectivityMetrics == null) { - Log.wtf(TAG, "Missing IIpConnectivityMetrics"); - return; + @Override + public void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, + int uid) { } + @Override + public void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, + byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, + long timestampNs) { + } + + @Override + public void onTcpSocketStatsEvent(int[] networkIds, int[] sentPackets, int[] lostPackets, + int[] rttsUs, int[] sentAckDiffsMs) { + } + + @Override + public int getInterfaceVersion() throws RemoteException { + return this.VERSION; + } + + @Override + public String getInterfaceHash() { + return this.HASH; + } + }; + + @VisibleForTesting + protected final INetdEventListener mNetdEventCallback = new NetdEventCallback(); + + private void registerNetdEventCallback() { try { - ipConnectivityMetrics.addNetdEventCallback( - INetdEventCallback.CALLBACK_CALLER_CONNECTIVITY_SERVICE, - mNetdEventCallback); + mDnsResolver.registerEventListener(mNetdEventCallback); } catch (Exception e) { - loge("Error registering netd callback: " + e); + loge("Error registering DnsResolver callback: " + e); } } @@ -2007,29 +2054,18 @@ public class ConnectivityService extends IConnectivityManager.Stub void handleRestrictBackgroundChanged(boolean restrictBackground) { if (mRestrictBackground == restrictBackground) return; - for (final NetworkAgentInfo nai : mNetworkAgentInfos.values()) { + final List<UidRange> blockedRanges = mVpnBlockedUidRanges; + for (final NetworkAgentInfo nai : mNetworkAgentInfos) { final boolean curMetered = nai.networkCapabilities.isMetered(); maybeNotifyNetworkBlocked(nai, curMetered, curMetered, mRestrictBackground, - restrictBackground); + restrictBackground, blockedRanges, blockedRanges); } mRestrictBackground = restrictBackground; } - private boolean isUidNetworkingWithVpnBlocked(int uid, int uidRules, boolean isNetworkMetered, + private boolean isUidBlockedByRules(int uid, int uidRules, boolean isNetworkMetered, boolean isBackgroundRestricted) { - synchronized (mVpns) { - final Vpn vpn = mVpns.get(UserHandle.getUserId(uid)); - // Because the return value of this function depends on the list of UIDs the - // always-on VPN blocks when in lockdown mode, when the always-on VPN changes that - // list all state depending on the return value of this function has to be recomputed. - // TODO: add a trigger when the always-on VPN sets its blocked UIDs to reevaluate and - // send the necessary onBlockedStatusChanged callbacks. - if (vpn != null && vpn.getLockdown() && vpn.isBlockingUid(uid)) { - return true; - } - } - return NetworkPolicyManagerInternal.isUidNetworkingBlocked(uid, uidRules, isNetworkMetered, isBackgroundRestricted); } @@ -2372,13 +2408,13 @@ public class ConnectivityService extends IConnectivityManager.Stub timeout = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE, 10); - type = ConnectivityManager.TYPE_MOBILE; + type = NetworkCapabilities.TRANSPORT_CELLULAR; } else if (networkAgent.networkCapabilities.hasTransport( NetworkCapabilities.TRANSPORT_WIFI)) { timeout = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI, 15); - type = ConnectivityManager.TYPE_WIFI; + type = NetworkCapabilities.TRANSPORT_WIFI; } else { return; // do not track any other networks } @@ -2715,7 +2751,7 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private NetworkAgentInfo[] networksSortedById() { NetworkAgentInfo[] networks = new NetworkAgentInfo[0]; - networks = mNetworkAgentInfos.values().toArray(networks); + networks = mNetworkAgentInfos.toArray(networks); Arrays.sort(networks, Comparator.comparingInt(nai -> nai.network.getNetId())); return networks; } @@ -2761,11 +2797,6 @@ public class ConnectivityService extends IConnectivityManager.Stub handleAsyncChannelHalfConnect(msg); break; } - case AsyncChannel.CMD_CHANNEL_DISCONNECT: { - NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo); - if (nai != null) nai.asyncChannel.disconnect(); - break; - } case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { handleAsyncChannelDisconnected(msg); break; @@ -2775,8 +2806,9 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void maybeHandleNetworkAgentMessage(Message msg) { - NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo); - if (nai == null) { + final Pair<NetworkAgentInfo, Object> arg = (Pair<NetworkAgentInfo, Object>) msg.obj; + final NetworkAgentInfo nai = arg.first; + if (!mNetworkAgentInfos.contains(nai)) { if (VDBG) { log(String.format("%s from unknown NetworkAgent", eventName(msg.what))); } @@ -2785,7 +2817,7 @@ public class ConnectivityService extends IConnectivityManager.Stub switch (msg.what) { case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: { - NetworkCapabilities networkCapabilities = (NetworkCapabilities) msg.obj; + NetworkCapabilities networkCapabilities = (NetworkCapabilities) arg.second; if (networkCapabilities.hasConnectivityManagedCapability()) { Log.wtf(TAG, "BUG: " + nai + " has CS-managed capability."); } @@ -2802,13 +2834,13 @@ public class ConnectivityService extends IConnectivityManager.Stub break; } case NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED: { - LinkProperties newLp = (LinkProperties) msg.obj; + LinkProperties newLp = (LinkProperties) arg.second; processLinkPropertiesFromAgent(nai, newLp); handleUpdateLinkProperties(nai, newLp); break; } case NetworkAgent.EVENT_NETWORK_INFO_CHANGED: { - NetworkInfo info = (NetworkInfo) msg.obj; + NetworkInfo info = (NetworkInfo) arg.second; updateNetworkInfo(nai, info); break; } @@ -2833,7 +2865,7 @@ public class ConnectivityService extends IConnectivityManager.Stub break; } case NetworkAgent.EVENT_SOCKET_KEEPALIVE: { - mKeepaliveTracker.handleEventSocketKeepalive(nai, msg); + mKeepaliveTracker.handleEventSocketKeepalive(nai, msg.arg1, msg.arg2); break; } case NetworkAgent.EVENT_UNDERLYING_NETWORKS_CHANGED: { @@ -2844,7 +2876,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } final ArrayList<Network> underlying; try { - underlying = ((Bundle) msg.obj).getParcelableArrayList( + underlying = ((Bundle) arg.second).getParcelableArrayList( NetworkAgent.UNDERLYING_NETWORKS_KEY); } catch (NullPointerException | ClassCastException e) { break; @@ -2923,8 +2955,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (nai.lastCaptivePortalDetected && Settings.Global.CAPTIVE_PORTAL_MODE_AVOID == getCaptivePortalMode()) { if (DBG) log("Avoiding captive portal network: " + nai.toShortString()); - nai.asyncChannel.sendMessage( - NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT); + nai.onPreventAutomaticReconnect(); teardownUnneededNetwork(nai); break; } @@ -2958,7 +2989,7 @@ public class ConnectivityService extends IConnectivityManager.Stub case EVENT_CAPPORT_DATA_CHANGED: { final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2); if (nai == null) break; - handleCaptivePortalDataUpdate(nai, (CaptivePortalData) msg.obj); + handleCapportApiDataUpdate(nai, (CaptivePortalData) msg.obj); break; } } @@ -2984,9 +3015,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } if (valid != nai.lastValidated) { if (wasDefault) { - mDeps.getMetricsLogger() - .defaultNetworkMetrics().logDefaultNetworkValidity( - SystemClock.elapsedRealtime(), valid); + mMetricsLog.logDefaultNetworkValidity(valid); } final int oldScore = nai.getCurrentScore(); nai.lastValidated = valid; @@ -3016,13 +3045,10 @@ public class ConnectivityService extends IConnectivityManager.Stub } updateInetCondition(nai); // Let the NetworkAgent know the state of its network - Bundle redirectUrlBundle = new Bundle(); - redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, redirectUrl); // TODO: Evaluate to update partial connectivity to status to NetworkAgent. - nai.asyncChannel.sendMessage( - NetworkAgent.CMD_REPORT_NETWORK_STATUS, - (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK), - 0, redirectUrlBundle); + nai.onValidationStatusChanged( + valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK, + redirectUrl); // If NetworkMonitor detects partial connectivity before // EVENT_PROMPT_UNVALIDATED arrives, show the partial connectivity notification @@ -3056,6 +3082,14 @@ public class ConnectivityService extends IConnectivityManager.Stub } break; } + case NetworkAgentInfo.EVENT_AGENT_REGISTERED: { + handleNetworkAgentRegistered(msg); + break; + } + case NetworkAgentInfo.EVENT_AGENT_DISCONNECTED: { + handleNetworkAgentDisconnected(msg); + break; + } } return true; } @@ -3232,7 +3266,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handlePrivateDnsSettingsChanged() { final PrivateDnsConfig cfg = mDnsManager.getPrivateDnsConfig(); - for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { + for (NetworkAgentInfo nai : mNetworkAgentInfos) { handlePerNetworkPrivateDnsConfig(nai, cfg); if (networkRequiresPrivateDnsValidation(nai)) { handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties)); @@ -3293,9 +3327,9 @@ public class ConnectivityService extends IConnectivityManager.Stub handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties)); } - private void handleCaptivePortalDataUpdate(@NonNull final NetworkAgentInfo nai, + private void handleCapportApiDataUpdate(@NonNull final NetworkAgentInfo nai, @Nullable final CaptivePortalData data) { - nai.captivePortalData = data; + nai.capportApiData = data; // CaptivePortalData will be merged into LinkProperties from NetworkAgentInfo handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties)); } @@ -3330,7 +3364,6 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleAsyncChannelHalfConnect(Message msg) { ensureRunningOnConnectivityServiceThread(); - final AsyncChannel ac = (AsyncChannel) msg.obj; if (mNetworkProviderInfos.containsKey(msg.replyTo)) { if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { if (VDBG) log("NetworkFactory connected"); @@ -3342,39 +3375,45 @@ public class ConnectivityService extends IConnectivityManager.Stub loge("Error connecting NetworkFactory"); mNetworkProviderInfos.remove(msg.obj); } - } else if (mNetworkAgentInfos.containsKey(msg.replyTo)) { - if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { - if (VDBG) log("NetworkAgent connected"); - // A network agent has requested a connection. Establish the connection. - mNetworkAgentInfos.get(msg.replyTo).asyncChannel. - sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); - } else { - loge("Error connecting NetworkAgent"); - NetworkAgentInfo nai = mNetworkAgentInfos.remove(msg.replyTo); - if (nai != null) { - final boolean wasDefault = isDefaultNetwork(nai); - synchronized (mNetworkForNetId) { - mNetworkForNetId.remove(nai.network.getNetId()); - } - mNetIdManager.releaseNetId(nai.network.getNetId()); - // Just in case. - mLegacyTypeTracker.remove(nai, wasDefault); + } + } + + private void handleNetworkAgentRegistered(Message msg) { + final NetworkAgentInfo nai = (NetworkAgentInfo) msg.obj; + if (!mNetworkAgentInfos.contains(nai)) { + return; + } + + if (msg.arg1 == NetworkAgentInfo.ARG_AGENT_SUCCESS) { + if (VDBG) log("NetworkAgent registered"); + } else { + loge("Error connecting NetworkAgent"); + mNetworkAgentInfos.remove(nai); + if (nai != null) { + final boolean wasDefault = isDefaultNetwork(nai); + synchronized (mNetworkForNetId) { + mNetworkForNetId.remove(nai.network.getNetId()); } + mNetIdManager.releaseNetId(nai.network.getNetId()); + // Just in case. + mLegacyTypeTracker.remove(nai, wasDefault); } } } - // This is a no-op if it's called with a message designating a network that has + private void handleNetworkAgentDisconnected(Message msg) { + NetworkAgentInfo nai = (NetworkAgentInfo) msg.obj; + if (mNetworkAgentInfos.contains(nai)) { + disconnectAndDestroyNetwork(nai); + } + } + + // This is a no-op if it's called with a message designating a provider that has // already been destroyed, because its reference will not be found in the relevant // maps. private void handleAsyncChannelDisconnected(Message msg) { - NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo); - if (nai != null) { - disconnectAndDestroyNetwork(nai); - } else { - NetworkProviderInfo npi = mNetworkProviderInfos.remove(msg.replyTo); - if (DBG && npi != null) log("unregisterNetworkFactory for " + npi.name); - } + NetworkProviderInfo npi = mNetworkProviderInfos.remove(msg.replyTo); + if (DBG && npi != null) log("unregisterNetworkFactory for " + npi.name); } // Destroys a network, remove references to it from the internal state managed by @@ -3404,7 +3443,9 @@ public class ConnectivityService extends IConnectivityManager.Stub // if there is a fallback. Taken together, the two form a X -> 0, 0 -> Y sequence // whose timestamps tell how long it takes to recover a default network. long now = SystemClock.elapsedRealtime(); - mDeps.getMetricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(now, null, nai); + mMetricsLog.logDefaultNetworkEvent(null, 0, false, + null /* lp */, null /* nc */, nai.network, nai.getCurrentScore(), + nai.linkProperties, nai.networkCapabilities); } notifyIfacesChangedForNetworkStats(); // TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied @@ -3418,7 +3459,7 @@ public class ConnectivityService extends IConnectivityManager.Stub wakeupModifyInterface(iface, nai.networkCapabilities, false); } nai.networkMonitor().notifyNetworkDisconnected(); - mNetworkAgentInfos.remove(nai.messenger); + mNetworkAgentInfos.remove(nai); nai.clatd.update(); synchronized (mNetworkForNetId) { // Remove the NetworkAgent, but don't mark the netId as @@ -3526,7 +3567,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mNetworkRequests.put(nri.request, nri); mNetworkRequestInfoLogs.log("REGISTER " + nri); if (nri.request.isListen()) { - for (NetworkAgentInfo network : mNetworkAgentInfos.values()) { + for (NetworkAgentInfo network : mNetworkAgentInfos) { if (nri.request.networkCapabilities.hasSignalStrength() && network.satisfiesImmutableCapabilitiesOf(nri.request)) { updateSignalStrengthThresholds(network, "REGISTER", nri.request); @@ -3591,8 +3632,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private boolean isNetworkPotentialSatisfier( @NonNull final NetworkAgentInfo candidate, @NonNull final NetworkRequestInfo nri) { // listen requests won't keep up a network satisfying it. If this is not a multilayer - // request, we can return immediately. For multilayer requests, we have to check to see if - // any of the multilayer requests may have a potential satisfier. + // request, return immediately. For multilayer requests, check to see if any of the + // multilayer requests may have a potential satisfier. if (!nri.isMultilayerRequest() && nri.mRequests.get(0).isListen()) { return false; } @@ -3742,7 +3783,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } else { // listens don't have a singular affectedNetwork. Check all networks to see // if this listen request applies and remove it. - for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { + for (NetworkAgentInfo nai : mNetworkAgentInfos) { nai.removeRequest(nri.request.requestId); if (nri.request.networkCapabilities.hasSignalStrength() && nai.satisfiesImmutableCapabilitiesOf(nri.request)) { @@ -3815,13 +3856,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } if (always) { - nai.asyncChannel.sendMessage( - NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED, encodeBool(accept)); + nai.onSaveAcceptUnvalidated(accept); } if (!accept) { // Tell the NetworkAgent to not automatically reconnect to the network. - nai.asyncChannel.sendMessage(NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT); + nai.onPreventAutomaticReconnect(); // Teardown the network. teardownUnneededNetwork(nai); } @@ -3852,13 +3892,12 @@ public class ConnectivityService extends IConnectivityManager.Stub // TODO: Use the current design or save the user choice into IpMemoryStore. if (always) { - nai.asyncChannel.sendMessage( - NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED, encodeBool(accept)); + nai.onSaveAcceptUnvalidated(accept); } if (!accept) { // Tell the NetworkAgent to not automatically reconnect to the network. - nai.asyncChannel.sendMessage(NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT); + nai.onPreventAutomaticReconnect(); // Tear down the network. teardownUnneededNetwork(nai); } else { @@ -3996,7 +4035,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private void rematchForAvoidBadWifiUpdate() { rematchAllNetworksAndRequests(); - for (NetworkAgentInfo nai: mNetworkAgentInfos.values()) { + for (NetworkAgentInfo nai: mNetworkAgentInfos) { if (nai.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { sendUpdatedScoreToFactories(nai); } @@ -4139,7 +4178,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // to a network that provides no or limited connectivity is not useful, because the user // cannot use that network except through the notification shown by this method, and the // notification is only shown if the network is explicitly selected by the user. - nai.asyncChannel.sendMessage(NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT); + nai.onPreventAutomaticReconnect(); // TODO: Evaluate if it's needed to wait 8 seconds for triggering notification when // NetworkMonitor detects the network is partial connectivity. Need to change the design to @@ -4301,6 +4340,9 @@ public class ConnectivityService extends IConnectivityManager.Stub case EVENT_DATA_SAVER_CHANGED: handleRestrictBackgroundChanged(toBool(msg.arg1)); break; + case EVENT_SET_REQUIRE_VPN_FOR_UIDS: + handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj); + break; } } } @@ -4469,8 +4511,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (!nai.everConnected) { return; } - LinkProperties lp = getLinkProperties(nai); - if (isNetworkWithLinkPropertiesBlocked(lp, uid, false)) { + final NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai); + if (isNetworkWithCapabilitiesBlocked(nc, uid, false)) { return; } nai.networkMonitor().forceReevaluation(uid); @@ -4789,7 +4831,7 @@ public class ConnectivityService extends IConnectivityManager.Stub return new VpnInfo[0]; } List<VpnInfo> infoList = new ArrayList<>(); - for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { + for (NetworkAgentInfo nai : mNetworkAgentInfos) { VpnInfo info = createVpnInfo(nai); if (info != null) { infoList.add(info); @@ -4890,13 +4932,63 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private void propagateUnderlyingNetworkCapabilities(Network updatedNetwork) { ensureRunningOnConnectivityServiceThread(); - for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { + for (NetworkAgentInfo nai : mNetworkAgentInfos) { if (updatedNetwork == null || hasUnderlyingNetwork(nai, updatedNetwork)) { updateCapabilitiesForNetwork(nai); } } } + private boolean isUidBlockedByVpn(int uid, List<UidRange> blockedUidRanges) { + // Determine whether this UID is blocked because of always-on VPN lockdown. If a VPN applies + // to the UID, then the UID is not blocked because always-on VPN lockdown applies only when + // a VPN is not up. + final NetworkAgentInfo vpnNai = getVpnForUid(uid); + if (vpnNai != null && !vpnNai.networkAgentConfig.allowBypass) return false; + for (UidRange range : blockedUidRanges) { + if (range.contains(uid)) return true; + } + return false; + } + + @Override + public void setRequireVpnForUids(boolean requireVpn, UidRange[] ranges) { + NetworkStack.checkNetworkStackPermission(mContext); + mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_REQUIRE_VPN_FOR_UIDS, + encodeBool(requireVpn), 0 /* arg2 */, ranges)); + } + + private void handleSetRequireVpnForUids(boolean requireVpn, UidRange[] ranges) { + if (DBG) { + Log.d(TAG, "Setting VPN " + (requireVpn ? "" : "not ") + "required for UIDs: " + + Arrays.toString(ranges)); + } + // Cannot use a Set since the list of UID ranges might contain duplicates. + final List<UidRange> newVpnBlockedUidRanges = new ArrayList(mVpnBlockedUidRanges); + for (int i = 0; i < ranges.length; i++) { + if (requireVpn) { + newVpnBlockedUidRanges.add(ranges[i]); + } else { + newVpnBlockedUidRanges.remove(ranges[i]); + } + } + + try { + mNetd.networkRejectNonSecureVpn(requireVpn, toUidRangeStableParcels(ranges)); + } catch (RemoteException | ServiceSpecificException e) { + Log.e(TAG, "setRequireVpnForUids(" + requireVpn + ", " + + Arrays.toString(ranges) + "): netd command failed: " + e); + } + + for (final NetworkAgentInfo nai : mNetworkAgentInfos) { + final boolean curMetered = nai.networkCapabilities.isMetered(); + maybeNotifyNetworkBlocked(nai, curMetered, curMetered, mRestrictBackground, + mRestrictBackground, mVpnBlockedUidRanges, newVpnBlockedUidRanges); + } + + mVpnBlockedUidRanges = newVpnBlockedUidRanges; + } + @Override public boolean updateLockdownVpn() { if (mDeps.getCallingUid() != Process.SYSTEM_UID) { @@ -5081,101 +5173,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override - public int checkMobileProvisioning(int suggestedTimeOutMs) { - // TODO: Remove? Any reason to trigger a provisioning check? - return -1; - } - - /** Location to an updatable file listing carrier provisioning urls. - * An example: - * - * <?xml version="1.0" encoding="utf-8"?> - * <provisioningUrls> - * <provisioningUrl mcc="310" mnc="4">http://myserver.com/foo?mdn=%3$s&iccid=%1$s&imei=%2$s</provisioningUrl> - * </provisioningUrls> - */ - private static final String PROVISIONING_URL_PATH = - "/data/misc/radio/provisioning_urls.xml"; - private final File mProvisioningUrlFile = new File(PROVISIONING_URL_PATH); - - /** XML tag for root element. */ - private static final String TAG_PROVISIONING_URLS = "provisioningUrls"; - /** XML tag for individual url */ - private static final String TAG_PROVISIONING_URL = "provisioningUrl"; - /** XML attribute for mcc */ - private static final String ATTR_MCC = "mcc"; - /** XML attribute for mnc */ - private static final String ATTR_MNC = "mnc"; - - private String getProvisioningUrlBaseFromFile() { - XmlPullParser parser; - Configuration config = mContext.getResources().getConfiguration(); - - try (FileReader fileReader = new FileReader(mProvisioningUrlFile)) { - parser = Xml.newPullParser(); - parser.setInput(fileReader); - XmlUtils.beginDocument(parser, TAG_PROVISIONING_URLS); - - while (true) { - XmlUtils.nextElement(parser); - - String element = parser.getName(); - if (element == null) break; - - if (element.equals(TAG_PROVISIONING_URL)) { - String mcc = parser.getAttributeValue(null, ATTR_MCC); - try { - if (mcc != null && Integer.parseInt(mcc) == config.mcc) { - String mnc = parser.getAttributeValue(null, ATTR_MNC); - if (mnc != null && Integer.parseInt(mnc) == config.mnc) { - parser.next(); - if (parser.getEventType() == XmlPullParser.TEXT) { - return parser.getText(); - } - } - } - } catch (NumberFormatException e) { - loge("NumberFormatException in getProvisioningUrlBaseFromFile: " + e); - } - } - } - return null; - } catch (FileNotFoundException e) { - loge("Carrier Provisioning Urls file not found"); - } catch (XmlPullParserException e) { - loge("Xml parser exception reading Carrier Provisioning Urls file: " + e); - } catch (IOException e) { - loge("I/O exception reading Carrier Provisioning Urls file: " + e); - } - return null; - } - - @Override - public String getMobileProvisioningUrl() { - enforceSettingsPermission(); - String url = getProvisioningUrlBaseFromFile(); - if (TextUtils.isEmpty(url)) { - url = mContext.getResources().getString(R.string.mobile_provisioning_url); - log("getMobileProvisioningUrl: mobile_provisioining_url from resource =" + url); - } else { - log("getMobileProvisioningUrl: mobile_provisioning_url from File =" + url); - } - // populate the iccid, imei and phone number in the provisioning url. - if (!TextUtils.isEmpty(url)) { - String phoneNumber = mTelephonyManager.getLine1Number(); - if (TextUtils.isEmpty(phoneNumber)) { - phoneNumber = "0000000000"; - } - url = String.format(url, - mTelephonyManager.getSimSerialNumber() /* ICCID */, - mTelephonyManager.getDeviceId() /* IMEI */, - phoneNumber /* Phone number */); - } - - return url; - } - - @Override public void setProvisioningNotificationVisible(boolean visible, int networkType, String action) { enforceSettingsPermission(); @@ -5591,7 +5588,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mAppOpsManager.checkPackage(callerUid, callerPackageName); } - private ArrayList<Integer> getSignalStrengthThresholds(@NonNull final NetworkAgentInfo nai) { + private int[] getSignalStrengthThresholds(@NonNull final NetworkAgentInfo nai) { final SortedSet<Integer> thresholds = new TreeSet<>(); synchronized (nai) { for (final NetworkRequestInfo nri : mNetworkRequests.values()) { @@ -5603,14 +5600,13 @@ public class ConnectivityService extends IConnectivityManager.Stub } } } - return new ArrayList<>(thresholds); + // TODO: use NetworkStackUtils.convertToIntArray after moving it + return ArrayUtils.convertToIntArray(new ArrayList<>(thresholds)); } private void updateSignalStrengthThresholds( NetworkAgentInfo nai, String reason, NetworkRequest request) { - ArrayList<Integer> thresholdsArray = getSignalStrengthThresholds(nai); - Bundle thresholds = new Bundle(); - thresholds.putIntegerArrayList("thresholds", thresholdsArray); + final int[] thresholdsArray = getSignalStrengthThresholds(nai); if (VDBG || (DBG && !"CONNECT".equals(reason))) { String detail; @@ -5620,12 +5616,10 @@ public class ConnectivityService extends IConnectivityManager.Stub detail = reason; } log(String.format("updateSignalStrengthThresholds: %s, sending %s to %s", - detail, Arrays.toString(thresholdsArray.toArray()), nai.toShortString())); + detail, Arrays.toString(thresholdsArray), nai.toShortString())); } - nai.asyncChannel.sendMessage( - android.net.NetworkAgent.CMD_SET_SIGNAL_STRENGTH_THRESHOLDS, - 0, 0, thresholds); + nai.onSignalStrengthThresholdsUpdated(thresholdsArray); } private void ensureValidNetworkSpecifier(NetworkCapabilities nc) { @@ -5735,7 +5729,7 @@ public class ConnectivityService extends IConnectivityManager.Stub nai = mNetworkForNetId.get(network.getNetId()); } if (nai != null) { - nai.asyncChannel.sendMessage(android.net.NetworkAgent.CMD_REQUEST_BANDWIDTH_UPDATE); + nai.onBandwidthUpdateRequested(); synchronized (mBandwidthRequests) { final int uid = mDeps.getCallingUid(); Integer uidReqs = mBandwidthRequests.get(uid); @@ -5978,7 +5972,13 @@ public class ConnectivityService extends IConnectivityManager.Stub // NetworkAgentInfo keyed off its connecting messenger // TODO - eval if we can reduce the number of lists/hashmaps/sparsearrays // NOTE: Only should be accessed on ConnectivityServiceThread, except dump(). - private final HashMap<Messenger, NetworkAgentInfo> mNetworkAgentInfos = new HashMap<>(); + private final ArraySet<NetworkAgentInfo> mNetworkAgentInfos = new ArraySet<>(); + + // UID ranges for users that are currently blocked by VPNs. + // This array is accessed and iterated on multiple threads without holding locks, so its + // contents must never be mutated. When the ranges change, the array is replaced with a new one + // (on the handler thread). + private volatile List<UidRange> mVpnBlockedUidRanges = new ArrayList<>(); @GuardedBy("mBlockedAppUids") private final HashSet<Integer> mBlockedAppUids = new HashSet<>(); @@ -5997,6 +5997,9 @@ public class ConnectivityService extends IConnectivityManager.Stub // priority networks like ethernet are active. private final NetworkRequest mDefaultWifiRequest; + // Request used to optionally keep vehicle internal network always active + private final NetworkRequest mDefaultVehicleRequest; + private NetworkAgentInfo getDefaultNetwork() { return mDefaultNetworkNai; } @@ -6026,17 +6029,17 @@ public class ConnectivityService extends IConnectivityManager.Stub /** * Register a new agent. {@see #registerNetworkAgent} below. */ - public Network registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo, + public Network registerNetworkAgent(INetworkAgent na, NetworkInfo networkInfo, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, int currentScore, NetworkAgentConfig networkAgentConfig) { - return registerNetworkAgent(messenger, networkInfo, linkProperties, networkCapabilities, + return registerNetworkAgent(na, networkInfo, linkProperties, networkCapabilities, currentScore, networkAgentConfig, NetworkProvider.ID_NONE); } /** * Register a new agent with ConnectivityService to handle a network. * - * @param messenger a messenger for ConnectivityService to contact the agent asynchronously. + * @param na a reference for ConnectivityService to contact the agent asynchronously. * @param networkInfo the initial info associated with this network. It can be updated later : * see {@link #updateNetworkInfo}. * @param linkProperties the initial link properties of this network. They can be updated @@ -6049,7 +6052,7 @@ public class ConnectivityService extends IConnectivityManager.Stub * @param providerId the ID of the provider owning this NetworkAgent. * @return the network created for this agent. */ - public Network registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo, + public Network registerNetworkAgent(INetworkAgent na, NetworkInfo networkInfo, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, int currentScore, NetworkAgentConfig networkAgentConfig, int providerId) { if (networkCapabilities.hasTransport(TRANSPORT_TEST)) { @@ -6061,14 +6064,14 @@ public class ConnectivityService extends IConnectivityManager.Stub final int uid = mDeps.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { - return registerNetworkAgentInternal(messenger, networkInfo, linkProperties, + return registerNetworkAgentInternal(na, networkInfo, linkProperties, networkCapabilities, currentScore, networkAgentConfig, providerId, uid); } finally { Binder.restoreCallingIdentity(token); } } - private Network registerNetworkAgentInternal(Messenger messenger, NetworkInfo networkInfo, + private Network registerNetworkAgentInternal(INetworkAgent na, NetworkInfo networkInfo, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, int currentScore, NetworkAgentConfig networkAgentConfig, int providerId, int uid) { if (networkCapabilities.hasTransport(TRANSPORT_TEST)) { @@ -6084,7 +6087,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // TODO: Instead of passing mDefaultRequest, provide an API to determine whether a Network // satisfies mDefaultRequest. final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities); - final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(), + final NetworkAgentInfo nai = new NetworkAgentInfo(na, new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc, currentScore, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig), this, mNetd, mDnsResolver, mNMS, providerId, uid); @@ -6102,7 +6105,7 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.network, name, new NetworkMonitorCallbacks(nai)); // NetworkAgentInfo registration will finish when the NetworkMonitor is created. // If the network disconnects or sends any other event before that, messages are deferred by - // NetworkAgent until nai.asyncChannel.connect(), which will be called when finalizing the + // NetworkAgent until nai.connect(), which will be called when finalizing the // registration. return nai.network; } @@ -6110,7 +6113,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) { nai.onNetworkMonitorCreated(networkMonitor); if (VDBG) log("Got NetworkAgent Messenger"); - mNetworkAgentInfos.put(nai.messenger, nai); + mNetworkAgentInfos.add(nai); synchronized (mNetworkForNetId) { mNetworkForNetId.put(nai.network.getNetId(), nai); } @@ -6120,7 +6123,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } catch (RemoteException e) { e.rethrowAsRuntimeException(); } - nai.asyncChannel.connect(mContext, mTrackerHandler, nai.messenger); + nai.notifyRegistered(); NetworkInfo networkInfo = nai.networkInfo; updateNetworkInfo(nai, networkInfo); updateUids(nai, null, nai.networkCapabilities); @@ -6136,6 +6139,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private void processLinkPropertiesFromAgent(NetworkAgentInfo nai, LinkProperties lp) { lp.ensureDirectlyConnectedRoutes(); nai.clatd.setNat64PrefixFromRa(lp.getNat64Prefix()); + nai.networkAgentPortalData = lp.getCaptivePortalData(); } private void updateLinkProperties(NetworkAgentInfo networkAgent, LinkProperties newLp, @@ -6179,9 +6183,11 @@ public class ConnectivityService extends IConnectivityManager.Stub updateWakeOnLan(newLp); - // Captive portal data is obtained from NetworkMonitor and stored in NetworkAgentInfo, - // it is not contained in LinkProperties sent from NetworkAgents so needs to be merged here. - newLp.setCaptivePortalData(networkAgent.captivePortalData); + // Captive portal data is obtained from NetworkMonitor and stored in NetworkAgentInfo. + // It is not always contained in the LinkProperties sent from NetworkAgents, and if it + // does, it needs to be merged here. + newLp.setCaptivePortalData(mergeCaptivePortalData(networkAgent.networkAgentPortalData, + networkAgent.capportApiData)); // TODO - move this check to cover the whole function if (!Objects.equals(newLp, oldLp)) { @@ -6201,6 +6207,57 @@ public class ConnectivityService extends IConnectivityManager.Stub mKeepaliveTracker.handleCheckKeepalivesStillValid(networkAgent); } + /** + * @param naData captive portal data from NetworkAgent + * @param apiData captive portal data from capport API + */ + @Nullable + private CaptivePortalData mergeCaptivePortalData(CaptivePortalData naData, + CaptivePortalData apiData) { + if (naData == null || apiData == null) { + return naData == null ? apiData : naData; + } + final CaptivePortalData.Builder captivePortalBuilder = + new CaptivePortalData.Builder(naData); + + if (apiData.isCaptive()) { + captivePortalBuilder.setCaptive(true); + } + if (apiData.isSessionExtendable()) { + captivePortalBuilder.setSessionExtendable(true); + } + if (apiData.getExpiryTimeMillis() >= 0 || apiData.getByteLimit() >= 0) { + // Expiry time, bytes remaining, refresh time all need to come from the same source, + // otherwise data would be inconsistent. Prefer the capport API info if present, + // as it can generally be refreshed more often. + captivePortalBuilder.setExpiryTime(apiData.getExpiryTimeMillis()); + captivePortalBuilder.setBytesRemaining(apiData.getByteLimit()); + captivePortalBuilder.setRefreshTime(apiData.getRefreshTimeMillis()); + } else if (naData.getExpiryTimeMillis() < 0 && naData.getByteLimit() < 0) { + // No source has time / bytes remaining information: surface the newest refresh time + // for other fields + captivePortalBuilder.setRefreshTime( + Math.max(naData.getRefreshTimeMillis(), apiData.getRefreshTimeMillis())); + } + + // Prioritize the user portal URL from the network agent. + if (apiData.getUserPortalUrl() != null && (naData.getUserPortalUrl() == null + || TextUtils.isEmpty(naData.getUserPortalUrl().toSafeString()))) { + captivePortalBuilder.setUserPortalUrl(apiData.getUserPortalUrl()); + } + // Prioritize the venue information URL from the network agent. + if (apiData.getVenueInfoUrl() != null && (naData.getVenueInfoUrl() == null + || TextUtils.isEmpty(naData.getVenueInfoUrl().toSafeString()))) { + captivePortalBuilder.setVenueInfoUrl(apiData.getVenueInfoUrl()); + + // Note that venue friendly name can only come from the network agent because it is not + // in use in RFC8908. However, if using the Capport venue URL, make sure that the + // friendly name is not set from the network agent. + captivePortalBuilder.setVenueFriendlyName(null); + } + return captivePortalBuilder.build(); + } + private void wakeupModifyInterface(String iface, NetworkCapabilities caps, boolean add) { // Marks are only available on WiFi interfaces. Checking for // marks on unsupported interfaces is harmless. @@ -6634,7 +6691,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (meteredChanged) { maybeNotifyNetworkBlocked(nai, oldMetered, newMetered, mRestrictBackground, - mRestrictBackground); + mRestrictBackground, mVpnBlockedUidRanges, mVpnBlockedUidRanges); } final boolean roamingChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING) != @@ -6699,6 +6756,48 @@ public class ConnectivityService extends IConnectivityManager.Stub return stableRanges; } + private static UidRangeParcel[] toUidRangeStableParcels(UidRange[] ranges) { + final UidRangeParcel[] stableRanges = new UidRangeParcel[ranges.length]; + for (int i = 0; i < ranges.length; i++) { + stableRanges[i] = new UidRangeParcel(ranges[i].start, ranges[i].stop); + } + return stableRanges; + } + + private void maybeCloseSockets(NetworkAgentInfo nai, UidRangeParcel[] ranges, + int[] exemptUids) { + if (nai.isVPN() && !nai.networkAgentConfig.allowBypass) { + try { + mNetd.socketDestroy(ranges, exemptUids); + } catch (Exception e) { + loge("Exception in socket destroy: ", e); + } + } + } + + private void updateUidRanges(boolean add, NetworkAgentInfo nai, Set<UidRange> uidRanges) { + int[] exemptUids = new int[2]; + // TODO: Excluding VPN_UID is necessary in order to not to kill the TCP connection used + // by PPTP. Fix this by making Vpn set the owner UID to VPN_UID instead of system when + // starting a legacy VPN, and remove VPN_UID here. (b/176542831) + exemptUids[0] = VPN_UID; + exemptUids[1] = nai.networkCapabilities.getOwnerUid(); + UidRangeParcel[] ranges = toUidRangeStableParcels(uidRanges); + + maybeCloseSockets(nai, ranges, exemptUids); + try { + if (add) { + mNetd.networkAddUidRanges(nai.network.netId, ranges); + } else { + mNetd.networkRemoveUidRanges(nai.network.netId, ranges); + } + } catch (Exception e) { + loge("Exception while " + (add ? "adding" : "removing") + " uid ranges " + uidRanges + + " on netId " + nai.network.netId + ". " + e); + } + maybeCloseSockets(nai, ranges, exemptUids); + } + private void updateUids(NetworkAgentInfo nai, NetworkCapabilities prevNc, NetworkCapabilities newNc) { Set<UidRange> prevRanges = null == prevNc ? null : prevNc.getUids(); @@ -6717,12 +6816,21 @@ public class ConnectivityService extends IConnectivityManager.Stub // in both ranges are not subject to any VPN routing rules. Adding new range before // removing old range works because, unlike the filtering rules below, it's possible to // add duplicate UID routing rules. + // TODO: calculate the intersection of add & remove. Imagining that we are trying to + // remove uid 3 from a set containing 1-5. Intersection of the prev and new sets is: + // [1-5] & [1-2],[4-5] == [3] + // Then we can do: + // maybeCloseSockets([3]) + // mNetd.networkAddUidRanges([1-2],[4-5]) + // mNetd.networkRemoveUidRanges([1-5]) + // maybeCloseSockets([3]) + // This can prevent the sockets of uid 1-2, 4-5 from being closed. It also reduce the + // number of binder calls from 6 to 4. if (!newRanges.isEmpty()) { - mNetd.networkAddUidRanges(nai.network.netId, toUidRangeStableParcels(newRanges)); + updateUidRanges(true, nai, newRanges); } if (!prevRanges.isEmpty()) { - mNetd.networkRemoveUidRanges( - nai.network.netId, toUidRangeStableParcels(prevRanges)); + updateUidRanges(false, nai, prevRanges); } final boolean wasFiltering = requiresVpnIsolation(nai, prevNc, nai.linkProperties); final boolean shouldFilter = requiresVpnIsolation(nai, newNc, nai.linkProperties); @@ -6933,7 +7041,7 @@ public class ConnectivityService extends IConnectivityManager.Stub break; } } - nai.asyncChannel.disconnect(); + nai.disconnect(); } private void handleLingerComplete(NetworkAgentInfo oldNetwork) { @@ -7097,11 +7205,11 @@ public class ConnectivityService extends IConnectivityManager.Stub log(" accepting network in place of " + previousSatisfier.toShortString()); } previousSatisfier.removeRequest(nri.request.requestId); - previousSatisfier.lingerRequest(nri.request, now, mLingerDelayMs); + previousSatisfier.lingerRequest(nri.request.requestId, now, mLingerDelayMs); } else { if (VDBG || DDBG) log(" accepting network in place of null"); } - newSatisfier.unlingerRequest(nri.request); + newSatisfier.unlingerRequest(nri.request.requestId); if (!newSatisfier.addRequest(nri.request)) { Log.wtf(TAG, "BUG: " + newSatisfier.toShortString() + " already has " + nri.request); @@ -7123,7 +7231,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // Gather the list of all relevant agents and sort them by score. final ArrayList<NetworkAgentInfo> nais = new ArrayList<>(); - for (final NetworkAgentInfo nai : mNetworkAgentInfos.values()) { + for (final NetworkAgentInfo nai : mNetworkAgentInfos) { if (!nai.everConnected) continue; nais.add(nai); } @@ -7158,7 +7266,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private void applyNetworkReassignment(@NonNull final NetworkReassignment changes, final long now) { - final Collection<NetworkAgentInfo> nais = mNetworkAgentInfos.values(); + final Collection<NetworkAgentInfo> nais = mNetworkAgentInfos; // Since most of the time there are only 0 or 1 background networks, it would probably // be more efficient to just use an ArrayList here. TODO : measure performance @@ -7190,9 +7298,28 @@ public class ConnectivityService extends IConnectivityManager.Stub updateDataActivityTracking(newDefaultNetwork, oldDefaultNetwork); // Notify system services of the new default. makeDefault(newDefaultNetwork); + // Log 0 -> X and Y -> X default network transitions, where X is the new default. - mDeps.getMetricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent( - now, newDefaultNetwork, oldDefaultNetwork); + final Network network = (newDefaultNetwork != null) ? newDefaultNetwork.network : null; + final int score = (newDefaultNetwork != null) ? newDefaultNetwork.getCurrentScore() : 0; + final boolean validated = newDefaultNetwork != null && newDefaultNetwork.lastValidated; + final LinkProperties lp = (newDefaultNetwork != null) + ? newDefaultNetwork.linkProperties : null; + final NetworkCapabilities nc = (newDefaultNetwork != null) + ? newDefaultNetwork.networkCapabilities : null; + + final Network prevNetwork = (oldDefaultNetwork != null) + ? oldDefaultNetwork.network : null; + final int prevScore = (oldDefaultNetwork != null) + ? oldDefaultNetwork.getCurrentScore() : 0; + final LinkProperties prevLp = (oldDefaultNetwork != null) + ? oldDefaultNetwork.linkProperties : null; + final NetworkCapabilities prevNc = (oldDefaultNetwork != null) + ? oldDefaultNetwork.networkCapabilities : null; + + mMetricsLog.logDefaultNetworkEvent(network, score, validated, lp, nc, + prevNetwork, prevScore, prevLp, prevNc); + // Have a new default network, release the transition wakelock in scheduleReleaseNetworkTransitionWakelock(); } @@ -7251,7 +7378,7 @@ public class ConnectivityService extends IConnectivityManager.Stub updateLegacyTypeTrackerAndVpnLockdownForRematch(oldDefaultNetwork, newDefaultNetwork, nais); // Tear down all unneeded networks. - for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { + for (NetworkAgentInfo nai : mNetworkAgentInfos) { if (unneeded(nai, UnneededFor.TEARDOWN)) { if (nai.getLingerExpiry() > 0) { // This network has active linger timers and no requests, but is not @@ -7488,7 +7615,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // This has to happen after matching the requests, because callbacks are just requests. notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK); } else if (state == NetworkInfo.State.DISCONNECTED) { - networkAgent.asyncChannel.disconnect(); + networkAgent.disconnect(); if (networkAgent.isVPN()) { updateUids(networkAgent, networkAgent.networkCapabilities, null); } @@ -7526,7 +7653,9 @@ public class ConnectivityService extends IConnectivityManager.Stub } final boolean metered = nai.networkCapabilities.isMetered(); - final boolean blocked = isUidNetworkingWithVpnBlocked(nri.mUid, mUidRules.get(nri.mUid), + boolean blocked; + blocked = isUidBlockedByVpn(nri.mUid, mVpnBlockedUidRanges); + blocked |= isUidBlockedByRules(nri.mUid, mUidRules.get(nri.mUid), metered, mRestrictBackground); callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, blocked ? 1 : 0); } @@ -7548,21 +7677,25 @@ public class ConnectivityService extends IConnectivityManager.Stub * @param newRestrictBackground True if data saver is enabled. */ private void maybeNotifyNetworkBlocked(NetworkAgentInfo nai, boolean oldMetered, - boolean newMetered, boolean oldRestrictBackground, boolean newRestrictBackground) { + boolean newMetered, boolean oldRestrictBackground, boolean newRestrictBackground, + List<UidRange> oldBlockedUidRanges, List<UidRange> newBlockedUidRanges) { for (int i = 0; i < nai.numNetworkRequests(); i++) { NetworkRequest nr = nai.requestAt(i); NetworkRequestInfo nri = mNetworkRequests.get(nr); final int uidRules = mUidRules.get(nri.mUid); - final boolean oldBlocked, newBlocked; - // mVpns lock needs to be hold here to ensure that the active VPN cannot be changed - // between these two calls. - synchronized (mVpns) { - oldBlocked = isUidNetworkingWithVpnBlocked(nri.mUid, uidRules, oldMetered, - oldRestrictBackground); - newBlocked = isUidNetworkingWithVpnBlocked(nri.mUid, uidRules, newMetered, - newRestrictBackground); - } + final boolean oldBlocked, newBlocked, oldVpnBlocked, newVpnBlocked; + + oldVpnBlocked = isUidBlockedByVpn(nri.mUid, oldBlockedUidRanges); + newVpnBlocked = (oldBlockedUidRanges != newBlockedUidRanges) + ? isUidBlockedByVpn(nri.mUid, newBlockedUidRanges) + : oldVpnBlocked; + + oldBlocked = oldVpnBlocked || isUidBlockedByRules(nri.mUid, uidRules, oldMetered, + oldRestrictBackground); + newBlocked = newVpnBlocked || isUidBlockedByRules(nri.mUid, uidRules, newMetered, + newRestrictBackground); + if (oldBlocked != newBlocked) { callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED, encodeBool(newBlocked)); @@ -7576,19 +7709,14 @@ public class ConnectivityService extends IConnectivityManager.Stub * @param newRules The new rules to apply. */ private void maybeNotifyNetworkBlockedForNewUidRules(int uid, int newRules) { - for (final NetworkAgentInfo nai : mNetworkAgentInfos.values()) { + for (final NetworkAgentInfo nai : mNetworkAgentInfos) { final boolean metered = nai.networkCapabilities.isMetered(); + final boolean vpnBlocked = isUidBlockedByVpn(uid, mVpnBlockedUidRanges); final boolean oldBlocked, newBlocked; - // TODO: Consider that doze mode or turn on/off battery saver would deliver lots of uid - // rules changed event. And this function actually loop through all connected nai and - // its requests. It seems that mVpns lock will be grabbed frequently in this case. - // Reduce the number of locking or optimize the use of lock are likely needed in future. - synchronized (mVpns) { - oldBlocked = isUidNetworkingWithVpnBlocked( - uid, mUidRules.get(uid), metered, mRestrictBackground); - newBlocked = isUidNetworkingWithVpnBlocked( - uid, newRules, metered, mRestrictBackground); - } + oldBlocked = vpnBlocked || isUidBlockedByRules( + uid, mUidRules.get(uid), metered, mRestrictBackground); + newBlocked = vpnBlocked || isUidBlockedByRules( + uid, newRules, metered, mRestrictBackground); if (oldBlocked == newBlocked) { continue; } @@ -7682,7 +7810,7 @@ public class ConnectivityService extends IConnectivityManager.Stub ensureRunningOnConnectivityServiceThread(); ArrayList<Network> defaultNetworks = new ArrayList<>(); NetworkAgentInfo defaultNetwork = getDefaultNetwork(); - for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { + for (NetworkAgentInfo nai : mNetworkAgentInfos) { if (nai.everConnected && (nai == defaultNetwork || nai.isVPN())) { defaultNetworks.add(nai.network); } @@ -8233,6 +8361,13 @@ public class ConnectivityService extends IConnectivityManager.Stub final IBinder iCb = cb.asBinder(); final NetworkRequestInfo nri = cbInfo.mRequestInfo; + // Connectivity Diagnostics are meant to be used with a single network request. It would be + // confusing for these networks to change when an NRI is satisfied in another layer. + if (nri.isMultilayerRequest()) { + throw new IllegalArgumentException("Connectivity Diagnostics do not support multilayer " + + "network requests."); + } + // This means that the client registered the same callback multiple times. Do // not override the previous entry, and exit silently. if (mConnectivityDiagnosticsCallbacks.containsKey(iCb)) { @@ -8259,7 +8394,8 @@ public class ConnectivityService extends IConnectivityManager.Stub synchronized (mNetworkForNetId) { for (int i = 0; i < mNetworkForNetId.size(); i++) { final NetworkAgentInfo nai = mNetworkForNetId.valueAt(i); - if (nai.satisfies(nri.request)) { + // Connectivity Diagnostics rejects multilayer requests at registration hence get(0) + if (nai.satisfies(nri.mRequests.get(0))) { matchingNetworks.add(nai); } } @@ -8387,7 +8523,8 @@ public class ConnectivityService extends IConnectivityManager.Stub mConnectivityDiagnosticsCallbacks.entrySet()) { final ConnectivityDiagnosticsCallbackInfo cbInfo = entry.getValue(); final NetworkRequestInfo nri = cbInfo.mRequestInfo; - if (nai.satisfies(nri.request)) { + // Connectivity Diagnostics rejects multilayer requests at registration hence get(0). + if (nai.satisfies(nri.mRequests.get(0))) { if (checkConnectivityDiagnosticsPermissions( nri.mPid, nri.mUid, nai, cbInfo.mCallingPackageName)) { results.add(entry.getValue().mCb); @@ -8416,7 +8553,7 @@ public class ConnectivityService extends IConnectivityManager.Stub return false; } - for (NetworkAgentInfo virtual : mNetworkAgentInfos.values()) { + for (NetworkAgentInfo virtual : mNetworkAgentInfos) { if (virtual.supportsUnderlyingNetworks() && virtual.networkCapabilities.getOwnerUid() == callbackUid && ArrayUtils.contains(virtual.declaredUnderlyingNetworks, nai.network)) { diff --git a/services/core/java/com/android/server/ConnectivityServiceInitializer.java b/services/core/java/com/android/server/ConnectivityServiceInitializer.java index 2bc8925be019..0779f7117d82 100644 --- a/services/core/java/com/android/server/ConnectivityServiceInitializer.java +++ b/services/core/java/com/android/server/ConnectivityServiceInitializer.java @@ -20,7 +20,6 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; import android.content.Context; -import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; import android.os.INetworkManagementService; import android.os.ServiceManager; @@ -36,9 +35,11 @@ public final class ConnectivityServiceInitializer extends SystemService { public ConnectivityServiceInitializer(Context context) { super(context); + // Load JNI libraries used by ConnectivityService and its dependencies + System.loadLibrary("service-connectivity"); // TODO: Define formal APIs to get the needed services. mConnectivity = new ConnectivityService(context, getNetworkManagementService(), - getNetworkStatsService(), getNetworkPolicyManager()); + getNetworkStatsService()); } @Override @@ -57,10 +58,4 @@ public final class ConnectivityServiceInitializer extends SystemService { return INetworkStatsService.Stub.asInterface( ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); } - - private INetworkPolicyManager getNetworkPolicyManager() { - return INetworkPolicyManager.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); - } - } diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java index 500e768372f5..f2b63a642c29 100644 --- a/services/core/java/com/android/server/DynamicSystemService.java +++ b/services/core/java/com/android/server/DynamicSystemService.java @@ -236,4 +236,9 @@ public class DynamicSystemService extends IDynamicSystemService.Stub { throw new RuntimeException(e.toString()); } } + + @Override + public long suggestScratchSize() throws RemoteException { + return getGsiService().suggestScratchSize(); + } } diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS index a82e146113b6..f6b72d6bfe2c 100644 --- a/services/core/java/com/android/server/OWNERS +++ b/services/core/java/com/android/server/OWNERS @@ -1,5 +1,5 @@ # Connectivity / Networking -per-file ConnectivityService.java,NetworkManagementService.java,NsdService.java = codewiz@google.com, ek@google.com, jchalard@google.com, junyulai@google.com, lorenzo@google.com, reminv@google.com, satk@google.com +per-file ConnectivityService.java,ConnectivityServiceInitializer.java,NetworkManagementService.java,NsdService.java = file:/services/core/java/com/android/server/net/OWNERS # Vibrator / Threads per-file VibratorService.java, DisplayThread.java = michaelwr@google.com @@ -14,6 +14,9 @@ per-file UserspaceRebootLogger.java = ioffe@google.com, tomcherry@google.com # Sensor Privacy per-file SensorPrivacyService.java = file:platform/frameworks/native:/libs/sensorprivacy/OWNERS +# ServiceWatcher +per-file ServiceWatcher.java = sooniln@google.com + per-file *Alarm* = file:/apex/jobscheduler/OWNERS per-file *AppOp* = file:/core/java/android/permission/OWNERS per-file *Bluetooth* = file:/core/java/android/bluetooth/OWNERS @@ -22,7 +25,6 @@ per-file *Location* = file:/services/core/java/com/android/server/location/OWNER per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS per-file *Storage* = file:/core/java/android/os/storage/OWNERS per-file *TimeUpdate* = file:/core/java/android/app/timezone/OWNERS -per-file ConnectivityService.java = file:/services/core/java/com/android/server/net/OWNERS per-file DynamicSystemService.java = file:/packages/DynamicSystemInstallationService/OWNERS per-file GestureLauncherService.java = file:platform/packages/apps/EmergencyInfo:/OWNERS per-file IpSecService.java = file:/services/core/java/com/android/server/net/OWNERS diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index eb1123e47f52..5f6e8df30f8d 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -304,7 +304,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private final LocalLog mLocalLog = new LocalLog(200); - private final LocalLog mListenLog = new LocalLog(00); + private final LocalLog mListenLog = new LocalLog(200); /** * Per-phone map of precise data connection state. The key of the map is the pair of transport @@ -2316,7 +2316,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println("local logs:"); pw.increaseIndent(); mLocalLog.dump(fd, pw, args); + pw.decreaseIndent(); pw.println("listen logs:"); + pw.increaseIndent(); mListenLog.dump(fd, pw, args); pw.decreaseIndent(); pw.println("registrations: count=" + recordCount); diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java index e8687e57a07b..a08d066513c7 100644 --- a/services/core/java/com/android/server/TestNetworkService.java +++ b/services/core/java/com/android/server/TestNetworkService.java @@ -242,6 +242,7 @@ class TestNetworkService extends ITestNetworkManager.Stub { nc.addTransportType(NetworkCapabilities.TRANSPORT_TEST); nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED); nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED); + nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED); nc.setNetworkSpecifier(new StringNetworkSpecifier(iface)); nc.setAdministratorUids(administratorUids); if (!isMetered) { diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 74e38510770b..c191a78aad0e 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -16,13 +16,15 @@ package com.android.server; +import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; +import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; + import static java.util.Objects.requireNonNull; import android.annotation.NonNull; +import android.app.AppOpsManager; import android.content.Context; import android.net.ConnectivityManager; -import android.net.NetworkProvider; -import android.net.NetworkRequest; import android.net.vcn.IVcnManagementService; import android.net.vcn.VcnConfig; import android.os.Binder; @@ -43,6 +45,10 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.server.vcn.TelephonySubscriptionTracker; +import com.android.server.vcn.Vcn; +import com.android.server.vcn.VcnContext; +import com.android.server.vcn.VcnNetworkProvider; import com.android.server.vcn.util.PersistableBundleUtils; import java.io.IOException; @@ -51,6 +57,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; /** * VcnManagementService manages Virtual Carrier Network profiles and lifecycles. @@ -115,6 +122,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { @VisibleForTesting(visibility = Visibility.PRIVATE) static final String VCN_CONFIG_FILE = "/data/system/vcn/configs.xml"; + // TODO(b/176956496): Directly use CarrierServiceBindHelper.UNBIND_DELAY_MILLIS + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final long CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS = TimeUnit.SECONDS.toMillis(30); + /* Binder context for this service */ @NonNull private final Context mContext; @NonNull private final Dependencies mDeps; @@ -122,11 +133,23 @@ public class VcnManagementService extends IVcnManagementService.Stub { @NonNull private final Looper mLooper; @NonNull private final Handler mHandler; @NonNull private final VcnNetworkProvider mNetworkProvider; + @NonNull private final TelephonySubscriptionTrackerCallback mTelephonySubscriptionTrackerCb; + @NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker; + @NonNull private final VcnContext mVcnContext; @GuardedBy("mLock") @NonNull private final Map<ParcelUuid, VcnConfig> mConfigs = new ArrayMap<>(); + @GuardedBy("mLock") + @NonNull + private final Map<ParcelUuid, Vcn> mVcns = new ArrayMap<>(); + + @GuardedBy("mLock") + @NonNull + private TelephonySubscriptionSnapshot mLastSnapshot = + TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT; + @NonNull private final Object mLock = new Object(); @NonNull private final PersistableBundleUtils.LockingReadWriteHelper mConfigDiskRwHelper; @@ -139,8 +162,12 @@ public class VcnManagementService extends IVcnManagementService.Stub { mLooper = mDeps.getLooper(); mHandler = new Handler(mLooper); mNetworkProvider = new VcnNetworkProvider(mContext, mLooper); + mTelephonySubscriptionTrackerCb = new VcnSubscriptionTrackerCallback(); + mTelephonySubscriptionTracker = mDeps.newTelephonySubscriptionTracker( + mContext, mLooper, mTelephonySubscriptionTrackerCb); mConfigDiskRwHelper = mDeps.newPersistableBundleLockingReadWriteHelper(VCN_CONFIG_FILE); + mVcnContext = mDeps.newVcnContext(mContext, mLooper, mNetworkProvider); // Run on handler to ensure I/O does not block system server startup mHandler.post(() -> { @@ -174,7 +201,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { mConfigs.put(entry.getKey(), entry.getValue()); } } - // TODO: Trigger re-evaluation of active VCNs; start/stop VCNs as needed. + + // Re-evaluate subscriptions, and start/stop VCNs. This starts with an empty + // snapshot, and therefore safe even before telephony subscriptions are loaded. + mTelephonySubscriptionTrackerCb.onNewSnapshot(mLastSnapshot); } } }); @@ -203,6 +233,14 @@ public class VcnManagementService extends IVcnManagementService.Stub { return mHandlerThread.getLooper(); } + /** Creates a new VcnInstance using the provided configuration */ + public TelephonySubscriptionTracker newTelephonySubscriptionTracker( + @NonNull Context context, + @NonNull Looper looper, + @NonNull TelephonySubscriptionTrackerCallback callback) { + return new TelephonySubscriptionTracker(context, new Handler(looper), callback); + } + /** * Retrieves the caller's UID * @@ -225,12 +263,29 @@ public class VcnManagementService extends IVcnManagementService.Stub { newPersistableBundleLockingReadWriteHelper(@NonNull String path) { return new PersistableBundleUtils.LockingReadWriteHelper(path); } + + /** Creates a new VcnContext */ + public VcnContext newVcnContext( + @NonNull Context context, + @NonNull Looper looper, + @NonNull VcnNetworkProvider vcnNetworkProvider) { + return new VcnContext(context, looper, vcnNetworkProvider); + } + + /** Creates a new Vcn instance using the provided configuration */ + public Vcn newVcn( + @NonNull VcnContext vcnContext, + @NonNull ParcelUuid subscriptionGroup, + @NonNull VcnConfig config) { + return new Vcn(vcnContext, subscriptionGroup, config); + } } /** Notifies the VcnManagementService that external dependencies can be set up. */ public void systemReady() { mContext.getSystemService(ConnectivityManager.class) .registerNetworkProvider(mNetworkProvider); + mTelephonySubscriptionTracker.register(); } private void enforcePrimaryUser() { @@ -277,27 +332,112 @@ public class VcnManagementService extends IVcnManagementService.Stub { "Carrier privilege required for subscription group to set VCN Config"); } + private class VcnSubscriptionTrackerCallback implements TelephonySubscriptionTrackerCallback { + /** + * Handles subscription group changes, as notified by {@link TelephonySubscriptionTracker} + * + * <p>Start any unstarted VCN instances + * + * @hide + */ + public void onNewSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) { + // Startup VCN instances + synchronized (mLock) { + mLastSnapshot = snapshot; + + // Start any VCN instances as necessary + for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) { + if (snapshot.packageHasPermissionsForSubscriptionGroup( + entry.getKey(), entry.getValue().getProvisioningPackageName())) { + if (!mVcns.containsKey(entry.getKey())) { + startVcnLocked(entry.getKey(), entry.getValue()); + } + + // Cancel any scheduled teardowns for active subscriptions + mHandler.removeCallbacksAndMessages(mVcns.get(entry.getKey())); + } + } + + // Schedule teardown of any VCN instances that have lost carrier privileges (after a + // delay) + for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) { + final VcnConfig config = mConfigs.get(entry.getKey()); + if (config == null + || !snapshot.packageHasPermissionsForSubscriptionGroup( + entry.getKey(), config.getProvisioningPackageName())) { + final ParcelUuid uuidToTeardown = entry.getKey(); + final Vcn instanceToTeardown = entry.getValue(); + + mHandler.postDelayed(() -> { + synchronized (mLock) { + // Guard against case where this is run after a old instance was + // torn down, and a new instance was started. Verify to ensure + // correct instance is torn down. This could happen as a result of a + // Carrier App manually removing/adding a VcnConfig. + if (mVcns.get(uuidToTeardown) == instanceToTeardown) { + mVcns.remove(uuidToTeardown).teardownAsynchronously(); + } + } + }, instanceToTeardown, CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + } + } + } + } + } + + @GuardedBy("mLock") + private void startVcnLocked(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { + Slog.v(TAG, "Starting VCN config for subGrp: " + subscriptionGroup); + + // TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active + // VCN. + + final Vcn newInstance = mDeps.newVcn(mVcnContext, subscriptionGroup, config); + mVcns.put(subscriptionGroup, newInstance); + } + + @GuardedBy("mLock") + private void startOrUpdateVcnLocked( + @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { + Slog.v(TAG, "Starting or updating VCN config for subGrp: " + subscriptionGroup); + + if (mVcns.containsKey(subscriptionGroup)) { + mVcns.get(subscriptionGroup).updateConfig(config); + } else { + startVcnLocked(subscriptionGroup, config); + } + } + /** * Sets a VCN config for a given subscription group. * * <p>Implements the IVcnManagementService Binder interface. */ @Override - public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { + public void setVcnConfig( + @NonNull ParcelUuid subscriptionGroup, + @NonNull VcnConfig config, + @NonNull String opPkgName) { requireNonNull(subscriptionGroup, "subscriptionGroup was null"); requireNonNull(config, "config was null"); + requireNonNull(opPkgName, "opPkgName was null"); + if (!config.getProvisioningPackageName().equals(opPkgName)) { + throw new IllegalArgumentException("Mismatched caller and VcnConfig creator"); + } + Slog.v(TAG, "VCN config updated for subGrp: " + subscriptionGroup); + mContext.getSystemService(AppOpsManager.class) + .checkPackage(mDeps.getBinderCallingUid(), config.getProvisioningPackageName()); enforceCallingUserAndCarrierPrivilege(subscriptionGroup); - synchronized (mLock) { - mConfigs.put(subscriptionGroup, config); + Binder.withCleanCallingIdentity(() -> { + synchronized (mLock) { + mConfigs.put(subscriptionGroup, config); + startOrUpdateVcnLocked(subscriptionGroup, config); - // Must be done synchronously to ensure that writes do not happen out-of-order. - writeConfigsToDiskLocked(); - } - - // TODO: Clear Binder calling identity - // TODO: Trigger startup as necessary + writeConfigsToDiskLocked(); + } + }); } /** @@ -308,18 +448,21 @@ public class VcnManagementService extends IVcnManagementService.Stub { @Override public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) { requireNonNull(subscriptionGroup, "subscriptionGroup was null"); + Slog.v(TAG, "VCN config cleared for subGrp: " + subscriptionGroup); enforceCallingUserAndCarrierPrivilege(subscriptionGroup); - synchronized (mLock) { - mConfigs.remove(subscriptionGroup); + Binder.withCleanCallingIdentity(() -> { + synchronized (mLock) { + mConfigs.remove(subscriptionGroup); - // Must be done synchronously to ensure that writes do not happen out-of-order. - writeConfigsToDiskLocked(); - } + if (mVcns.containsKey(subscriptionGroup)) { + mVcns.remove(subscriptionGroup).teardownAsynchronously(); + } - // TODO: Clear Binder calling identity - // TODO: Trigger teardown as necessary + writeConfigsToDiskLocked(); + } + }); } @GuardedBy("mLock") @@ -345,19 +488,11 @@ public class VcnManagementService extends IVcnManagementService.Stub { } } - /** - * Network provider for VCN networks. - * - * @hide - */ - public class VcnNetworkProvider extends NetworkProvider { - VcnNetworkProvider(Context context, Looper looper) { - super(context, looper, VcnNetworkProvider.class.getSimpleName()); - } - - @Override - public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) { - // TODO: Handle network requests - Ensure VCN started, and start appropriate tunnels. + /** Get current configuration list for testing purposes */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public Map<ParcelUuid, Vcn> getAllVcns() { + synchronized (mLock) { + return Collections.unmodifiableMap(mVcns); } } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 40f24283d1b8..928ddab9dca7 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -9481,6 +9481,7 @@ public class ActivityManagerService extends IActivityManager.Stub final long waitForNetworkTimeoutMs = Settings.Global.getLong(resolver, NETWORK_ACCESS_TIMEOUT_MS, NETWORK_ACCESS_TIMEOUT_DEFAULT_MS); mHiddenApiBlacklist.registerObserver(); + mPlatformCompat.registerContentObserver(); final long pssDeferralMs = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, ACTIVITY_START_PSS_DEFER_CONFIG, 0L); @@ -10459,12 +10460,10 @@ public class ActivityManagerService extends IActivityManager.Stub } finally { Binder.restoreCallingIdentity(identity); } - if (uid == Process.INVALID_UID) { - return Process.INVALID_UID; - } + // If the uid is Process.INVALID_UID, the below 'if' check will be always true if (UserHandle.getAppId(uid) != UserHandle.getAppId(callingUid)) { // Requires the DUMP permission if the target package doesn't belong - // to the caller. + // to the caller or it doesn't exist. enforceCallingPermission(android.Manifest.permission.DUMP, function); } return uid; diff --git a/services/core/java/com/android/server/apphibernation/OWNERS b/services/core/java/com/android/server/apphibernation/OWNERS new file mode 100644 index 000000000000..c2e27e084c8c --- /dev/null +++ b/services/core/java/com/android/server/apphibernation/OWNERS @@ -0,0 +1 @@ +include /core/java/android/apphibernation/OWNERS diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java index ef1b574c29a8..b4d74e0900f9 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java @@ -16,16 +16,22 @@ package com.android.server.biometrics; +import android.app.ActivityManager; +import android.app.ActivityTaskManager; +import android.content.ComponentName; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; import android.os.RemoteException; import android.security.KeyStore; +import android.util.EventLog; import android.util.Slog; import java.util.ArrayList; +import java.util.List; /** * A class to keep track of the authentication state for a given client. @@ -148,7 +154,54 @@ public abstract class AuthenticationClient extends ClientMonitor { + ", requireConfirmation: " + mRequireConfirmation + ", user: " + getTargetUserId()); + // Ensure authentication only succeeds if the client activity is on top or is keyguard. + boolean isBackgroundAuth = false; + if (authenticated && !Utils.isKeyguard(getContext(), getOwnerString())) { + try { + final List<ActivityManager.RunningTaskInfo> tasks = + ActivityTaskManager.getService().getTasks(1); + if (tasks == null || tasks.isEmpty()) { + Slog.e(TAG, "No running tasks reported"); + isBackgroundAuth = true; + } else { + final ComponentName topActivity = tasks.get(0).topActivity; + if (topActivity == null) { + Slog.e(TAG, "Unable to get top activity"); + isBackgroundAuth = true; + } else { + final String topPackage = topActivity.getPackageName(); + if (!topPackage.contentEquals(getOwnerString())) { + Slog.e(TAG, "Background authentication detected, top: " + topPackage + + ", client: " + this); + isBackgroundAuth = true; + } + } + } + } catch (RemoteException e) { + Slog.e(TAG, "Unable to get running tasks", e); + isBackgroundAuth = true; + } + } + + // Fail authentication if we can't confirm the client activity is on top. + if (isBackgroundAuth) { + Slog.e(TAG, "Failing possible background authentication"); + authenticated = false; + + // SafetyNet logging for exploitation attempts of b/159249069. + final ApplicationInfo appInfo = getContext().getApplicationInfo(); + EventLog.writeEvent(0x534e4554, "159249069", appInfo != null ? appInfo.uid : -1, + "Attempted background authentication"); + } + if (authenticated) { + // SafetyNet logging for b/159249069 if constraint is violated. + if (isBackgroundAuth) { + final ApplicationInfo appInfo = getContext().getApplicationInfo(); + EventLog.writeEvent(0x534e4554, "159249069", appInfo != null ? appInfo.uid : -1, + "Successful background authentication!"); + } + mAlreadyDone = true; if (listener != null) { diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java index a8aa9aada607..9ba957ef27ae 100644 --- a/services/core/java/com/android/server/compat/CompatChange.java +++ b/services/core/java/com/android/server/compat/CompatChange.java @@ -61,9 +61,10 @@ public final class CompatChange extends CompatibilityChangeInfo { ChangeListener mListener = null; private Map<String, Boolean> mPackageOverrides; + private Map<String, Boolean> mDeferredOverrides; public CompatChange(long changeId) { - this(changeId, null, -1, -1, false, false, null); + this(changeId, null, -1, -1, false, false, null, false); } /** @@ -76,9 +77,10 @@ public final class CompatChange extends CompatibilityChangeInfo { * @param disabled If {@code true}, overrides any {@code enableAfterTargetSdk} set. */ public CompatChange(long changeId, @Nullable String name, int enableAfterTargetSdk, - int enableSinceTargetSdk, boolean disabled, boolean loggingOnly, String description) { + int enableSinceTargetSdk, boolean disabled, boolean loggingOnly, String description, + boolean overridable) { super(changeId, name, enableAfterTargetSdk, enableSinceTargetSdk, disabled, loggingOnly, - description); + description, overridable); } /** @@ -87,7 +89,7 @@ public final class CompatChange extends CompatibilityChangeInfo { public CompatChange(Change change) { super(change.getId(), change.getName(), change.getEnableAfterTargetSdk(), change.getEnableSinceTargetSdk(), change.getDisabled(), change.getLoggingOnly(), - change.getDescription()); + change.getDescription(), change.getOverridable()); } void registerListener(ChangeListener listener) { @@ -121,6 +123,56 @@ public final class CompatChange extends CompatibilityChangeInfo { } /** + * Tentatively set the state of this change for a given package name. + * The override will only take effect after that package is installed, if applicable. + * + * <p>Note, this method is not thread safe so callers must ensure thread safety. + * + * @param packageName Package name to tentatively enable the change for. + * @param enabled Whether or not to enable the change. + */ + void addPackageDeferredOverride(String packageName, boolean enabled) { + if (getLoggingOnly()) { + throw new IllegalArgumentException( + "Can't add overrides for a logging only change " + toString()); + } + if (mDeferredOverrides == null) { + mDeferredOverrides = new HashMap<>(); + } + mDeferredOverrides.put(packageName, enabled); + } + + /** + * Rechecks an existing (and possibly deferred) override. + * + * <p>For deferred overrides, check if they can be promoted to a regular override. For regular + * overrides, check if they need to be demoted to deferred.</p> + * + * @param packageName Package name to apply deferred overrides for. + * @param allowed Whether the override is allowed. + * + * @return {@code true} if the recheck yielded a result that requires invalidating caches + * (a deferred override was consolidated or a regular override was removed). + */ + boolean recheckOverride(String packageName, boolean allowed) { + // A deferred override now is allowed by the policy, so promote it to a regular override. + if (hasDeferredOverride(packageName) && allowed) { + boolean overrideValue = mDeferredOverrides.remove(packageName); + addPackageOverride(packageName, overrideValue); + return true; + } + // A previously set override is no longer allowed by the policy, so make it deferred. + if (hasOverride(packageName) && !allowed) { + boolean overrideValue = mPackageOverrides.remove(packageName); + addPackageDeferredOverride(packageName, overrideValue); + // Notify because the override was removed. + notifyListener(packageName); + return true; + } + return false; + } + + /** * Remove any package override for the given package name, restoring the default behaviour. * * <p>Note, this method is not thread safe so callers must ensure thread safety. @@ -133,6 +185,9 @@ public final class CompatChange extends CompatibilityChangeInfo { notifyListener(pname); } } + if (mDeferredOverrides != null) { + mDeferredOverrides.remove(pname); + } } /** @@ -159,6 +214,19 @@ public final class CompatChange extends CompatibilityChangeInfo { } /** + * Find if this change will be enabled for the given package after installation. + * + * @param packageName The package name in question + * @return {@code true} if the change should be enabled for the package. + */ + boolean willBeEnabled(String packageName) { + if (hasDeferredOverride(packageName)) { + return mDeferredOverrides.get(packageName); + } + return defaultValue(); + } + + /** * Returns the default value for the change id, assuming there are no overrides. * * @return {@code false} if it's a default disabled change, {@code true} otherwise. @@ -176,6 +244,15 @@ public final class CompatChange extends CompatibilityChangeInfo { return mPackageOverrides != null && mPackageOverrides.containsKey(packageName); } + /** + * Checks whether a change has a deferred override for a package. + * @param packageName name of the package + * @return true if there is such a deferred override + */ + boolean hasDeferredOverride(String packageName) { + return mDeferredOverrides != null && mDeferredOverrides.containsKey(packageName); + } + @Override public String toString() { StringBuilder sb = new StringBuilder("ChangeId(") @@ -195,6 +272,12 @@ public final class CompatChange extends CompatibilityChangeInfo { if (mPackageOverrides != null && mPackageOverrides.size() > 0) { sb.append("; packageOverrides=").append(mPackageOverrides); } + if (mDeferredOverrides != null && mDeferredOverrides.size() > 0) { + sb.append("; deferredOverrides=").append(mDeferredOverrides); + } + if (getOverridable()) { + sb.append("; overridable"); + } return sb.append(")").toString(); } diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index 8511118cc840..9376e8dc16ea 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -65,7 +65,7 @@ final class CompatConfig { @GuardedBy("mChanges") private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>(); - private IOverrideValidator mOverrideValidator; + private OverrideValidatorImpl mOverrideValidator; @VisibleForTesting CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context) { @@ -146,6 +146,25 @@ final class CompatConfig { } /** + * Find if a given change will be enabled for a given package name, prior to installation. + * + * @param changeId The ID of the change in question + * @param packageName Package name to check for + * @return {@code true} if the change would be enabled for this package name. Also returns + * {@code true} if the change ID is not known, as unknown changes are enabled by default. + */ + boolean willChangeBeEnabled(long changeId, String packageName) { + synchronized (mChanges) { + CompatChange c = mChanges.get(changeId); + if (c == null) { + // we know nothing about this change: default behaviour is enabled. + return true; + } + return c.willBeEnabled(packageName); + } + } + + /** * Overrides the enabled state for a given change and app. This method is intended to be used * *only* for debugging purposes, ultimately invoked either by an adb command, or from some * developer settings UI. @@ -161,7 +180,7 @@ final class CompatConfig { * @return {@code true} if the change existed before adding the override. */ boolean addOverride(long changeId, String packageName, boolean enabled) - throws RemoteException, SecurityException { + throws SecurityException { boolean alreadyKnown = true; OverrideAllowedState allowedState = mOverrideValidator.getOverrideAllowedState(changeId, packageName); @@ -173,7 +192,17 @@ final class CompatConfig { c = new CompatChange(changeId); addChange(c); } - c.addPackageOverride(packageName, enabled); + switch (allowedState.state) { + case OverrideAllowedState.ALLOWED: + c.addPackageOverride(packageName, enabled); + break; + case OverrideAllowedState.DEFERRED_VERIFICATION: + c.addPackageDeferredOverride(packageName, enabled); + break; + default: + throw new IllegalStateException("Should only be able to override changes that " + + "are allowed or can be deferred."); + } invalidateCache(); } return alreadyKnown; @@ -244,26 +273,26 @@ final class CompatConfig { * @return {@code true} if an override existed; */ boolean removeOverride(long changeId, String packageName) - throws RemoteException, SecurityException { + throws SecurityException { boolean overrideExists = false; synchronized (mChanges) { CompatChange c = mChanges.get(changeId); - try { - if (c != null) { - overrideExists = c.hasOverride(packageName); - if (overrideExists) { - OverrideAllowedState allowedState = - mOverrideValidator.getOverrideAllowedState(changeId, packageName); - allowedState.enforce(changeId, packageName); - c.removePackageOverride(packageName); - } + if (c != null) { + // Always allow removing a deferred override. + if (c.hasDeferredOverride(packageName)) { + c.removePackageOverride(packageName); + overrideExists = true; + } else if (c.hasOverride(packageName)) { + // Regular overrides need to pass the policy. + overrideExists = true; + OverrideAllowedState allowedState = + mOverrideValidator.getOverrideAllowedState(changeId, packageName); + allowedState.enforce(changeId, packageName); + c.removePackageOverride(packageName); } - } catch (RemoteException e) { - // Should never occur, since validator is in the same process. - throw new RuntimeException("Unable to call override validator!", e); } - invalidateCache(); } + invalidateCache(); return overrideExists; } @@ -293,29 +322,15 @@ final class CompatConfig { * Removes all overrides previously added via {@link #addOverride(long, String, boolean)} or * {@link #addOverrides(CompatibilityChangeConfig, String)} for a certain package. * - * <p>This restores the default behaviour for the given change and app, once any app - * processes have been restarted. + * <p>This restores the default behaviour for the given app. * * @param packageName The package for which the overrides should be purged. */ - void removePackageOverrides(String packageName) throws RemoteException, SecurityException { + void removePackageOverrides(String packageName) throws SecurityException { synchronized (mChanges) { for (int i = 0; i < mChanges.size(); ++i) { - try { - CompatChange change = mChanges.valueAt(i); - if (change.hasOverride(packageName)) { - OverrideAllowedState allowedState = - mOverrideValidator.getOverrideAllowedState(change.getId(), - packageName); - allowedState.enforce(change.getId(), packageName); - if (change != null) { - mChanges.valueAt(i).removePackageOverride(packageName); - } - } - } catch (RemoteException e) { - // Should never occur, since validator is in the same process. - throw new RuntimeException("Unable to call override validator!", e); - } + CompatChange change = mChanges.valueAt(i); + removeOverride(change.getId(), packageName); } invalidateCache(); } @@ -327,20 +342,15 @@ final class CompatConfig { LongArray allowed = new LongArray(); synchronized (mChanges) { for (int i = 0; i < mChanges.size(); ++i) { - try { - CompatChange change = mChanges.valueAt(i); - if (change.getEnableSinceTargetSdk() != targetSdkVersion) { - continue; - } - OverrideAllowedState allowedState = - mOverrideValidator.getOverrideAllowedState(change.getId(), - packageName); - if (allowedState.state == OverrideAllowedState.ALLOWED) { - allowed.add(change.getId()); - } - } catch (RemoteException e) { - // Should never occur, since validator is in the same process. - throw new RuntimeException("Unable to call override validator!", e); + CompatChange change = mChanges.valueAt(i); + if (change.getEnableSinceTargetSdk() != targetSdkVersion) { + continue; + } + OverrideAllowedState allowedState = + mOverrideValidator.getOverrideAllowedState(change.getId(), + packageName); + if (allowedState.state == OverrideAllowedState.ALLOWED) { + allowed.add(change.getId()); } } } @@ -401,6 +411,11 @@ final class CompatConfig { } @VisibleForTesting + void forceNonDebuggableFinalForTest(boolean value) { + mOverrideValidator.forceNonDebuggableFinalForTest(value); + } + + @VisibleForTesting void clearChanges() { synchronized (mChanges) { mChanges.clear(); @@ -511,4 +526,26 @@ final class CompatConfig { private void invalidateCache() { ChangeIdStateCache.invalidate(); } + /** + * Rechecks all the existing overrides for a package. + */ + void recheckOverrides(String packageName) { + synchronized (mChanges) { + boolean shouldInvalidateCache = false; + for (int idx = 0; idx < mChanges.size(); ++idx) { + CompatChange c = mChanges.valueAt(idx); + OverrideAllowedState allowedState = + mOverrideValidator.getOverrideAllowedState(c.getId(), packageName); + boolean allowedOverride = (allowedState.state == OverrideAllowedState.ALLOWED); + shouldInvalidateCache |= c.recheckOverride(packageName, allowedOverride); + } + if (shouldInvalidateCache) { + invalidateCache(); + } + } + } + + void registerContentObserver() { + mOverrideValidator.registerContentObserver(); + } } diff --git a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java index 79a13ca242c1..fe5b4a98797d 100644 --- a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java +++ b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java @@ -17,16 +17,19 @@ package com.android.server.compat; import static com.android.internal.compat.OverrideAllowedState.ALLOWED; +import static com.android.internal.compat.OverrideAllowedState.DEFERRED_VERIFICATION; import static com.android.internal.compat.OverrideAllowedState.DISABLED_NON_TARGET_SDK; import static com.android.internal.compat.OverrideAllowedState.DISABLED_NOT_DEBUGGABLE; import static com.android.internal.compat.OverrideAllowedState.DISABLED_TARGET_SDK_TOO_HIGH; import static com.android.internal.compat.OverrideAllowedState.LOGGING_ONLY_CHANGE; -import static com.android.internal.compat.OverrideAllowedState.PACKAGE_DOES_NOT_EXIST; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.database.ContentObserver; +import android.os.Handler; +import android.provider.Settings; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.compat.AndroidBuildClassifier; @@ -41,6 +44,20 @@ public class OverrideValidatorImpl extends IOverrideValidator.Stub { private AndroidBuildClassifier mAndroidBuildClassifier; private Context mContext; private CompatConfig mCompatConfig; + private boolean mForceNonDebuggableFinalBuild; + + private class SettingsObserver extends ContentObserver { + SettingsObserver() { + super(new Handler()); + } + @Override + public void onChange(boolean selfChange) { + mForceNonDebuggableFinalBuild = Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT, + 0) == 1; + } + } @VisibleForTesting OverrideValidatorImpl(AndroidBuildClassifier androidBuildClassifier, @@ -48,6 +65,7 @@ public class OverrideValidatorImpl extends IOverrideValidator.Stub { mAndroidBuildClassifier = androidBuildClassifier; mContext = context; mCompatConfig = config; + mForceNonDebuggableFinalBuild = false; } @Override @@ -56,8 +74,10 @@ public class OverrideValidatorImpl extends IOverrideValidator.Stub { return new OverrideAllowedState(LOGGING_ONLY_CHANGE, -1, -1); } - boolean debuggableBuild = mAndroidBuildClassifier.isDebuggableBuild(); - boolean finalBuild = mAndroidBuildClassifier.isFinalBuild(); + boolean debuggableBuild = mAndroidBuildClassifier.isDebuggableBuild() + && !mForceNonDebuggableFinalBuild; + boolean finalBuild = mAndroidBuildClassifier.isFinalBuild() + || mForceNonDebuggableFinalBuild; int maxTargetSdk = mCompatConfig.maxTargetSdkForChangeIdOptIn(changeId); boolean disabled = mCompatConfig.isDisabled(changeId); @@ -73,7 +93,7 @@ public class OverrideValidatorImpl extends IOverrideValidator.Stub { try { applicationInfo = packageManager.getApplicationInfo(packageName, 0); } catch (NameNotFoundException e) { - return new OverrideAllowedState(PACKAGE_DOES_NOT_EXIST, -1, -1); + return new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1); } int appTargetSdk = applicationInfo.targetSdkVersion; // Only allow overriding debuggable apps. @@ -94,4 +114,17 @@ public class OverrideValidatorImpl extends IOverrideValidator.Stub { } return new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, appTargetSdk, maxTargetSdk); } + + void registerContentObserver() { + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor( + Settings.Global.FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT), + false, + new SettingsObserver()); + } + + void forceNonDebuggableFinalForTest(boolean value) { + mForceNonDebuggableFinalBuild = value; + } + } diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index aa85f7f0f55f..1ea468c341d2 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -25,9 +25,13 @@ import static android.os.Process.SYSTEM_UID; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.IActivityManager; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManagerInternal; +import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.RemoteException; @@ -74,6 +78,7 @@ public class PlatformCompat extends IPlatformCompat.Stub { mChangeReporter = new ChangeReporter( ChangeReporter.SOURCE_SYSTEM_SERVER); mCompatConfig = compatConfig; + registerPackageReceiver(context); } @Override @@ -132,6 +137,9 @@ public class PlatformCompat extends IPlatformCompat.Stub { @UserIdInt int userId) { checkCompatChangeReadAndLogPermission(); ApplicationInfo appInfo = getApplicationInfo(packageName, userId); + if (appInfo == null) { + return mCompatConfig.willChangeBeEnabled(changeId, packageName); + } return isChangeEnabled(changeId, appInfo); } @@ -389,4 +397,42 @@ public class PlatformCompat extends IPlatformCompat.Stub { } return true; } + + /** + * Registers a broadcast receiver that listens for package install, replace or remove. + * @param context the context where the receiver should be registered. + */ + public void registerPackageReceiver(Context context) { + final BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent == null) { + return; + } + final Uri packageData = intent.getData(); + if (packageData == null) { + return; + } + final String packageName = packageData.getSchemeSpecificPart(); + if (packageName == null) { + return; + } + mCompatConfig.recheckOverrides(packageName); + } + }; + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REPLACED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addDataScheme("package"); + context.registerReceiver(receiver, filter); + } + + /** + * Register the observer for + * {@link android.provider.Settings.Global#FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT} + */ + public void registerContentObserver() { + mCompatConfig.registerContentObserver(); + } } diff --git a/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java index 995bb2422de2..8cd1fd6f2b64 100644 --- a/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java +++ b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java @@ -17,6 +17,8 @@ package com.android.server.connectivity; import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; import android.net.metrics.DefaultNetworkEvent; import android.os.SystemClock; @@ -61,7 +63,7 @@ public class DefaultNetworkMetrics { private int mLastTransports; public DefaultNetworkMetrics() { - newDefaultNetwork(creationTimeMs, null); + newDefaultNetwork(creationTimeMs, null, 0, false, null, null); } public synchronized void listEvents(PrintWriter pw) { @@ -117,13 +119,21 @@ public class DefaultNetworkMetrics { mCurrentDefaultNetwork.validatedMs += timeMs - mLastValidationTimeMs; } - public synchronized void logDefaultNetworkEvent( - long timeMs, NetworkAgentInfo newNai, NetworkAgentInfo oldNai) { - logCurrentDefaultNetwork(timeMs, oldNai); - newDefaultNetwork(timeMs, newNai); + /** + * Logs a default network event. + * @see {IpConnectivityLog#logDefaultNetworkEvent}. + */ + public synchronized void logDefaultNetworkEvent(long timeMs, Network defaultNetwork, int score, + boolean validated, LinkProperties lp, NetworkCapabilities nc, + Network previousDefaultNetwork, int previousScore, LinkProperties previousLp, + NetworkCapabilities previousNc) { + logCurrentDefaultNetwork(timeMs, previousDefaultNetwork, previousScore, previousLp, + previousNc); + newDefaultNetwork(timeMs, defaultNetwork, score, validated, lp, nc); } - private void logCurrentDefaultNetwork(long timeMs, NetworkAgentInfo oldNai) { + private void logCurrentDefaultNetwork(long timeMs, Network network, int score, + LinkProperties lp, NetworkCapabilities nc) { if (mIsCurrentlyValid) { updateValidationTime(timeMs); } @@ -131,10 +141,10 @@ public class DefaultNetworkMetrics { ev.updateDuration(timeMs); ev.previousTransports = mLastTransports; // oldNai is null if the system had no default network before the transition. - if (oldNai != null) { + if (network != null) { // The system acquired a new default network. - fillLinkInfo(ev, oldNai); - ev.finalScore = oldNai.getCurrentScore(); + fillLinkInfo(ev, network, lp, nc); + ev.finalScore = score; } // Only change transport of the previous default network if the event currently logged // corresponds to an existing default network, and not to the absence of a default network. @@ -147,14 +157,15 @@ public class DefaultNetworkMetrics { mEventsLog.append(ev); } - private void newDefaultNetwork(long timeMs, NetworkAgentInfo newNai) { + private void newDefaultNetwork(long timeMs, Network network, int score, boolean validated, + LinkProperties lp, NetworkCapabilities nc) { DefaultNetworkEvent ev = new DefaultNetworkEvent(timeMs); ev.durationMs = timeMs; // newNai is null if the system has no default network after the transition. - if (newNai != null) { - fillLinkInfo(ev, newNai); - ev.initialScore = newNai.getCurrentScore(); - if (newNai.lastValidated) { + if (network != null) { + fillLinkInfo(ev, network, lp, nc); + ev.initialScore = score; + if (validated) { mIsCurrentlyValid = true; mLastValidationTimeMs = timeMs; } @@ -164,10 +175,10 @@ public class DefaultNetworkMetrics { mCurrentDefaultNetwork = ev; } - private static void fillLinkInfo(DefaultNetworkEvent ev, NetworkAgentInfo nai) { - LinkProperties lp = nai.linkProperties; - ev.netId = nai.network().getNetId(); - ev.transports |= BitUtils.packBits(nai.networkCapabilities.getTransportTypes()); + private static void fillLinkInfo(DefaultNetworkEvent ev, Network network, LinkProperties lp, + NetworkCapabilities nc) { + ev.netId = network.getNetId(); + ev.transports |= BitUtils.packBits(nc.getTransportTypes()); ev.ipv4 |= lp.hasIpv4Address() && lp.hasIpv4DefaultRoute(); ev.ipv6 |= lp.hasGlobalIpv6Address() && lp.hasIpv6DefaultRoute(); } diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java index 2c06d8230f13..1024556c17eb 100644 --- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java +++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java @@ -20,11 +20,15 @@ import android.content.Context; import android.net.ConnectivityMetricsEvent; import android.net.IIpConnectivityMetrics; import android.net.INetdEventCallback; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; import android.net.NetworkStack; import android.net.metrics.ApfProgramEvent; import android.net.metrics.IpConnectivityLog; import android.os.Binder; import android.os.Process; +import android.os.SystemClock; import android.provider.Settings; import android.text.TextUtils; import android.text.format.DateUtils; @@ -361,6 +365,21 @@ final public class IpConnectivityMetrics extends SystemService { } return mNetdListener.removeNetdEventCallback(callerType); } + + @Override + public void logDefaultNetworkValidity(boolean valid) { + mDefaultNetworkMetrics.logDefaultNetworkValidity(SystemClock.elapsedRealtime(), valid); + } + + @Override + public void logDefaultNetworkEvent(Network defaultNetwork, int score, boolean validated, + LinkProperties lp, NetworkCapabilities nc, Network previousDefaultNetwork, + int previousScore, LinkProperties previousLp, NetworkCapabilities previousNc) { + final long timeMs = SystemClock.elapsedRealtime(); + mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs, defaultNetwork, score, validated, + lp, nc, previousDefaultNetwork, previousScore, previousLp, previousNc); + } + }; private static final ToIntFunction<Context> READ_BUFFER_SIZE = (ctx) -> { diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java index 96cbfde2a0a0..34d9ccc15dba 100644 --- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java +++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java @@ -18,10 +18,7 @@ package com.android.server.connectivity; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.NattSocketKeepalive.NATT_PORT; -import static android.net.NetworkAgent.CMD_ADD_KEEPALIVE_PACKET_FILTER; -import static android.net.NetworkAgent.CMD_REMOVE_KEEPALIVE_PACKET_FILTER; import static android.net.NetworkAgent.CMD_START_SOCKET_KEEPALIVE; -import static android.net.NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE; import static android.net.SocketKeepalive.BINDER_DIED; import static android.net.SocketKeepalive.DATA_RECEIVED; import static android.net.SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES; @@ -330,10 +327,9 @@ public class KeepaliveTracker { Log.d(TAG, "Starting keepalive " + mSlot + " on " + mNai.toShortString()); switch (mType) { case TYPE_NATT: - mNai.asyncChannel.sendMessage( - CMD_ADD_KEEPALIVE_PACKET_FILTER, slot, 0 /* Unused */, mPacket); - mNai.asyncChannel - .sendMessage(CMD_START_SOCKET_KEEPALIVE, slot, mInterval, mPacket); + final NattKeepalivePacketData nattData = (NattKeepalivePacketData) mPacket; + mNai.onAddNattKeepalivePacketFilter(slot, nattData); + mNai.onStartNattSocketKeepalive(slot, mInterval, nattData); break; case TYPE_TCP: try { @@ -342,11 +338,10 @@ public class KeepaliveTracker { handleStopKeepalive(mNai, mSlot, ERROR_INVALID_SOCKET); return; } - mNai.asyncChannel.sendMessage( - CMD_ADD_KEEPALIVE_PACKET_FILTER, slot, 0 /* Unused */, mPacket); + final TcpKeepalivePacketData tcpData = (TcpKeepalivePacketData) mPacket; + mNai.onAddTcpKeepalivePacketFilter(slot, tcpData); // TODO: check result from apf and notify of failure as needed. - mNai.asyncChannel - .sendMessage(CMD_START_SOCKET_KEEPALIVE, slot, mInterval, mPacket); + mNai.onStartTcpSocketKeepalive(slot, mInterval, tcpData); break; default: Log.wtf(TAG, "Starting keepalive with unknown type: " + mType); @@ -394,9 +389,8 @@ public class KeepaliveTracker { mTcpController.stopSocketMonitor(mSlot); // fall through case TYPE_NATT: - mNai.asyncChannel.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, mSlot); - mNai.asyncChannel.sendMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, - mSlot); + mNai.onStopSocketKeepalive(mSlot); + mNai.onRemoveKeepalivePacketFilter(mSlot); break; default: Log.wtf(TAG, "Stopping keepalive with unknown type: " + mType); @@ -548,17 +542,13 @@ public class KeepaliveTracker { } /** Handle keepalive events from lower layer. */ - public void handleEventSocketKeepalive(@NonNull NetworkAgentInfo nai, - @NonNull Message message) { - int slot = message.arg1; - int reason = message.arg2; - + public void handleEventSocketKeepalive(@NonNull NetworkAgentInfo nai, int slot, int reason) { KeepaliveInfo ki = null; try { ki = mKeepalives.get(nai).get(slot); } catch(NullPointerException e) {} if (ki == null) { - Log.e(TAG, "Event " + message.what + "," + slot + "," + reason + Log.e(TAG, "Event " + NetworkAgent.EVENT_SOCKET_KEEPALIVE + "," + slot + "," + reason + " for unknown keepalive " + slot + " on " + nai.toShortString()); return; } @@ -601,7 +591,7 @@ public class KeepaliveTracker { ki.mStartedState = KeepaliveInfo.NOT_STARTED; cleanupStoppedKeepalive(nai, slot); } else { - Log.wtf(TAG, "Event " + message.what + "," + slot + "," + reason + Log.wtf(TAG, "Event " + NetworkAgent.EVENT_SOCKET_KEEPALIVE + "," + slot + "," + reason + " for keepalive in wrong state: " + ki.toString()); } } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 52b9f5c15c50..b0a73f105725 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -27,25 +27,35 @@ import android.net.IDnsResolver; import android.net.INetd; import android.net.INetworkMonitor; import android.net.LinkProperties; +import android.net.NattKeepalivePacketData; import android.net.Network; +import android.net.NetworkAgent; import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkMonitorManager; import android.net.NetworkRequest; import android.net.NetworkState; +import android.net.TcpKeepalivePacketData; +import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.INetworkManagementService; -import android.os.Messenger; +import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; +import android.util.Pair; import android.util.SparseArray; -import com.android.internal.util.AsyncChannel; +import com.android.connectivity.aidl.INetworkAgent; +import com.android.connectivity.aidl.INetworkAgentRegistry; import com.android.internal.util.WakeupMessage; import com.android.server.ConnectivityService; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.SortedSet; import java.util.TreeSet; @@ -136,7 +146,11 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // Underlying networks declared by the agent. Only set if supportsUnderlyingNetworks is true. // The networks in this list might be declared by a VPN app using setUnderlyingNetworks and are // not guaranteed to be current or correct, or even to exist. - public @Nullable Network[] declaredUnderlyingNetworks; + // + // This array is read and iterated on multiple threads with no locking so its contents must + // never be modified. When the list of networks changes, replace with a new array, on the + // handler thread. + public @Nullable volatile Network[] declaredUnderlyingNetworks; // The capabilities originally announced by the NetworkAgent, regardless of any capabilities // that were added or removed due to this network's underlying networks. @@ -175,41 +189,46 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // Set to true when partial connectivity was detected. public boolean partialConnectivity; - // Captive portal info of the network, if any. + // Captive portal info of the network from RFC8908, if any. // Obtained by ConnectivityService and merged into NetworkAgent-provided information. - public CaptivePortalData captivePortalData; + public CaptivePortalData capportApiData; // The UID of the remote entity that created this Network. public final int creatorUid; + // Network agent portal info of the network, if any. This information is provided from + // non-RFC8908 sources, such as Wi-Fi Passpoint, which can provide information such as Venue + // URL, Terms & Conditions URL, and network friendly name. + public CaptivePortalData networkAgentPortalData; + // Networks are lingered when they become unneeded as a result of their NetworkRequests being // satisfied by a higher-scoring network. so as to allow communication to wrap up before the // network is taken down. This usually only happens to the default network. Lingering ends with // either the linger timeout expiring and the network being taken down, or the network // satisfying a request again. public static class LingerTimer implements Comparable<LingerTimer> { - public final NetworkRequest request; + public final int requestId; public final long expiryMs; - public LingerTimer(NetworkRequest request, long expiryMs) { - this.request = request; + public LingerTimer(int requestId, long expiryMs) { + this.requestId = requestId; this.expiryMs = expiryMs; } public boolean equals(Object o) { if (!(o instanceof LingerTimer)) return false; LingerTimer other = (LingerTimer) o; - return (request.requestId == other.request.requestId) && (expiryMs == other.expiryMs); + return (requestId == other.requestId) && (expiryMs == other.expiryMs); } public int hashCode() { - return Objects.hash(request.requestId, expiryMs); + return Objects.hash(requestId, expiryMs); } public int compareTo(LingerTimer other) { return (expiryMs != other.expiryMs) ? Long.compare(expiryMs, other.expiryMs) : - Integer.compare(request.requestId, other.request.requestId); + Integer.compare(requestId, other.requestId); } public String toString() { - return String.format("%s, expires %dms", request.toString(), + return String.format("%s, expires %dms", requestId, expiryMs - SystemClock.elapsedRealtime()); } } @@ -221,6 +240,31 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { */ public static final int EVENT_NETWORK_LINGER_COMPLETE = 1001; + /** + * Inform ConnectivityService that the agent is half-connected. + * arg1 = ARG_AGENT_SUCCESS or ARG_AGENT_FAILURE + * obj = NetworkAgentInfo + * @hide + */ + public static final int EVENT_AGENT_REGISTERED = 1002; + + /** + * Inform ConnectivityService that the agent was disconnected. + * obj = NetworkAgentInfo + * @hide + */ + public static final int EVENT_AGENT_DISCONNECTED = 1003; + + /** + * Argument for EVENT_AGENT_HALF_CONNECTED indicating failure. + */ + public static final int ARG_AGENT_FAILURE = 0; + + /** + * Argument for EVENT_AGENT_HALF_CONNECTED indicating success. + */ + public static final int ARG_AGENT_SUCCESS = 1; + // All linger timers for this network, sorted by expiry time. A linger timer is added whenever // a request is moved to a network with a better score, regardless of whether the network is or // was lingering or not. @@ -262,8 +306,9 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // report is generated. Once non-null, it will never be null again. @Nullable private ConnectivityReport mConnectivityReport; - public final Messenger messenger; - public final AsyncChannel asyncChannel; + public final INetworkAgent networkAgent; + // Only accessed from ConnectivityService handler thread + private final AgentDeathMonitor mDeathMonitor = new AgentDeathMonitor(); public final int factorySerialNumber; @@ -279,13 +324,12 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { private final Context mContext; private final Handler mHandler; - public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info, + public NetworkAgentInfo(INetworkAgent na, Network net, NetworkInfo info, LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd, IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber, int creatorUid) { - this.messenger = messenger; - asyncChannel = ac; + networkAgent = na; network = net; networkInfo = info; linkProperties = lp; @@ -300,6 +344,249 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { this.creatorUid = creatorUid; } + private class AgentDeathMonitor implements IBinder.DeathRecipient { + @Override + public void binderDied() { + notifyDisconnected(); + } + } + + /** + * Notify the NetworkAgent that it was registered, and should be unregistered if it dies. + * + * Must be called from the ConnectivityService handler thread. A NetworkAgent can only be + * registered once. + */ + public void notifyRegistered() { + try { + networkAgent.asBinder().linkToDeath(mDeathMonitor, 0); + networkAgent.onRegistered(new NetworkAgentMessageHandler(mHandler)); + } catch (RemoteException e) { + Log.e(TAG, "Error registering NetworkAgent", e); + maybeUnlinkDeathMonitor(); + mHandler.obtainMessage(EVENT_AGENT_REGISTERED, ARG_AGENT_FAILURE, 0, this) + .sendToTarget(); + return; + } + + mHandler.obtainMessage(EVENT_AGENT_REGISTERED, ARG_AGENT_SUCCESS, 0, this).sendToTarget(); + } + + /** + * Disconnect the NetworkAgent. Must be called from the ConnectivityService handler thread. + */ + public void disconnect() { + try { + networkAgent.onDisconnected(); + } catch (RemoteException e) { + Log.i(TAG, "Error disconnecting NetworkAgent", e); + // Fall through: it's fine if the remote has died + } + + notifyDisconnected(); + maybeUnlinkDeathMonitor(); + } + + private void maybeUnlinkDeathMonitor() { + try { + networkAgent.asBinder().unlinkToDeath(mDeathMonitor, 0); + } catch (NoSuchElementException e) { + // Was not linked: ignore + } + } + + private void notifyDisconnected() { + // Note this may be called multiple times if ConnectivityService disconnects while the + // NetworkAgent also dies. ConnectivityService ignores disconnects of already disconnected + // agents. + mHandler.obtainMessage(EVENT_AGENT_DISCONNECTED, this).sendToTarget(); + } + + /** + * Notify the NetworkAgent that bandwidth update was requested. + */ + public void onBandwidthUpdateRequested() { + try { + networkAgent.onBandwidthUpdateRequested(); + } catch (RemoteException e) { + Log.e(TAG, "Error sending bandwidth update request event", e); + } + } + + /** + * Notify the NetworkAgent that validation status has changed. + */ + public void onValidationStatusChanged(int validationStatus, @Nullable String captivePortalUrl) { + try { + networkAgent.onValidationStatusChanged(validationStatus, captivePortalUrl); + } catch (RemoteException e) { + Log.e(TAG, "Error sending validation status change event", e); + } + } + + /** + * Notify the NetworkAgent that the acceptUnvalidated setting should be saved. + */ + public void onSaveAcceptUnvalidated(boolean acceptUnvalidated) { + try { + networkAgent.onSaveAcceptUnvalidated(acceptUnvalidated); + } catch (RemoteException e) { + Log.e(TAG, "Error sending accept unvalidated event", e); + } + } + + /** + * Notify the NetworkAgent that NATT socket keepalive should be started. + */ + public void onStartNattSocketKeepalive(int slot, int intervalDurationMs, + @NonNull NattKeepalivePacketData packetData) { + try { + networkAgent.onStartNattSocketKeepalive(slot, intervalDurationMs, packetData); + } catch (RemoteException e) { + Log.e(TAG, "Error sending NATT socket keepalive start event", e); + } + } + + /** + * Notify the NetworkAgent that TCP socket keepalive should be started. + */ + public void onStartTcpSocketKeepalive(int slot, int intervalDurationMs, + @NonNull TcpKeepalivePacketData packetData) { + try { + networkAgent.onStartTcpSocketKeepalive(slot, intervalDurationMs, packetData); + } catch (RemoteException e) { + Log.e(TAG, "Error sending TCP socket keepalive start event", e); + } + } + + /** + * Notify the NetworkAgent that socket keepalive should be stopped. + */ + public void onStopSocketKeepalive(int slot) { + try { + networkAgent.onStopSocketKeepalive(slot); + } catch (RemoteException e) { + Log.e(TAG, "Error sending TCP socket keepalive stop event", e); + } + } + + /** + * Notify the NetworkAgent that signal strength thresholds should be updated. + */ + public void onSignalStrengthThresholdsUpdated(@NonNull int[] thresholds) { + try { + networkAgent.onSignalStrengthThresholdsUpdated(thresholds); + } catch (RemoteException e) { + Log.e(TAG, "Error sending signal strength thresholds event", e); + } + } + + /** + * Notify the NetworkAgent that automatic reconnect should be prevented. + */ + public void onPreventAutomaticReconnect() { + try { + networkAgent.onPreventAutomaticReconnect(); + } catch (RemoteException e) { + Log.e(TAG, "Error sending prevent automatic reconnect event", e); + } + } + + /** + * Notify the NetworkAgent that a NATT keepalive packet filter should be added. + */ + public void onAddNattKeepalivePacketFilter(int slot, + @NonNull NattKeepalivePacketData packetData) { + try { + networkAgent.onAddNattKeepalivePacketFilter(slot, packetData); + } catch (RemoteException e) { + Log.e(TAG, "Error sending add NATT keepalive packet filter event", e); + } + } + + /** + * Notify the NetworkAgent that a TCP keepalive packet filter should be added. + */ + public void onAddTcpKeepalivePacketFilter(int slot, + @NonNull TcpKeepalivePacketData packetData) { + try { + networkAgent.onAddTcpKeepalivePacketFilter(slot, packetData); + } catch (RemoteException e) { + Log.e(TAG, "Error sending add TCP keepalive packet filter event", e); + } + } + + /** + * Notify the NetworkAgent that a keepalive packet filter should be removed. + */ + public void onRemoveKeepalivePacketFilter(int slot) { + try { + networkAgent.onRemoveKeepalivePacketFilter(slot); + } catch (RemoteException e) { + Log.e(TAG, "Error sending remove keepalive packet filter event", e); + } + } + + // TODO: consider moving out of NetworkAgentInfo into its own class + private class NetworkAgentMessageHandler extends INetworkAgentRegistry.Stub { + private final Handler mHandler; + + private NetworkAgentMessageHandler(Handler handler) { + mHandler = handler; + } + + @Override + public void sendNetworkCapabilities(NetworkCapabilities nc) { + mHandler.obtainMessage(NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED, + new Pair<>(NetworkAgentInfo.this, nc)).sendToTarget(); + } + + @Override + public void sendLinkProperties(LinkProperties lp) { + mHandler.obtainMessage(NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED, + new Pair<>(NetworkAgentInfo.this, lp)).sendToTarget(); + } + + @Override + public void sendNetworkInfo(NetworkInfo info) { + mHandler.obtainMessage(NetworkAgent.EVENT_NETWORK_INFO_CHANGED, + new Pair<>(NetworkAgentInfo.this, info)).sendToTarget(); + } + + @Override + public void sendScore(int score) { + mHandler.obtainMessage(NetworkAgent.EVENT_NETWORK_SCORE_CHANGED, score, 0, + new Pair<>(NetworkAgentInfo.this, null)).sendToTarget(); + } + + @Override + public void sendExplicitlySelected(boolean explicitlySelected, boolean acceptPartial) { + mHandler.obtainMessage(NetworkAgent.EVENT_SET_EXPLICITLY_SELECTED, + explicitlySelected ? 1 : 0, acceptPartial ? 1 : 0, + new Pair<>(NetworkAgentInfo.this, null)).sendToTarget(); + } + + @Override + public void sendSocketKeepaliveEvent(int slot, int reason) { + mHandler.obtainMessage(NetworkAgent.EVENT_SOCKET_KEEPALIVE, + slot, reason, new Pair<>(NetworkAgentInfo.this, null)).sendToTarget(); + } + + @Override + public void sendUnderlyingNetworks(@Nullable List<Network> networks) { + final Bundle args = new Bundle(); + if (networks instanceof ArrayList<?>) { + args.putParcelableArrayList(NetworkAgent.UNDERLYING_NETWORKS_KEY, + (ArrayList<Network>) networks); + } else { + args.putParcelableArrayList(NetworkAgent.UNDERLYING_NETWORKS_KEY, + networks == null ? null : new ArrayList<>(networks)); + } + mHandler.obtainMessage(NetworkAgent.EVENT_UNDERLYING_NETWORKS_CHANGED, + new Pair<>(NetworkAgentInfo.this, args)).sendToTarget(); + } + } + /** * Inform NetworkAgentInfo that a new NetworkMonitor was created. */ @@ -411,7 +698,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { updateRequestCounts(REMOVE, existing); mNetworkRequests.remove(requestId); if (existing.isRequest()) { - unlingerRequest(existing); + unlingerRequest(existing.requestId); } } @@ -557,33 +844,33 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { } /** - * Sets the specified request to linger on this network for the specified time. Called by + * Sets the specified requestId to linger on this network for the specified time. Called by * ConnectivityService when the request is moved to another network with a higher score. */ - public void lingerRequest(NetworkRequest request, long now, long duration) { - if (mLingerTimerForRequest.get(request.requestId) != null) { + public void lingerRequest(int requestId, long now, long duration) { + if (mLingerTimerForRequest.get(requestId) != null) { // Cannot happen. Once a request is lingering on a particular network, we cannot // re-linger it unless that network becomes the best for that request again, in which // case we should have unlingered it. - Log.wtf(TAG, toShortString() + ": request " + request.requestId + " already lingered"); + Log.wtf(TAG, toShortString() + ": request " + requestId + " already lingered"); } final long expiryMs = now + duration; - LingerTimer timer = new LingerTimer(request, expiryMs); + LingerTimer timer = new LingerTimer(requestId, expiryMs); if (VDBG) Log.d(TAG, "Adding LingerTimer " + timer + " to " + toShortString()); mLingerTimers.add(timer); - mLingerTimerForRequest.put(request.requestId, timer); + mLingerTimerForRequest.put(requestId, timer); } /** * Cancel lingering. Called by ConnectivityService when a request is added to this network. - * Returns true if the given request was lingering on this network, false otherwise. + * Returns true if the given requestId was lingering on this network, false otherwise. */ - public boolean unlingerRequest(NetworkRequest request) { - LingerTimer timer = mLingerTimerForRequest.get(request.requestId); + public boolean unlingerRequest(int requestId) { + LingerTimer timer = mLingerTimerForRequest.get(requestId); if (timer != null) { if (VDBG) Log.d(TAG, "Removing LingerTimer " + timer + " from " + toShortString()); mLingerTimers.remove(timer); - mLingerTimerForRequest.remove(request.requestId); + mLingerTimerForRequest.remove(requestId); return true; } return false; diff --git a/services/core/java/com/android/server/connectivity/PacManager.java b/services/core/java/com/android/server/connectivity/PacManager.java index 06721aea5540..93930ae16de8 100644 --- a/services/core/java/com/android/server/connectivity/PacManager.java +++ b/services/core/java/com/android/server/connectivity/PacManager.java @@ -177,7 +177,7 @@ public class PacManager { * @param proxy Proxy information that is about to be broadcast. * @return Returns whether the broadcast should be sent : either DO_ or DONT_SEND_BROADCAST */ - synchronized boolean setCurrentProxyScriptUrl(ProxyInfo proxy) { + public synchronized boolean setCurrentProxyScriptUrl(ProxyInfo proxy) { if (!Uri.EMPTY.equals(proxy.getPacFileUrl())) { if (proxy.getPacFileUrl().equals(mPacUrl) && (proxy.getPort() > 0)) { // Allow to send broadcast, nothing to do. diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 228ad588525f..b250f164a264 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -101,6 +101,7 @@ import android.security.KeyStore; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; +import android.util.Range; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; @@ -223,7 +224,7 @@ public class Vpn { private @NonNull List<String> mLockdownAllowlist = Collections.emptyList(); /** - * A memory of what UIDs this class told netd to block for the lockdown feature. + * A memory of what UIDs this class told ConnectivityService to block for the lockdown feature. * * Netd maintains ranges of UIDs for which network should be restricted to using only the VPN * for the lockdown feature. This class manages these UIDs and sends this information to netd. @@ -237,7 +238,7 @@ public class Vpn { * @see mLockdown */ @GuardedBy("this") - private final Set<UidRangeParcel> mBlockedUidsAsToldToNetd = new ArraySet<>(); + private final Set<UidRangeParcel> mBlockedUidsAsToldToConnectivity = new ArraySet<>(); // The user id of initiating VPN. private final int mUserId; @@ -1588,7 +1589,7 @@ public class Vpn { * {@link Vpn} goes through a VPN connection or is blocked until one is * available, {@code false} to lift the requirement. * - * @see #mBlockedUidsAsToldToNetd + * @see #mBlockedUidsAsToldToConnectivity */ @GuardedBy("this") private void setVpnForcedLocked(boolean enforce) { @@ -1599,10 +1600,8 @@ public class Vpn { exemptedPackages = new ArrayList<>(mLockdownAllowlist); exemptedPackages.add(mPackage); } - final Set<UidRangeParcel> rangesToTellNetdToRemove = - new ArraySet<>(mBlockedUidsAsToldToNetd); - - final Set<UidRangeParcel> rangesToTellNetdToAdd; + final Set<UidRangeParcel> rangesToRemove = new ArraySet<>(mBlockedUidsAsToldToConnectivity); + final Set<UidRangeParcel> rangesToAdd; if (enforce) { final Set<UidRange> restrictedProfilesRanges = createUserAndRestrictedProfilesRanges(mUserId, @@ -1621,26 +1620,27 @@ public class Vpn { } } - rangesToTellNetdToRemove.removeAll(rangesThatShouldBeBlocked); - rangesToTellNetdToAdd = rangesThatShouldBeBlocked; - // The ranges to tell netd to add are the ones that should be blocked minus the - // ones it already knows to block. Note that this will change the contents of + rangesToRemove.removeAll(rangesThatShouldBeBlocked); + rangesToAdd = rangesThatShouldBeBlocked; + // The ranges to tell ConnectivityService to add are the ones that should be blocked + // minus the ones it already knows to block. Note that this will change the contents of // rangesThatShouldBeBlocked, but the list of ranges that should be blocked is // not used after this so it's fine to destroy it. - rangesToTellNetdToAdd.removeAll(mBlockedUidsAsToldToNetd); + rangesToAdd.removeAll(mBlockedUidsAsToldToConnectivity); } else { - rangesToTellNetdToAdd = Collections.emptySet(); + rangesToAdd = Collections.emptySet(); } // If mBlockedUidsAsToldToNetd used to be empty, this will always be a no-op. - setAllowOnlyVpnForUids(false, rangesToTellNetdToRemove); + setAllowOnlyVpnForUids(false, rangesToRemove); // If nothing should be blocked now, this will now be a no-op. - setAllowOnlyVpnForUids(true, rangesToTellNetdToAdd); + setAllowOnlyVpnForUids(true, rangesToAdd); } /** - * Tell netd to add or remove a list of {@link UidRange}s to the list of UIDs that are only - * allowed to make connections through sockets that have had {@code protect()} called on them. + * Tell ConnectivityService to add or remove a list of {@link UidRange}s to the list of UIDs + * that are only allowed to make connections through sockets that have had {@code protect()} + * called on them. * * @param enforce {@code true} to add to the denylist, {@code false} to remove. * @param ranges {@link Collection} of {@link UidRange}s to add (if {@param enforce} is @@ -1653,18 +1653,22 @@ public class Vpn { if (ranges.size() == 0) { return true; } - final UidRangeParcel[] stableRanges = ranges.toArray(new UidRangeParcel[ranges.size()]); + // Convert to Collection<Range> which is what the ConnectivityManager API takes. + ArrayList<Range<Integer>> integerRanges = new ArrayList<>(ranges.size()); + for (UidRangeParcel uidRange : ranges) { + integerRanges.add(new Range<>(uidRange.start, uidRange.stop)); + } try { - mNetd.networkRejectNonSecureVpn(enforce, stableRanges); - } catch (RemoteException | RuntimeException e) { + mConnectivityManager.setRequireVpnForUids(enforce, integerRanges); + } catch (RuntimeException e) { Log.e(TAG, "Updating blocked=" + enforce + " for UIDs " + Arrays.toString(ranges.toArray()) + " failed", e); return false; } if (enforce) { - mBlockedUidsAsToldToNetd.addAll(ranges); + mBlockedUidsAsToldToConnectivity.addAll(ranges); } else { - mBlockedUidsAsToldToNetd.removeAll(ranges); + mBlockedUidsAsToldToConnectivity.removeAll(ranges); } return true; } @@ -1783,9 +1787,6 @@ public class Vpn { /** * Updates underlying network set. - * - * <p>Note: Does not updates capabilities. Call {@link #updateCapabilities} from - * ConnectivityService thread to get updated capabilities. */ public synchronized boolean setUnderlyingNetworks(Network[] networks) { if (!isCallerEstablishedOwnerLocked()) { @@ -1808,13 +1809,6 @@ public class Vpn { return true; } - public synchronized Network[] getUnderlyingNetworks() { - if (!isRunningLocked()) { - return null; - } - return mConfig.underlyingNetworks; - } - /** * This method should only be called by ConnectivityService because it doesn't * have enough data to fill VpnInfo.primaryUnderlyingIface field. @@ -1856,34 +1850,6 @@ public class Vpn { } } - /** - * @param uid The target uid. - * - * @return {@code true} if {@code uid} is included in one of the mBlockedUidsAsToldToNetd - * ranges and the VPN is not connected, or if the VPN is connected but does not apply to - * the {@code uid}. - * - * @apiNote This method don't check VPN lockdown status. - * @see #mBlockedUidsAsToldToNetd - */ - public synchronized boolean isBlockingUid(int uid) { - if (mNetworkInfo.isConnected()) { - return !appliesToUid(uid); - } else { - return containsUid(mBlockedUidsAsToldToNetd, uid); - } - } - - private boolean containsUid(Collection<UidRangeParcel> ranges, int uid) { - if (ranges == null) return false; - for (UidRangeParcel range : ranges) { - if (range.start <= uid && uid <= range.stop) { - return true; - } - } - return false; - } - private void updateAlwaysOnNotification(DetailedState networkState) { final boolean visible = (mAlwaysOn && networkState != DetailedState.CONNECTED); diff --git a/services/core/java/com/android/server/content/OWNERS b/services/core/java/com/android/server/content/OWNERS new file mode 100644 index 000000000000..b6a9fe869ffa --- /dev/null +++ b/services/core/java/com/android/server/content/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/am/OWNERS
\ No newline at end of file diff --git a/services/core/java/com/android/server/graphics/fonts/OWNERS b/services/core/java/com/android/server/graphics/fonts/OWNERS new file mode 100644 index 000000000000..34ac813f02e0 --- /dev/null +++ b/services/core/java/com/android/server/graphics/fonts/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 24939 + +include /graphics/java/android/graphics/fonts/OWNERS diff --git a/services/core/java/com/android/server/net/NetworkIdentitySet.java b/services/core/java/com/android/server/net/NetworkIdentitySet.java index 2326ad39fd27..bce80696f72c 100644 --- a/services/core/java/com/android/server/net/NetworkIdentitySet.java +++ b/services/core/java/com/android/server/net/NetworkIdentitySet.java @@ -20,8 +20,8 @@ import android.net.NetworkIdentity; import android.service.NetworkIdentitySetProto; import android.util.proto.ProtoOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; +import java.io.DataInput; +import java.io.DataOutput; import java.io.IOException; import java.util.HashSet; @@ -44,7 +44,7 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements public NetworkIdentitySet() { } - public NetworkIdentitySet(DataInputStream in) throws IOException { + public NetworkIdentitySet(DataInput in) throws IOException { final int version = in.readInt(); final int size = in.readInt(); for (int i = 0; i < size; i++) { @@ -89,7 +89,7 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements } } - public void writeToStream(DataOutputStream out) throws IOException { + public void writeToStream(DataOutput out) throws IOException { out.writeInt(VERSION_ADD_DEFAULT_NETWORK); out.writeInt(size()); for (NetworkIdentity ident : this) { @@ -143,7 +143,7 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements return true; } - private static void writeOptionalString(DataOutputStream out, String value) throws IOException { + private static void writeOptionalString(DataOutput out, String value) throws IOException { if (value != null) { out.writeByte(1); out.writeUTF(value); @@ -152,7 +152,7 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements } } - private static String readOptionalString(DataInputStream in) throws IOException { + private static String readOptionalString(DataInput in) throws IOException { if (in.readByte() != 0) { return in.readUTF(); } else { diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java index 5bd352c8f8e8..676f4218f745 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java +++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java @@ -78,6 +78,7 @@ public class NetworkPolicyLogger { static final int NTWK_BLOCKED_BG_RESTRICT = 5; static final int NTWK_ALLOWED_DEFAULT = 6; static final int NTWK_ALLOWED_SYSTEM = 7; + static final int NTWK_BLOCKED_RESTRICTED_MODE = 8; private final LogBuffer mNetworkBlockedBuffer = new LogBuffer(MAX_NETWORK_BLOCKED_LOG_SIZE); private final LogBuffer mUidStateChangeBuffer = new LogBuffer(MAX_LOG_SIZE); @@ -281,6 +282,8 @@ public class NetworkPolicyLogger { return "blocked when background is restricted"; case NTWK_ALLOWED_DEFAULT: return "allowed by default"; + case NTWK_BLOCKED_RESTRICTED_MODE: + return "blocked by restricted networking mode"; default: return String.valueOf(reason); } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java index 407cedf38917..141fa6a17873 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java @@ -44,12 +44,6 @@ public abstract class NetworkPolicyManagerInternal { public abstract boolean isUidRestrictedOnMeteredNetworks(int uid); /** - * @return true if networking is blocked on the given interface for the given uid according - * to current networking policies. - */ - public abstract boolean isUidNetworkingBlocked(int uid, String ifname); - - /** * Figure out if networking is blocked for a given set of conditions. * * This is used by ConnectivityService via passing stale copies of conditions, so it must not diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index bd80befe84be..1c41dc073ac8 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -18,6 +18,7 @@ package com.android.server.net; import static android.Manifest.permission.ACCESS_NETWORK_STATE; import static android.Manifest.permission.CONNECTIVITY_INTERNAL; +import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS; import static android.Manifest.permission.MANAGE_NETWORK_POLICY; import static android.Manifest.permission.MANAGE_SUBSCRIPTION_PLANS; import static android.Manifest.permission.NETWORK_SETTINGS; @@ -44,6 +45,7 @@ import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELI import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.INetd.FIREWALL_CHAIN_DOZABLE; import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE; +import static android.net.INetd.FIREWALL_CHAIN_RESTRICTED; import static android.net.INetd.FIREWALL_CHAIN_STANDBY; import static android.net.INetd.FIREWALL_RULE_ALLOW; import static android.net.INetd.FIREWALL_RULE_DENY; @@ -57,6 +59,7 @@ import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE; import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; import static android.net.NetworkPolicyManager.MASK_ALL_NETWORKS; import static android.net.NetworkPolicyManager.MASK_METERED_NETWORKS; +import static android.net.NetworkPolicyManager.MASK_RESTRICTED_MODE_NETWORKS; import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND; import static android.net.NetworkPolicyManager.POLICY_NONE; import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; @@ -65,12 +68,14 @@ import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED; import static android.net.NetworkPolicyManager.RULE_NONE; import static android.net.NetworkPolicyManager.RULE_REJECT_ALL; import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; +import static android.net.NetworkPolicyManager.RULE_REJECT_RESTRICTED_MODE; import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED; import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode; import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground; import static android.net.NetworkPolicyManager.resolveNetworkId; import static android.net.NetworkPolicyManager.uidPoliciesToString; import static android.net.NetworkPolicyManager.uidRulesToString; +import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.net.NetworkTemplate.MATCH_MOBILE; import static android.net.NetworkTemplate.MATCH_WIFI; import static android.net.NetworkTemplate.buildTemplateMobileAll; @@ -111,6 +116,7 @@ import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_TMP_ALLOWL import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BG_RESTRICT; import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_DENYLIST; import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_POWER; +import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_RESTRICTED_MODE; import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; @@ -143,6 +149,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.content.res.Resources; +import android.database.ContentObserver; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.IConnectivityManager; @@ -270,6 +277,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.function.IntConsumer; /** * Service that maintains low-level network policy rules, using @@ -444,7 +452,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @GuardedBy("mUidRulesFirstLock") volatile boolean mRestrictPower; @GuardedBy("mUidRulesFirstLock") volatile boolean mDeviceIdleMode; // Store whether user flipped restrict background in battery saver mode - @GuardedBy("mUidRulesFirstLock") volatile boolean mRestrictBackgroundChangedInBsm; + @GuardedBy("mUidRulesFirstLock") + volatile boolean mRestrictBackgroundChangedInBsm; + @GuardedBy("mUidRulesFirstLock") + volatile boolean mRestrictedNetworkingMode; private final boolean mSuppressDefaultPolicy; @@ -478,6 +489,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final SparseIntArray mUidFirewallDozableRules = new SparseIntArray(); @GuardedBy("mUidRulesFirstLock") final SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray(); + @GuardedBy("mUidRulesFirstLock") + final SparseIntArray mUidFirewallRestrictedModeRules = new SparseIntArray(); /** Set of states for the child firewall chains. True if the chain is active. */ @GuardedBy("mUidRulesFirstLock") @@ -597,6 +610,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @GuardedBy("mUidRulesFirstLock") private final SparseBooleanArray mInternetPermissionMap = new SparseBooleanArray(); + private RestrictedModeObserver mRestrictedModeObserver; + // TODO: keep allowlist of system-critical services that should never have // rules enforced, such as system, phone, and radio UIDs. @@ -610,7 +625,35 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { int COUNT = IS_UID_NETWORKING_BLOCKED + 1; } - public final StatLogger mStatLogger = new StatLogger(new String[] { + private static class RestrictedModeObserver extends ContentObserver { + private final Context mContext; + private final RestrictedModeListener mListener; + + RestrictedModeObserver(Context ctx, RestrictedModeListener listener) { + super(null); + mContext = ctx; + mListener = listener; + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.RESTRICTED_NETWORKING_MODE), false, + this); + } + + public boolean isRestrictedModeEnabled() { + return Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.RESTRICTED_NETWORKING_MODE, 0) != 0; + } + + @Override + public void onChange(boolean selfChange) { + mListener.onChange(isRestrictedModeEnabled()); + } + + public interface RestrictedModeListener { + void onChange(boolean enabled); + } + } + + public final StatLogger mStatLogger = new StatLogger(new String[]{ "updateNetworkEnabledNL()", "isUidNetworkingBlocked()", }); @@ -785,6 +828,15 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mRestrictPower = mPowerManagerInternal.getLowPowerState( ServiceType.NETWORK_FIREWALL).batterySaverEnabled; + mRestrictedModeObserver = new RestrictedModeObserver(mContext, + enabled -> { + synchronized (mUidRulesFirstLock) { + mRestrictedNetworkingMode = enabled; + updateRestrictedModeAllowlistUL(); + } + }); + mRestrictedNetworkingMode = mRestrictedModeObserver.isRestrictedModeEnabled(); + mSystemReady = true; waitForAdminData(); @@ -3500,6 +3552,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { fout.print("Restrict background: "); fout.println(mRestrictBackground); fout.print("Restrict power: "); fout.println(mRestrictPower); fout.print("Device idle: "); fout.println(mDeviceIdleMode); + fout.print("Restricted networking mode: "); fout.println(mRestrictedNetworkingMode); synchronized (mMeteredIfacesLock) { fout.print("Metered ifaces: "); fout.println(mMeteredIfaces); @@ -3811,6 +3864,100 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } + @VisibleForTesting + boolean isRestrictedModeEnabled() { + synchronized (mUidRulesFirstLock) { + return mRestrictedNetworkingMode; + } + } + + /** + * updates restricted mode state / access for all apps + * Called on initialization and when restricted mode is enabled / disabled. + */ + @VisibleForTesting + @GuardedBy("mUidRulesFirstLock") + void updateRestrictedModeAllowlistUL() { + mUidFirewallRestrictedModeRules.clear(); + forEachUid("updateRestrictedModeAllowlist", uid -> { + final int oldUidRule = mUidRules.get(uid); + final int newUidRule = getNewRestrictedModeUidRule(uid, oldUidRule); + final boolean hasUidRuleChanged = oldUidRule != newUidRule; + final int newFirewallRule = getRestrictedModeFirewallRule(newUidRule); + + // setUidFirewallRulesUL will allowlist all uids that are passed to it, so only add + // non-default rules. + if (newFirewallRule != FIREWALL_RULE_DEFAULT) { + mUidFirewallRestrictedModeRules.append(uid, newFirewallRule); + } + + if (hasUidRuleChanged) { + mUidRules.put(uid, newUidRule); + mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRule).sendToTarget(); + } + }); + if (mRestrictedNetworkingMode) { + // firewall rules only need to be set when this mode is being enabled. + setUidFirewallRulesUL(FIREWALL_CHAIN_RESTRICTED, mUidFirewallRestrictedModeRules); + } + enableFirewallChainUL(FIREWALL_CHAIN_RESTRICTED, mRestrictedNetworkingMode); + } + + // updates restricted mode state / access for a single app / uid. + @VisibleForTesting + @GuardedBy("mUidRulesFirstLock") + void updateRestrictedModeForUidUL(int uid) { + final int oldUidRule = mUidRules.get(uid); + final int newUidRule = getNewRestrictedModeUidRule(uid, oldUidRule); + final boolean hasUidRuleChanged = oldUidRule != newUidRule; + + if (hasUidRuleChanged) { + mUidRules.put(uid, newUidRule); + mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRule).sendToTarget(); + } + + // if restricted networking mode is on, and the app has an access exemption, the uid rule + // will not change, but the firewall rule will have to be updated. + if (mRestrictedNetworkingMode) { + // Note: setUidFirewallRule also updates mUidFirewallRestrictedModeRules. + // In this case, default firewall rules can also be added. + setUidFirewallRule(FIREWALL_CHAIN_RESTRICTED, uid, + getRestrictedModeFirewallRule(newUidRule)); + } + } + + private int getNewRestrictedModeUidRule(int uid, int oldUidRule) { + int newRule = oldUidRule; + newRule &= ~MASK_RESTRICTED_MODE_NETWORKS; + if (mRestrictedNetworkingMode && !hasRestrictedModeAccess(uid)) { + newRule |= RULE_REJECT_RESTRICTED_MODE; + } + return newRule; + } + + private static int getRestrictedModeFirewallRule(int uidRule) { + if ((uidRule & RULE_REJECT_RESTRICTED_MODE) != 0) { + // rejected in restricted mode, this is the default behavior. + return FIREWALL_RULE_DEFAULT; + } else { + return FIREWALL_RULE_ALLOW; + } + } + + private boolean hasRestrictedModeAccess(int uid) { + try { + // TODO: this needs to be kept in sync with + // PermissionMonitor#hasRestrictedNetworkPermission + return mIPm.checkUidPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, uid) + == PERMISSION_GRANTED + || mIPm.checkUidPermission(NETWORK_STACK, uid) == PERMISSION_GRANTED + || mIPm.checkUidPermission(PERMISSION_MAINLINE_NETWORK_STACK, uid) + == PERMISSION_GRANTED; + } catch (RemoteException e) { + return false; + } + } + @GuardedBy("mUidRulesFirstLock") void updateRulesForPowerSaveUL() { Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForPowerSaveUL"); @@ -4032,6 +4179,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { updateRulesForAppIdleUL(); updateRulesForRestrictPowerUL(); updateRulesForRestrictBackgroundUL(); + updateRestrictedModeAllowlistUL(); // If the set of restricted networks may have changed, re-evaluate those. if (restrictedNetworksChanged) { @@ -4050,7 +4198,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { try { updateRulesForDeviceIdleUL(); updateRulesForPowerSaveUL(); - updateRulesForAllAppsUL(TYPE_RESTRICT_POWER); + forEachUid("updateRulesForRestrictPower", + uid -> updateRulesForPowerRestrictionsUL(uid)); } finally { Trace.traceEnd(Trace.TRACE_TAG_NETWORK); } @@ -4060,31 +4209,19 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private void updateRulesForRestrictBackgroundUL() { Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForRestrictBackgroundUL"); try { - updateRulesForAllAppsUL(TYPE_RESTRICT_BACKGROUND); + forEachUid("updateRulesForRestrictBackground", + uid -> updateRulesForDataUsageRestrictionsUL(uid)); } finally { Trace.traceEnd(Trace.TRACE_TAG_NETWORK); } } - private static final int TYPE_RESTRICT_BACKGROUND = 1; - private static final int TYPE_RESTRICT_POWER = 2; - @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = false, value = { - TYPE_RESTRICT_BACKGROUND, - TYPE_RESTRICT_POWER, - }) - public @interface RestrictType { - } - - // TODO: refactor / consolidate all those updateXyz methods, there are way too many of them... - @GuardedBy("mUidRulesFirstLock") - private void updateRulesForAllAppsUL(@RestrictType int type) { + private void forEachUid(String tag, IntConsumer consumer) { if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) { - Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForRestrictPowerUL-" + type); + Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "forEachUid-" + tag); } try { // update rules for all installed applications - final PackageManager pm = mContext.getPackageManager(); final List<UserInfo> users; final List<ApplicationInfo> apps; @@ -4112,16 +4249,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { for (int j = 0; j < appsSize; j++) { final ApplicationInfo app = apps.get(j); final int uid = UserHandle.getUid(user.id, app.uid); - switch (type) { - case TYPE_RESTRICT_BACKGROUND: - updateRulesForDataUsageRestrictionsUL(uid); - break; - case TYPE_RESTRICT_POWER: - updateRulesForPowerRestrictionsUL(uid); - break; - default: - Slog.w(TAG, "Invalid type for updateRulesForAllApps: " + type); - } + consumer.accept(uid); } } } finally { @@ -4268,6 +4396,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mPowerSaveWhitelistAppIds.delete(uid); mPowerSaveTempWhitelistAppIds.delete(uid); mAppIdleTempWhitelistAppIds.delete(uid); + mUidFirewallRestrictedModeRules.delete(uid); // ...then update iptables asynchronously. mHandler.obtainMessage(MSG_RESET_FIREWALL_RULES_BY_UID, uid, 0).sendToTarget(); @@ -4293,6 +4422,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { updateRuleForAppIdleUL(uid); updateRuleForRestrictPowerUL(uid); + // If the uid has the necessary permissions, then it should be added to the restricted mode + // firewall allowlist. + updateRestrictedModeForUidUL(uid); + // Update internal state for power-related modes. updateRulesForPowerRestrictionsUL(uid); @@ -4365,26 +4498,26 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final boolean isDenied = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0; final boolean isAllowed = (uidPolicy & POLICY_ALLOW_METERED_BACKGROUND) != 0; - final int oldRule = oldUidRules & MASK_METERED_NETWORKS; - int newRule = RULE_NONE; + + // copy oldUidRules and clear out METERED_NETWORKS rules. + int newUidRules = oldUidRules & (~MASK_METERED_NETWORKS); // First step: define the new rule based on user restrictions and foreground state. if (isRestrictedByAdmin) { - newRule = RULE_REJECT_METERED; + newUidRules |= RULE_REJECT_METERED; } else if (isForeground) { if (isDenied || (mRestrictBackground && !isAllowed)) { - newRule = RULE_TEMPORARY_ALLOW_METERED; + newUidRules |= RULE_TEMPORARY_ALLOW_METERED; } else if (isAllowed) { - newRule = RULE_ALLOW_METERED; + newUidRules |= RULE_ALLOW_METERED; } } else { if (isDenied) { - newRule = RULE_REJECT_METERED; + newUidRules |= RULE_REJECT_METERED; } else if (mRestrictBackground && isAllowed) { - newRule = RULE_ALLOW_METERED; + newUidRules |= RULE_ALLOW_METERED; } } - final int newUidRules = newRule | (oldUidRules & MASK_ALL_NETWORKS); if (LOGV) { Log.v(TAG, "updateRuleForRestrictBackgroundUL(" + uid + ")" @@ -4392,8 +4525,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { + ", isDenied=" + isDenied + ", isAllowed=" + isAllowed + ", isRestrictedByAdmin=" + isRestrictedByAdmin - + ", oldRule=" + uidRulesToString(oldRule) - + ", newRule=" + uidRulesToString(newRule) + + ", oldRule=" + uidRulesToString(oldUidRules & MASK_METERED_NETWORKS) + + ", newRule=" + uidRulesToString(newUidRules & MASK_METERED_NETWORKS) + ", newUidRules=" + uidRulesToString(newUidRules) + ", oldUidRules=" + uidRulesToString(oldUidRules)); } @@ -4405,8 +4538,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } // Second step: apply bw changes based on change of state. - if (newRule != oldRule) { - if (hasRule(newRule, RULE_TEMPORARY_ALLOW_METERED)) { + if (newUidRules != oldUidRules) { + if (hasRule(newUidRules, RULE_TEMPORARY_ALLOW_METERED)) { // Temporarily allow foreground app, removing from denylist if necessary // (since bw_penalty_box prevails over bw_happy_box). @@ -4417,7 +4550,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { if (isDenied) { setMeteredNetworkDenylist(uid, false); } - } else if (hasRule(oldRule, RULE_TEMPORARY_ALLOW_METERED)) { + } else if (hasRule(oldUidRules, RULE_TEMPORARY_ALLOW_METERED)) { // Remove temporary exemption from app that is not on foreground anymore. // TODO: if statements below are used to avoid unnecessary calls to netd / iptables, @@ -4430,18 +4563,18 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { if (isDenied || isRestrictedByAdmin) { setMeteredNetworkDenylist(uid, true); } - } else if (hasRule(newRule, RULE_REJECT_METERED) - || hasRule(oldRule, RULE_REJECT_METERED)) { + } else if (hasRule(newUidRules, RULE_REJECT_METERED) + || hasRule(oldUidRules, RULE_REJECT_METERED)) { // Flip state because app was explicitly added or removed to denylist. setMeteredNetworkDenylist(uid, (isDenied || isRestrictedByAdmin)); - if (hasRule(oldRule, RULE_REJECT_METERED) && isAllowed) { + if (hasRule(oldUidRules, RULE_REJECT_METERED) && isAllowed) { // Since denial prevails over allowance, we need to handle the special case // where app is allowed and denied at the same time (although such // scenario should be blocked by the UI), then it is removed from the denylist. setMeteredNetworkAllowlist(uid, isAllowed); } - } else if (hasRule(newRule, RULE_ALLOW_METERED) - || hasRule(oldRule, RULE_ALLOW_METERED)) { + } else if (hasRule(newUidRules, RULE_ALLOW_METERED) + || hasRule(oldUidRules, RULE_ALLOW_METERED)) { // Flip state because app was explicitly added or removed to allowlist. setMeteredNetworkAllowlist(uid, isAllowed); } else { @@ -4527,8 +4660,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid); final boolean isWhitelisted = isWhitelistedFromPowerSaveUL(uid, mDeviceIdleMode); - final int oldRule = oldUidRules & MASK_ALL_NETWORKS; - int newRule = RULE_NONE; + + // Copy existing uid rules and clear ALL_NETWORK rules. + int newUidRules = oldUidRules & (~MASK_ALL_NETWORKS); // First step: define the new rule based on user restrictions and foreground state. @@ -4536,14 +4670,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // by considering the foreground and non-foreground states. if (isForeground) { if (restrictMode) { - newRule = RULE_ALLOW_ALL; + newUidRules |= RULE_ALLOW_ALL; } } else if (restrictMode) { - newRule = isWhitelisted ? RULE_ALLOW_ALL : RULE_REJECT_ALL; + newUidRules |= isWhitelisted ? RULE_ALLOW_ALL : RULE_REJECT_ALL; } - final int newUidRules = (oldUidRules & MASK_METERED_NETWORKS) | newRule; - if (LOGV) { Log.v(TAG, "updateRulesForPowerRestrictionsUL(" + uid + ")" + ", isIdle: " + isUidIdle @@ -4551,17 +4683,18 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { + ", mDeviceIdleMode: " + mDeviceIdleMode + ", isForeground=" + isForeground + ", isWhitelisted=" + isWhitelisted - + ", oldRule=" + uidRulesToString(oldRule) - + ", newRule=" + uidRulesToString(newRule) + + ", oldRule=" + uidRulesToString(oldUidRules & MASK_ALL_NETWORKS) + + ", newRule=" + uidRulesToString(newUidRules & MASK_ALL_NETWORKS) + ", newUidRules=" + uidRulesToString(newUidRules) + ", oldUidRules=" + uidRulesToString(oldUidRules)); } // Second step: notify listeners if state changed. - if (newRule != oldRule) { - if (newRule == RULE_NONE || hasRule(newRule, RULE_ALLOW_ALL)) { + if (newUidRules != oldUidRules) { + if ((newUidRules & MASK_ALL_NETWORKS) == RULE_NONE || hasRule(newUidRules, + RULE_ALLOW_ALL)) { if (LOGV) Log.v(TAG, "Allowing non-metered access for UID " + uid); - } else if (hasRule(newRule, RULE_REJECT_ALL)) { + } else if (hasRule(newUidRules, RULE_REJECT_ALL)) { if (LOGV) Log.v(TAG, "Rejecting non-metered access for UID " + uid); } else { // All scenarios should have been covered above @@ -5018,6 +5151,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mUidFirewallStandbyRules.put(uid, rule); } else if (chain == FIREWALL_CHAIN_POWERSAVE) { mUidFirewallPowerSaveRules.put(uid, rule); + } else if (chain == FIREWALL_CHAIN_RESTRICTED) { + mUidFirewallRestrictedModeRules.put(uid, rule); } try { @@ -5063,6 +5198,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT); mNetworkManager .setFirewallUidRule(FIREWALL_CHAIN_POWERSAVE, uid, FIREWALL_RULE_DEFAULT); + mNetworkManager + .setFirewallUidRule(FIREWALL_CHAIN_RESTRICTED, uid, FIREWALL_RULE_DEFAULT); mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false); mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false); } catch (IllegalStateException e) { @@ -5224,7 +5361,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { public boolean isUidNetworkingBlocked(int uid, boolean isNetworkMetered) { final long startTime = mStatLogger.getTime(); - mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG); + enforceAnyPermissionOf(OBSERVE_NETWORK_POLICY, PERMISSION_MAINLINE_NETWORK_STACK); final int uidRules; final boolean isBackgroundRestricted; synchronized (mUidRulesFirstLock) { @@ -5249,26 +5386,21 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // Networks are never blocked for system components if (isSystem(uid)) { reason = NTWK_ALLOWED_SYSTEM; - } - else if (hasRule(uidRules, RULE_REJECT_ALL)) { + } else if (hasRule(uidRules, RULE_REJECT_RESTRICTED_MODE)) { + reason = NTWK_BLOCKED_RESTRICTED_MODE; + } else if (hasRule(uidRules, RULE_REJECT_ALL)) { reason = NTWK_BLOCKED_POWER; - } - else if (!isNetworkMetered) { + } else if (!isNetworkMetered) { reason = NTWK_ALLOWED_NON_METERED; - } - else if (hasRule(uidRules, RULE_REJECT_METERED)) { + } else if (hasRule(uidRules, RULE_REJECT_METERED)) { reason = NTWK_BLOCKED_DENYLIST; - } - else if (hasRule(uidRules, RULE_ALLOW_METERED)) { + } else if (hasRule(uidRules, RULE_ALLOW_METERED)) { reason = NTWK_ALLOWED_ALLOWLIST; - } - else if (hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED)) { + } else if (hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED)) { reason = NTWK_ALLOWED_TMP_ALLOWLIST; - } - else if (isBackgroundRestricted) { + } else if (isBackgroundRestricted) { reason = NTWK_BLOCKED_BG_RESTRICT; - } - else { + } else { reason = NTWK_ALLOWED_DEFAULT; } @@ -5281,6 +5413,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { case NTWK_ALLOWED_SYSTEM: blocked = false; break; + case NTWK_BLOCKED_RESTRICTED_MODE: case NTWK_BLOCKED_POWER: case NTWK_BLOCKED_DENYLIST: case NTWK_BLOCKED_BG_RESTRICT: @@ -5327,32 +5460,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { && !hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED); } - /** - * @return true if networking is blocked on the given interface for the given uid according - * to current networking policies. - */ - @Override - public boolean isUidNetworkingBlocked(int uid, String ifname) { - final long startTime = mStatLogger.getTime(); - - final int uidRules; - final boolean isBackgroundRestricted; - synchronized (mUidRulesFirstLock) { - uidRules = mUidRules.get(uid, RULE_NONE); - isBackgroundRestricted = mRestrictBackground; - } - final boolean isNetworkMetered; - synchronized (mMeteredIfacesLock) { - isNetworkMetered = mMeteredIfaces.contains(ifname); - } - final boolean ret = isUidNetworkingBlockedInternal(uid, uidRules, isNetworkMetered, - isBackgroundRestricted, mLogger); - - mStatLogger.logDurationStat(Stats.IS_UID_NETWORKING_BLOCKED, startTime); - - return ret; - } - @Override public void onTempPowerSaveWhitelistChange(int appId, boolean added) { synchronized (mUidRulesFirstLock) { diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java index 7bcf3183bf69..47bb8f009920 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java @@ -119,6 +119,8 @@ class NetworkPolicyManagerShellCommand extends ShellCommand { switch(type) { case "restrict-background": return getRestrictBackground(); + case "restricted-mode": + return getRestrictedModeState(); } pw.println("Error: unknown get type '" + type + "'"); return -1; @@ -255,6 +257,13 @@ class NetworkPolicyManagerShellCommand extends ShellCommand { return listUidList("App Idle whitelisted UIDs", uids); } + private int getRestrictedModeState() { + final PrintWriter pw = getOutPrintWriter(); + pw.print("Restricted mode status: "); + pw.println(mInterface.isRestrictedModeEnabled() ? "enabled" : "disabled"); + return 0; + } + private int getRestrictBackground() throws RemoteException { final PrintWriter pw = getOutPrintWriter(); pw.print("Restrict background status: "); diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java index c4beddd42eaf..6aefe41891f9 100644 --- a/services/core/java/com/android/server/net/NetworkStatsCollection.java +++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java @@ -63,12 +63,15 @@ import com.google.android.collect.Lists; import com.google.android.collect.Maps; import java.io.BufferedInputStream; +import java.io.DataInput; import java.io.DataInputStream; +import java.io.DataOutput; import java.io.DataOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.PrintWriter; import java.net.ProtocolException; import java.time.ZonedDateTime; @@ -82,7 +85,7 @@ import java.util.Objects; * Collection of {@link NetworkStatsHistory}, stored based on combined key of * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself. */ -public class NetworkStatsCollection implements FileRotator.Reader { +public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.Writer { /** File header magic number: "ANET" */ private static final int FILE_MAGIC = 0x414E4554; @@ -431,10 +434,10 @@ public class NetworkStatsCollection implements FileRotator.Reader { @Override public void read(InputStream in) throws IOException { - read(new DataInputStream(in)); + read((DataInput) new DataInputStream(in)); } - public void read(DataInputStream in) throws IOException { + private void read(DataInput in) throws IOException { // verify file magic header intact final int magic = in.readInt(); if (magic != FILE_MAGIC) { @@ -468,7 +471,13 @@ public class NetworkStatsCollection implements FileRotator.Reader { } } - public void write(DataOutputStream out) throws IOException { + @Override + public void write(OutputStream out) throws IOException { + write((DataOutput) new DataOutputStream(out)); + out.flush(); + } + + private void write(DataOutput out) throws IOException { // cluster key lists grouped by ident final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = Maps.newHashMap(); for (Key key : mStats.keySet()) { @@ -497,8 +506,6 @@ public class NetworkStatsCollection implements FileRotator.Reader { history.writeToStream(out); } } - - out.flush(); } @Deprecated diff --git a/services/core/java/com/android/server/net/NetworkStatsRecorder.java b/services/core/java/com/android/server/net/NetworkStatsRecorder.java index ce741693cb4b..978ae87d39d5 100644 --- a/services/core/java/com/android/server/net/NetworkStatsRecorder.java +++ b/services/core/java/com/android/server/net/NetworkStatsRecorder.java @@ -42,7 +42,6 @@ import com.google.android.collect.Sets; import libcore.io.IoUtils; import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -375,7 +374,7 @@ public class NetworkStatsRecorder { @Override public void write(OutputStream out) throws IOException { - mCollection.write(new DataOutputStream(out)); + mCollection.write(out); mCollection.reset(); } } @@ -412,7 +411,7 @@ public class NetworkStatsRecorder { @Override public void write(OutputStream out) throws IOException { - mTemp.write(new DataOutputStream(out)); + mTemp.write(out); } } diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 0f8c9c789a3f..437e33d66c91 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -29,6 +29,8 @@ import static android.os.Trace.TRACE_TAG_RRO; import static android.os.Trace.traceBegin; import static android.os.Trace.traceEnd; +import static com.android.server.om.OverlayManagerServiceImpl.OperationFailedException; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -48,6 +50,7 @@ import android.content.res.ApkAssets; import android.net.Uri; import android.os.Binder; import android.os.Environment; +import android.os.HandlerThread; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; @@ -64,7 +67,6 @@ import android.util.SparseArray; import com.android.internal.content.om.OverlayConfig; import com.android.server.FgThread; -import com.android.server.IoThread; import com.android.server.LocalServices; import com.android.server.SystemConfig; import com.android.server.SystemService; @@ -87,7 +89,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; /** * Service to manage asset overlays. @@ -236,7 +238,9 @@ public final class OverlayManagerService extends SystemService { private final OverlayActorEnforcer mActorEnforcer; - private final AtomicBoolean mPersistSettingsScheduled = new AtomicBoolean(false); + private final Consumer<PackageAndUser> mOnOverlaysChanged = (pair) -> { + onOverlaysChanged(pair.packageName, pair.userId); + }; public OverlayManagerService(@NonNull final Context context) { super(context); @@ -249,17 +253,19 @@ public final class OverlayManagerService extends SystemService { IdmapManager im = new IdmapManager(IdmapDaemon.getInstance(), mPackageManager); mSettings = new OverlayManagerSettings(); mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings, - OverlayConfig.getSystemInstance(), getDefaultOverlayPackages(), - new OverlayChangeListener()); + OverlayConfig.getSystemInstance(), getDefaultOverlayPackages()); mActorEnforcer = new OverlayActorEnforcer(mPackageManager); + HandlerThread packageReceiverThread = new HandlerThread(TAG); + packageReceiverThread.start(); + final IntentFilter packageFilter = new IntentFilter(); packageFilter.addAction(ACTION_PACKAGE_ADDED); packageFilter.addAction(ACTION_PACKAGE_CHANGED); packageFilter.addAction(ACTION_PACKAGE_REMOVED); packageFilter.addDataScheme("package"); getContext().registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL, - packageFilter, null, null); + packageFilter, null, packageReceiverThread.getThreadHandler()); final IntentFilter userFilter = new IntentFilter(); userFilter.addAction(ACTION_USER_ADDED); @@ -312,7 +318,7 @@ public final class OverlayManagerService extends SystemService { final List<String> targets = mImpl.updateOverlaysForUser(newUserId); updateAssets(newUserId, targets); } - schedulePersistSettings(); + persistSettings(); } finally { traceEnd(TRACE_TAG_RRO); } @@ -396,10 +402,17 @@ public final class OverlayManagerService extends SystemService { false); if (pi != null && !pi.applicationInfo.isInstantApp()) { mPackageManager.cachePackageInfo(packageName, userId, pi); - if (pi.isOverlayPackage()) { - mImpl.onOverlayPackageAdded(packageName, userId); - } else { - mImpl.onTargetPackageAdded(packageName, userId); + + try { + if (pi.isOverlayPackage()) { + mImpl.onOverlayPackageAdded(packageName, userId) + .ifPresent(mOnOverlaysChanged); + } else { + mImpl.onTargetPackageAdded(packageName, userId) + .ifPresent(mOnOverlaysChanged); + } + } catch (OperationFailedException e) { + Slog.e(TAG, "onPackageAdded internal error", e); } } } @@ -419,10 +432,17 @@ public final class OverlayManagerService extends SystemService { false); if (pi != null && pi.applicationInfo.isInstantApp()) { mPackageManager.cachePackageInfo(packageName, userId, pi); - if (pi.isOverlayPackage()) { - mImpl.onOverlayPackageChanged(packageName, userId); - } else { - mImpl.onTargetPackageChanged(packageName, userId); + + try { + if (pi.isOverlayPackage()) { + mImpl.onOverlayPackageChanged(packageName, userId) + .ifPresent(mOnOverlaysChanged); + } else { + mImpl.onTargetPackageChanged(packageName, userId) + .ifPresent(mOnOverlaysChanged); + } + } catch (OperationFailedException e) { + Slog.e(TAG, "onPackageChanged internal error", e); } } } @@ -441,7 +461,12 @@ public final class OverlayManagerService extends SystemService { mPackageManager.forgetPackageInfo(packageName, userId); final OverlayInfo oi = mImpl.getOverlayInfo(packageName, userId); if (oi != null) { - mImpl.onOverlayPackageReplacing(packageName, userId); + try { + mImpl.onOverlayPackageReplacing(packageName, userId) + .ifPresent(mOnOverlaysChanged); + } catch (OperationFailedException e) { + Slog.e(TAG, "onPackageReplacing internal error", e); + } } } } @@ -460,10 +485,16 @@ public final class OverlayManagerService extends SystemService { false); if (pi != null && !pi.applicationInfo.isInstantApp()) { mPackageManager.cachePackageInfo(packageName, userId, pi); - if (pi.isOverlayPackage()) { - mImpl.onOverlayPackageReplaced(packageName, userId); - } else { - mImpl.onTargetPackageReplaced(packageName, userId); + try { + if (pi.isOverlayPackage()) { + mImpl.onOverlayPackageReplaced(packageName, userId) + .ifPresent(mOnOverlaysChanged); + } else { + mImpl.onTargetPackageReplaced(packageName, userId) + .ifPresent(mOnOverlaysChanged); + } + } catch (OperationFailedException e) { + Slog.e(TAG, "onPackageReplaced internal error", e); } } } @@ -481,10 +512,17 @@ public final class OverlayManagerService extends SystemService { synchronized (mLock) { mPackageManager.forgetPackageInfo(packageName, userId); final OverlayInfo oi = mImpl.getOverlayInfo(packageName, userId); - if (oi != null) { - mImpl.onOverlayPackageRemoved(packageName, userId); - } else { - mImpl.onTargetPackageRemoved(packageName, userId); + + try { + if (oi != null) { + mImpl.onOverlayPackageRemoved(packageName, userId) + .ifPresent(mOnOverlaysChanged); + } else { + mImpl.onTargetPackageRemoved(packageName, userId) + .ifPresent(mOnOverlaysChanged); + } + } catch (OperationFailedException e) { + Slog.e(TAG, "onPackageRemoved internal error", e); } } } @@ -602,7 +640,13 @@ public final class OverlayManagerService extends SystemService { final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { - return mImpl.setEnabled(packageName, enable, realUserId); + try { + mImpl.setEnabled(packageName, enable, realUserId) + .ifPresent(mOnOverlaysChanged); + return true; + } catch (OperationFailedException e) { + return false; + } } } finally { Binder.restoreCallingIdentity(ident); @@ -627,8 +671,14 @@ public final class OverlayManagerService extends SystemService { final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { - return mImpl.setEnabledExclusive(packageName, false /* withinCategory */, - realUserId); + try { + mImpl.setEnabledExclusive(packageName, + false /* withinCategory */, realUserId) + .ifPresent(mOnOverlaysChanged); + return true; + } catch (OperationFailedException e) { + return false; + } } } finally { Binder.restoreCallingIdentity(ident); @@ -654,8 +704,14 @@ public final class OverlayManagerService extends SystemService { final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { - return mImpl.setEnabledExclusive(packageName, true /* withinCategory */, - realUserId); + try { + mImpl.setEnabledExclusive(packageName, + true /* withinCategory */, realUserId) + .ifPresent(mOnOverlaysChanged); + return true; + } catch (OperationFailedException e) { + return false; + } } } finally { Binder.restoreCallingIdentity(ident); @@ -681,7 +737,13 @@ public final class OverlayManagerService extends SystemService { final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { - return mImpl.setPriority(packageName, parentPackageName, realUserId); + try { + mImpl.setPriority(packageName, parentPackageName, realUserId) + .ifPresent(mOnOverlaysChanged); + return true; + } catch (OperationFailedException e) { + return false; + } } } finally { Binder.restoreCallingIdentity(ident); @@ -705,7 +767,13 @@ public final class OverlayManagerService extends SystemService { final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { - return mImpl.setHighestPriority(packageName, realUserId); + try { + mImpl.setHighestPriority(packageName, realUserId) + .ifPresent(mOnOverlaysChanged); + return true; + } catch (OperationFailedException e) { + return false; + } } } finally { Binder.restoreCallingIdentity(ident); @@ -729,7 +797,13 @@ public final class OverlayManagerService extends SystemService { final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { - return mImpl.setLowestPriority(packageName, realUserId); + try { + mImpl.setLowestPriority(packageName, realUserId) + .ifPresent(mOnOverlaysChanged); + return true; + } catch (OperationFailedException e) { + return false; + } } } finally { Binder.restoreCallingIdentity(ident); @@ -898,31 +972,27 @@ public final class OverlayManagerService extends SystemService { } }; - private final class OverlayChangeListener - implements OverlayManagerServiceImpl.OverlayChangeListener { - @Override - public void onOverlaysChanged(@NonNull final String targetPackageName, final int userId) { - schedulePersistSettings(); - FgThread.getHandler().post(() -> { - updateAssets(userId, targetPackageName); + private void onOverlaysChanged(@NonNull final String targetPackageName, final int userId) { + persistSettings(); + FgThread.getHandler().post(() -> { + updateAssets(userId, targetPackageName); - final Intent intent = new Intent(ACTION_OVERLAY_CHANGED, - Uri.fromParts("package", targetPackageName, null)); - intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + final Intent intent = new Intent(ACTION_OVERLAY_CHANGED, + Uri.fromParts("package", targetPackageName, null)); + intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - if (DEBUG) { - Slog.d(TAG, "send broadcast " + intent); - } + if (DEBUG) { + Slog.d(TAG, "send broadcast " + intent); + } - try { - ActivityManager.getService().broadcastIntentWithFeature(null, null, intent, - null, null, 0, null, null, null, android.app.AppOpsManager.OP_NONE, - null, false, false, userId); - } catch (RemoteException e) { - // Intentionally left empty. - } - }); - } + try { + ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, + null, null, null, android.app.AppOpsManager.OP_NONE, null, false, false, + userId); + } catch (RemoteException e) { + // Intentionally left empty. + } + }); } /** @@ -995,27 +1065,21 @@ public final class OverlayManagerService extends SystemService { } } - private void schedulePersistSettings() { - if (mPersistSettingsScheduled.getAndSet(true)) { - return; + private void persistSettings() { + if (DEBUG) { + Slog.d(TAG, "Writing overlay settings"); } - IoThread.getHandler().post(() -> { - mPersistSettingsScheduled.set(false); - if (DEBUG) { - Slog.d(TAG, "Writing overlay settings"); - } - synchronized (mLock) { - FileOutputStream stream = null; - try { - stream = mSettingsFile.startWrite(); - mSettings.persist(stream); - mSettingsFile.finishWrite(stream); - } catch (IOException | XmlPullParserException e) { - mSettingsFile.failWrite(stream); - Slog.e(TAG, "failed to persist overlay state", e); - } + synchronized (mLock) { + FileOutputStream stream = null; + try { + stream = mSettingsFile.startWrite(); + mSettings.persist(stream); + mSettingsFile.finishWrite(stream); + } catch (IOException | XmlPullParserException e) { + mSettingsFile.failWrite(stream); + Slog.e(TAG, "failed to persist overlay state", e); } - }); + } } private void restoreSettings() { diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index 05a4a38feef1..e60411bb78c5 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -45,6 +45,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; /** @@ -71,7 +72,6 @@ final class OverlayManagerServiceImpl { private final OverlayManagerSettings mSettings; private final OverlayConfig mOverlayConfig; private final String[] mDefaultOverlays; - private final OverlayChangeListener mListener; /** * Helper method to merge the overlay manager's (as read from overlays.xml) @@ -114,14 +114,12 @@ final class OverlayManagerServiceImpl { @NonNull final IdmapManager idmapManager, @NonNull final OverlayManagerSettings settings, @NonNull final OverlayConfig overlayConfig, - @NonNull final String[] defaultOverlays, - @NonNull final OverlayChangeListener listener) { + @NonNull final String[] defaultOverlays) { mPackageManager = packageManager; mIdmapManager = idmapManager; mSettings = settings; mOverlayConfig = overlayConfig; mDefaultOverlays = defaultOverlays; - mListener = listener; } /** @@ -259,52 +257,58 @@ final class OverlayManagerServiceImpl { mSettings.removeUser(userId); } - void onTargetPackageAdded(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> onTargetPackageAdded(@NonNull final String packageName, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "onTargetPackageAdded packageName=" + packageName + " userId=" + userId); } - updateAndRefreshOverlaysForTarget(packageName, userId, 0); + return updateAndRefreshOverlaysForTarget(packageName, userId, 0); } - void onTargetPackageChanged(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> onTargetPackageChanged(@NonNull final String packageName, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "onTargetPackageChanged packageName=" + packageName + " userId=" + userId); } - updateAndRefreshOverlaysForTarget(packageName, userId, 0); + return updateAndRefreshOverlaysForTarget(packageName, userId, 0); } - void onTargetPackageReplacing(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> onTargetPackageReplacing(@NonNull final String packageName, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "onTargetPackageReplacing packageName=" + packageName + " userId=" + userId); } - updateAndRefreshOverlaysForTarget(packageName, userId, 0); + return updateAndRefreshOverlaysForTarget(packageName, userId, 0); } - void onTargetPackageReplaced(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> onTargetPackageReplaced(@NonNull final String packageName, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "onTargetPackageReplaced packageName=" + packageName + " userId=" + userId); } - updateAndRefreshOverlaysForTarget(packageName, userId, 0); + return updateAndRefreshOverlaysForTarget(packageName, userId, 0); } - void onTargetPackageRemoved(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> onTargetPackageRemoved(@NonNull final String packageName, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "onTargetPackageRemoved packageName=" + packageName + " userId=" + userId); } - updateAndRefreshOverlaysForTarget(packageName, userId, 0); + return updateAndRefreshOverlaysForTarget(packageName, userId, 0); } /** * Update the state of any overlays for this target. */ - private void updateAndRefreshOverlaysForTarget(@NonNull final String targetPackageName, - final int userId, final int flags) { + private Optional<PackageAndUser> updateAndRefreshOverlaysForTarget( + @NonNull final String targetPackageName, final int userId, final int flags) + throws OperationFailedException { final List<OverlayInfo> targetOverlays = mSettings.getOverlaysForTarget(targetPackageName, userId); @@ -364,11 +368,13 @@ final class OverlayManagerServiceImpl { } if (modified) { - mListener.onOverlaysChanged(targetPackageName, userId); + return Optional.of(new PackageAndUser(targetPackageName, userId)); } + return Optional.empty(); } - void onOverlayPackageAdded(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> onOverlayPackageAdded(@NonNull final String packageName, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "onOverlayPackageAdded packageName=" + packageName + " userId=" + userId); } @@ -376,8 +382,7 @@ final class OverlayManagerServiceImpl { final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId); if (overlayPackage == null) { Slog.w(TAG, "overlay package " + packageName + " was added, but couldn't be found"); - onOverlayPackageRemoved(packageName, userId); - return; + return onOverlayPackageRemoved(packageName, userId); } mSettings.init(packageName, userId, overlayPackage.overlayTarget, @@ -389,15 +394,17 @@ final class OverlayManagerServiceImpl { overlayPackage.overlayCategory); try { if (updateState(overlayPackage.overlayTarget, packageName, userId, 0)) { - mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId); + return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId)); } + return Optional.empty(); } catch (OverlayManagerSettings.BadKeyException e) { - Slog.e(TAG, "failed to update settings", e); mSettings.remove(packageName, userId); + throw new OperationFailedException("failed to update settings", e); } } - void onOverlayPackageChanged(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> onOverlayPackageChanged(@NonNull final String packageName, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "onOverlayPackageChanged packageName=" + packageName + " userId=" + userId); } @@ -405,14 +412,16 @@ final class OverlayManagerServiceImpl { try { final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId); if (updateState(oi.targetPackageName, packageName, userId, 0)) { - mListener.onOverlaysChanged(oi.targetPackageName, userId); + return Optional.of(new PackageAndUser(oi.targetPackageName, userId)); } + return Optional.empty(); } catch (OverlayManagerSettings.BadKeyException e) { - Slog.e(TAG, "failed to update settings", e); + throw new OperationFailedException("failed to update settings", e); } } - void onOverlayPackageReplacing(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> onOverlayPackageReplacing(@NonNull final String packageName, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "onOverlayPackageReplacing packageName=" + packageName + " userId=" + userId); @@ -423,14 +432,16 @@ final class OverlayManagerServiceImpl { if (updateState(oi.targetPackageName, packageName, userId, FLAG_OVERLAY_IS_BEING_REPLACED)) { removeIdmapIfPossible(oi); - mListener.onOverlaysChanged(oi.targetPackageName, userId); + return Optional.of(new PackageAndUser(oi.targetPackageName, userId)); } + return Optional.empty(); } catch (OverlayManagerSettings.BadKeyException e) { - Slog.e(TAG, "failed to update settings", e); + throw new OperationFailedException("failed to update settings", e); } } - void onOverlayPackageReplaced(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> onOverlayPackageReplaced(@NonNull final String packageName, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "onOverlayPackageReplaced packageName=" + packageName + " userId=" + userId); @@ -439,16 +450,12 @@ final class OverlayManagerServiceImpl { final PackageInfo pkg = mPackageManager.getPackageInfo(packageName, userId); if (pkg == null) { Slog.w(TAG, "overlay package " + packageName + " was replaced, but couldn't be found"); - onOverlayPackageRemoved(packageName, userId); - return; + return onOverlayPackageRemoved(packageName, userId); } try { final OverlayInfo oldOi = mSettings.getOverlayInfo(packageName, userId); if (mustReinitializeOverlay(pkg, oldOi)) { - if (oldOi != null && !oldOi.targetPackageName.equals(pkg.overlayTarget)) { - mListener.onOverlaysChanged(pkg.overlayTarget, userId); - } mSettings.init(packageName, userId, pkg.overlayTarget, pkg.targetOverlayableName, pkg.applicationInfo.getBaseCodePath(), isPackageConfiguredMutable(pkg.packageName), @@ -457,22 +464,25 @@ final class OverlayManagerServiceImpl { } if (updateState(pkg.overlayTarget, packageName, userId, 0)) { - mListener.onOverlaysChanged(pkg.overlayTarget, userId); + return Optional.of(new PackageAndUser(pkg.overlayTarget, userId)); } + return Optional.empty(); } catch (OverlayManagerSettings.BadKeyException e) { - Slog.e(TAG, "failed to update settings", e); + throw new OperationFailedException("failed to update settings", e); } } - void onOverlayPackageRemoved(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> onOverlayPackageRemoved(@NonNull final String packageName, + final int userId) throws OperationFailedException { try { final OverlayInfo overlayInfo = mSettings.getOverlayInfo(packageName, userId); if (mSettings.remove(packageName, userId)) { removeIdmapIfPossible(overlayInfo); - mListener.onOverlaysChanged(overlayInfo.targetPackageName, userId); + return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId)); } + return Optional.empty(); } catch (OverlayManagerSettings.BadKeyException e) { - Slog.e(TAG, "failed to remove overlay", e); + throw new OperationFailedException("failed to remove overlay", e); } } @@ -493,8 +503,8 @@ final class OverlayManagerServiceImpl { return mSettings.getOverlaysForUser(userId); } - boolean setEnabled(@NonNull final String packageName, final boolean enable, - final int userId) { + Optional<PackageAndUser> setEnabled(@NonNull final String packageName, final boolean enable, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, String.format("setEnabled packageName=%s enable=%s userId=%d", packageName, enable, userId)); @@ -502,30 +512,33 @@ final class OverlayManagerServiceImpl { final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId); if (overlayPackage == null) { - return false; + throw new OperationFailedException( + String.format("failed to find overlay package %s for user %d", + packageName, userId)); } try { final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId); if (!oi.isMutable) { // Ignore immutable overlays. - return false; + throw new OperationFailedException( + "cannot enable immutable overlay packages in runtime"); } boolean modified = mSettings.setEnabled(packageName, userId, enable); modified |= updateState(oi.targetPackageName, oi.packageName, userId, 0); if (modified) { - mListener.onOverlaysChanged(oi.targetPackageName, userId); + return Optional.of(new PackageAndUser(oi.targetPackageName, userId)); } - return true; + return Optional.empty(); } catch (OverlayManagerSettings.BadKeyException e) { - return false; + throw new OperationFailedException("failed to update settings", e); } } - boolean setEnabledExclusive(@NonNull final String packageName, boolean withinCategory, - final int userId) { + Optional<PackageAndUser> setEnabledExclusive(@NonNull final String packageName, + boolean withinCategory, final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, String.format("setEnabledExclusive packageName=%s" + " withinCategory=%s userId=%d", packageName, withinCategory, userId)); @@ -533,7 +546,8 @@ final class OverlayManagerServiceImpl { final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId); if (overlayPackage == null) { - return false; + throw new OperationFailedException(String.format( + "failed to find overlay package %s for user %d", packageName, userId)); } try { @@ -576,11 +590,11 @@ final class OverlayManagerServiceImpl { modified |= updateState(targetPackageName, packageName, userId, 0); if (modified) { - mListener.onOverlaysChanged(targetPackageName, userId); + return Optional.of(new PackageAndUser(targetPackageName, userId)); } - return true; + return Optional.empty(); } catch (OverlayManagerSettings.BadKeyException e) { - return false; + throw new OperationFailedException("failed to update settings", e); } } @@ -596,66 +610,75 @@ final class OverlayManagerServiceImpl { return mOverlayConfig.isEnabled(packageName); } - boolean setPriority(@NonNull final String packageName, - @NonNull final String newParentPackageName, final int userId) { + Optional<PackageAndUser> setPriority(@NonNull final String packageName, + @NonNull final String newParentPackageName, final int userId) + throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "setPriority packageName=" + packageName + " newParentPackageName=" + newParentPackageName + " userId=" + userId); } if (!isPackageConfiguredMutable(packageName)) { - return false; + throw new OperationFailedException(String.format( + "overlay package %s user %d is not updatable", packageName, userId)); } final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId); if (overlayPackage == null) { - return false; + throw new OperationFailedException(String.format( + "failed to find overlay package %s for user %d", packageName, userId)); } if (mSettings.setPriority(packageName, newParentPackageName, userId)) { - mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId); + return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId)); } - return true; + return Optional.empty(); } - boolean setHighestPriority(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> setHighestPriority(@NonNull final String packageName, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "setHighestPriority packageName=" + packageName + " userId=" + userId); } if (!isPackageConfiguredMutable(packageName)) { - return false; + throw new OperationFailedException(String.format( + "overlay package %s user %d is not updatable", packageName, userId)); } final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId); if (overlayPackage == null) { - return false; + throw new OperationFailedException(String.format( + "failed to find overlay package %s for user %d", packageName, userId)); } if (mSettings.setHighestPriority(packageName, userId)) { - mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId); + return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId)); } - return true; + return Optional.empty(); } - boolean setLowestPriority(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> setLowestPriority(@NonNull final String packageName, final int userId) + throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "setLowestPriority packageName=" + packageName + " userId=" + userId); } if (!isPackageConfiguredMutable(packageName)) { - return false; + throw new OperationFailedException(String.format( + "overlay package %s user %d is not updatable", packageName, userId)); } final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId); if (overlayPackage == null) { - return false; + throw new OperationFailedException(String.format( + "failed to find overlay package %s for user %d", packageName, userId)); } if (mSettings.setLowestPriority(packageName, userId)) { - mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId); + return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId)); } - return true; + return Optional.empty(); } void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) { @@ -797,12 +820,13 @@ final class OverlayManagerServiceImpl { mIdmapManager.removeIdmap(oi, oi.userId); } - interface OverlayChangeListener { + static final class OperationFailedException extends Exception { + OperationFailedException(@NonNull final String message) { + super(message); + } - /** - * An event triggered by changes made to overlay state or settings as well as changes that - * add or remove target packages of overlays. - **/ - void onOverlaysChanged(@NonNull String targetPackage, int userId); + OperationFailedException(@NonNull final String message, @NonNull Throwable cause) { + super(message, cause); + } } } diff --git a/services/core/java/com/android/server/om/PackageAndUser.java b/services/core/java/com/android/server/om/PackageAndUser.java new file mode 100644 index 000000000000..5c38ba7ce97b --- /dev/null +++ b/services/core/java/com/android/server/om/PackageAndUser.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.om; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; + +final class PackageAndUser { + public final @NonNull String packageName; + public final @UserIdInt int userId; + + PackageAndUser(@NonNull String packageName, @UserIdInt int userId) { + this.packageName = packageName; + this.userId = userId; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PackageAndUser)) { + return false; + } + PackageAndUser other = (PackageAndUser) obj; + return packageName.equals(other.packageName) && userId == other.userId; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + packageName.hashCode(); + result = prime * result + userId; + return result; + } + + @Override + public String toString() { + return String.format("PackageAndUser{packageName=%s, userId=%d}", packageName, userId); + } +} diff --git a/services/core/java/com/android/server/pm/MULTIUSER_AND_ENTERPRISE_OWNERS b/services/core/java/com/android/server/pm/MULTIUSER_AND_ENTERPRISE_OWNERS new file mode 100644 index 000000000000..a52e9cf2f4c3 --- /dev/null +++ b/services/core/java/com/android/server/pm/MULTIUSER_AND_ENTERPRISE_OWNERS @@ -0,0 +1,7 @@ +# OWNERS of Multiuser related files related to Enterprise + +include /MULTIUSER_OWNERS + +# Enterprise owners +rubinxu@google.com +sandness@google.com diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS index 004259b7478c..43c5d5e4015e 100644 --- a/services/core/java/com/android/server/pm/OWNERS +++ b/services/core/java/com/android/server/pm/OWNERS @@ -30,13 +30,12 @@ per-file CrossProfileAppsServiceImpl.java = omakoto@google.com, yamasani@google. per-file CrossProfileAppsService.java = omakoto@google.com, yamasani@google.com per-file CrossProfileIntentFilter.java = omakoto@google.com, yamasani@google.com per-file CrossProfileIntentResolver.java = omakoto@google.com, yamasani@google.com -per-file RestrictionsSet.java = bookatz@google.com, omakoto@google.com, yamasani@google.com, rubinxu@google.com, sandness@google.com -per-file UserManagerInternal.java = bookatz@google.com, omakoto@google.com, yamasani@google.com -per-file UserManagerService.java = bookatz@google.com, omakoto@google.com, yamasani@google.com -per-file UserRestrictionsUtils.java = omakoto@google.com, rubinxu@google.com, sandness@google.com, yamasani@google.com -per-file UserSystemPackageInstaller.java = bookatz@google.com, omakoto@google.com, yamasani@google.com -per-file UserTypeDetails.java = bookatz@google.com, omakoto@google.com, yamasani@google.com -per-file UserTypeFactory.java = bookatz@google.com, omakoto@google.com, yamasani@google.com +per-file RestrictionsSet.java = file:MULTIUSER_AND_ENTERPRISE_OWNERS +per-file UserManager* = file:/MULTIUSER_OWNERS +per-file UserRestriction* = file:MULTIUSER_AND_ENTERPRISE_OWNERS +per-file UserSystemPackageInstaller* = file:/MULTIUSER_OWNERS +per-file UserTypeDetails.java = file:/MULTIUSER_OWNERS +per-file UserTypeFactory.java = file:/MULTIUSER_OWNERS # security per-file KeySetHandle.java = cbrubaker@google.com, nnk@google.com diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index c121d24099f3..a7b9622ab3c0 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -331,6 +331,7 @@ import com.android.internal.os.SomeArgs; import com.android.internal.os.Zygote; import com.android.internal.telephony.CarrierAppUtils; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.CollectionUtils; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; @@ -12455,12 +12456,17 @@ public class PackageManagerService extends IPackageManager.Stub mPermissionManager.addAllPermissionGroups(pkg, chatty); } + // If a permission has had its defining app changed, or it has had its protection + // upgraded, we need to revoke apps that hold it + final List<String> permissionsWithChangedDefinition; // Don't allow ephemeral applications to define new permissions. if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) { + permissionsWithChangedDefinition = null; Slog.w(TAG, "Permissions from package " + pkg.getPackageName() + " ignored: instant apps cannot define new permissions."); } else { - mPermissionManager.addAllPermissions(pkg, chatty); + permissionsWithChangedDefinition = + mPermissionManager.addAllPermissions(pkg, chatty); } int collectionSize = ArrayUtils.size(pkg.getInstrumentations()); @@ -12489,7 +12495,10 @@ public class PackageManagerService extends IPackageManager.Stub } } - if (oldPkg != null) { + boolean hasOldPkg = oldPkg != null; + boolean hasPermissionDefinitionChanges = + !CollectionUtils.isEmpty(permissionsWithChangedDefinition); + if (hasOldPkg || hasPermissionDefinitionChanges) { // We need to call revokeRuntimePermissionsIfGroupChanged async as permission // revoke callbacks from this method might need to kill apps which need the // mPackages lock on a different thread. This would dead lock. @@ -12500,9 +12509,16 @@ public class PackageManagerService extends IPackageManager.Stub // won't be granted yet, hence new packages are no problem. final ArrayList<String> allPackageNames = new ArrayList<>(mPackages.keySet()); - AsyncTask.execute(() -> + AsyncTask.execute(() -> { + if (hasOldPkg) { mPermissionManager.revokeRuntimePermissionsIfGroupChanged(pkg, oldPkg, - allPackageNames)); + allPackageNames); + } + if (hasPermissionDefinitionChanges) { + mPermissionManager.revokeRuntimePermissionsIfPermissionDefinitionChanged( + permissionsWithChangedDefinition, allPackageNames); + } + }); } } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index f5d7d9eda1ba..9aa47a67d4e2 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -3724,7 +3724,7 @@ public class UserManagerService extends IUserManager.Stub { UserData putUserInfo(UserInfo userInfo) { final UserData userData = new UserData(); userData.info = userInfo; - synchronized (mUsers) { + synchronized (mUsersLock) { mUsers.put(userInfo.id, userData); } return userData; @@ -3732,7 +3732,7 @@ public class UserManagerService extends IUserManager.Stub { @VisibleForTesting void removeUserInfo(@UserIdInt int userId) { - synchronized (mUsers) { + synchronized (mUsersLock) { mUsers.remove(userId); } } @@ -4052,7 +4052,7 @@ public class UserManagerService extends IUserManager.Stub { userFile.delete(); updateUserIds(); if (RELEASE_DELETED_USER_ID) { - synchronized (mUsers) { + synchronized (mUsersLock) { mRemovingUserIds.delete(userId); } } @@ -5183,6 +5183,9 @@ public class UserManagerService extends IUserManager.Stub { debugMsg + " for another profile " + targetUserId + " from " + callingUserId); } + Slog.w(LOG_TAG, debugMsg + " for another profile " + + targetUserId + " from " + callingUserId); + return false; } UserInfo targetUserInfo = getUserInfoLU(targetUserId); diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java index cfa0449aaf33..5e04171a3bca 100644 --- a/services/core/java/com/android/server/pm/permission/BasePermission.java +++ b/services/core/java/com/android/server/pm/permission/BasePermission.java @@ -83,6 +83,8 @@ public final class BasePermission { final @PermissionType int type; + private boolean mPermissionDefinitionChanged; + String sourcePackageName; int protectionLevel; @@ -126,6 +128,11 @@ public final class BasePermission { public String getSourcePackageName() { return sourcePackageName; } + + public boolean isPermissionDefinitionChanged() { + return mPermissionDefinitionChanged; + } + public int getType() { return type; } @@ -140,6 +147,10 @@ public final class BasePermission { this.perm = perm; } + public void setPermissionDefinitionChanged(boolean shouldOverride) { + mPermissionDefinitionChanged = shouldOverride; + } + public int[] computeGids(int userId) { if (perUser) { final int[] userGids = new int[gids.length]; @@ -322,6 +333,7 @@ public final class BasePermission { final PackageSettingBase pkgSetting = (PackageSettingBase) packageManagerInternal.getPackageSetting(pkg.getPackageName()); // Allow system apps to redefine non-system permissions + boolean ownerChanged = false; if (bp != null && !Objects.equals(bp.sourcePackageName, p.getPackageName())) { final boolean currentOwnerIsSystem; if (bp.perm == null) { @@ -347,6 +359,7 @@ public final class BasePermission { String msg = "New decl " + pkg + " of permission " + p.getName() + " is system; overriding " + bp.sourcePackageName; PackageManagerService.reportSettingsProblem(Log.WARN, msg); + ownerChanged = true; bp = null; } } @@ -354,6 +367,7 @@ public final class BasePermission { if (bp == null) { bp = new BasePermission(p.getName(), p.getPackageName(), TYPE_NORMAL); } + boolean wasNonRuntime = !bp.isRuntime(); StringBuilder r = null; if (bp.perm == null) { if (bp.sourcePackageName == null @@ -397,6 +411,11 @@ public final class BasePermission { && Objects.equals(bp.perm.getName(), p.getName())) { bp.protectionLevel = p.getProtectionLevel(); } + if (bp.isRuntime() && (ownerChanged || wasNonRuntime)) { + // If this is a runtime permission and the owner has changed, or this was a normal + // permission, then permission state should be cleaned up + bp.mPermissionDefinitionChanged = true; + } if (PackageManagerService.DEBUG_PACKAGE_SCANNING && r != null) { Log.d(TAG, " Permissions: " + r); } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 66d8b5974261..3ffca028b1c0 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -2344,8 +2344,74 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } - private void addAllPermissions(AndroidPackage pkg, boolean chatty) { + /** + * If permissions are upgraded to runtime, or their owner changes to the system, then any + * granted permissions must be revoked. + * + * @param permissionsToRevoke A list of permission names to revoke + * @param allPackageNames All package names + * @param permissionCallback Callback for permission changed + */ + private void revokeRuntimePermissionsIfPermissionDefinitionChanged( + @NonNull List<String> permissionsToRevoke, + @NonNull ArrayList<String> allPackageNames, + @NonNull PermissionCallback permissionCallback) { + + final int[] userIds = mUserManagerInt.getUserIds(); + final int numPermissions = permissionsToRevoke.size(); + final int numUserIds = userIds.length; + final int numPackages = allPackageNames.size(); + final int callingUid = Binder.getCallingUid(); + + for (int permNum = 0; permNum < numPermissions; permNum++) { + String permName = permissionsToRevoke.get(permNum); + BasePermission bp = mSettings.getPermission(permName); + if (bp == null || !bp.isRuntime()) { + continue; + } + for (int userIdNum = 0; userIdNum < numUserIds; userIdNum++) { + final int userId = userIds[userIdNum]; + for (int packageNum = 0; packageNum < numPackages; packageNum++) { + final String packageName = allPackageNames.get(packageNum); + final int uid = mPackageManagerInt.getPackageUid(packageName, 0, userId); + if (uid < Process.FIRST_APPLICATION_UID) { + // do not revoke from system apps + continue; + } + final int permissionState = checkPermissionImpl(permName, packageName, + userId); + final int flags = getPermissionFlags(permName, packageName, userId); + final int flagMask = FLAG_PERMISSION_SYSTEM_FIXED + | FLAG_PERMISSION_POLICY_FIXED + | FLAG_PERMISSION_GRANTED_BY_DEFAULT + | FLAG_PERMISSION_GRANTED_BY_ROLE; + if (permissionState == PackageManager.PERMISSION_GRANTED + && (flags & flagMask) == 0) { + EventLog.writeEvent(0x534e4554, "154505240", uid, + "Revoking permission " + permName + " from package " + + packageName + " due to definition change"); + EventLog.writeEvent(0x534e4554, "168319670", uid, + "Revoking permission " + permName + " from package " + + packageName + " due to definition change"); + Slog.e(TAG, "Revoking permission " + permName + " from package " + + packageName + " due to definition change"); + try { + revokeRuntimePermissionInternal(permName, packageName, + false, callingUid, userId, null, permissionCallback); + } catch (Exception e) { + Slog.e(TAG, "Could not revoke " + permName + " from " + + packageName, e); + } + } + } + } + bp.setPermissionDefinitionChanged(false); + } + } + + private List<String> addAllPermissions(AndroidPackage pkg, boolean chatty) { final int N = ArrayUtils.size(pkg.getPermissions()); + ArrayList<String> definitionChangedPermissions = new ArrayList<>(); for (int i=0; i<N; i++) { ParsedPermission p = pkg.getPermissions().get(i); @@ -2367,21 +2433,26 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } + final BasePermission bp; if (p.isTree()) { - final BasePermission bp = BasePermission.createOrUpdate( + bp = BasePermission.createOrUpdate( mPackageManagerInt, mSettings.getPermissionTreeLocked(p.getName()), p, pkg, mSettings.getAllPermissionTreesLocked(), chatty); mSettings.putPermissionTreeLocked(p.getName(), bp); } else { - final BasePermission bp = BasePermission.createOrUpdate( + bp = BasePermission.createOrUpdate( mPackageManagerInt, mSettings.getPermissionLocked(p.getName()), p, pkg, mSettings.getAllPermissionTreesLocked(), chatty); mSettings.putPermissionLocked(p.getName(), bp); } + if (bp.isPermissionDefinitionChanged()) { + definitionChangedPermissions.add(p.getName()); + } } } + return definitionChangedPermissions; } private void addAllPermissionGroups(AndroidPackage pkg, boolean chatty) { @@ -4672,9 +4743,18 @@ public class PermissionManagerService extends IPermissionManager.Stub { PermissionManagerService.this.revokeRuntimePermissionsIfGroupChanged(newPackage, oldPackage, allPackageNames, mDefaultPermissionCallback); } + + @Override + public void revokeRuntimePermissionsIfPermissionDefinitionChanged( + @NonNull List<String> permissionsToRevoke, + @NonNull ArrayList<String> allPackageNames) { + PermissionManagerService.this.revokeRuntimePermissionsIfPermissionDefinitionChanged( + permissionsToRevoke, allPackageNames, mDefaultPermissionCallback); + } + @Override - public void addAllPermissions(AndroidPackage pkg, boolean chatty) { - PermissionManagerService.this.addAllPermissions(pkg, chatty); + public List<String> addAllPermissions(AndroidPackage pkg, boolean chatty) { + return PermissionManagerService.this.addAllPermissions(pkg, chatty); } @Override public void addAllPermissionGroups(AndroidPackage pkg, boolean chatty) { diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java index 37f40595450d..393e8527a991 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java @@ -254,12 +254,26 @@ public abstract class PermissionManagerServiceInternal extends PermissionManager @NonNull ArrayList<String> allPackageNames); /** + * Some permissions might have been owned by a non-system package, and the system then defined + * said permission. Some other permissions may one have been install permissions, but are now + * runtime or higher. These permissions should be revoked. + * + * @param permissionsToRevoke A list of permission names to revoke + * @param allPackageNames All packages + */ + public abstract void revokeRuntimePermissionsIfPermissionDefinitionChanged( + @NonNull List<String> permissionsToRevoke, + @NonNull ArrayList<String> allPackageNames); + + /** * Add all permissions in the given package. * <p> * NOTE: argument {@code groupTEMP} is temporary until mPermissionGroups is moved to * the permission settings. + * + * @return A list of BasePermissions that were updated, and need to be revoked from packages */ - public abstract void addAllPermissions(@NonNull AndroidPackage pkg, boolean chatty); + public abstract List<String> addAllPermissions(@NonNull AndroidPackage pkg, boolean chatty); public abstract void addAllPermissionGroups(@NonNull AndroidPackage pkg, boolean chatty); public abstract void removeAllPermissions(@NonNull AndroidPackage pkg, boolean chatty); diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java index c0608072df9d..d8a145d9ae33 100644 --- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java +++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java @@ -36,6 +36,8 @@ import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; +import android.telephony.TelephonyManager; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; @@ -79,6 +81,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { @NonNull private final TelephonySubscriptionTrackerCallback mCallback; @NonNull private final Dependencies mDeps; + @NonNull private final TelephonyManager mTelephonyManager; @NonNull private final SubscriptionManager mSubscriptionManager; @NonNull private final CarrierConfigManager mCarrierConfigManager; @@ -106,6 +109,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { mCallback = Objects.requireNonNull(callback, "Missing callback"); mDeps = Objects.requireNonNull(deps, "Missing deps"); + mTelephonyManager = mContext.getSystemService(TelephonyManager.class); mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class); mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class); @@ -139,7 +143,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { * so callbacks & broadcasts are all serialized on mHandler, avoiding the need for locking. */ public void handleSubscriptionsChanged() { - final Set<ParcelUuid> activeSubGroups = new ArraySet<>(); + final Map<ParcelUuid, Set<String>> privilegedPackages = new HashMap<>(); final Map<Integer, ParcelUuid> newSubIdToGroupMap = new HashMap<>(); final List<SubscriptionInfo> allSubs = mSubscriptionManager.getAllSubscriptionInfoList(); @@ -166,12 +170,22 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { // group. if (subInfo.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX && mReadySubIdsBySlotId.values().contains(subInfo.getSubscriptionId())) { - activeSubGroups.add(subInfo.getGroupUuid()); + // TODO (b/172619301): Cache based on callbacks from CarrierPrivilegesTracker + + final TelephonyManager subIdSpecificTelephonyManager = + mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId()); + + final ParcelUuid subGroup = subInfo.getGroupUuid(); + final Set<String> pkgs = + privilegedPackages.getOrDefault(subGroup, new ArraySet<>()); + pkgs.addAll(subIdSpecificTelephonyManager.getPackagesWithCarrierPrivileges()); + + privilegedPackages.put(subGroup, pkgs); } } final TelephonySubscriptionSnapshot newSnapshot = - new TelephonySubscriptionSnapshot(newSubIdToGroupMap, activeSubGroups); + new TelephonySubscriptionSnapshot(newSubIdToGroupMap, privilegedPackages); // If snapshot was meaningfully updated, fire the callback if (!newSnapshot.equals(mCurrentSnapshot)) { @@ -231,22 +245,40 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { /** TelephonySubscriptionSnapshot is a class containing info about active subscriptions */ public static class TelephonySubscriptionSnapshot { private final Map<Integer, ParcelUuid> mSubIdToGroupMap; - private final Set<ParcelUuid> mActiveGroups; + private final Map<ParcelUuid, Set<String>> mPrivilegedPackages; + + public static final TelephonySubscriptionSnapshot EMPTY_SNAPSHOT = + new TelephonySubscriptionSnapshot(Collections.emptyMap(), Collections.emptyMap()); @VisibleForTesting(visibility = Visibility.PRIVATE) TelephonySubscriptionSnapshot( @NonNull Map<Integer, ParcelUuid> subIdToGroupMap, - @NonNull Set<ParcelUuid> activeGroups) { - mSubIdToGroupMap = Collections.unmodifiableMap( - Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null")); - mActiveGroups = Collections.unmodifiableSet( - Objects.requireNonNull(activeGroups, "activeGroups was null")); + @NonNull Map<ParcelUuid, Set<String>> privilegedPackages) { + Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null"); + Objects.requireNonNull(privilegedPackages, "privilegedPackages was null"); + + mSubIdToGroupMap = Collections.unmodifiableMap(subIdToGroupMap); + + final Map<ParcelUuid, Set<String>> unmodifiableInnerSets = new ArrayMap<>(); + for (Entry<ParcelUuid, Set<String>> entry : privilegedPackages.entrySet()) { + unmodifiableInnerSets.put( + entry.getKey(), Collections.unmodifiableSet(entry.getValue())); + } + mPrivilegedPackages = Collections.unmodifiableMap(unmodifiableInnerSets); } /** Returns the active subscription groups */ @NonNull public Set<ParcelUuid> getActiveSubscriptionGroups() { - return mActiveGroups; + return mPrivilegedPackages.keySet(); + } + + /** Checks if the provided package is carrier privileged for the specified sub group. */ + public boolean packageHasPermissionsForSubscriptionGroup( + @NonNull ParcelUuid subGrp, @NonNull String packageName) { + final Set<String> privilegedPackages = mPrivilegedPackages.get(subGrp); + + return privilegedPackages != null && privilegedPackages.contains(packageName); } /** Returns the Subscription Group for a given subId. */ @@ -273,7 +305,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { @Override public int hashCode() { - return Objects.hash(mSubIdToGroupMap, mActiveGroups); + return Objects.hash(mSubIdToGroupMap, mPrivilegedPackages); } @Override @@ -285,7 +317,15 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { final TelephonySubscriptionSnapshot other = (TelephonySubscriptionSnapshot) obj; return mSubIdToGroupMap.equals(other.mSubIdToGroupMap) - && mActiveGroups.equals(other.mActiveGroups); + && mPrivilegedPackages.equals(other.mPrivilegedPackages); + } + + @Override + public String toString() { + return "TelephonySubscriptionSnapshot{ " + + "mSubIdToGroupMap=" + mSubIdToGroupMap + + ", mPrivilegedPackages=" + mPrivilegedPackages + + " }"; } } diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java index d51d16b1b4df..9d21b9241c0d 100644 --- a/services/core/java/com/android/server/vcn/Vcn.java +++ b/services/core/java/com/android/server/vcn/Vcn.java @@ -16,32 +16,69 @@ package com.android.server.vcn; + import android.annotation.NonNull; +import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.vcn.VcnConfig; +import android.net.vcn.VcnGatewayConnectionConfig; import android.os.Handler; import android.os.Message; import android.os.ParcelUuid; +import android.util.Slog; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; /** * Represents an single instance of a VCN. * - * <p>Each Vcn instance manages all tunnels for a given subscription group, including per-capability - * networks, network selection, and multi-homing. + * <p>Each Vcn instance manages all {@link VcnGatewayConnection}(s) for a given subscription group, + * including per-capability networks, network selection, and multi-homing. * * @hide */ public class Vcn extends Handler { private static final String TAG = Vcn.class.getSimpleName(); + private static final int MSG_EVENT_BASE = 0; + private static final int MSG_CMD_BASE = 100; + + /** + * A carrier app updated the configuration. + * + * <p>Triggers update of config, re-evaluating all active and underlying networks. + * + * @param obj VcnConfig + */ + private static final int MSG_EVENT_CONFIG_UPDATED = MSG_EVENT_BASE; + + /** + * A NetworkRequest was added or updated. + * + * <p>Triggers an evaluation of all active networks, bringing up a new one if necessary. + * + * @param obj NetworkRequest + */ + private static final int MSG_EVENT_NETWORK_REQUESTED = MSG_EVENT_BASE + 1; + + /** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */ + private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE; + @NonNull private final VcnContext mVcnContext; @NonNull private final ParcelUuid mSubscriptionGroup; @NonNull private final Dependencies mDeps; + @NonNull private final VcnNetworkRequestListener mRequestListener; + + @NonNull + private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections = + new HashMap<>(); @NonNull private VcnConfig mConfig; + private boolean mIsRunning = true; + public Vcn( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @@ -58,31 +95,123 @@ public class Vcn extends Handler { mVcnContext = vcnContext; mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); mDeps = Objects.requireNonNull(deps, "Missing deps"); + mRequestListener = new VcnNetworkRequestListener(); mConfig = Objects.requireNonNull(config, "Missing config"); + + // Register to receive cached and future NetworkRequests + mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener); } /** Asynchronously updates the configuration and triggers a re-evaluation of Networks */ public void updateConfig(@NonNull VcnConfig config) { Objects.requireNonNull(config, "Missing config"); - // TODO: Proxy to handler, and make config there. + + sendMessage(obtainMessage(MSG_EVENT_CONFIG_UPDATED, config)); } - /** Asynchronously tears down this Vcn instance, along with all tunnels and Networks */ - public void teardown() { - // TODO: Proxy to handler, and teardown there. + /** Asynchronously tears down this Vcn instance, including VcnGatewayConnection(s) */ + public void teardownAsynchronously() { + sendMessageAtFrontOfQueue(obtainMessage(MSG_CMD_TEARDOWN)); } - /** Notifies this Vcn instance of a new NetworkRequest */ - public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) { - Objects.requireNonNull(request, "Missing request"); + private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener { + @Override + public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) { + Objects.requireNonNull(request, "Missing request"); - // TODO: Proxy to handler, and handle there. + sendMessage(obtainMessage(MSG_EVENT_NETWORK_REQUESTED, score, providerId, request)); + } } @Override public void handleMessage(@NonNull Message msg) { - // TODO: Do something + if (!mIsRunning) { + return; + } + + switch (msg.what) { + case MSG_EVENT_CONFIG_UPDATED: + handleConfigUpdated((VcnConfig) msg.obj); + break; + case MSG_EVENT_NETWORK_REQUESTED: + handleNetworkRequested((NetworkRequest) msg.obj, msg.arg1, msg.arg2); + break; + case MSG_CMD_TEARDOWN: + handleTeardown(); + break; + default: + Slog.wtf(getLogTag(), "Unknown msg.what: " + msg.what); + } + } + + private void handleConfigUpdated(@NonNull VcnConfig config) { + // TODO: Add a dump function in VcnConfig that omits PII. Until then, use hashCode() + Slog.v(getLogTag(), String.format("Config updated: config = %s", config.hashCode())); + + mConfig = config; + + // TODO: Reevaluate active VcnGatewayConnection(s) + } + + private void handleTeardown() { + mVcnContext.getVcnNetworkProvider().unregisterListener(mRequestListener); + + for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) { + gatewayConnection.teardownAsynchronously(); + } + + mIsRunning = false; + } + + private void handleNetworkRequested( + @NonNull NetworkRequest request, int score, int providerId) { + if (score > getNetworkScore()) { + Slog.v(getLogTag(), + "Request " + request.requestId + " already satisfied by higher-scoring (" + + score + ") network from provider " + providerId); + return; + } + + // If preexisting VcnGatewayConnection(s) satisfy request, return + for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) { + if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) { + Slog.v(getLogTag(), + "Request " + request.requestId + + " satisfied by existing VcnGatewayConnection"); + return; + } + } + + // If any supported (but not running) VcnGatewayConnection(s) can satisfy request, bring it + // up + for (VcnGatewayConnectionConfig gatewayConnectionConfig : + mConfig.getGatewayConnectionConfigs()) { + if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) { + Slog.v( + getLogTag(), + "Bringing up new VcnGatewayConnection for request " + request.requestId); + + final VcnGatewayConnection vcnGatewayConnection = + new VcnGatewayConnection( + mVcnContext, mSubscriptionGroup, gatewayConnectionConfig); + mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection); + } + } + } + + private boolean requestSatisfiedByGatewayConnectionConfig( + @NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) { + final NetworkCapabilities configCaps = new NetworkCapabilities(); + for (int cap : config.getAllExposedCapabilities()) { + configCaps.addCapability(cap); + } + + return request.networkCapabilities.satisfiedByNetworkCapabilities(configCaps); + } + + private String getLogTag() { + return String.format("%s [%d]", TAG, mSubscriptionGroup.hashCode()); } /** Retrieves the network score for a VCN Network */ diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java index 8ab52931cae3..dba59bdbee7d 100644 --- a/services/core/java/com/android/server/vcn/VcnContext.java +++ b/services/core/java/com/android/server/vcn/VcnContext.java @@ -20,8 +20,6 @@ import android.annotation.NonNull; import android.content.Context; import android.os.Looper; -import com.android.server.VcnManagementService.VcnNetworkProvider; - import java.util.Objects; /** diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 49c9b3297c77..7ea8e04580f7 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -65,8 +65,8 @@ public class VcnGatewayConnection extends Handler implements UnderlyingNetworkTr mDeps.newUnderlyingNetworkTracker(mVcnContext, subscriptionGroup, this); } - /** Tears down this GatewayConnection, and any resources used */ - public void teardown() { + /** Asynchronously tears down this GatewayConnection, and any resources used */ + public void teardownAsynchronously() { mUnderlyingNetworkTracker.teardown(); } diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java new file mode 100644 index 000000000000..7f5b23c9db6f --- /dev/null +++ b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vcn; + +import android.annotation.NonNull; +import android.content.Context; +import android.net.NetworkProvider; +import android.net.NetworkRequest; +import android.os.Looper; +import android.util.ArraySet; +import android.util.Slog; +import android.util.SparseArray; + +import java.util.Objects; +import java.util.Set; + +/** + * VCN Network Provider routes NetworkRequests to listeners to bring up tunnels as needed. + * + * <p>The VcnNetworkProvider provides a caching layer to ensure that all listeners receive all + * active NetworkRequest(s), including ones that were filed prior to listener registration. + * + * @hide + */ +public class VcnNetworkProvider extends NetworkProvider { + private static final String TAG = VcnNetworkProvider.class.getSimpleName(); + + private final Set<NetworkRequestListener> mListeners = new ArraySet<>(); + private final SparseArray<NetworkRequestEntry> mRequests = new SparseArray<>(); + + public VcnNetworkProvider(Context context, Looper looper) { + super(context, looper, VcnNetworkProvider.class.getSimpleName()); + } + + // Package-private + void registerListener(@NonNull NetworkRequestListener listener) { + mListeners.add(listener); + + // Send listener all cached requests + for (int i = 0; i < mRequests.size(); i++) { + notifyListenerForEvent(listener, mRequests.valueAt(i)); + } + } + + // Package-private + void unregisterListener(@NonNull NetworkRequestListener listener) { + mListeners.remove(listener); + } + + private void notifyListenerForEvent( + @NonNull NetworkRequestListener listener, @NonNull NetworkRequestEntry entry) { + listener.onNetworkRequested(entry.mRequest, entry.mScore, entry.mProviderId); + } + + @Override + public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) { + Slog.v( + TAG, + String.format( + "Network requested: Request = %s, score = %d, providerId = %d", + request, score, providerId)); + + final NetworkRequestEntry entry = new NetworkRequestEntry(request, score, providerId); + mRequests.put(request.requestId, entry); + + // TODO(b/176939047): Intelligently route requests to prioritized VcnInstances (based on + // Default Data Sub, or similar) + for (NetworkRequestListener listener : mListeners) { + notifyListenerForEvent(listener, entry); + } + } + + @Override + public void onNetworkRequestWithdrawn(@NonNull NetworkRequest request) { + mRequests.remove(request.requestId); + } + + private static class NetworkRequestEntry { + public final NetworkRequest mRequest; + public final int mScore; + public final int mProviderId; + + private NetworkRequestEntry(@NonNull NetworkRequest request, int score, int providerId) { + mRequest = Objects.requireNonNull(request, "Missing request"); + mScore = score; + mProviderId = providerId; + } + } + + // package-private + interface NetworkRequestListener { + void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId); + } +} diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 4f95696dce88..e0db93a01efd 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -168,7 +168,6 @@ cc_defaults { static_libs: [ "android.hardware.broadcastradio@common-utils-1x-lib", - "libservice-connectivity-static", ], product_variables: { diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp index 00342866aa30..f076ca9afbea 100644 --- a/services/core/jni/com_android_server_am_BatteryStatsService.cpp +++ b/services/core/jni/com_android_server_am_BatteryStatsService.cpp @@ -46,6 +46,8 @@ #include <utils/misc.h> #include <utils/Log.h> +#include <android-base/strings.h> + using android::hardware::hidl_vec; using android::hardware::Return; using android::hardware::Void; @@ -200,67 +202,13 @@ static jint nativeWaitWakeup(JNIEnv *env, jobject clazz, jobject outBuf) return 0; } - char* mergedreasonpos = mergedreason; - int i = 0; - for (auto wakeupReason : wakeupReasons) { - auto reasonline = const_cast<char*>(wakeupReason.c_str()); - char* pos = reasonline; - char* endPos; - int len; - // First field is the index or 'Abort'. - int irq = (int)strtol(pos, &endPos, 10); - if (pos != endPos) { - // Write the irq number to the merged reason string. - len = snprintf(mergedreasonpos, remainreasonlen, i == 0 ? "%d" : ":%d", irq); - } else { - // The first field is not an irq, it may be the word Abort. - const size_t abortPrefixLen = strlen("Abort:"); - if (strncmp(pos, "Abort:", abortPrefixLen) != 0) { - // Ooops. - ALOGE("Bad reason line: %s", reasonline); - continue; - } - - // Write 'Abort' to the merged reason string. - len = snprintf(mergedreasonpos, remainreasonlen, i == 0 ? "Abort" : ":Abort"); - endPos = pos + abortPrefixLen; - } - pos = endPos; - - if (len >= 0 && len < remainreasonlen) { - mergedreasonpos += len; - remainreasonlen -= len; - } - - // Skip whitespace; rest of the buffer is the reason string. - while (*pos == ' ') { - pos++; - } + std::string mergedReasonStr = ::android::base::Join(wakeupReasons, ":"); + strncpy(mergedreason, mergedReasonStr.c_str(), remainreasonlen); + mergedreason[remainreasonlen - 1] = '\0'; - // Chop newline at end. - char* endpos = pos; - while (*endpos != 0) { - if (*endpos == '\n') { - *endpos = 0; - break; - } - endpos++; - } - - len = snprintf(mergedreasonpos, remainreasonlen, ":%s", pos); - if (len >= 0 && len < remainreasonlen) { - mergedreasonpos += len; - remainreasonlen -= len; - } - i++; - } - - ALOGV("Got %d reasons", i); - if (i > 0) { - *mergedreasonpos = 0; - } + ALOGV("Got %d reasons", (int)wakeupReasons.size()); - return mergedreasonpos - mergedreason; + return strlen(mergedreason); } // The caller must be holding gPowerHalMutex. diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 8cb3e6d1ae73..ccf685c1abd7 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -40,8 +40,6 @@ int register_android_server_UsbHostManager(JNIEnv* env); int register_android_server_vr_VrManagerService(JNIEnv* env); int register_android_server_VibratorService(JNIEnv* env); int register_android_server_location_GnssLocationProvider(JNIEnv* env); -int register_android_server_connectivity_Vpn(JNIEnv* env); -int register_android_server_TestNetworkService(JNIEnv* env); int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv*); int register_android_server_tv_TvUinputBridge(JNIEnv* env); int register_android_server_tv_TvInputHal(JNIEnv* env); @@ -93,8 +91,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_VibratorService(env); register_android_server_SystemServer(env); register_android_server_location_GnssLocationProvider(env); - register_android_server_connectivity_Vpn(env); - register_android_server_TestNetworkService(env); register_android_server_devicepolicy_CryptoTestHelper(env); register_android_server_ConsumerIrService(env); register_android_server_BatteryStatsService(env); diff --git a/services/core/xsd/platform-compat-config.xsd b/services/core/xsd/platform-compat-config.xsd index 992470816068..a62e2c385766 100644 --- a/services/core/xsd/platform-compat-config.xsd +++ b/services/core/xsd/platform-compat-config.xsd @@ -31,6 +31,7 @@ <xs:attribute type="xs:int" name="enableAfterTargetSdk"/> <xs:attribute type="xs:int" name="enableSinceTargetSdk"/> <xs:attribute type="xs:string" name="description"/> + <xs:attribute type="xs:boolean" name="overridable"/> </xs:extension> </xs:simpleContent> </xs:complexType> @@ -48,7 +49,3 @@ </xs:unique> </xs:element> </xs:schema> - - - - diff --git a/services/core/xsd/platform-compat-schema/current.txt b/services/core/xsd/platform-compat-schema/current.txt index e3640edd0201..fb8bbefd8374 100644 --- a/services/core/xsd/platform-compat-schema/current.txt +++ b/services/core/xsd/platform-compat-schema/current.txt @@ -10,6 +10,7 @@ package com.android.server.compat.config { method public long getId(); method public boolean getLoggingOnly(); method public String getName(); + method public boolean getOverridable(); method public String getValue(); method public void setDescription(String); method public void setDisabled(boolean); @@ -18,6 +19,7 @@ package com.android.server.compat.config { method public void setId(long); method public void setLoggingOnly(boolean); method public void setName(String); + method public void setOverridable(boolean); method public void setValue(String); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index b5c5bb52d050..4cd1348a0f7a 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -221,6 +221,8 @@ public final class SystemServer { "com.android.server.companion.CompanionDeviceManagerService"; private static final String STATS_COMPANION_APEX_PATH = "/apex/com.android.os.statsd/javalib/service-statsd.jar"; + private static final String CONNECTIVITY_SERVICE_APEX_PATH = + "/apex/com.android.tethering/javalib/service-connectivity.jar"; private static final String STATS_COMPANION_LIFECYCLE_CLASS = "com.android.server.stats.StatsCompanion$Lifecycle"; private static final String STATS_PULL_ATOM_SERVICE_CLASS = @@ -910,6 +912,9 @@ public final class SystemServer { mActivityManagerService.setSystemProcess(); t.traceEnd(); + // The package receiver depends on the activity service in order to get registered. + platformCompat.registerPackageReceiver(mSystemContext); + // Complete the watchdog setup with an ActivityManager instance and listen for reboots // Do this only after the ActivityManagerService is properly started as a system process t.traceBegin("InitWatchdog"); @@ -1558,8 +1563,8 @@ public final class SystemServer { // This has to be called after NetworkManagementService, NetworkStatsService // and NetworkPolicyManager because ConnectivityService needs to take these // services to initialize. - // TODO: Dynamically load service-connectivity.jar by using startServiceFromJar. - mSystemServiceManager.startService(CONNECTIVITY_SERVICE_INITIALIZER_CLASS); + mSystemServiceManager.startServiceFromJar(CONNECTIVITY_SERVICE_INITIALIZER_CLASS, + CONNECTIVITY_SERVICE_APEX_PATH); connectivity = IConnectivityManager.Stub.asInterface( ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); // TODO: Use ConnectivityManager instead of ConnectivityService. diff --git a/services/musicrecognition/OWNERS b/services/musicrecognition/OWNERS new file mode 100644 index 000000000000..58f5d40dd8c3 --- /dev/null +++ b/services/musicrecognition/OWNERS @@ -0,0 +1,6 @@ +# Bug component: 830636 + +joannechung@google.com +oni@google.com +volnov@google.com + diff --git a/services/net/Android.bp b/services/net/Android.bp index eaf177e1e3c8..e0bb67a7e6d8 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -13,7 +13,7 @@ java_library_static { ":services.net-sources", ], static_libs: [ - "netd_aidl_interfaces-platform-java", + "netd-client", "netlink-client", "networkstack-client", "net-utils-services-common", diff --git a/services/tests/mockingservicestests/src/com/android/server/OWNERS b/services/tests/mockingservicestests/src/com/android/server/OWNERS index e779e21bb987..c0f0ce047da6 100644 --- a/services/tests/mockingservicestests/src/com/android/server/OWNERS +++ b/services/tests/mockingservicestests/src/com/android/server/OWNERS @@ -1 +1,3 @@ per-file *Alarm* = file:/apex/jobscheduler/OWNERS +per-file *AppStateTracker* = file:/apex/jobscheduler/OWNERS +per-file *DeviceIdleController* = file:/apex/jobscheduler/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/OWNERS b/services/tests/servicestests/src/com/android/server/apphibernation/OWNERS new file mode 100644 index 000000000000..c2e27e084c8c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/apphibernation/OWNERS @@ -0,0 +1 @@ +include /core/java/android/apphibernation/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java index 870fe4a0837e..f00edcc85404 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java @@ -40,83 +40,89 @@ class CompatConfigBuilder { } CompatConfigBuilder addEnableAfterSdkChangeWithId(int sdk, long id) { - mChanges.add(new CompatChange(id, "", sdk, -1, false, false, "")); + mChanges.add(new CompatChange(id, "", sdk, -1, false, false, "", false)); return this; } CompatConfigBuilder addEnableAfterSdkChangeWithIdAndName(int sdk, long id, String name) { - mChanges.add(new CompatChange(id, name, sdk, -1, false, false, "")); + mChanges.add(new CompatChange(id, name, sdk, -1, false, false, "", false)); return this; } CompatConfigBuilder addEnableAfterSdkChangeWithIdDefaultDisabled(int sdk, long id) { - mChanges.add(new CompatChange(id, "", sdk, -1, true, false, "")); + mChanges.add(new CompatChange(id, "", sdk, -1, true, false, "", false)); return this; } CompatConfigBuilder addEnableAfterSdkChangeWithIdAndDescription(int sdk, long id, String description) { - mChanges.add(new CompatChange(id, "", sdk, -1, false, false, description)); + mChanges.add(new CompatChange(id, "", sdk, -1, false, false, description, false)); return this; } CompatConfigBuilder addEnableSinceSdkChangeWithId(int sdk, long id) { - mChanges.add(new CompatChange(id, "", -1, sdk, false, false, "")); + mChanges.add(new CompatChange(id, "", -1, sdk, false, false, "", false)); return this; } CompatConfigBuilder addEnableSinceSdkChangeWithIdAndName(int sdk, long id, String name) { - mChanges.add(new CompatChange(id, name, -1, sdk, false, false, "")); + mChanges.add(new CompatChange(id, name, -1, sdk, false, false, "", false)); return this; } CompatConfigBuilder addEnableSinceSdkChangeWithIdDefaultDisabled(int sdk, long id) { - mChanges.add(new CompatChange(id, "", -1, sdk, true, false, "")); + mChanges.add(new CompatChange(id, "", -1, sdk, true, false, "", false)); return this; } CompatConfigBuilder addEnableSinceSdkChangeWithIdAndDescription(int sdk, long id, String description) { - mChanges.add(new CompatChange(id, "", -1, sdk, false, false, description)); + mChanges.add(new CompatChange(id, "", -1, sdk, false, false, description, false)); return this; } CompatConfigBuilder addEnabledChangeWithId(long id) { - mChanges.add(new CompatChange(id, "", -1, -1, false, false, "")); + mChanges.add(new CompatChange(id, "", -1, -1, false, false, "", false)); return this; } CompatConfigBuilder addEnabledChangeWithIdAndName(long id, String name) { - mChanges.add(new CompatChange(id, name, -1, -1, false, false, "")); + mChanges.add(new CompatChange(id, name, -1, -1, false, false, "", false)); return this; } CompatConfigBuilder addEnabledChangeWithIdAndDescription(long id, String description) { - mChanges.add(new CompatChange(id, "", -1, -1, false, false, description)); + mChanges.add(new CompatChange(id, "", -1, -1, false, false, description, false)); return this; } CompatConfigBuilder addDisabledChangeWithId(long id) { - mChanges.add(new CompatChange(id, "", -1, -1, true, false, "")); + mChanges.add(new CompatChange(id, "", -1, -1, true, false, "", false)); return this; } CompatConfigBuilder addDisabledChangeWithIdAndName(long id, String name) { - mChanges.add(new CompatChange(id, name, -1, -1, true, false, "")); + mChanges.add(new CompatChange(id, name, -1, -1, true, false, "", false)); return this; } CompatConfigBuilder addDisabledChangeWithIdAndDescription(long id, String description) { - mChanges.add(new CompatChange(id, "", -1, -1, true, false, description)); + mChanges.add(new CompatChange(id, "", -1, -1, true, false, description, false)); return this; } CompatConfigBuilder addLoggingOnlyChangeWithId(long id) { - mChanges.add(new CompatChange(id, "", -1, -1, false, true, "")); + mChanges.add(new CompatChange(id, "", -1, -1, false, true, "", false)); + return this; + } + + CompatConfigBuilder addOverridableChangeWithId(long id) { + mChanges.add(new CompatChange(id, "", -1, -1, false, true, "", true)); return this; } CompatConfig build() { CompatConfig config = new CompatConfig(mBuildClassifier, mContext); + config.forceNonDebuggableFinalForTest(false); for (CompatChange change : mChanges) { config.addChange(change); } diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java index 8c63bfcf1407..ac8dc341999a 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java @@ -28,6 +28,7 @@ import android.app.compat.ChangeIdStateCache; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import androidx.test.runner.AndroidJUnit4; @@ -81,6 +82,8 @@ public class CompatConfigTest { @Test public void testUnknownChangeEnabled() throws Exception { CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext); + compatConfig.forceNonDebuggableFinalForTest(false); + assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create().build())) .isTrue(); } @@ -180,6 +183,8 @@ public class CompatConfigTest { @Test public void testPackageOverrideUnknownPackage() throws Exception { CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext); + compatConfig.forceNonDebuggableFinalForTest(false); + compatConfig.addOverride(1234L, "com.some.package", false); @@ -230,6 +235,83 @@ public class CompatConfigTest { } @Test + public void testApplyDeferredOverridesAfterInstallingApp() throws Exception { + ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() + .withPackageName("com.notinstalled.foo") + .debuggable().build(); + when(mPackageManager.getApplicationInfo(eq("com.notinstalled.foo"), anyInt())) + .thenThrow(new NameNotFoundException()); + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1234L).build(); + when(mBuildClassifier.isDebuggableBuild()).thenReturn(false); + when(mBuildClassifier.isFinalBuild()).thenReturn(true); + + // Add override before the app is available. + compatConfig.addOverride(1234L, "com.notinstalled.foo", true); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); + + // Pretend the app is now installed. + when(mPackageManager.getApplicationInfo(eq("com.notinstalled.foo"), anyInt())) + .thenReturn(applicationInfo); + + compatConfig.recheckOverrides("com.notinstalled.foo"); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue(); + } + + @Test + public void testApplyDeferredOverrideClearsOverrideAfterUninstall() throws Exception { + ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() + .withPackageName("com.installedapp.foo") + .debuggable().build(); + when(mPackageManager.getApplicationInfo(eq("com.installedapp.foo"), anyInt())) + .thenReturn(applicationInfo); + + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1234L).build(); + when(mBuildClassifier.isDebuggableBuild()).thenReturn(false); + when(mBuildClassifier.isFinalBuild()).thenReturn(true); + + // Add override when app is installed. + compatConfig.addOverride(1234L, "com.installedapp.foo", true); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue(); + + // Pretend the app is now uninstalled. + when(mPackageManager.getApplicationInfo(eq("com.installedapp.foo"), anyInt())) + .thenThrow(new NameNotFoundException()); + + compatConfig.recheckOverrides("com.installedapp.foo"); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); + } + + @Test + public void testApplyDeferredOverrideClearsOverrideAfterChange() throws Exception { + ApplicationInfo debuggableApp = ApplicationInfoBuilder.create() + .withPackageName("com.installedapp.foo") + .debuggable().build(); + ApplicationInfo releaseApp = ApplicationInfoBuilder.create() + .withPackageName("com.installedapp.foo") + .build(); + when(mPackageManager.getApplicationInfo(eq("com.installedapp.foo"), anyInt())) + .thenReturn(debuggableApp); + + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1234L).build(); + when(mBuildClassifier.isDebuggableBuild()).thenReturn(false); + when(mBuildClassifier.isFinalBuild()).thenReturn(true); + + // Add override for debuggable app. + compatConfig.addOverride(1234L, "com.installedapp.foo", true); + assertThat(compatConfig.isChangeEnabled(1234L, debuggableApp)).isTrue(); + + // Pretend the app now is no longer debuggable, but has the same package. + when(mPackageManager.getApplicationInfo(eq("com.installedapp.foo"), anyInt())) + .thenReturn(releaseApp); + + compatConfig.recheckOverrides("com.installedapp.foo"); + assertThat(compatConfig.isChangeEnabled(1234L, releaseApp)).isFalse(); + } + + @Test public void testLoggingOnlyChangePreventAddOverride() throws Exception { CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) .addLoggingOnlyChangeWithId(1234L) @@ -259,7 +341,7 @@ public class CompatConfigTest { // Reject all override attempts. // Force the validator to prevent overriding the change by using a user build. when(mBuildClassifier.isDebuggableBuild()).thenReturn(false); - when(mBuildClassifier.isFinalBuild()).thenReturn(true); + when(mBuildClassifier.isFinalBuild()).thenReturn(false); // Try to turn off change, but validator prevents it. assertThrows(SecurityException.class, () -> compatConfig.removeOverride(1234L, "com.some.package")); @@ -360,6 +442,8 @@ public class CompatConfigTest { @Test public void testLookupChangeIdNotPresent() throws Exception { CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext); + compatConfig.forceNonDebuggableFinalForTest(false); + assertThat(compatConfig.lookupChangeId("MY_CHANGE")).isEqualTo(-1L); } @@ -374,6 +458,8 @@ public class CompatConfigTest { File dir = createTempDir(); writeToFile(dir, "platform_compat_config.xml", configXml); CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext); + compatConfig.forceNonDebuggableFinalForTest(false); + compatConfig.initConfigFromLib(dir); assertThat(compatConfig.isChangeEnabled(1234L, @@ -400,6 +486,8 @@ public class CompatConfigTest { writeToFile(dir, "libcore_platform_compat_config.xml", configXml1); writeToFile(dir, "frameworks_platform_compat_config.xml", configXml2); CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext); + compatConfig.forceNonDebuggableFinalForTest(false); + compatConfig.initConfigFromLib(dir); assertThat(compatConfig.isChangeEnabled(1234L, diff --git a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java index c53b29a08a4a..0fd6445fbeeb 100644 --- a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java @@ -17,6 +17,7 @@ package com.android.server.compat; import static com.android.internal.compat.OverrideAllowedState.ALLOWED; +import static com.android.internal.compat.OverrideAllowedState.DEFERRED_VERIFICATION; import static com.android.internal.compat.OverrideAllowedState.DISABLED_NON_TARGET_SDK; import static com.android.internal.compat.OverrideAllowedState.DISABLED_NOT_DEBUGGABLE; import static com.android.internal.compat.OverrideAllowedState.DISABLED_TARGET_SDK_TOO_HIGH; @@ -31,6 +32,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import androidx.test.runner.AndroidJUnit4; @@ -409,4 +411,216 @@ public class OverrideValidatorImplTest { assertThat(stateDLoggingOnlyChange) .isEqualTo(new OverrideAllowedState(LOGGING_ONLY_CHANGE, -1, -1)); } + @Test + public void getOverrideAllowedState_finalBuildAnyChangeNotInstalledApp_deferOverride() + throws Exception { + CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext) + .addEnableAfterSdkChangeWithId(TARGET_SDK_BEFORE, 1) + .addEnableAfterSdkChangeWithId(TARGET_SDK, 2) + .addEnableAfterSdkChangeWithId(TARGET_SDK_AFTER, 3) + .addEnabledChangeWithId(4) + .addDisabledChangeWithId(5) + .addLoggingOnlyChangeWithId(6).build(); + IOverrideValidator overrideValidator = config.getOverrideValidator(); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenThrow(new NameNotFoundException()); + + OverrideAllowedState stateTargetSdkLessChange = + overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkEqualChange = + overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkAfterChange = + overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME); + OverrideAllowedState stateEnabledChange = + overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME); + OverrideAllowedState stateDisabledChange = + overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME); + OverrideAllowedState stateDLoggingOnlyChange = + overrideValidator.getOverrideAllowedState(6, PACKAGE_NAME); + + assertThat(stateTargetSdkLessChange) + .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1)); + assertThat(stateTargetSdkEqualChange) + .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1)); + assertThat(stateTargetSdkAfterChange) + .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1)); + assertThat(stateEnabledChange) + .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1)); + assertThat(stateDisabledChange) + .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1)); + assertThat(stateDLoggingOnlyChange) + .isEqualTo(new OverrideAllowedState(LOGGING_ONLY_CHANGE, -1, -1)); + } + + @Test + public void getOverrideAllowedState_forceFinalBuildTargetSdkChangeDebugAppOptin_allowOverride() + throws Exception { + CompatConfig config = CompatConfigBuilder.create(debuggableBuild(), mContext) + .addEnableAfterSdkChangeWithId(TARGET_SDK_AFTER, 1) + .addEnableAfterSdkChangeWithId(TARGET_SDK, 2).build(); + config.forceNonDebuggableFinalForTest(true); + IOverrideValidator overrideValidator = config.getOverrideValidator(); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create() + .debuggable() + .withTargetSdk(TARGET_SDK) + .withPackageName(PACKAGE_NAME).build()); + + OverrideAllowedState stateTargetSdkGreaterChange = + overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkEqualChange = + overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME); + + assertThat(stateTargetSdkGreaterChange) + .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK_AFTER)); + + assertThat(stateTargetSdkEqualChange) + .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK)); + } + + @Test + public void getOverrideAllowedState_forceFinalBldTargetSdkChangeDebugAppOptout_rejectOverride() + throws Exception { + CompatConfig config = CompatConfigBuilder.create(debuggableBuild(), mContext) + .addEnableAfterSdkChangeWithId(TARGET_SDK_BEFORE, 1).build(); + config.forceNonDebuggableFinalForTest(true); + IOverrideValidator overrideValidator = config.getOverrideValidator(); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create() + .withPackageName(PACKAGE_NAME) + .withTargetSdk(TARGET_SDK) + .debuggable() + .build()); + + OverrideAllowedState stateTargetSdkLessChange = + overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + + assertThat(stateTargetSdkLessChange).isEqualTo( + new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, TARGET_SDK, + TARGET_SDK_BEFORE)); + } + + @Test + public void getOverrideAllowedState_forceFinalBuildEnabledChangeDebugApp_rejectOverride() + throws Exception { + CompatConfig config = CompatConfigBuilder.create(debuggableBuild(), mContext) + .addEnabledChangeWithId(1).build(); + config.forceNonDebuggableFinalForTest(true); + IOverrideValidator overrideValidator = config.getOverrideValidator(); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create() + .withPackageName(PACKAGE_NAME) + .debuggable().build()); + + OverrideAllowedState allowedState = + overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + + assertThat(allowedState) + .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1)); + } + + @Test + public void getOverrideAllowedState_forceFinalBuildDisabledChangeDebugApp_allowOverride() + throws Exception { + CompatConfig config = CompatConfigBuilder.create(debuggableBuild(), mContext) + .addDisabledChangeWithId(1).build(); + config.forceNonDebuggableFinalForTest(true); + IOverrideValidator overrideValidator = config.getOverrideValidator(); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create() + .withPackageName(PACKAGE_NAME) + .withTargetSdk(TARGET_SDK) + .debuggable().build()); + + OverrideAllowedState allowedState = + overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + + assertThat(allowedState) + .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, -1)); + } + + @Test + public void getOverrideAllowedState_forceFinalBuildAnyChangeReleaseApp_rejectOverride() + throws Exception { + CompatConfig config = CompatConfigBuilder.create(debuggableBuild(), mContext) + .addEnableAfterSdkChangeWithId(TARGET_SDK_BEFORE, 1) + .addEnableAfterSdkChangeWithId(TARGET_SDK, 2) + .addEnableAfterSdkChangeWithId(TARGET_SDK_AFTER, 3) + .addEnabledChangeWithId(4) + .addDisabledChangeWithId(5) + .addLoggingOnlyChangeWithId(6).build(); + config.forceNonDebuggableFinalForTest(true); + IOverrideValidator overrideValidator = config.getOverrideValidator(); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create() + .withPackageName(PACKAGE_NAME) + .withTargetSdk(TARGET_SDK).build()); + + OverrideAllowedState stateTargetSdkLessChange = + overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkEqualChange = + overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkAfterChange = + overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME); + OverrideAllowedState stateEnabledChange = + overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME); + OverrideAllowedState stateDisabledChange = + overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME); + OverrideAllowedState stateDLoggingOnlyChange = + overrideValidator.getOverrideAllowedState(6, PACKAGE_NAME); + + assertThat(stateTargetSdkLessChange) + .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1)); + assertThat(stateTargetSdkEqualChange) + .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1)); + assertThat(stateTargetSdkAfterChange) + .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1)); + assertThat(stateEnabledChange) + .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1)); + assertThat(stateDisabledChange) + .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1)); + assertThat(stateDLoggingOnlyChange) + .isEqualTo(new OverrideAllowedState(LOGGING_ONLY_CHANGE, -1, -1)); + } + @Test + public void getOverrideAllowedState_forceFinalBuildAnyChangeNotInstalledApp_deferOverride() + throws Exception { + CompatConfig config = CompatConfigBuilder.create(debuggableBuild(), mContext) + .addEnableAfterSdkChangeWithId(TARGET_SDK_BEFORE, 1) + .addEnableAfterSdkChangeWithId(TARGET_SDK, 2) + .addEnableAfterSdkChangeWithId(TARGET_SDK_AFTER, 3) + .addEnabledChangeWithId(4) + .addDisabledChangeWithId(5) + .addLoggingOnlyChangeWithId(6).build(); + config.forceNonDebuggableFinalForTest(true); + IOverrideValidator overrideValidator = config.getOverrideValidator(); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenThrow(new NameNotFoundException()); + + OverrideAllowedState stateTargetSdkLessChange = + overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkEqualChange = + overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkAfterChange = + overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME); + OverrideAllowedState stateEnabledChange = + overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME); + OverrideAllowedState stateDisabledChange = + overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME); + OverrideAllowedState stateDLoggingOnlyChange = + overrideValidator.getOverrideAllowedState(6, PACKAGE_NAME); + + assertThat(stateTargetSdkLessChange) + .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1)); + assertThat(stateTargetSdkEqualChange) + .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1)); + assertThat(stateTargetSdkAfterChange) + .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1)); + assertThat(stateEnabledChange) + .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1)); + assertThat(stateDisabledChange) + .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1)); + assertThat(stateDLoggingOnlyChange) + .isEqualTo(new OverrideAllowedState(LOGGING_ONLY_CHANGE, -1, -1)); + } } diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java index 1d3b643ba83f..a1b2dc8bd82d 100644 --- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.when; import static org.mockito.internal.verification.VerificationModeFactory.times; import static org.testng.Assert.assertThrows; +import android.compat.Compatibility.ChangeConfig; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -35,6 +36,7 @@ import android.os.Build; import androidx.test.runner.AndroidJUnit4; import com.android.internal.compat.AndroidBuildClassifier; +import com.android.internal.compat.CompatibilityChangeConfig; import com.android.internal.compat.CompatibilityChangeInfo; import com.android.server.LocalServices; @@ -44,6 +46,9 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.HashSet; +import java.util.Set; + @RunWith(AndroidJUnit4.class) public class PlatformCompatTest { private static final String PACKAGE_NAME = "my.package"; @@ -70,9 +75,12 @@ public class PlatformCompatTest { new PackageManager.NameNotFoundException()); when(mPackageManagerInternal.getPackageUid(eq(PACKAGE_NAME), eq(0), anyInt())) .thenReturn(-1); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenThrow(new PackageManager.NameNotFoundException()); mCompatConfig = new CompatConfig(mBuildClassifier, mContext); mPlatformCompat = new PlatformCompat(mContext, mCompatConfig); // Assume userdebug/eng non-final build + mCompatConfig.forceNonDebuggableFinalForTest(false); when(mBuildClassifier.isDebuggableBuild()).thenReturn(true); when(mBuildClassifier.isFinalBuild()).thenReturn(false); LocalServices.removeServiceForTest(PackageManagerInternal.class); @@ -89,17 +97,22 @@ public class PlatformCompatTest { .addEnableAfterSdkChangeWithId(Build.VERSION_CODES.Q, 5L) .addEnableAfterSdkChangeWithId(Build.VERSION_CODES.R, 6L) .addLoggingOnlyChangeWithId(7L) + .addOverridableChangeWithId(8L) .build(); mPlatformCompat = new PlatformCompat(mContext, mCompatConfig); assertThat(mPlatformCompat.listAllChanges()).asList().containsExactly( - new CompatibilityChangeInfo(1L, "", -1, -1, false, false, ""), - new CompatibilityChangeInfo(2L, "change2", -1, -1, true, false, ""), + new CompatibilityChangeInfo(1L, "", -1, -1, false, false, "", false), + new CompatibilityChangeInfo(2L, "change2", -1, -1, true, false, "", false), new CompatibilityChangeInfo(3L, "", Build.VERSION_CODES.O, -1, false, false, - "desc"), - new CompatibilityChangeInfo(4L, "", Build.VERSION_CODES.P, -1, false, false, ""), - new CompatibilityChangeInfo(5L, "", Build.VERSION_CODES.Q, -1, false, false, ""), - new CompatibilityChangeInfo(6L, "", Build.VERSION_CODES.R, -1, false, false, ""), - new CompatibilityChangeInfo(7L, "", -1, -1, false, true, "")); + "desc", false), + new CompatibilityChangeInfo( + 4L, "", Build.VERSION_CODES.P, -1, false, false, "", false), + new CompatibilityChangeInfo( + 5L, "", Build.VERSION_CODES.Q, -1, false, false, "", false), + new CompatibilityChangeInfo( + 6L, "", Build.VERSION_CODES.R, -1, false, false, "", false), + new CompatibilityChangeInfo(7L, "", -1, -1, false, true, "", false), + new CompatibilityChangeInfo(8L, "", -1, -1, false, true, "", true)); } @Test @@ -115,12 +128,44 @@ public class PlatformCompatTest { .build(); mPlatformCompat = new PlatformCompat(mContext, mCompatConfig); assertThat(mPlatformCompat.listUIChanges()).asList().containsExactly( - new CompatibilityChangeInfo(1L, "", -1, -1, false, false, ""), - new CompatibilityChangeInfo(2L, "change2", -1, -1, true, false, ""), - new CompatibilityChangeInfo(5L, "", /*enableAfter*/ -1, - /*enableSince*/ Build.VERSION_CODES.Q, false, false, ""), - new CompatibilityChangeInfo(6L, "", /*enableAfter*/ -1, - /*enableSince*/ Build.VERSION_CODES.R, false, false, "")); + new CompatibilityChangeInfo(1L, "", -1, -1, false, false, "", false), + new CompatibilityChangeInfo(2L, "change2", -1, -1, true, false, "", false), + new CompatibilityChangeInfo( + 5L, "", Build.VERSION_CODES.P, -1, false, false, "", false), + new CompatibilityChangeInfo( + 6L, "", Build.VERSION_CODES.Q, -1, false, false, "", false)); + } + + @Test + public void testOverrideAtInstallTime() throws Exception { + mCompatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addEnabledChangeWithId(1L) + .addDisabledChangeWithId(2L) + .addEnableAfterSdkChangeWithId(Build.VERSION_CODES.O, 3L) + .build(); + mCompatConfig.forceNonDebuggableFinalForTest(true); + mPlatformCompat = new PlatformCompat(mContext, mCompatConfig); + + // Before adding overrides. + assertThat(mPlatformCompat.isChangeEnabledByPackageName(1, PACKAGE_NAME, 0)).isTrue(); + assertThat(mPlatformCompat.isChangeEnabledByPackageName(2, PACKAGE_NAME, 0)).isFalse(); + assertThat(mPlatformCompat.isChangeEnabledByPackageName(3, PACKAGE_NAME, 0)).isTrue(); + + // Add overrides. + Set<Long> enabled = new HashSet<>(); + enabled.add(2L); + Set<Long> disabled = new HashSet<>(); + disabled.add(1L); + disabled.add(3L); + ChangeConfig changeConfig = new ChangeConfig(enabled, disabled); + CompatibilityChangeConfig compatibilityChangeConfig = + new CompatibilityChangeConfig(changeConfig); + mPlatformCompat.setOverridesForTest(compatibilityChangeConfig, PACKAGE_NAME); + + // After adding overrides. + assertThat(mPlatformCompat.isChangeEnabledByPackageName(1, PACKAGE_NAME, 0)).isFalse(); + assertThat(mPlatformCompat.isChangeEnabledByPackageName(2, PACKAGE_NAME, 0)).isTrue(); + assertThat(mPlatformCompat.isChangeEnabledByPackageName(3, PACKAGE_NAME, 0)).isFalse(); } @Test diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/OWNERS b/services/tests/servicestests/src/com/android/server/graphics/fonts/OWNERS new file mode 100644 index 000000000000..34ac813f02e0 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 24939 + +include /graphics/java/android/graphics/fonts/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/OWNERS b/services/tests/servicestests/src/com/android/server/location/timezone/OWNERS new file mode 100644 index 000000000000..28aff188dbd8 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/location/timezone/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 847766 +nfuller@google.com +include /core/java/android/app/timedetector/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index fec0273383e7..4db7ce2e6ef5 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -16,13 +16,18 @@ package com.android.server.net; +import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS; +import static android.Manifest.permission.NETWORK_STACK; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.net.INetd.FIREWALL_CHAIN_RESTRICTED; +import static android.net.INetd.FIREWALL_RULE_ALLOW; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkPolicy.LIMIT_DISABLED; import static android.net.NetworkPolicy.SNOOZE_NEVER; import static android.net.NetworkPolicy.WARNING_DISABLED; +import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND; import static android.net.NetworkPolicyManager.POLICY_NONE; import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; @@ -34,6 +39,7 @@ import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED; import static android.net.NetworkPolicyManager.uidPoliciesToString; import static android.net.NetworkPolicyManager.uidRulesToString; +import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.net.NetworkStats.IFACE_ALL; import static android.net.NetworkStats.SET_ALL; import static android.net.NetworkStats.TAG_ALL; @@ -74,6 +80,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; @@ -97,6 +104,7 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; +import android.content.pm.UserInfo; import android.net.ConnectivityManager; import android.net.IConnectivityManager; import android.net.INetworkManagementEventObserver; @@ -123,6 +131,7 @@ import android.os.RemoteException; import android.os.SimpleClock; import android.os.SystemClock; import android.os.UserHandle; +import android.os.UserManager; import android.platform.test.annotations.Presubmit; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionInfo; @@ -131,6 +140,7 @@ import android.telephony.SubscriptionPlan; import android.telephony.TelephonyManager; import android.test.suitebuilder.annotation.MediumTest; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.DataUnit; import android.util.Log; import android.util.Pair; @@ -187,6 +197,7 @@ import java.util.Calendar; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.TimeZone; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -240,6 +251,7 @@ public class NetworkPolicyManagerServiceTest { private @Mock SubscriptionManager mSubscriptionManager; private @Mock CarrierConfigManager mCarrierConfigManager; private @Mock TelephonyManager mTelephonyManager; + private @Mock UserManager mUserManager; private ArgumentCaptor<ConnectivityManager.NetworkCallback> mNetworkCallbackCaptor = ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class); @@ -351,6 +363,8 @@ public class NetworkPolicyManagerServiceTest { return mNotifManager; case Context.CONNECTIVITY_SERVICE: return mConnectivityManager; + case Context.USER_SERVICE: + return mUserManager; default: return super.getSystemService(name); } @@ -407,11 +421,14 @@ public class NetworkPolicyManagerServiceTest { when(mPackageManager.getPackagesForUid(UID_B)).thenReturn(new String[] {PKG_NAME_B}); when(mPackageManager.getPackagesForUid(UID_C)).thenReturn(new String[] {PKG_NAME_C}); when(mPackageManager.getApplicationInfo(eq(PKG_NAME_A), anyInt())) - .thenReturn(buildApplicationInfo(PKG_NAME_A)); + .thenReturn(buildApplicationInfo(PKG_NAME_A, UID_A)); when(mPackageManager.getApplicationInfo(eq(PKG_NAME_B), anyInt())) - .thenReturn(buildApplicationInfo(PKG_NAME_B)); + .thenReturn(buildApplicationInfo(PKG_NAME_B, UID_B)); when(mPackageManager.getApplicationInfo(eq(PKG_NAME_C), anyInt())) - .thenReturn(buildApplicationInfo(PKG_NAME_C)); + .thenReturn(buildApplicationInfo(PKG_NAME_C, UID_C)); + when(mPackageManager.getInstalledApplications(anyInt())).thenReturn( + buildInstalledApplicationInfoList()); + when(mUserManager.getUsers()).thenReturn(buildUserInfoList()); when(mNetworkManager.isBandwidthControlEnabled()).thenReturn(true); when(mNetworkManager.setDataSaverModeEnabled(anyBoolean())).thenReturn(true); doNothing().when(mConnectivityManager) @@ -1874,6 +1891,66 @@ public class NetworkPolicyManagerServiceTest { } } + private void enableRestrictedMode(boolean enable) throws Exception { + mService.mRestrictedNetworkingMode = enable; + mService.updateRestrictedModeAllowlistUL(); + verify(mNetworkManager).setFirewallChainEnabled(FIREWALL_CHAIN_RESTRICTED, + enable); + } + + @Test + public void testUpdateRestrictedModeAllowlist() throws Exception { + // initialization calls setFirewallChainEnabled, so we want to reset the invocations. + clearInvocations(mNetworkManager); + expectHasUseRestrictedNetworksPermission(UID_A, true); + expectHasUseRestrictedNetworksPermission(UID_B, false); + + Map<Integer, Integer> firewallUidRules = new ArrayMap<>(); + doAnswer(arg -> { + int[] uids = arg.getArgument(1); + int[] rules = arg.getArgument(2); + assertTrue(uids.length == rules.length); + + for (int i = 0; i < uids.length; ++i) { + firewallUidRules.put(uids[i], rules[i]); + } + return null; + }).when(mNetworkManager).setFirewallUidRules(eq(FIREWALL_CHAIN_RESTRICTED), + any(int[].class), any(int[].class)); + + enableRestrictedMode(true); + assertEquals(FIREWALL_RULE_ALLOW, firewallUidRules.get(UID_A).intValue()); + assertFalse(mService.isUidNetworkingBlocked(UID_A, false)); + assertTrue(mService.isUidNetworkingBlocked(UID_B, false)); + + enableRestrictedMode(false); + assertFalse(mService.isUidNetworkingBlocked(UID_A, false)); + assertFalse(mService.isUidNetworkingBlocked(UID_B, false)); + } + + @Test + public void testUpdateRestrictedModeForUid() throws Exception { + // initialization calls setFirewallChainEnabled, so we want to reset the invocations. + clearInvocations(mNetworkManager); + expectHasUseRestrictedNetworksPermission(UID_A, true); + expectHasUseRestrictedNetworksPermission(UID_B, false); + enableRestrictedMode(true); + + // UID_D and UID_E are not part of installed applications list, so it won't have any + // firewall rules set yet + expectHasUseRestrictedNetworksPermission(UID_D, false); + mService.updateRestrictedModeForUidUL(UID_D); + verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_RESTRICTED, UID_D, + FIREWALL_RULE_DEFAULT); + assertTrue(mService.isUidNetworkingBlocked(UID_D, false)); + + expectHasUseRestrictedNetworksPermission(UID_E, true); + mService.updateRestrictedModeForUidUL(UID_E); + verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_RESTRICTED, UID_E, + FIREWALL_RULE_ALLOW); + assertFalse(mService.isUidNetworkingBlocked(UID_E, false)); + } + private String formatBlockedStateError(int uid, int rule, boolean metered, boolean backgroundRestricted) { return String.format( @@ -1888,12 +1965,27 @@ public class NetworkPolicyManagerServiceTest { .build(); } - private ApplicationInfo buildApplicationInfo(String label) { + private ApplicationInfo buildApplicationInfo(String label, int uid) { final ApplicationInfo ai = new ApplicationInfo(); ai.nonLocalizedLabel = label; + ai.uid = uid; return ai; } + private List<ApplicationInfo> buildInstalledApplicationInfoList() { + final List<ApplicationInfo> installedApps = new ArrayList<>(); + installedApps.add(buildApplicationInfo(PKG_NAME_A, UID_A)); + installedApps.add(buildApplicationInfo(PKG_NAME_B, UID_B)); + installedApps.add(buildApplicationInfo(PKG_NAME_C, UID_C)); + return installedApps; + } + + private List<UserInfo> buildUserInfoList() { + final List<UserInfo> users = new ArrayList<>(); + users.add(new UserInfo(USER_ID, "user1", 0)); + return users; + } + private NetworkInfo buildNetworkInfo() { final NetworkInfo ni = new NetworkInfo(ConnectivityManager.TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_LTE, null, null); @@ -1967,6 +2059,15 @@ public class NetworkPolicyManagerServiceTest { hasIt ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED); } + private void expectHasUseRestrictedNetworksPermission(int uid, boolean hasIt) throws Exception { + when(mIpm.checkUidPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, uid)).thenReturn( + hasIt ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED); + when(mIpm.checkUidPermission(NETWORK_STACK, uid)).thenReturn( + PackageManager.PERMISSION_DENIED); + when(mIpm.checkUidPermission(PERMISSION_MAINLINE_NETWORK_STACK, uid)).thenReturn( + PackageManager.PERMISSION_DENIED); + } + private void expectNetworkState(boolean roaming) throws Exception { when(mCarrierConfigManager.getConfigForSubId(eq(TEST_SUB_ID))) .thenReturn(mCarrierConfig); diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/NetworkWatchlistServiceTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/NetworkWatchlistServiceTests.java index 9c8a38219a9c..ac9316e7d908 100644 --- a/services/tests/servicestests/src/com/android/server/net/watchlist/NetworkWatchlistServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/net/watchlist/NetworkWatchlistServiceTests.java @@ -24,6 +24,9 @@ import static org.junit.Assert.fail; import android.net.ConnectivityMetricsEvent; import android.net.IIpConnectivityMetrics; import android.net.INetdEventCallback; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -106,6 +109,16 @@ public class NetworkWatchlistServiceTests { counter--; return true; } + + // TODO: mark @Override when aosp/1541935 automerges to master. + public void logDefaultNetworkValidity(boolean valid) { + } + + // TODO: mark @Override when aosp/1541935 automerges to master. + public void logDefaultNetworkEvent(Network defaultNetwork, int score, boolean validated, + LinkProperties lp, NetworkCapabilities nc, Network previousDefaultNetwork, + int previousScore, LinkProperties previousLp, NetworkCapabilities previousNc) { + } }; ServiceThread mHandlerThread; diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java index 391611b72dab..5468fba59c10 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java @@ -78,7 +78,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI } @Test - public void testImmutableEnabledChange() { + public void testImmutableEnabledChange() throws Exception { final OverlayManagerServiceImpl impl = getImpl(); installNewPackage(target(TARGET), USER); installNewPackage(overlay(OVERLAY, TARGET), USER); @@ -106,7 +106,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI } @Test - public void testMutableEnabledChangeHasNoEffect() { + public void testMutableEnabledChangeHasNoEffect() throws Exception { final OverlayManagerServiceImpl impl = getImpl(); installNewPackage(target(TARGET), USER); installNewPackage(overlay(OVERLAY, TARGET), USER); @@ -134,7 +134,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI } @Test - public void testMutableEnabledToImmutableEnabled() { + public void testMutableEnabledToImmutableEnabled() throws Exception { final OverlayManagerServiceImpl impl = getImpl(); installNewPackage(target(TARGET), USER); installNewPackage(overlay(OVERLAY, TARGET), USER); @@ -178,7 +178,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI } @Test - public void testMutablePriorityChange() { + public void testMutablePriorityChange() throws Exception { final OverlayManagerServiceImpl impl = getImpl(); installNewPackage(target(TARGET), USER); installNewPackage(overlay(OVERLAY, TARGET), USER); @@ -218,7 +218,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI } @Test - public void testImmutablePriorityChange() { + public void testImmutablePriorityChange() throws Exception { final OverlayManagerServiceImpl impl = getImpl(); installNewPackage(target(TARGET), USER); installNewPackage(overlay(OVERLAY, TARGET), USER); diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java index 4f882ce13dd4..33dbcc0855be 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java @@ -22,11 +22,14 @@ import static android.content.om.OverlayInfo.STATE_MISSING_TARGET; import static android.os.OverlayablePolicy.CONFIG_SIGNATURE; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.testng.Assert.assertThrows; import android.content.om.OverlayInfo; +import android.util.Pair; import androidx.test.runner.AndroidJUnit4; @@ -35,6 +38,7 @@ import org.junit.runner.RunWith; import java.util.List; import java.util.Map; +import java.util.Optional; @RunWith(AndroidJUnit4.class) public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTestsBase { @@ -55,7 +59,7 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes private static final String CERT_CONFIG_NOK = "config_certificate_nok"; @Test - public void testGetOverlayInfo() { + public void testGetOverlayInfo() throws Exception { installNewPackage(overlay(OVERLAY, TARGET), USER); final OverlayManagerServiceImpl impl = getImpl(); @@ -67,7 +71,7 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @Test - public void testGetOverlayInfosForTarget() { + public void testGetOverlayInfosForTarget() throws Exception { installNewPackage(overlay(OVERLAY, TARGET), USER); installNewPackage(overlay(OVERLAY2, TARGET), USER); installNewPackage(overlay(OVERLAY3, TARGET), USER2); @@ -92,7 +96,7 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @Test - public void testGetOverlayInfosForUser() { + public void testGetOverlayInfosForUser() throws Exception { installNewPackage(target(TARGET), USER); installNewPackage(overlay(OVERLAY, TARGET), USER); installNewPackage(overlay(OVERLAY2, TARGET), USER); @@ -119,7 +123,7 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @Test - public void testPriority() { + public void testPriority() throws Exception { installNewPackage(overlay(OVERLAY, TARGET), USER); installNewPackage(overlay(OVERLAY2, TARGET), USER); installNewPackage(overlay(OVERLAY3, TARGET), USER); @@ -131,18 +135,21 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3); - assertTrue(impl.setLowestPriority(OVERLAY3, USER)); + assertEquals(impl.setLowestPriority(OVERLAY3, USER), + Optional.of(new PackageAndUser(TARGET, USER))); assertOverlayInfoForTarget(TARGET, USER, o3, o1, o2); - assertTrue(impl.setHighestPriority(OVERLAY3, USER)); + assertEquals(impl.setHighestPriority(OVERLAY3, USER), + Optional.of(new PackageAndUser(TARGET, USER))); assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3); - assertTrue(impl.setPriority(OVERLAY, OVERLAY2, USER)); + assertEquals(impl.setPriority(OVERLAY, OVERLAY2, USER), + Optional.of(new PackageAndUser(TARGET, USER))); assertOverlayInfoForTarget(TARGET, USER, o2, o1, o3); } @Test - public void testOverlayInfoStateTransitions() { + public void testOverlayInfoStateTransitions() throws Exception { final OverlayManagerServiceImpl impl = getImpl(); assertNull(impl.getOverlayInfo(OVERLAY, USER)); @@ -153,7 +160,8 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes installNewPackage(target, USER); assertState(STATE_DISABLED, OVERLAY, USER); - impl.setEnabled(OVERLAY, true, USER); + assertEquals(impl.setEnabled(OVERLAY, true, USER), + Optional.of(new PackageAndUser(TARGET, USER))); assertState(STATE_ENABLED, OVERLAY, USER); // target upgrades do not change the state of the overlay @@ -168,50 +176,40 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @Test - public void testOnOverlayPackageUpgraded() { - final FakeListener listener = getListener(); + public void testOnOverlayPackageUpgraded() throws Exception { final FakeDeviceState.PackageBuilder target = target(TARGET); final FakeDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET); installNewPackage(target, USER); installNewPackage(overlay, USER); - listener.count = 0; upgradePackage(overlay, USER); - assertEquals(2, listener.count); // upgrade to a version where the overlay has changed its target - // expect once for the old target package, once for the new target package - listener.count = 0; final FakeDeviceState.PackageBuilder overlay2 = overlay(OVERLAY, "some.other.target"); - upgradePackage(overlay2, USER); - assertEquals(3, listener.count); - - listener.count = 0; - upgradePackage(overlay2, USER); - assertEquals(2, listener.count); + final Pair<Optional<PackageAndUser>, Optional<PackageAndUser>> pair = + upgradePackage(overlay2, USER); + assertEquals(pair.first, Optional.of(new PackageAndUser(TARGET, USER))); + assertEquals(pair.second, Optional.of(new PackageAndUser("some.other.target", USER))); } @Test - public void testListener() { + public void testSetEnabledAtVariousConditions() throws Exception { final OverlayManagerServiceImpl impl = getImpl(); - final FakeListener listener = getListener(); - installNewPackage(overlay(OVERLAY, TARGET), USER); - assertEquals(1, listener.count); - listener.count = 0; + assertThrows(OverlayManagerServiceImpl.OperationFailedException.class, + () -> impl.setEnabled(OVERLAY, true, USER)); + // request succeeded, and there was a change that needs to be + // propagated to the rest of the system installNewPackage(target(TARGET), USER); - assertEquals(1, listener.count); - listener.count = 0; - - impl.setEnabled(OVERLAY, true, USER); - assertEquals(1, listener.count); - listener.count = 0; + installNewPackage(overlay(OVERLAY, TARGET), USER); + assertEquals(impl.setEnabled(OVERLAY, true, USER), + Optional.of(new PackageAndUser(TARGET, USER))); - impl.setEnabled(OVERLAY, true, USER); - assertEquals(0, listener.count); + // request succeeded, but nothing changed + assertFalse(impl.setEnabled(OVERLAY, true, USER).isPresent()); } @Test - public void testConfigSignaturePolicyOk() { + public void testConfigSignaturePolicyOk() throws Exception { setConfigSignaturePackageName(CONFIG_SIGNATURE_REFERENCE_PKG); reinitializeImpl(); @@ -229,7 +227,7 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @Test - public void testConfigSignaturePolicyCertNok() { + public void testConfigSignaturePolicyCertNok() throws Exception { setConfigSignaturePackageName(CONFIG_SIGNATURE_REFERENCE_PKG); reinitializeImpl(); @@ -247,7 +245,7 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @Test - public void testConfigSignaturePolicyNoConfig() { + public void testConfigSignaturePolicyNoConfig() throws Exception { addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER); installNewPackage(target(TARGET), USER); installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER); @@ -262,7 +260,7 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @Test - public void testConfigSignaturePolicyNoRefPkg() { + public void testConfigSignaturePolicyNoRefPkg() throws Exception { installNewPackage(target(TARGET), USER); installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER); @@ -276,7 +274,7 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @Test - public void testConfigSignaturePolicyRefPkgNotSystem() { + public void testConfigSignaturePolicyRefPkgNotSystem() throws Exception { setConfigSignaturePackageName(CONFIG_SIGNATURE_REFERENCE_PKG); reinitializeImpl(); diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java index 006dda0f80e3..2c477c897b30 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java @@ -16,6 +16,8 @@ package com.android.server.om; +import static com.android.server.om.OverlayManagerServiceImpl.OperationFailedException; + import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; @@ -30,6 +32,7 @@ import android.content.pm.PackageInfo; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Pair; import androidx.annotation.Nullable; @@ -43,13 +46,13 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; /** Base class for creating {@link OverlayManagerServiceImplTests} tests. */ class OverlayManagerServiceImplTestsBase { private OverlayManagerServiceImpl mImpl; private FakeDeviceState mState; - private FakeListener mListener; private FakePackageManagerHelper mPackageManager; private FakeIdmapDaemon mIdmapDaemon; private OverlayConfig mOverlayConfig; @@ -58,7 +61,6 @@ class OverlayManagerServiceImplTestsBase { @Before public void setUp() { mState = new FakeDeviceState(); - mListener = new FakeListener(); mPackageManager = new FakePackageManagerHelper(mState); mIdmapDaemon = new FakeIdmapDaemon(mState); mOverlayConfig = mock(OverlayConfig.class); @@ -73,18 +75,13 @@ class OverlayManagerServiceImplTestsBase { new IdmapManager(mIdmapDaemon, mPackageManager), new OverlayManagerSettings(), mOverlayConfig, - new String[0], - mListener); + new String[0]); } OverlayManagerServiceImpl getImpl() { return mImpl; } - FakeListener getListener() { - return mListener; - } - FakeIdmapDaemon getIdmapd() { return mIdmapDaemon; } @@ -155,7 +152,8 @@ class OverlayManagerServiceImplTestsBase { * * @throws IllegalStateException if the package is currently installed */ - void installNewPackage(FakeDeviceState.PackageBuilder pkg, int userId) { + void installNewPackage(FakeDeviceState.PackageBuilder pkg, int userId) + throws OperationFailedException { if (mState.select(pkg.packageName, userId) != null) { throw new IllegalStateException("package " + pkg.packageName + " already installed"); } @@ -176,23 +174,30 @@ class OverlayManagerServiceImplTestsBase { * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast with the * {@link android.content.Intent#EXTRA_REPLACING} extra. * + * @return the two Optional<PackageAndUser> objects from starting and finishing the upgrade + * * @throws IllegalStateException if the package is not currently installed */ - void upgradePackage(FakeDeviceState.PackageBuilder pkg, int userId) { + Pair<Optional<PackageAndUser>, Optional<PackageAndUser>> upgradePackage( + FakeDeviceState.PackageBuilder pkg, int userId) throws OperationFailedException { final FakeDeviceState.Package replacedPackage = mState.select(pkg.packageName, userId); if (replacedPackage == null) { throw new IllegalStateException("package " + pkg.packageName + " not installed"); } + Optional<PackageAndUser> opt1 = Optional.empty(); if (replacedPackage.targetPackageName != null) { - mImpl.onOverlayPackageReplacing(pkg.packageName, userId); + opt1 = mImpl.onOverlayPackageReplacing(pkg.packageName, userId); } mState.add(pkg, userId); + Optional<PackageAndUser> opt2; if (pkg.targetPackage == null) { - mImpl.onTargetPackageReplaced(pkg.packageName, userId); + opt2 = mImpl.onTargetPackageReplaced(pkg.packageName, userId); } else { - mImpl.onOverlayPackageReplaced(pkg.packageName, userId); + opt2 = mImpl.onOverlayPackageReplaced(pkg.packageName, userId); } + + return Pair.create(opt1, opt2); } /** @@ -203,7 +208,7 @@ class OverlayManagerServiceImplTestsBase { * * @throws IllegalStateException if the package is not currently installed */ - void uninstallPackage(String packageName, int userId) { + void uninstallPackage(String packageName, int userId) throws OperationFailedException { final FakeDeviceState.Package pkg = mState.select(packageName, userId); if (pkg == null) { throw new IllegalStateException("package " + packageName+ " not installed"); @@ -485,12 +490,4 @@ class OverlayManagerServiceImplTestsBase { } } } - - static class FakeListener implements OverlayManagerServiceImpl.OverlayChangeListener { - public int count; - - public void onOverlaysChanged(@NonNull String targetPackage, int userId) { - count++; - } - } } diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 98b9dcd2cc2f..477592ba4129 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -170,7 +170,10 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser // Delay for debouncing USB disconnects. // We often get rapid connect/disconnect events when enabling USB functions, // which need debouncing. - private static final int UPDATE_DELAY = 1000; + private static final int DEVICE_STATE_UPDATE_DELAY = 3000; + + // Delay for debouncing USB disconnects on Type-C ports in host mode + private static final int HOST_STATE_UPDATE_DELAY = 1000; // Timeout for entering USB request mode. // Request is cancelled if host does not configure device within 10 seconds. @@ -583,7 +586,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser msg.arg1 = connected; msg.arg2 = configured; // debounce disconnects to avoid problems bringing up USB tethering - sendMessageDelayed(msg, (connected == 0) ? UPDATE_DELAY : 0); + sendMessageDelayed(msg, (connected == 0) ? DEVICE_STATE_UPDATE_DELAY : 0); } public void updateHostState(UsbPort port, UsbPortStatus status) { @@ -598,7 +601,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser removeMessages(MSG_UPDATE_PORT_STATE); Message msg = obtainMessage(MSG_UPDATE_PORT_STATE, args); // debounce rapid transitions of connect/disconnect on type-c ports - sendMessageDelayed(msg, UPDATE_DELAY); + sendMessageDelayed(msg, HOST_STATE_UPDATE_DELAY); } private void setAdbEnabled(boolean enable) { diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java index 5024ae27ee49..835ecaa8c90d 100644 --- a/telecomm/java/android/telecom/PhoneAccount.java +++ b/telecomm/java/android/telecom/PhoneAccount.java @@ -361,7 +361,13 @@ public final class PhoneAccount implements Parcelable { */ public static final int CAPABILITY_ADHOC_CONFERENCE_CALLING = 0x4000; - /* NEXT CAPABILITY: 0x8000 */ + /** + * Flag indicating whether this {@link PhoneAccount} is capable of supporting the call composer + * functionality for enriched calls. + */ + public static final int CAPABILITY_CALL_COMPOSER = 0x8000; + + /* NEXT CAPABILITY: 0x10000 */ /** * URI scheme for telephone number URIs. @@ -1088,6 +1094,9 @@ public final class PhoneAccount implements Parcelable { if (hasCapabilities(CAPABILITY_ADHOC_CONFERENCE_CALLING)) { sb.append("AdhocConf"); } + if (hasCapabilities(CAPABILITY_CALL_COMPOSER)) { + sb.append("CallComposer "); + } return sb.toString(); } diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index da2d4d82481b..3f8b68305914 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -266,10 +266,69 @@ public class TelecomManager { /** * Optional extra for {@link android.content.Intent#ACTION_CALL} containing a string call * subject which will be associated with an outgoing call. Should only be specified if the - * {@link PhoneAccount} supports the capability {@link PhoneAccount#CAPABILITY_CALL_SUBJECT}. + * {@link PhoneAccount} supports the capability {@link PhoneAccount#CAPABILITY_CALL_SUBJECT} + * or {@link PhoneAccount#CAPABILITY_CALL_COMPOSER}. */ public static final String EXTRA_CALL_SUBJECT = "android.telecom.extra.CALL_SUBJECT"; + // Values for EXTRA_PRIORITY + /** + * Indicates the call composer call priority is normal. + * + * Reference: RCC.20 Section 2.4.4.2 + */ + public static final int PRIORITY_NORMAL = 0; + + /** + * Indicates the call composer call priority is urgent. + * + * Reference: RCC.20 Section 2.4.4.2 + */ + public static final int PRIORITY_URGENT = 1; + + /** + * Extra for the call composer call priority, either {@link #PRIORITY_NORMAL} or + * {@link #PRIORITY_URGENT}. + * + * Reference: RCC.20 Section 2.4.4.2 + */ + public static final String EXTRA_PRIORITY = "android.telecom.extra.PRIORITY"; + + /** + * Extra for the call composer call location, an {@link android.location.Location} parcelable + * class to represent the geolocation as a latitude and longitude pair. + * + * Reference: RCC.20 Section 2.4.3.2 + */ + public static final String EXTRA_LOCATION = "android.telecom.extra.LOCATION"; + + /** + * A boolean extra set on incoming calls to indicate that the call has a picture specified. + * Given that image download could take a (short) time, the EXTRA is set immediately upon + * adding the call to the Dialer app, this allows the Dialer app to reserve space for an image + * if one is expected. The EXTRA may be unset if the image download ends up failing for some + * reason. + */ + public static final String EXTRA_HAS_PICTURE = "android.telecom.extra.HAS_PICTURE"; + + /** + * A URI representing the picture that was downloaded when a call is received. + * This is a content URI within the call log provider which can be used to open a file + * descriptor. This could be set a short time after a call is added to the Dialer app if the + * download is delayed for some reason. The Dialer app will receive a callback via + * {@link Call.Callback#onDetailsChanged} when this value has changed. + * + * Reference: RCC.20 Section 2.4.3.2 + */ + public static final String EXTRA_INCOMING_PICTURE = "android.telecom.extra.INCOMING_PICTURE"; + + // TODO(hallliu), This UUID is obtained from TelephonyManager#uploadCallComposerPicture. + /** + * A ParcelUuid used as a token to represent a picture that was uploaded prior to the call + * being placed. + */ + public static final String EXTRA_OUTGOING_PICTURE = "android.telecom.extra.OUTGOING_PICTURE"; + /** * The extra used by a {@link ConnectionService} to provide the handle of the caller that * has initiated a new incoming call. diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 4e9e6a8440a9..74b2aad5293e 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -4056,6 +4056,17 @@ public class CarrierConfigManager { "default_preferred_apn_name_string"; /** + * Indicates if the carrier supports call composer. + */ + public static final String KEY_SUPPORTS_CALL_COMPOSER_BOOL = "supports_call_composer_bool"; + + /** + * Indicates the carrier server url that serves the call composer picture. + */ + public static final String KEY_CALL_COMPOSER_PICTURE_SERVER_URL_STRING = + "call_composer_picture_server_url_string"; + + /** * For Android 11, provide a temporary solution for OEMs to use the lower of the two MTU values * for IPv4 and IPv6 if both are sent. * TODO: remove in later release @@ -4613,6 +4624,8 @@ public class CarrierConfigManager { sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY, new String[0]); sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false); sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, ""); + sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false); + sDefaults.putString(KEY_CALL_COMPOSER_PICTURE_SERVER_URL_STRING, ""); sDefaults.putBoolean(KEY_USE_LOWER_MTU_VALUE_IF_BOTH_RECEIVED, false); sDefaults.putBoolean(KEY_USE_ACS_FOR_RCS_BOOL, false); } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 7d3cef559672..c05e90b28fa8 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -8679,6 +8679,77 @@ public class TelephonyManager { return Collections.EMPTY_LIST; } + /** + * Call composer status OFF from user setting. + */ + public static final int CALL_COMPOSER_STATUS_OFF = 0; + + /** + * Call composer status ON from user setting. + */ + public static final int CALL_COMPOSER_STATUS_ON = 1; + + /** @hide */ + @IntDef(prefix = {"CALL_COMPOSER_STATUS_"}, + value = { + CALL_COMPOSER_STATUS_ON, + CALL_COMPOSER_STATUS_OFF, + }) + public @interface CallComposerStatus {} + + /** + * Set the user-set status for enriched calling with call composer. + * + * @param status user-set status for enriched calling with call composer; + * it must be a value of either {@link #CALL_COMPOSER_STATUS_ON} + * or {@link #CALL_COMPOSER_STATUS_OFF}. + * + * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the + * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()} + * + * @throws IllegalArgumentException if requested state is invalid. + * @throws SecurityException if the caller does not have the permission. + */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void setCallComposerStatus(@CallComposerStatus int status) { + if (status != CALL_COMPOSER_STATUS_ON && status != CALL_COMPOSER_STATUS_OFF) { + throw new IllegalArgumentException("requested status is invalid"); + } + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + telephony.setCallComposerStatus(getSubId(), status); + } + } catch (RemoteException ex) { + Log.e(TAG, "Error calling ITelephony#setCallComposerStatus", ex); + ex.rethrowFromSystemServer(); + } + } + + /** + * Get the user-set status for enriched calling with call composer. + * + * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the + * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()} + * + * @throws SecurityException if the caller does not have the permission. + * + * @return the user-set status for enriched calling with call composer either + * {@link #CALL_COMPOSER_STATUS_ON} or {@link #CALL_COMPOSER_STATUS_OFF}. + */ + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public @CallComposerStatus int getCallComposerStatus() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getCallComposerStatus(getSubId()); + } + } catch (RemoteException ex) { + Log.e(TAG, "Error calling ITelephony#getCallComposerStatus", ex); + ex.rethrowFromSystemServer(); + } + return CALL_COMPOSER_STATUS_OFF; + } /** @hide */ @SystemApi diff --git a/telephony/java/android/telephony/data/ApnThrottleStatus.aidl b/telephony/java/android/telephony/data/ApnThrottleStatus.aidl new file mode 100644 index 000000000000..46bc4abde159 --- /dev/null +++ b/telephony/java/android/telephony/data/ApnThrottleStatus.aidl @@ -0,0 +1,20 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @hide */ +package android.telephony.data; + +parcelable ApnThrottleStatus; diff --git a/telephony/java/android/telephony/data/ApnThrottleStatus.java b/telephony/java/android/telephony/data/ApnThrottleStatus.java new file mode 100644 index 000000000000..51461d17690a --- /dev/null +++ b/telephony/java/android/telephony/data/ApnThrottleStatus.java @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.data; + +import android.annotation.ElapsedRealtimeLong; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; +import android.telephony.AccessNetworkConstants; +import android.telephony.Annotation; + +import java.util.Objects; + +/** + * Status information regarding the throttle status of an APN type. + * + * @hide + */ +@SystemApi +public final class ApnThrottleStatus implements Parcelable { + /** + * The APN type is not throttled. + */ + public static final int THROTTLE_TYPE_NONE = 1; + + /** + * The APN type is throttled until {@link android.os.SystemClock#elapsedRealtime()} + * has reached {@link ApnThrottleStatus#getThrottleExpiryTimeMillis} + */ + public static final int THROTTLE_TYPE_ELAPSED_TIME = 2; + + /** {@hide} */ + @IntDef(flag = true, prefix = {"THROTTLE_TYPE_"}, value = { + ApnThrottleStatus.THROTTLE_TYPE_NONE, + ApnThrottleStatus.THROTTLE_TYPE_ELAPSED_TIME, + }) + public @interface ThrottleType { + } + + /** + * The framework will not retry the APN type. + */ + public static final int RETRY_TYPE_NONE = 1; + + /** + * The next time the framework retries, it will attempt to establish a new connection. + */ + public static final int RETRY_TYPE_NEW_CONNECTION = 2; + + /** + * The next time the framework retires, it will retry to handover. + */ + public static final int RETRY_TYPE_HANDOVER = 3; + + /** {@hide} */ + @IntDef(flag = true, prefix = {"RETRY_TYPE_"}, value = { + ApnThrottleStatus.RETRY_TYPE_NONE, + ApnThrottleStatus.RETRY_TYPE_NEW_CONNECTION, + ApnThrottleStatus.RETRY_TYPE_HANDOVER, + }) + public @interface RetryType { + } + + private final int mSlotIndex; + private final @AccessNetworkConstants.TransportType int mTransportType; + private final @Annotation.ApnType int mApnType; + private final long mThrottleExpiryTimeMillis; + private final @RetryType int mRetryType; + private final @ThrottleType int mThrottleType; + + /** + * The slot index that the status applies to. + * + * @return the slot index + */ + public int getSlotIndex() { + return mSlotIndex; + } + + /** + * The type of transport that the status applies to. + * + * @return the transport type + */ + @AccessNetworkConstants.TransportType + public int getTransportType() { + return mTransportType; + } + + /** + * The APN type that the status applies to. + * + * @return the apn type + */ + @Annotation.ApnType + public int getApnType() { + return mApnType; + } + + /** + * The type of throttle applied to the APN type. + * + * @return the throttle type + */ + @ThrottleType + public int getThrottleType() { + return mThrottleType; + } + + /** + * Indicates the type of request that the framework will make the next time it retries + * to call {@link IDataService#setupDataCall}. + * + * @return the retry type + */ + @RetryType + public int getRetryType() { + return mRetryType; + } + + /** + * Gets the time at which the throttle expires. The value is based off of + * {@link SystemClock#elapsedRealtime}. + * + * This value only applies when the throttle type is set to + * {@link ApnThrottleStatus#THROTTLE_TYPE_ELAPSED_TIME}. + * + * A value of {@link Long#MAX_VALUE} implies that the APN type is throttled indefinitely. + * + * @return the time at which the throttle expires + */ + @ElapsedRealtimeLong + public long getThrottleExpiryTimeMillis() { + return mThrottleExpiryTimeMillis; + } + + private ApnThrottleStatus(int slotIndex, + @AccessNetworkConstants.TransportType int transportType, + @Annotation.ApnType int apnTypes, + @ThrottleType int throttleType, + long throttleExpiryTimeMillis, + @RetryType int retryType) { + mSlotIndex = slotIndex; + mTransportType = transportType; + mApnType = apnTypes; + mThrottleType = throttleType; + mThrottleExpiryTimeMillis = throttleExpiryTimeMillis; + mRetryType = retryType; + } + + private ApnThrottleStatus(@NonNull Parcel source) { + mSlotIndex = source.readInt(); + mTransportType = source.readInt(); + mApnType = source.readInt(); + mThrottleExpiryTimeMillis = source.readLong(); + mRetryType = source.readInt(); + mThrottleType = source.readInt(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mSlotIndex); + dest.writeInt(mTransportType); + dest.writeInt(mApnType); + dest.writeLong(mThrottleExpiryTimeMillis); + dest.writeInt(mRetryType); + dest.writeInt(mThrottleType); + } + + public static final @NonNull Parcelable.Creator<ApnThrottleStatus> CREATOR = + new Parcelable.Creator<ApnThrottleStatus>() { + @Override + public ApnThrottleStatus createFromParcel(@NonNull Parcel source) { + return new ApnThrottleStatus(source); + } + + @Override + public ApnThrottleStatus[] newArray(int size) { + return new ApnThrottleStatus[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public int hashCode() { + return Objects.hash(mSlotIndex, mApnType, mRetryType, mThrottleType, + mThrottleExpiryTimeMillis, mTransportType); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } else if (obj instanceof ApnThrottleStatus) { + ApnThrottleStatus other = (ApnThrottleStatus) obj; + return this.mSlotIndex == other.mSlotIndex + && this.mApnType == other.mApnType + && this.mRetryType == other.mRetryType + && this.mThrottleType == other.mThrottleType + && this.mThrottleExpiryTimeMillis == other.mThrottleExpiryTimeMillis + && this.mTransportType == other.mTransportType; + } else { + return false; + } + } + + @Override + public String toString() { + return "ApnThrottleStatus{" + + "mSlotIndex=" + mSlotIndex + + ", mTransportType=" + mTransportType + + ", mApnType=" + ApnSetting.getApnTypeString(mApnType) + + ", mThrottleExpiryTimeMillis=" + mThrottleExpiryTimeMillis + + ", mRetryType=" + mRetryType + + ", mThrottleType=" + mThrottleType + + '}'; + } + + /** + * Provides a convenient way to set the fields of an {@link ApnThrottleStatus} when creating a + * new instance. + * + * <p>The example below shows how you might create a new {@code ApnThrottleStatus}: + * + * <pre><code> + * + * DataCallResponseApnThrottleStatus = new ApnThrottleStatus.Builder() + * .setSlotIndex(1) + * .setApnType({@link ApnSetting#TYPE_EMERGENCY}) + * .setNoThrottle() + * .setRetryType({@link ApnThrottleStatus#RETRY_TYPE_NEW_CONNECTION}) + * .build(); + * </code></pre> + */ + public static final class Builder { + private int mSlotIndex; + private @AccessNetworkConstants.TransportType int mTransportType; + private @Annotation.ApnType int mApnType; + private long mThrottleExpiryTimeMillis; + private @RetryType int mRetryType; + private @ThrottleType int mThrottleType; + public static final long NO_THROTTLE_EXPIRY_TIME = + DataCallResponse.RETRY_DURATION_UNDEFINED; + + /** + * Default constructor for the Builder. + */ + public Builder() { + } + + /** + * Set the slot index. + * + * @param slotIndex the slot index. + * @return The same instance of the builder. + */ + @NonNull + public Builder setSlotIndex(int slotIndex) { + this.mSlotIndex = slotIndex; + return this; + } + + /** + * Set the transport type. + * + * @param transportType the transport type. + * @return The same instance of the builder. + */ + @NonNull + public Builder setTransportType(@AccessNetworkConstants.TransportType + int transportType) { + this.mTransportType = transportType; + return this; + } + + /** + * Set the APN type. + * + * @param apnType the APN type. + * @return The same instance of the builder. + */ + @NonNull + public Builder setApnType(@Annotation.ApnType int apnType) { + this.mApnType = apnType; + return this; + } + + /** + * Sets the time at which the throttle will expire. The value is based off of + * {@link SystemClock#elapsedRealtime}. + * + * When setting this value, the throttle type is set to + * {@link ApnThrottleStatus#THROTTLE_TYPE_ELAPSED_TIME}. + * + * A value of {@link Long#MAX_VALUE} implies that the APN type is throttled indefinitely. + * + * @param throttleExpiryTimeMillis The elapsed time at which the throttle expires. + * Throws {@link IllegalArgumentException} for values less + * than 0. + * @return The same instance of the builder. + */ + @NonNull + public Builder setThrottleExpiryTimeMillis( + @ElapsedRealtimeLong long throttleExpiryTimeMillis) { + if (throttleExpiryTimeMillis >= 0) { + this.mThrottleExpiryTimeMillis = throttleExpiryTimeMillis; + this.mThrottleType = THROTTLE_TYPE_ELAPSED_TIME; + } else { + throw new IllegalArgumentException("throttleExpiryTimeMillis must be greater than " + + "or equal to 0"); + } + return this; + } + + /** + * Sets the status of the APN type as not being throttled. + * + * When setting this value, the throttle type is set to + * {@link ApnThrottleStatus#THROTTLE_TYPE_NONE} and the expiry time is set to + * {@link Builder#NO_THROTTLE_EXPIRY_TIME}. + * + * @return The same instance of the builder. + */ + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public Builder setNoThrottle() { + mThrottleType = THROTTLE_TYPE_NONE; + mThrottleExpiryTimeMillis = NO_THROTTLE_EXPIRY_TIME; + return this; + } + + /** + * Set the type of request that the framework will make the next time it retries + * to call {@link IDataService#setupDataCall}. + * + * @param retryType the type of request + * @return The same instance of the builder. + */ + @NonNull + public Builder setRetryType(@RetryType int retryType) { + this.mRetryType = retryType; + return this; + } + + /** + * Build the {@link ApnThrottleStatus} + * + * @return the {@link ApnThrottleStatus} object + */ + @NonNull + public ApnThrottleStatus build() { + return new ApnThrottleStatus( + mSlotIndex, + mTransportType, + mApnType, + mThrottleType, + mThrottleExpiryTimeMillis, + mRetryType); + } + } +} diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java index f0088b913d4e..8348502586a5 100644 --- a/telephony/java/android/telephony/data/DataCallResponse.java +++ b/telephony/java/android/telephony/data/DataCallResponse.java @@ -109,10 +109,10 @@ public final class DataCallResponse implements Parcelable { public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL = 3; /** - * Indicates that data retry interval is not specified. Platform can determine when to + * Indicates that data retry duration is not specified. Platform can determine when to * perform data setup appropriately. */ - public static final int RETRY_INTERVAL_UNDEFINED = -1; + public static final int RETRY_DURATION_UNDEFINED = -1; /** * Indicates that the pdu session id is not set. @@ -254,19 +254,26 @@ public final class DataCallResponse implements Parcelable { /** * @return The suggested data retry time in milliseconds. * - * @deprecated Use {@link #getRetryIntervalMillis()} instead. + * @deprecated Use {@link #getRetryDurationMillis()} instead. */ @Deprecated public int getSuggestedRetryTime() { + + // To match the pre-deprecated getSuggestedRetryTime() behavior. + if (mSuggestedRetryTime == RETRY_DURATION_UNDEFINED) { + return 0; + } else if (mSuggestedRetryTime > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } return (int) mSuggestedRetryTime; } /** - * @return The network suggested data retry interval in milliseconds. {@code Long.MAX_VALUE} - * indicates data retry should not occur. {@link #RETRY_INTERVAL_UNDEFINED} indicates network - * did not suggest any retry interval. + * @return The network suggested data retry duration in milliseconds. {@code Long.MAX_VALUE} + * indicates data retry should not occur. {@link #RETRY_DURATION_UNDEFINED} indicates network + * did not suggest any retry duration. */ - public long getRetryIntervalMillis() { + public long getRetryDurationMillis() { return mSuggestedRetryTime; } @@ -537,7 +544,7 @@ public final class DataCallResponse implements Parcelable { public static final class Builder { private @DataFailureCause int mCause; - private long mSuggestedRetryTime = RETRY_INTERVAL_UNDEFINED; + private long mSuggestedRetryTime = RETRY_DURATION_UNDEFINED; private int mId; @@ -592,7 +599,7 @@ public final class DataCallResponse implements Parcelable { * @param suggestedRetryTime The suggested data retry time in milliseconds. * @return The same instance of the builder. * - * @deprecated Use {@link #setRetryIntervalMillis(long)} instead. + * @deprecated Use {@link #setRetryDurationMillis(long)} instead. */ @Deprecated public @NonNull Builder setSuggestedRetryTime(int suggestedRetryTime) { @@ -601,13 +608,13 @@ public final class DataCallResponse implements Parcelable { } /** - * Set the network suggested data retry interval. + * Set the network suggested data retry duration. * - * @param retryIntervalMillis The suggested data retry interval in milliseconds. + * @param retryDurationMillis The suggested data retry duration in milliseconds. * @return The same instance of the builder. */ - public @NonNull Builder setRetryIntervalMillis(long retryIntervalMillis) { - mSuggestedRetryTime = retryIntervalMillis; + public @NonNull Builder setRetryDurationMillis(long retryDurationMillis) { + mSuggestedRetryTime = retryDurationMillis; return this; } diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java index 77685971c138..2ec965101930 100644 --- a/telephony/java/android/telephony/data/DataService.java +++ b/telephony/java/android/telephony/data/DataService.java @@ -107,6 +107,9 @@ public abstract class DataService extends Service { private static final int DATA_SERVICE_INDICATION_DATA_CALL_LIST_CHANGED = 11; private static final int DATA_SERVICE_REQUEST_START_HANDOVER = 12; private static final int DATA_SERVICE_REQUEST_CANCEL_HANDOVER = 13; + private static final int DATA_SERVICE_REQUEST_REGISTER_APN_UNTHROTTLED = 14; + private static final int DATA_SERVICE_REQUEST_UNREGISTER_APN_UNTHROTTLED = 15; + private static final int DATA_SERVICE_INDICATION_APN_UNTHROTTLED = 16; private final HandlerThread mHandlerThread; @@ -129,6 +132,8 @@ public abstract class DataService extends Service { private final List<IDataServiceCallback> mDataCallListChangedCallbacks = new ArrayList<>(); + private final List<IDataServiceCallback> mApnUnthrottledCallbacks = new ArrayList<>(); + /** * Constructor * @param slotIndex SIM slot index the data service provider associated with. @@ -326,6 +331,19 @@ public abstract class DataService extends Service { } } + private void registerForApnUnthrottled(IDataServiceCallback callback) { + synchronized (mApnUnthrottledCallbacks) { + mApnUnthrottledCallbacks.add(callback); + } + } + + private void unregisterForApnUnthrottled(IDataServiceCallback callback) { + synchronized (mApnUnthrottledCallbacks) { + mApnUnthrottledCallbacks.remove(callback); + } + } + + /** * Notify the system that current data call list changed. Data service must invoke this * method whenever there is any data call status changed. @@ -343,6 +361,21 @@ public abstract class DataService extends Service { } /** + * Notify the system that a given APN was unthrottled. + * + * @param apn Access Point Name defined by the carrier. + */ + public final void notifyApnUnthrottled(@NonNull String apn) { + synchronized (mApnUnthrottledCallbacks) { + for (IDataServiceCallback callback : mApnUnthrottledCallbacks) { + mHandler.obtainMessage(DATA_SERVICE_INDICATION_APN_UNTHROTTLED, + mSlotIndex, 0, new ApnUnthrottledIndication(apn, + callback)).sendToTarget(); + } + } + } + + /** * 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. @@ -429,6 +462,16 @@ public abstract class DataService extends Service { } } + private static final class ApnUnthrottledIndication { + public final String apn; + public final IDataServiceCallback callback; + ApnUnthrottledIndication(String apn, + IDataServiceCallback callback) { + this.apn = apn; + this.callback = callback; + } + } + private class DataServiceHandler extends Handler { DataServiceHandler(Looper looper) { @@ -544,6 +587,26 @@ public abstract class DataService extends Service { (cReq.callback != null) ? new DataServiceCallback(cReq.callback) : null); break; + case DATA_SERVICE_REQUEST_REGISTER_APN_UNTHROTTLED: + if (serviceProvider == null) break; + serviceProvider.registerForApnUnthrottled((IDataServiceCallback) message.obj); + break; + case DATA_SERVICE_REQUEST_UNREGISTER_APN_UNTHROTTLED: + if (serviceProvider == null) break; + callback = (IDataServiceCallback) message.obj; + serviceProvider.unregisterForApnUnthrottled(callback); + break; + case DATA_SERVICE_INDICATION_APN_UNTHROTTLED: + if (serviceProvider == null) break; + ApnUnthrottledIndication apnUnthrottledIndication = + (ApnUnthrottledIndication) message.obj; + try { + apnUnthrottledIndication.callback + .onApnUnthrottled(apnUnthrottledIndication.apn); + } catch (RemoteException e) { + loge("Failed to call onApnUnthrottled. " + e); + } + break; } } } @@ -695,6 +758,26 @@ public abstract class DataService extends Service { mHandler.obtainMessage(DATA_SERVICE_REQUEST_CANCEL_HANDOVER, slotIndex, 0, req).sendToTarget(); } + + @Override + public void registerForUnthrottleApn(int slotIndex, IDataServiceCallback callback) { + if (callback == null) { + loge("registerForUnthrottleApn: callback is null"); + return; + } + mHandler.obtainMessage(DATA_SERVICE_REQUEST_REGISTER_APN_UNTHROTTLED, slotIndex, + 0, callback).sendToTarget(); + } + + @Override + public void unregisterForUnthrottleApn(int slotIndex, IDataServiceCallback callback) { + if (callback == null) { + loge("uregisterForUnthrottleApn: callback is null"); + return; + } + mHandler.obtainMessage(DATA_SERVICE_REQUEST_UNREGISTER_APN_UNTHROTTLED, + slotIndex, 0, callback).sendToTarget(); + } } private void log(String s) { diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java index eef0e017f998..52bf15fd16c3 100644 --- a/telephony/java/android/telephony/data/DataServiceCallback.java +++ b/telephony/java/android/telephony/data/DataServiceCallback.java @@ -233,7 +233,7 @@ public class DataServiceCallback { */ @NonNull public static String resultCodeToString(@DataServiceCallback.ResultCode int resultCode) { - switch(resultCode) { + switch (resultCode) { case RESULT_SUCCESS: return "RESULT_SUCCESS"; case RESULT_ERROR_UNSUPPORTED: @@ -248,4 +248,22 @@ public class DataServiceCallback { return "Missing case for result code=" + resultCode; } } + + /** + * Indicates that the specified APN is no longer throttled. + * + * @param apn Access Point Name defined by the carrier. + */ + public void onApnUnthrottled(@NonNull String apn) { + if (mCallback != null) { + try { + if (DBG) Rlog.d(TAG, "onApnUnthrottled"); + mCallback.onApnUnthrottled(apn); + } catch (RemoteException e) { + Rlog.e(TAG, "onApnUnthrottled: remote exception", e); + } + } else { + Rlog.e(TAG, "onApnUnthrottled: callback is null!"); + } + } } diff --git a/telephony/java/android/telephony/data/IDataService.aidl b/telephony/java/android/telephony/data/IDataService.aidl index 33226feb0e35..3f1f033d6f11 100644 --- a/telephony/java/android/telephony/data/IDataService.aidl +++ b/telephony/java/android/telephony/data/IDataService.aidl @@ -40,4 +40,6 @@ oneway interface IDataService void unregisterForDataCallListChanged(int slotId, IDataServiceCallback callback); void startHandover(int slotId, int cid, IDataServiceCallback callback); void cancelHandover(int slotId, int cid, IDataServiceCallback callback); + void registerForUnthrottleApn(int slotIndex, IDataServiceCallback callback); + void unregisterForUnthrottleApn(int slotIndex, IDataServiceCallback callback); } diff --git a/telephony/java/android/telephony/data/IDataServiceCallback.aidl b/telephony/java/android/telephony/data/IDataServiceCallback.aidl index d296e7b19be8..9cc2feac331a 100644 --- a/telephony/java/android/telephony/data/IDataServiceCallback.aidl +++ b/telephony/java/android/telephony/data/IDataServiceCallback.aidl @@ -32,4 +32,5 @@ oneway interface IDataServiceCallback void onDataCallListChanged(in List<DataCallResponse> dataCallList); void onHandoverStarted(int result); void onHandoverCancelled(int result); + void onApnUnthrottled(in String apn); } diff --git a/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl b/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl index 3bf09bc19788..2904082616e7 100644 --- a/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl +++ b/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl @@ -17,6 +17,7 @@ package android.telephony.data; import android.telephony.data.IQualifiedNetworksServiceCallback; +import android.telephony.data.ApnThrottleStatus; /** * {@hide} @@ -25,4 +26,5 @@ interface IQualifiedNetworksService { oneway void createNetworkAvailabilityProvider(int slotId, IQualifiedNetworksServiceCallback callback); oneway void removeNetworkAvailabilityProvider(int slotId); + oneway void reportApnThrottleStatusChanged(int slotId, in List<ApnThrottleStatus> statuses); } diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java index 05971c4d2e70..4af63b4cf981 100644 --- a/telephony/java/android/telephony/data/QualifiedNetworksService.java +++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java @@ -28,6 +28,7 @@ import android.os.Message; import android.os.RemoteException; import android.telephony.AccessNetworkConstants.AccessNetworkType; import android.telephony.Annotation.ApnType; +import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; @@ -65,6 +66,7 @@ public abstract class QualifiedNetworksService extends Service { private static final int QNS_REMOVE_NETWORK_AVAILABILITY_PROVIDER = 2; private static final int QNS_REMOVE_ALL_NETWORK_AVAILABILITY_PROVIDERS = 3; private static final int QNS_UPDATE_QUALIFIED_NETWORKS = 4; + private static final int QNS_APN_THROTTLE_STATUS_CHANGED = 5; private final HandlerThread mHandlerThread; @@ -160,6 +162,17 @@ public abstract class QualifiedNetworksService extends Service { } /** + * The framework calls this method when the throttle status of an APN changes. + * + * This method is meant to be overridden. + * + * @param statuses the statuses that have changed + */ + public void reportApnThrottleStatusChanged(@NonNull List<ApnThrottleStatus> statuses) { + Log.d(TAG, "reportApnThrottleStatusChanged: statuses size=" + statuses.size()); + } + + /** * Called when the qualified networks provider is removed. The extended class should * implement this method to perform cleanup works. */ @@ -197,6 +210,12 @@ public abstract class QualifiedNetworksService extends Service { + slotIndex); } break; + case QNS_APN_THROTTLE_STATUS_CHANGED: + if (provider != null) { + List<ApnThrottleStatus> statuses = (List<ApnThrottleStatus>) message.obj; + provider.reportApnThrottleStatusChanged(statuses); + } + break; case QNS_REMOVE_NETWORK_AVAILABILITY_PROVIDER: if (provider != null) { @@ -286,6 +305,13 @@ public abstract class QualifiedNetworksService extends Service { mHandler.obtainMessage(QNS_REMOVE_NETWORK_AVAILABILITY_PROVIDER, slotIndex, 0) .sendToTarget(); } + + @Override + public void reportApnThrottleStatusChanged(int slotIndex, + List<ApnThrottleStatus> statuses) { + mHandler.obtainMessage(QNS_APN_THROTTLE_STATUS_CHANGED, slotIndex, 0, statuses) + .sendToTarget(); + } } private void log(String s) { diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java index 1b51936e873b..aaa68d6f7d57 100644 --- a/telephony/java/android/telephony/ims/ImsCallProfile.java +++ b/telephony/java/android/telephony/ims/ImsCallProfile.java @@ -18,6 +18,7 @@ package android.telephony.ims; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; @@ -207,6 +208,42 @@ public final class ImsCallProfile implements Parcelable { "android.telephony.ims.extra.RETRY_CALL_FAIL_NETWORKTYPE"; /** + * Extra for the call composer call priority, either {@link ImsCallProfile#PRIORITY_NORMAL} or + * {@link ImsCallProfile#PRIORITY_URGENT}. It can be set via + * {@link #setCallExtraInt(String, int)}. + * + * Reference: RCC.20 Section 2.4.4.2 + */ + public static final String EXTRA_PRIORITY = "android.telephony.ims.extra.PRIORITY"; + + // TODO(hallliu) remove the reference to the maximum length and update it later. + /** + * Extra for the call composer call subject, a string of maximum length 60 characters. + * It can be set via {@link #setCallExtra(String, String)}. + * + * Reference: RCC.20 Section 2.4.3.2 + */ + public static final String EXTRA_CALL_SUBJECT = "android.telephony.ims.extra.CALL_SUBJECT"; + + /** + * Extra for the call composer call location, an {@Link android.location.Location} parcelable + * class to represent the geolocation as a latitude and longitude pair. It can be set via + * {@link #setCallExtraParcelable(String, Parcelable)}. + * + * Reference: RCC.20 Section 2.4.3.2 + */ + public static final String EXTRA_LOCATION = "android.telephony.ims.extra.LOCATION"; + + /** + * Extra for the call composer picture URL, a String that indicates the URL on the carrier’s + * server infrastructure to get the picture. It can be set via + * {@link #setCallExtra(String, String)}. + * + * Reference: RCC.20 Section 2.4.3.2 + */ + public static final String EXTRA_PICTURE_URL = "android.telephony.ims.extra.PICTURE_URL"; + + /** * Values for EXTRA_OIR / EXTRA_CNAP */ /** @@ -244,6 +281,21 @@ public final class ImsCallProfile implements Parcelable { */ public static final int DIALSTRING_USSD = 2; + // Values for EXTRA_PRIORITY + /** + * Indicates the call composer call priority is normal. + * + * Reference: RCC.20 Section 2.4.4.2 + */ + public static final int PRIORITY_NORMAL = 0; + + /** + * Indicates the call composer call priority is urgent. + * + * Reference: RCC.20 Section 2.4.4.2 + */ + public static final int PRIORITY_URGENT = 1; + /** * Call is not restricted on peer side and High Definition media is supported */ @@ -588,6 +640,19 @@ public final class ImsCallProfile implements Parcelable { return mCallExtras.getInt(name, defaultValue); } + /** + * Get the call extras (Parcelable), given the extra name. + * @param name call extra name + * @return the corresponding call extra Parcelable or null if not applicable + */ + @Nullable + public <T extends Parcelable> T getCallExtraParcelable(@Nullable String name) { + if (mCallExtras != null) { + return mCallExtras.getParcelable(name); + } + return null; + } + public void setCallExtra(String name, String value) { if (mCallExtras != null) { mCallExtras.putString(name, value); @@ -607,6 +672,17 @@ public final class ImsCallProfile implements Parcelable { } /** + * Set the call extra value (Parcelable), given the call extra name. + * @param name call extra name + * @param parcelable call extra value + */ + public void setCallExtraParcelable(@NonNull String name, @NonNull Parcelable parcelable) { + if (mCallExtras != null) { + mCallExtras.putParcelable(name, parcelable); + } + } + + /** * Set the call restrict cause, which provides the reason why a call has been restricted from * using High Definition media. */ diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java index a3efb799029a..0aff99709a52 100755 --- a/telephony/java/android/telephony/ims/ImsCallSession.java +++ b/telephony/java/android/telephony/ims/ImsCallSession.java @@ -101,10 +101,29 @@ public class ImsCallSession { */ public static class Listener { /** - * Called when a request is sent out to initiate a new session - * and 1xx response is received from the network. + * Called when the session is initiating. * - * @param session the session object that carries out the IMS session + * see: {@link ImsCallSessionListener#callSessionInitiating(ImsCallProfile)} + */ + public void callSessionInitiating(ImsCallSession session, + ImsCallProfile profile) { + // no-op + } + + /** + * Called when the session failed before initiating was called. + * + * see: {@link ImsCallSessionListener#callSessionInitiatingFailed(ImsReasonInfo)} + */ + public void callSessionInitiatingFailed(ImsCallSession session, + ImsReasonInfo reasonInfo) { + // no-op + } + + /** + * Called when the session is progressing. + * + * see: {@link ImsCallSessionListener#callSessionProgressing(ImsStreamMediaProfile)} */ public void callSessionProgressing(ImsCallSession session, ImsStreamMediaProfile profile) { @@ -1179,6 +1198,13 @@ public class ImsCallSession { * Notifies the result of the basic session operation (setup / terminate). */ @Override + public void callSessionInitiating(ImsCallProfile profile) { + if (mListener != null) { + mListener.callSessionInitiating(ImsCallSession.this, profile); + } + } + + @Override public void callSessionProgressing(ImsStreamMediaProfile profile) { if (mListener != null) { mListener.callSessionProgressing(ImsCallSession.this, profile); @@ -1193,6 +1219,13 @@ public class ImsCallSession { } @Override + public void callSessionInitiatingFailed(ImsReasonInfo reasonInfo) { + if (mListener != null) { + mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo); + } + } + + @Override public void callSessionInitiatedFailed(ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo); diff --git a/telephony/java/android/telephony/ims/ImsCallSessionListener.java b/telephony/java/android/telephony/ims/ImsCallSessionListener.java index 86bb5d9f0b09..db99acfd9a35 100644 --- a/telephony/java/android/telephony/ims/ImsCallSessionListener.java +++ b/telephony/java/android/telephony/ims/ImsCallSessionListener.java @@ -53,8 +53,45 @@ public class ImsCallSessionListener { } /** - * A request has been sent out to initiate a new IMS call session and a 1xx response has been - * received from the network. + * Called when the network first begins to establish the call session and is now connecting + * to the remote party. This must be called once after {@link ImsCallSessionImplBase#start} and + * before any other method on this listener. After this is called, + * {@link #callSessionProgressing(ImsStreamMediaProfile)} must be called to communicate any + * further updates. + * <p/> + * Once this is called, {@link #callSessionTerminated} must be called + * to end the call session. In the event that the session failed before the remote party + * was contacted, {@link #callSessionInitiatingFailed} must be called. + * + * @param profile the associated {@link ImsCallProfile}. + */ + public void callSessionInitiating(@NonNull ImsCallProfile profile) { + try { + mListener.callSessionInitiating(profile); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * The IMS call session establishment has failed while initiating. + * + * @param reasonInfo {@link ImsReasonInfo} detailing the reason of the IMS call session + * establishment failure. + */ + public void callSessionInitiatingFailed(@NonNull ImsReasonInfo reasonInfo) { + try { + mListener.callSessionInitiatingFailed(reasonInfo); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Called after the network has contacted the remote party and the call state should move to + * ALERTING. + * + * @param profile the associated {@link ImsCallProfile}. */ public void callSessionProgressing(ImsStreamMediaProfile profile) { try { @@ -65,7 +102,8 @@ public class ImsCallSessionListener { } /** - * The IMS call session has been initiated. + * Called once the outgoing IMS call session has been begun between the local and remote party. + * The call state should move to ACTIVE. * * @param profile the associated {@link ImsCallProfile}. */ @@ -82,7 +120,12 @@ public class ImsCallSessionListener { * * @param reasonInfo {@link ImsReasonInfo} detailing the reason of the IMS call session * establishment failure. + * @deprecated {@link #callSessionInitiated(ImsCallProfile)} is called immediately after + * the session is first started which meant that there was no time in which a call to this + * method was technically valid. This method is replaced starting Android S in favor of + * {@link #callSessionInitiatingFailed(ImsReasonInfo)}. */ + @Deprecated public void callSessionInitiatedFailed(ImsReasonInfo reasonInfo) { try { mListener.callSessionInitiatedFailed(reasonInfo); diff --git a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java index b0aaa92dd0ac..519d0164b0d6 100644 --- a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java +++ b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java @@ -34,11 +34,15 @@ import java.util.List; * network during a SUBSCRIBE request. See RFC3863 for more information. * @hide */ -public class RcsContactPresenceTuple implements Parcelable { +public final class RcsContactPresenceTuple implements Parcelable { /** The service id of the MMTEL */ public static final String SERVICE_ID_MMTEL = "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.mmtel"; + /** The service id of the Call Composer */ + public static final String SERVICE_ID_CALL_COMPOSER = + "org.3gpp.urn:urn-7:3gppservice.ims.icsi.gsma.callcomposer"; + /** The service capabilities is available. */ public static final String TUPLE_BASIC_STATUS_OPEN = "open"; @@ -57,7 +61,7 @@ public class RcsContactPresenceTuple implements Parcelable { * An optional addition to the PIDF Presence Tuple containing service capabilities, which is * defined in the servcaps element. See RFC5196, section 3.2.1. */ - public static class ServiceCapabilities implements Parcelable { + public static final class ServiceCapabilities implements Parcelable { /** The service can simultaneously send and receive data. */ public static final String DUPLEX_MODE_FULL = "full"; @@ -84,7 +88,7 @@ public class RcsContactPresenceTuple implements Parcelable { /** * Builder to help construct {@link ServiceCapabilities} instances. */ - public static class Builder { + public static final class Builder { private ServiceCapabilities mCapabilities; @@ -102,7 +106,7 @@ public class RcsContactPresenceTuple implements Parcelable { * Add the supported duplex mode. * @param mode The supported duplex mode */ - public Builder addSupportedDuplexMode(@NonNull @DuplexMode String mode) { + public @NonNull Builder addSupportedDuplexMode(@NonNull @DuplexMode String mode) { mCapabilities.mSupportedDuplexModeList.add(mode); return this; } @@ -111,7 +115,7 @@ public class RcsContactPresenceTuple implements Parcelable { * Add the unsupported duplex mode. * @param mode The unsupported duplex mode */ - public Builder addUnsupportedDuplexMode(@NonNull @DuplexMode String mode) { + public @NonNull Builder addUnsupportedDuplexMode(@NonNull @DuplexMode String mode) { mCapabilities.mUnsupportedDuplexModeList.add(mode); return this; } @@ -119,7 +123,7 @@ public class RcsContactPresenceTuple implements Parcelable { /** * @return the ServiceCapabilities instance. */ - public ServiceCapabilities build() { + public @NonNull ServiceCapabilities build() { return mCapabilities; } } @@ -207,9 +211,9 @@ public class RcsContactPresenceTuple implements Parcelable { /** * Builder to help construct {@link RcsContactPresenceTuple} instances. */ - public static class Builder { + public static final class Builder { - private RcsContactPresenceTuple mPresenceTuple; + private final RcsContactPresenceTuple mPresenceTuple; /** * Builds a RcsContactPresenceTuple instance. @@ -226,7 +230,7 @@ public class RcsContactPresenceTuple implements Parcelable { /** * The optional SIP Contact URI associated with the PIDF tuple element. */ - public Builder addContactUri(@NonNull Uri contactUri) { + public @NonNull Builder addContactUri(@NonNull Uri contactUri) { mPresenceTuple.mContactUri = contactUri; return this; } @@ -235,7 +239,7 @@ public class RcsContactPresenceTuple implements Parcelable { * The optional timestamp indicating the data and time of the status change of this tuple. * See RFC3863, section 4.1.7 for more information on the expected format. */ - public Builder addTimeStamp(@NonNull String timestamp) { + public @NonNull Builder addTimeStamp(@NonNull String timestamp) { mPresenceTuple.mTimestamp = timestamp; return this; } @@ -244,7 +248,7 @@ public class RcsContactPresenceTuple implements Parcelable { * An optional parameter containing the description element of the service-description. See * OMA Presence SIMPLE specification v1.1 */ - public Builder addDescription(@NonNull String description) { + public @NonNull Builder addDescription(@NonNull String description) { mPresenceTuple.mServiceDescription = description; return this; } @@ -253,7 +257,7 @@ public class RcsContactPresenceTuple implements Parcelable { * An optional parameter containing the service capabilities of the presence tuple if they * are present in the servcaps element. */ - public Builder addServiceCapabilities(@NonNull ServiceCapabilities caps) { + public @NonNull Builder addServiceCapabilities(@NonNull ServiceCapabilities caps) { mPresenceTuple.mServiceCapabilities = caps; return this; } @@ -261,7 +265,7 @@ public class RcsContactPresenceTuple implements Parcelable { /** * @return the constructed instance. */ - public RcsContactPresenceTuple build() { + public @NonNull RcsContactPresenceTuple build() { return mPresenceTuple; } } diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java index 5848be8b0bf2..d4715bfeeb3e 100644 --- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java +++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java @@ -144,7 +144,7 @@ public final class RcsContactUceCapability implements Parcelable { * @param tag the supported feature tag * @return this OptionBuilder */ - public @NonNull OptionsBuilder addFeatureTag(String tag) { + public @NonNull OptionsBuilder addFeatureTag(@NonNull String tag) { mCapabilities.mFeatureTags.add(tag); return this; } @@ -154,7 +154,7 @@ public final class RcsContactUceCapability implements Parcelable { * @param tags the list of the supported feature tags * @return this OptionBuilder */ - public @NonNull OptionsBuilder addFeatureTags(List<String> tags) { + public @NonNull OptionsBuilder addFeatureTags(@NonNull List<String> tags) { mCapabilities.mFeatureTags.addAll(tags); return this; } @@ -195,7 +195,7 @@ public final class RcsContactUceCapability implements Parcelable { * @param tuple The {@link RcsContactPresenceTuple} to be added into. * @return this PresenceBuilder */ - public @NonNull PresenceBuilder addCapabilityTuple(RcsContactPresenceTuple tuple) { + public @NonNull PresenceBuilder addCapabilityTuple(@NonNull RcsContactPresenceTuple tuple) { mCapabilities.mPresenceTuples.add(tuple); return this; } @@ -205,7 +205,8 @@ public final class RcsContactUceCapability implements Parcelable { * @param tuples The list of the {@link RcsContactPresenceTuple} to be added into. * @return this PresenceBuilder */ - public @NonNull PresenceBuilder addCapabilityTuples(List<RcsContactPresenceTuple> tuples) { + public @NonNull PresenceBuilder addCapabilityTuples( + @NonNull List<RcsContactPresenceTuple> tuples) { mCapabilities.mPresenceTuples.addAll(tuples); return this; } @@ -282,7 +283,7 @@ public final class RcsContactUceCapability implements Parcelable { * @return The feature tags present in the OPTIONS response from the network. * <p> * Note: this is only populated if {@link #getCapabilityMechanism} is - * {@link CAPABILITY_MECHANISM_OPTIONS} + * {@link RcsContactUceCapability#CAPABILITY_MECHANISM_OPTIONS} */ public @NonNull List<String> getOptionsFeatureTags() { if (mCapabilityMechanism != CAPABILITY_MECHANISM_OPTIONS) { @@ -296,7 +297,7 @@ public final class RcsContactUceCapability implements Parcelable { * contained in the NOTIFY response from the network. * <p> * Note: this is only populated if {@link #getCapabilityMechanism} is - * {@link CAPABILITY_MECHANISM_PRESENCE} + * {@link RcsContactUceCapability#CAPABILITY_MECHANISM_PRESENCE} */ public @NonNull List<RcsContactPresenceTuple> getPresenceTuples() { if (mCapabilityMechanism != CAPABILITY_MECHANISM_PRESENCE) { @@ -312,9 +313,9 @@ public final class RcsContactUceCapability implements Parcelable { * * <p> * Note: this is only populated if {@link #getCapabilityMechanism} is - * {@link CAPABILITY_MECHANISM_PRESENCE} + * {@link RcsContactUceCapability#CAPABILITY_MECHANISM_PRESENCE} */ - public @Nullable RcsContactPresenceTuple getPresenceTuple(String serviceId) { + public @Nullable RcsContactPresenceTuple getPresenceTuple(@NonNull String serviceId) { if (mCapabilityMechanism != CAPABILITY_MECHANISM_PRESENCE) { return null; } diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java index 8d7742b7510b..6c31466c2a89 100644 --- a/telephony/java/android/telephony/ims/RcsUceAdapter.java +++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java @@ -36,7 +36,9 @@ import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.Executor; /** @@ -110,7 +112,7 @@ public class RcsUceAdapter { public static final int ERROR_FORBIDDEN = 6; /** - * The contact URI requested is not provisioned for VoLTE or it is not known as an IMS + * The contact URI requested is not provisioned for voice or it is not known as an IMS * subscriber to the carrier network. * @hide */ @@ -128,26 +130,26 @@ public class RcsUceAdapter { * The network did not respond to the capabilities request before the request timed out. * @hide */ - public static final int ERROR_REQUEST_TIMEOUT = 10; + public static final int ERROR_REQUEST_TIMEOUT = 9; /** * The request failed due to the service having insufficient memory. * @hide */ - public static final int ERROR_INSUFFICIENT_MEMORY = 11; + public static final int ERROR_INSUFFICIENT_MEMORY = 10; /** * The network was lost while trying to complete the request. * @hide */ - public static final int ERROR_LOST_NETWORK = 12; + public static final int ERROR_LOST_NETWORK = 11; /** * The network is temporarily unavailable or busy. Retries should only be done after the retry * time returned in {@link CapabilitiesCallback#onError} has elapsed. * @hide */ - public static final int ERROR_SERVER_UNAVAILABLE = 13; + public static final int ERROR_SERVER_UNAVAILABLE = 12; /**@hide*/ @Retention(RetentionPolicy.SOURCE) @@ -168,69 +170,93 @@ public class RcsUceAdapter { public @interface ErrorCode {} /** + * A capability update has been requested but the reason is unknown. + * @hide + */ + @SystemApi + public static final int CAPABILITY_UPDATE_TRIGGER_UNKNOWN = 0; + + /** * A capability update has been requested due to the Entity Tag (ETag) expiring. * @hide */ - public static final int CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED = 0; + @SystemApi + public static final int CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED = 1; + /** * A capability update has been requested due to moving to LTE with VoPS disabled. * @hide */ - public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_DISABLED = 1; + @SystemApi + public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_DISABLED = 2; + /** * A capability update has been requested due to moving to LTE with VoPS enabled. * @hide */ - public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_ENABLED = 2; + @SystemApi + public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_ENABLED = 3; + /** * A capability update has been requested due to moving to eHRPD. * @hide */ - public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_EHRPD = 3; + @SystemApi + public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_EHRPD = 4; + /** * A capability update has been requested due to moving to HSPA+. * @hide */ - public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_HSPAPLUS = 4; + @SystemApi + public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_HSPAPLUS = 5; + /** * A capability update has been requested due to moving to 3G. * @hide */ - public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_3G = 5; + @SystemApi + public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_3G = 6; + /** * A capability update has been requested due to moving to 2G. * @hide */ - public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_2G = 6; + @SystemApi + public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_2G = 7; + /** * A capability update has been requested due to moving to WLAN * @hide */ - public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN = 7; + @SystemApi + public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN = 8; + /** * A capability update has been requested due to moving to IWLAN * @hide */ - public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_IWLAN = 8; - /** - * A capability update has been requested but the reason is unknown. - * @hide - */ - public static final int CAPABILITY_UPDATE_TRIGGER_UNKNOWN = 9; + @SystemApi + public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_IWLAN = 9; + /** * A capability update has been requested due to moving to 5G NR with VoPS disabled. * @hide */ + @SystemApi public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_DISABLED = 10; + /** * A capability update has been requested due to moving to 5G NR with VoPS enabled. * @hide */ + @SystemApi public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED = 11; /**@hide*/ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = "ERROR_", value = { + CAPABILITY_UPDATE_TRIGGER_UNKNOWN, CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED, CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_DISABLED, CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_ENABLED, @@ -240,7 +266,6 @@ public class RcsUceAdapter { CAPABILITY_UPDATE_TRIGGER_MOVE_TO_2G, CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN, CAPABILITY_UPDATE_TRIGGER_MOVE_TO_IWLAN, - CAPABILITY_UPDATE_TRIGGER_UNKNOWN, CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_DISABLED, CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED }) @@ -251,32 +276,37 @@ public class RcsUceAdapter { * UCE. * @hide */ + @SystemApi public static final int PUBLISH_STATE_OK = 1; /** * The hasn't published its capabilities since boot or hasn't gotten any publish response yet. * @hide */ + @SystemApi public static final int PUBLISH_STATE_NOT_PUBLISHED = 2; /** * The device has tried to publish its capabilities, which has resulted in an error. This error - * is related to the fact that the device is not VoLTE provisioned. + * is related to the fact that the device is not provisioned for voice. * @hide */ - public static final int PUBLISH_STATE_VOLTE_PROVISION_ERROR = 3; + @SystemApi + public static final int PUBLISH_STATE_VOICE_PROVISION_ERROR = 3; /** * The device has tried to publish its capabilities, which has resulted in an error. This error * is related to the fact that the device is not RCS or UCE provisioned. * @hide */ + @SystemApi public static final int PUBLISH_STATE_RCS_PROVISION_ERROR = 4; /** * The last publish resulted in a "408 Request Timeout" response. * @hide */ + @SystemApi public static final int PUBLISH_STATE_REQUEST_TIMEOUT = 5; /** @@ -286,6 +316,7 @@ public class RcsUceAdapter { * Device shall retry with exponential back-off. * @hide */ + @SystemApi public static final int PUBLISH_STATE_OTHER_ERROR = 6; /**@hide*/ @@ -293,7 +324,7 @@ public class RcsUceAdapter { @IntDef(prefix = "PUBLISH_STATE_", value = { PUBLISH_STATE_OK, PUBLISH_STATE_NOT_PUBLISHED, - PUBLISH_STATE_VOLTE_PROVISION_ERROR, + PUBLISH_STATE_VOICE_PROVISION_ERROR, PUBLISH_STATE_RCS_PROVISION_ERROR, PUBLISH_STATE_REQUEST_TIMEOUT, PUBLISH_STATE_OTHER_ERROR @@ -301,56 +332,62 @@ public class RcsUceAdapter { public @interface PublishState {} /** - * An application can use {@link #registerPublishStateCallback} to register a - * {@link PublishStateCallback), which will notify the user when the publish state to the - * network changes. + * An application can use {@link #addOnPublishStateChangedListener} to register a + * {@link OnPublishStateChangedListener ), which will notify the user when the publish state to + * the network changes. * @hide */ - public static class PublishStateCallback { + @SystemApi + public interface OnPublishStateChangedListener { + /** + * Notifies the callback when the publish state has changed. + * @param publishState The latest update to the publish state. + */ + void onPublishStateChange(@PublishState int publishState); + } - private static class PublishStateBinder extends IRcsUcePublishStateCallback.Stub { + /** + * An application can use {@link #addOnPublishStateChangedListener} to register a + * {@link OnPublishStateChangedListener ), which will notify the user when the publish state to + * the network changes. + * @hide + */ + public static class PublishStateCallbackAdapter { - private final PublishStateCallback mLocalCallback; - private Executor mExecutor; + private static class PublishStateBinder extends IRcsUcePublishStateCallback.Stub { + private final OnPublishStateChangedListener mPublishStateChangeListener; + private final Executor mExecutor; - PublishStateBinder(PublishStateCallback c) { - mLocalCallback = c; + PublishStateBinder(Executor executor, OnPublishStateChangedListener listener) { + mExecutor = executor; + mPublishStateChangeListener = listener; } @Override public void onPublishStateChanged(int publishState) { - if (mLocalCallback == null) return; + if (mPublishStateChangeListener == null) return; final long callingIdentity = Binder.clearCallingIdentity(); try { - mExecutor.execute(() -> mLocalCallback.onChanged(publishState)); + mExecutor.execute(() -> + mPublishStateChangeListener.onPublishStateChange(publishState)); } finally { restoreCallingIdentity(callingIdentity); } } - - private void setExecutor(Executor executor) { - mExecutor = executor; - } } - private final PublishStateBinder mBinder = new PublishStateBinder(this); + private final PublishStateBinder mBinder; + + public PublishStateCallbackAdapter(@NonNull Executor executor, + @NonNull OnPublishStateChangedListener listener) { + mBinder = new PublishStateBinder(executor, listener); + } /**@hide*/ public final IRcsUcePublishStateCallback getBinder() { return mBinder; } - - private void setExecutor(Executor executor) { - mBinder.setExecutor(executor); - } - - /** - * Notifies the callback when the publish state has changed. - * @param publishState The latest update to the publish state. - */ - public void onChanged(@PublishState int publishState) { - } } /** @@ -395,6 +432,8 @@ public class RcsUceAdapter { private final Context mContext; private final int mSubId; + private final Map<OnPublishStateChangedListener, PublishStateCallbackAdapter> + mPublishStateCallbacks; /** * Not to be instantiated directly, use {@link ImsRcsManager#getUceAdapter()} to instantiate @@ -404,6 +443,7 @@ public class RcsUceAdapter { RcsUceAdapter(Context context, int subId) { mContext = context; mSubId = subId; + mPublishStateCallbacks = new HashMap<>(); } /** @@ -588,6 +628,7 @@ public class RcsUceAdapter { * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes. * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public @PublishState int getUcePublishState() throws ImsException { IImsRcsController imsRcsController = getIImsRcsController(); @@ -609,81 +650,90 @@ public class RcsUceAdapter { } /** - * Registers a {@link PublishStateCallback} with the system, which will provide publish state - * updates for the subscription specified in {@link ImsManager@getRcsManager(subid)}. + * Registers a {@link OnPublishStateChangedListener} with the system, which will provide publish + * state updates for the subscription specified in {@link ImsManager@getRcsManager(subid)}. * <p> * Use {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to subscription * changed events and call {@link #unregisterPublishStateCallback} to clean up. * <p> - * The registered {@link PublishStateCallback} will also receive a callback when it is + * The registered {@link OnPublishStateChangedListener} will also receive a callback when it is * registered with the current publish state. * * @param executor The executor the listener callback events should be run on. - * @param c The {@link PublishStateCallback} to be added. + * @param listener The {@link OnPublishStateChangedListener} to be added. * @throws ImsException if the subscription associated with this callback is valid, but * the {@link ImsService} associated with the subscription is not available. This can happen if * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed * reason. * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public void registerPublishStateCallback(@NonNull @CallbackExecutor Executor executor, - @NonNull PublishStateCallback c) throws ImsException { - if (c == null) { - throw new IllegalArgumentException("Must include a non-null PublishStateCallback."); - } + public void addOnPublishStateChangedListener(@NonNull @CallbackExecutor Executor executor, + @NonNull OnPublishStateChangedListener listener) throws ImsException { if (executor == null) { throw new IllegalArgumentException("Must include a non-null Executor."); } + if (listener == null) { + throw new IllegalArgumentException( + "Must include a non-null OnPublishStateChangedListener."); + } IImsRcsController imsRcsController = getIImsRcsController(); if (imsRcsController == null) { - Log.e(TAG, "registerPublishStateCallback : IImsRcsController is null"); + Log.e(TAG, "addOnPublishStateChangedListener : IImsRcsController is null"); throw new ImsException("Cannot find remote IMS service", - ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } - c.setExecutor(executor); + PublishStateCallbackAdapter stateCallback = addPublishStateCallback(executor, listener); try { - imsRcsController.registerUcePublishStateCallback(mSubId, c.getBinder()); + imsRcsController.registerUcePublishStateCallback(mSubId, stateCallback.getBinder()); } catch (ServiceSpecificException e) { throw new ImsException(e.getMessage(), e.errorCode); } catch (RemoteException e) { Log.e(TAG, "Error calling IImsRcsController#registerUcePublishStateCallback", e); throw new ImsException("Remote IMS Service is not available", - ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } /** - * Removes an existing {@link PublishStateCallback}. + * Removes an existing {@link OnPublishStateChangedListener}. * <p> * When the subscription associated with this callback is removed * (SIM removed, ESIM swap,etc...), this callback will automatically be removed. If this method * is called for an inactive subscription, it will result in a no-op. * - * @param c The callback to be unregistered. + * @param listener The callback to be unregistered. * @throws ImsException if the subscription associated with this callback is valid, but * the {@link ImsService} associated with the subscription is not available. This can happen if * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed * reason. * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public void unregisterPublishStateCallback(@NonNull PublishStateCallback c) - throws ImsException { - if (c == null) { - throw new IllegalArgumentException("Must include a non-null PublishStateCallback."); + public void removeOnPublishStateChangedListener( + @NonNull OnPublishStateChangedListener listener) throws ImsException { + if (listener == null) { + throw new IllegalArgumentException( + "Must include a non-null OnPublishStateChangedListener."); } IImsRcsController imsRcsController = getIImsRcsController(); if (imsRcsController == null) { - Log.e(TAG, "unregisterPublishStateCallback: IImsRcsController is null"); + Log.e(TAG, "removeOnPublishStateChangedListener: IImsRcsController is null"); throw new ImsException("Cannot find remote IMS service", ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } + PublishStateCallbackAdapter callback = removePublishStateCallback(listener); + if (callback == null) { + return; + } + try { - imsRcsController.unregisterUcePublishStateCallback(mSubId, c.getBinder()); + imsRcsController.unregisterUcePublishStateCallback(mSubId, callback.getBinder()); } catch (android.os.ServiceSpecificException e) { throw new ImsException(e.getMessage(), e.errorCode); } catch (RemoteException e) { @@ -763,6 +813,36 @@ public class RcsUceAdapter { } } + /** + * Add the {@link OnPublishStateChangedListener} to collection for tracking. + * @param executor The executor that will be used when the publish state is changed and the + * {@link OnPublishStateChangedListener} is called. + * @param listener The {@link OnPublishStateChangedListener} to call the publish state changed. + * @return The {@link PublishStateCallbackAdapter} to wrapper the + * {@link OnPublishStateChangedListener} + */ + private PublishStateCallbackAdapter addPublishStateCallback(@NonNull Executor executor, + @NonNull OnPublishStateChangedListener listener) { + PublishStateCallbackAdapter adapter = new PublishStateCallbackAdapter(executor, listener); + synchronized (mPublishStateCallbacks) { + mPublishStateCallbacks.put(listener, adapter); + } + return adapter; + } + + /** + * Remove the existing {@link OnPublishStateChangedListener}. + * @param listener The {@link OnPublishStateChangedListener} to remove from the collection. + * @return The wrapper class {@link PublishStateCallbackAdapter} associated with the + * {@link OnPublishStateChangedListener}. + */ + private PublishStateCallbackAdapter removePublishStateCallback( + @NonNull OnPublishStateChangedListener listener) { + synchronized (mPublishStateCallbacks) { + return mPublishStateCallbacks.remove(listener); + } + } + private IImsRcsController getIImsRcsController() { IBinder binder = TelephonyFrameworkInitializer .getTelephonyServiceManager() diff --git a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java new file mode 100644 index 000000000000..4435640e008c --- /dev/null +++ b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.ims.aidl; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.Uri; +import android.os.Binder; +import android.os.RemoteException; +import android.telephony.ims.RcsContactUceCapability; +import android.telephony.ims.stub.CapabilityExchangeEventListener; +import android.util.Log; + +import java.util.List; + +/** + * The ICapabilityExchangeEventListener wrapper class to store the listener which is registered by + * the framework. This wrapper class also delivers the request to the framework when receive the + * request from the network. + * @hide + */ +public class CapabilityExchangeAidlWrapper implements CapabilityExchangeEventListener { + + private static final String LOG_TAG = "CapExchangeListener"; + + private final ICapabilityExchangeEventListener mListenerBinder; + + public CapabilityExchangeAidlWrapper(@Nullable ICapabilityExchangeEventListener listener) { + mListenerBinder = listener; + } + + /** + * Receives the request of publishing capabilities from the network and deliver this request + * to the framework via the registered capability exchange event listener. + */ + public void onRequestPublishCapabilities(int publishTriggerType) { + ICapabilityExchangeEventListener listener = mListenerBinder; + if (listener == null) { + return; + } + try { + listener.onRequestPublishCapabilities(publishTriggerType); + } catch (RemoteException e) { + Log.w(LOG_TAG, "request publish capabilities exception: " + e); + } + } + + /** + * Receives the unpublish notification and deliver this callback to the framework. + */ + public void onUnpublish() { + ICapabilityExchangeEventListener listener = mListenerBinder; + if (listener == null) { + return; + } + try { + listener.onUnpublish(); + } catch (RemoteException e) { + Log.w(LOG_TAG, "Unpublish exception: " + e); + } + } + + /** + * Receives the callback of the remote capability request from the network and deliver this + * request to the framework. + */ + public void onRemoteCapabilityRequest(@NonNull Uri contactUri, + @NonNull List<String> remoteCapabilities, @NonNull OptionsRequestCallback callback) { + ICapabilityExchangeEventListener listener = mListenerBinder; + if (listener == null) { + return; + } + + IOptionsRequestCallback internalCallback = new IOptionsRequestCallback.Stub() { + @Override + public void respondToCapabilityRequest(RcsContactUceCapability ownCapabilities) { + final long callingIdentity = Binder.clearCallingIdentity(); + try { + callback.onRespondToCapabilityRequest(ownCapabilities); + } finally { + restoreCallingIdentity(callingIdentity); + } + } + @Override + public void respondToCapabilityRequestWithError(int code, String reason) { + final long callingIdentity = Binder.clearCallingIdentity(); + try { + callback.onRespondToCapabilityRequestWithError(code, reason); + } finally { + restoreCallingIdentity(callingIdentity); + } + } + }; + + try { + listener.onRemoteCapabilityRequest(contactUri, remoteCapabilities, internalCallback); + } catch (RemoteException e) { + Log.w(LOG_TAG, "Remote capability request exception: " + e); + } + } +} diff --git a/telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl b/telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl index a4ffbef9fa84..078ac919b75e 100644 --- a/telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl +++ b/telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl @@ -22,54 +22,15 @@ import android.telephony.ims.aidl.IOptionsRequestCallback; import java.util.List; /** - * Listener interface for the ImsService to use to notify the framework of UCE events. + * Listener interface for the ImsService to use to notify the framework of UCE + * events. + * + * See CapabilityExchangeEventListener for more information. * {@hide} */ oneway interface ICapabilityExchangeEventListener { - /** - * Trigger the framework to provide a capability update using - * {@link RcsCapabilityExchangeImplBase#publishCapabilities}. - * <p> - * This is typically used when trying to generate an initial PUBLISH for a new - * subscription to the network. The device will cache all presence publications - * after boot until this method is called the first time. - * @param publishTriggerType {@link StackPublishTriggerType} The reason for the - * capability update request. - * @throws ImsException If this {@link RcsPresenceExchangeImplBase} instance is - * not currently connected to the framework. This can happen if the - * {@link RcsFeature} is not {@link ImsFeature#STATE_READY} and the - * {@link RcsFeature} has not received the - * {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare - * cases when the Telephony stack has crashed. - */ void onRequestPublishCapabilities(int publishTriggerType); - - /** - * Notify the framework that the device's capabilities have been unpublished from the network. - * - * @throws ImsException If this {@link RcsPresenceExchangeImplBase} instance is not currently - * connected to the framework. This can happen if the {@link RcsFeature} is not - * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received the - * {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare cases when the - * Telephony stack has crashed. - */ void onUnpublish(); - - /** - * Inform the framework of a query for this device's UCE capabilities. - * <p> - * The framework will respond via the - * {@link IOptionsRequestCallback#respondToCapabilityRequest} or - * {@link IOptionsRequestCallback#respondToCapabilityRequestWithError} method. - * @param contactUri The URI associated with the remote contact that is requesting capabilities. - * @param remoteCapabilities The remote contact's capability information. - * @throws ImsException If this {@link RcsSipOptionsImplBase} instance is not currently - * connected to the framework. This can happen if the {@link RcsFeature} is not - * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received - * the {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare cases when - * the Telephony stack has crashed. - */ void onRemoteCapabilityRequest(in Uri contactUri, - in List<String> remoteCapabilities, - IOptionsRequestCallback cb); + in List<String> remoteCapabilities, IOptionsRequestCallback cb); } diff --git a/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl index ed895b77a164..ed0375251ffb 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl @@ -37,6 +37,8 @@ oneway interface IImsCallSessionListener { /** * Notifies the result of the basic session operation (setup / terminate). */ + void callSessionInitiating(in ImsCallProfile profile); + void callSessionInitiatingFailed(in ImsReasonInfo reasonInfo); void callSessionProgressing(in ImsStreamMediaProfile profile); void callSessionInitiated(in ImsCallProfile profile); void callSessionInitiatedFailed(in ImsReasonInfo reasonInfo); diff --git a/telephony/java/android/telephony/ims/aidl/IOptionsRequestCallback.aidl b/telephony/java/android/telephony/ims/aidl/IOptionsRequestCallback.aidl index d55670dd313b..d4d5301f38fa 100644 --- a/telephony/java/android/telephony/ims/aidl/IOptionsRequestCallback.aidl +++ b/telephony/java/android/telephony/ims/aidl/IOptionsRequestCallback.aidl @@ -33,7 +33,6 @@ oneway interface IOptionsRequestCallback { /** * Respond to a remote capability request from the contact specified with the * specified error. - * @param contactUri A URI containing the remote contact. * @param code The SIP response code to respond with. * @param reason A non-null String containing the reason associated with the SIP code. */ diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java index 96ca0225040f..8b26c3b27a3d 100644 --- a/telephony/java/android/telephony/ims/feature/ImsFeature.java +++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java @@ -336,7 +336,7 @@ public abstract class ImsFeature { /** * @hide */ - public final void initialize(Context context, int slotId) { + public void initialize(Context context, int slotId) { mContext = context; mSlotId = slotId; } diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java index e570fb6f5612..0b2c6d974980 100644 --- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java +++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java @@ -231,8 +231,9 @@ public class MmTelFeature extends ImsFeature { * The capabilities that are used in MmTelFeature are defined as * {@link MmTelCapabilities#CAPABILITY_TYPE_VOICE}, * {@link MmTelCapabilities#CAPABILITY_TYPE_VIDEO}, - * {@link MmTelCapabilities#CAPABILITY_TYPE_UT}, and - * {@link MmTelCapabilities#CAPABILITY_TYPE_SMS}. + * {@link MmTelCapabilities#CAPABILITY_TYPE_UT}, + * {@link MmTelCapabilities#CAPABILITY_TYPE_SMS}, and + * {@link MmTelCapabilities#CAPABILITY_TYPE_CALL_COMPOSER}. * * The capabilities of this MmTelFeature will be set by the framework. */ @@ -275,7 +276,8 @@ public class MmTelFeature extends ImsFeature { CAPABILITY_TYPE_VOICE, CAPABILITY_TYPE_VIDEO, CAPABILITY_TYPE_UT, - CAPABILITY_TYPE_SMS + CAPABILITY_TYPE_SMS, + CAPABILITY_TYPE_CALL_COMPOSER }) @Retention(RetentionPolicy.SOURCE) public @interface MmTelCapability {} @@ -301,6 +303,11 @@ public class MmTelFeature extends ImsFeature { public static final int CAPABILITY_TYPE_SMS = 1 << 3; /** + * This MmTelFeature supports Call Composer (section 2.4 of RC.20) + */ + public static final int CAPABILITY_TYPE_CALL_COMPOSER = 1 << 4; + + /** * @hide */ @Override @@ -343,6 +350,8 @@ public class MmTelFeature extends ImsFeature { builder.append(isCapable(CAPABILITY_TYPE_UT)); builder.append(" SMS: "); builder.append(isCapable(CAPABILITY_TYPE_SMS)); + builder.append(" CALL_COMPOSER: "); + builder.append(isCapable(CAPABILITY_TYPE_CALL_COMPOSER)); builder.append("]"); return builder.toString(); } diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java index cde7067e8bf3..22df921c4214 100644 --- a/telephony/java/android/telephony/ims/feature/RcsFeature.java +++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java @@ -21,9 +21,11 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.content.Context; import android.net.Uri; import android.os.RemoteException; import android.telephony.ims.RcsUceAdapter; +import android.telephony.ims.aidl.CapabilityExchangeAidlWrapper; import android.telephony.ims.aidl.ICapabilityExchangeEventListener; import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.aidl.IImsRcsFeature; @@ -33,6 +35,7 @@ import android.telephony.ims.aidl.ISubscribeResponseCallback; import android.telephony.ims.aidl.RcsOptionsResponseAidlWrapper; import android.telephony.ims.aidl.RcsPublishResponseAidlWrapper; import android.telephony.ims.aidl.RcsSubscribeResponseAidlWrapper; +import android.telephony.ims.stub.CapabilityExchangeEventListener; import android.telephony.ims.stub.ImsRegistrationImplBase; import android.telephony.ims.stub.RcsCapabilityExchangeImplBase; import android.telephony.ims.stub.RcsCapabilityExchangeImplBase.OptionsResponseCallback; @@ -114,8 +117,10 @@ public class RcsFeature extends ImsFeature { @Override public void setCapabilityExchangeEventListener( @Nullable ICapabilityExchangeEventListener listener) throws RemoteException { - executeMethodAsync(() -> mReference.setCapabilityExchangeEventListener(listener), - "setCapabilityExchangeEventListener"); + CapabilityExchangeEventListener listenerWrapper = + new CapabilityExchangeAidlWrapper(listener); + executeMethodAsync(() -> mReference.setCapabilityExchangeEventListener( + mExecutor, listenerWrapper), "setCapabilityExchangeEventListener"); } @Override @@ -245,9 +250,10 @@ public class RcsFeature extends ImsFeature { } } + private final Executor mExecutor; private final RcsFeatureBinder mImsRcsBinder; private RcsCapabilityExchangeImplBase mCapabilityExchangeImpl; - private ICapabilityExchangeEventListener mCapExchangeEventListener; + private CapabilityExchangeEventListener mCapExchangeEventListener; /** * Create a new RcsFeature. @@ -255,26 +261,45 @@ public class RcsFeature extends ImsFeature { * Method stubs called from the framework will be called asynchronously. To specify the * {@link Executor} that the methods stubs will be called, use * {@link RcsFeature#RcsFeature(Executor)} instead. + * + * @deprecated Use {@link #RcsFeature(Executor)} to create the RcsFeature. */ + @Deprecated public RcsFeature() { super(); + mExecutor = Runnable::run; // Run on the Binder threads that call them. - mImsRcsBinder = new RcsFeatureBinder(this, Runnable::run); + mImsRcsBinder = new RcsFeatureBinder(this, mExecutor); } /** * Create a new RcsFeature using the Executor specified for methods being called by the * framework. - * @param executor The executor for the framework to use when making calls to this service. - * @hide + * @param executor The executor for the framework to use when executing the methods overridden + * by the implementation of RcsFeature. */ public RcsFeature(@NonNull Executor executor) { super(); if (executor == null) { throw new IllegalArgumentException("executor can not be null."); } + mExecutor = executor; // Run on the Binder thread by default. - mImsRcsBinder = new RcsFeatureBinder(this, executor); + mImsRcsBinder = new RcsFeatureBinder(this, mExecutor); + } + + /** + * Called when the RcsFeature is initialized. + * + * @param context The context that is used in the ImsService. + * @param slotId The slot ID associated with the RcsFeature. + * @hide + */ + @Override + public void initialize(Context context, int slotId) { + super.initialize(context, slotId); + // Notify that the RcsFeature is ready. + mExecutor.execute(() -> onFeatureReady()); } /** @@ -348,13 +373,26 @@ public class RcsFeature extends ImsFeature { * operation and the RcsFeature sets the status of the capability to true using * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. * - * @return An instance of {@link RcsCapabilityExchangeImplBase} that implements presence + * @param executor The executor for the framework to use when request RCS resquests to this + * service. + * @param listener A {@link CapabilityExchangeEventListener} to send the capability exchange + * event to the framework. + * @return An instance of {@link RcsCapabilityExchangeImplBase} that implements capability * exchange if it is supported by the device. - * @hide */ - public @NonNull RcsCapabilityExchangeImplBase createCapabilityExchangeImpl() { + public @NonNull RcsCapabilityExchangeImplBase createCapabilityExchangeImpl( + @NonNull Executor executor, @NonNull CapabilityExchangeEventListener listener) { // Base Implementation, override to implement functionality - return new RcsCapabilityExchangeImplBase(); + return new RcsCapabilityExchangeImplBase(executor); + } + + /** + * Remove the given CapabilityExchangeImplBase instance. + * @param capExchangeImpl The {@link RcsCapabilityExchangeImplBase} instance to be removed. + */ + public void removeCapabilityExchangeImpl( + @NonNull RcsCapabilityExchangeImplBase capExchangeImpl) { + // Override to implement the process of removing RcsCapabilityExchangeImplBase instance. } /**{@inheritDoc}*/ @@ -377,18 +415,58 @@ public class RcsFeature extends ImsFeature { return mImsRcsBinder; } - private void setCapabilityExchangeEventListener(ICapabilityExchangeEventListener listener) { - mCapExchangeEventListener = listener; - if (mCapExchangeEventListener != null) { - onFeatureReady(); + /** + * Set the capability exchange listener. + * @param executor The executor for the framework to use when request RCS requests to this + * service. + * @param listener A {@link CapabilityExchangeEventListener} to send the capability exchange + * event to the framework. + */ + private void setCapabilityExchangeEventListener(@NonNull Executor executor, + @Nullable CapabilityExchangeEventListener listener) { + synchronized (mLock) { + mCapExchangeEventListener = listener; + if (mCapExchangeEventListener != null) { + initRcsCapabilityExchangeImplBase(executor, mCapExchangeEventListener); + } else { + // Remove the RcsCapabilityExchangeImplBase instance when the capability exchange + // instance has been removed in the framework. + if (mCapabilityExchangeImpl != null) { + removeCapabilityExchangeImpl(mCapabilityExchangeImpl); + } + mCapabilityExchangeImpl = null; + } } } - private RcsCapabilityExchangeImplBase getCapabilityExchangeImplBaseInternal() { + /** + * Initialize the RcsCapabilityExchangeImplBase instance if the capability exchange instance + * has already been created in the framework. + * @param executor The executor for the framework to use when request RCS requests to this + * service. + * @param listener A {@link CapabilityExchangeEventListener} to send the capability exchange + * event to the framework. + */ + private void initRcsCapabilityExchangeImplBase(@NonNull Executor executor, + @NonNull CapabilityExchangeEventListener listener) { + synchronized (mLock) { + // Remove the original instance + if (mCapabilityExchangeImpl != null) { + removeCapabilityExchangeImpl(mCapabilityExchangeImpl); + } + mCapabilityExchangeImpl = createCapabilityExchangeImpl(executor, listener); + } + } + + /** + * @return the {@link RcsCapabilityExchangeImplBase} associated with the RcsFeature. + */ + private @NonNull RcsCapabilityExchangeImplBase getCapabilityExchangeImplBaseInternal() { synchronized (mLock) { + // The method should not be called if the instance of RcsCapabilityExchangeImplBase has + // not been created yet. if (mCapabilityExchangeImpl == null) { - mCapabilityExchangeImpl = createCapabilityExchangeImpl(); - mCapabilityExchangeImpl.setEventListener(mCapExchangeEventListener); + throw new IllegalStateException("Session is not available."); } return mCapabilityExchangeImpl; } diff --git a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java new file mode 100644 index 000000000000..d2cb9761a028 --- /dev/null +++ b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.ims.stub; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.net.Uri; +import android.telephony.ims.ImsException; +import android.telephony.ims.RcsContactUceCapability; +import android.telephony.ims.RcsUceAdapter; +import android.telephony.ims.feature.ImsFeature; +import android.telephony.ims.feature.RcsFeature; + +import java.util.List; + +/** + * The interface of the capabilities event listener for ImsService to notify the framework of the + * UCE request and status updated. + * @hide + */ +@SystemApi +public interface CapabilityExchangeEventListener { + /** + * Interface used by the framework to respond to OPTIONS requests. + * @hide + */ + interface OptionsRequestCallback { + /** + * Respond to a remote capability request from the contact specified with the + * capabilities of this device. + * @param ownCapabilities The capabilities of this device. + */ + void onRespondToCapabilityRequest(@NonNull RcsContactUceCapability ownCapabilities); + + /** + * Respond to a remote capability request from the contact specified with the + * specified error. + * @param code The SIP response code to respond with. + * @param reason A non-null String containing the reason associated with the SIP code. + */ + void onRespondToCapabilityRequestWithError(int code, @NonNull String reason); + } + + /** + * Trigger the framework to provide a capability update using + * {@link RcsCapabilityExchangeImplBase#publishCapabilities}. + * <p> + * This is typically used when trying to generate an initial PUBLISH for a new subscription to + * the network. The device will cache all presence publications after boot until this method is + * called the first time. + * @param publishTriggerType {@link RcsUceAdapter#StackPublishTriggerType} The reason for the + * capability update request. + * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is not currently + * connected to the framework. This can happen if the {@link RcsFeature} is not + * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received the + * {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare cases when the + * Telephony stack has crashed. + */ + void onRequestPublishCapabilities( + @RcsUceAdapter.StackPublishTriggerType int publishTriggerType) throws ImsException; + + /** + * Notify the framework that the device's capabilities have been unpublished + * from the network. + * + * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is not currently + * connected to the framework. This can happen if the {@link RcsFeature} is not + * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received the + * {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare cases when the + * Telephony stack has crashed. + */ + void onUnpublish() throws ImsException; + + /** + * Inform the framework of a query for this device's UCE capabilities. + * <p> + * The framework will respond via the + * {@link OptionsRequestCallback#onRespondToCapabilityRequest} or + * {@link OptionsRequestCallback#onRespondToCapabilityRequestWithError} + * @param contactUri The URI associated with the remote contact that is + * requesting capabilities. + * @param remoteCapabilities The remote contact's capability information. + * @param callback The callback of this request which is sent from the remote user. + * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is not + * currently connected to the framework. This can happen if the {@link RcsFeature} is not + * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received + * the {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare + * cases when the Telephony stack has crashed. + * @hide + */ + void onRemoteCapabilityRequest(@NonNull Uri contactUri, + @NonNull List<String> remoteCapabilities, + @NonNull OptionsRequestCallback callback) throws ImsException; +} diff --git a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java index 3a0fb6edb2fb..c84e23c38e97 100644 --- a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java +++ b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java @@ -20,20 +20,28 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; import android.net.Uri; import android.telephony.ims.ImsException; -import android.telephony.ims.aidl.ICapabilityExchangeEventListener; +import android.telephony.ims.feature.ImsFeature; +import android.telephony.ims.feature.RcsFeature; import android.util.Log; import android.util.Pair; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.concurrent.Executor; /** - * Base class for different types of Capability exchange. + * Extend this base class to implement RCS User Capability Exchange (UCE) for the AOSP platform + * using the vendor ImsService. + * <p> + * See RCC.07 for more details on UCE as well as how UCE should be implemented. * @hide */ +@SystemApi public class RcsCapabilityExchangeImplBase { private static final String LOG_TAG = "RcsCapExchangeImplBase"; @@ -70,13 +78,11 @@ public class RcsCapabilityExchangeImplBase { /** * Network connection is lost. - * @hide */ public static final int COMMAND_CODE_LOST_NETWORK_CONNECTION = 6; /** * Requested feature/resource is not supported. - * @hide */ public static final int COMMAND_CODE_NOT_SUPPORTED = 7; @@ -117,7 +123,8 @@ public class RcsCapabilityExchangeImplBase { */ public interface PublishResponseCallback { /** - * Notify the framework that the command associated with this callback has failed. + * Notify the framework that the command associated with the + * {@link #publishCapabilities(String, PublishResponseCallback)} has failed. * * @param code The reason why the associated command has failed. * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is @@ -128,15 +135,15 @@ public class RcsCapabilityExchangeImplBase { */ void onCommandError(@CommandCode int code) throws ImsException; - /** * Provide the framework with a subsequent network response update to * {@link #publishCapabilities(String, PublishResponseCallback)}. * * @param code The SIP response code sent from the network for the operation * token specified. - * @param reason The optional reason response from the network. If the network - * provided no reason with the code, the string should be empty. + * @param reason The optional reason response from the network. If there is a reason header + * included in the response, that should take precedence over the reason provided in the + * status line. If the network provided no reason with the code, the string should be empty. * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is * not currently connected to the framework. This can happen if the {@link RcsFeature} * is not {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received @@ -149,6 +156,7 @@ public class RcsCapabilityExchangeImplBase { /** * Interface used by the framework to respond to OPTIONS requests. + * @hide */ public interface OptionsResponseCallback { /** @@ -171,7 +179,7 @@ public class RcsCapabilityExchangeImplBase { * If none was sent, this should be an empty string. * @param theirCaps the contact's UCE capabilities associated with the * capability request. - * @throws ImsException If this {@link RcsSipOptionsImplBase} instance is not + * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is not * currently connected to the framework. This can happen if the * {@link RcsFeature} is not {@link ImsFeature#STATE_READY} and the * {@link RcsFeature} has not received the @@ -184,6 +192,7 @@ public class RcsCapabilityExchangeImplBase { /** * Interface used by the framework to receive the response of the subscribe request. + * @hide */ public interface SubscribeResponseCallback { /** @@ -219,17 +228,16 @@ public class RcsCapabilityExchangeImplBase { /** * Provides the framework with latest XML PIDF documents included in the * network response for the requested contacts' capabilities requested by the - * Framework using {@link #requestCapabilities(List, int)}. This should be + * Framework using {@link #requestCapabilities(List, int)}. This should be * called every time a new NOTIFY event is received with new capability * information. * * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is - * not currently - * connected to the framework. This can happen if the {@link RcsFeature} is not - * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received - * the {@link ImsFeature#onFeatureReady()} callback. This may also happen in - * rare cases when the - * Telephony stack has crashed. + * not currently connected to the framework. + * This can happen if the {@link RcsFeature} is not {@link ImsFeature#STATE_READY} and the + * {@link RcsFeature} {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not + * received the {@link ImsFeature#onFeatureReady()} callback. This may also happen in + * rare cases when the Telephony stack has crashed. */ void onNotifyCapabilitiesUpdate(@NonNull List<String> pidfXmls) throws ImsException; @@ -250,24 +258,21 @@ public class RcsCapabilityExchangeImplBase { * This allows the framework to know that there will no longer be any * capability updates for the requested operationToken. */ - void onTerminated(String reason, long retryAfterMilliseconds) throws ImsException; + void onTerminated(@NonNull String reason, long retryAfterMilliseconds) throws ImsException; } - - private ICapabilityExchangeEventListener mListener; + private final Executor mBinderExecutor; /** - * Set the event listener to send the request to Framework. + * Create a new RcsCapabilityExchangeImplBase instance. + * + * @param executor The executor that remote calls from the framework will be called on. */ - public void setEventListener(ICapabilityExchangeEventListener listener) { - mListener = listener; - } - - /** - * Get the event listener. - */ - public ICapabilityExchangeEventListener getEventListener() { - return mListener; + public RcsCapabilityExchangeImplBase(@NonNull Executor executor) { + if (executor == null) { + throw new IllegalArgumentException("executor must not be null"); + } + mBinderExecutor = executor; } /** @@ -284,7 +289,10 @@ public class RcsCapabilityExchangeImplBase { * @param uris A {@link List} of the {@link Uri}s that the framework is requesting the UCE * capabilities for. * @param cb The callback of the subscribe request. + * @hide */ + // executor used is defined in the constructor. + @SuppressLint("ExecutorRegistration") public void subscribeForCapabilities(@NonNull List<Uri> uris, @NonNull SubscribeResponseCallback cb) { // Stub - to be implemented by service @@ -300,11 +308,13 @@ public class RcsCapabilityExchangeImplBase { * The capabilities of this device have been updated and should be published to the network. * <p> * If this operation succeeds, network response updates should be sent to the framework using - * {@link #onNetworkResponse(int, String)}. + * {@link PublishResponseCallback#onNetworkResponse(int, String)}. * @param pidfXml The XML PIDF document containing the capabilities of this device to be sent * to the carrier’s presence server. * @param cb The callback of the publish request */ + // executor used is defined in the constructor. + @SuppressLint("ExecutorRegistration") public void publishCapabilities(@NonNull String pidfXml, @NonNull PublishResponseCallback cb) { // Stub - to be implemented by service Log.w(LOG_TAG, "publishCapabilities called with no implementation."); @@ -324,7 +334,10 @@ public class RcsCapabilityExchangeImplBase { * @param contactUri The URI of the remote user that we wish to get the capabilities of. * @param myCapabilities The capabilities of this device to send to the remote user. * @param callback The callback of this request which is sent from the remote user. + * @hide */ + // executor used is defined in the constructor. + @SuppressLint("ExecutorRegistration") public void sendOptionsCapabilityRequest(@NonNull Uri contactUri, @NonNull List<String> myCapabilities, @NonNull OptionsResponseCallback callback) { // Stub - to be implemented by service diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java index 76fc4f7d0519..6fbde503c3a0 100644 --- a/telephony/java/com/android/internal/telephony/DctConstants.java +++ b/telephony/java/com/android/internal/telephony/DctConstants.java @@ -113,6 +113,7 @@ public class DctConstants { public static final int EVENT_NR_TIMER_WATCHDOG = BASE + 53; public static final int EVENT_CARRIER_CONFIG_CHANGED = BASE + 54; public static final int EVENT_SIM_STATE_UPDATED = BASE + 55; + public static final int EVENT_APN_UNTHROTTLED = BASE + 56; /***** Constants *****/ diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 15557e34125c..541ec040d4dd 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -128,6 +128,15 @@ interface ITelephony { */ boolean isRadioOnForSubscriberWithFeature(int subId, String callingPackage, String callingFeatureId); + /** + * Set the user-set status for enriched calling with call composer. + */ + void setCallComposerStatus(int subId, int status); + + /** + * Get the user-set status for enriched calling with call composer. + */ + int getCallComposerStatus(int subId); /** * Supply a pin to unlock the SIM for particular subId. diff --git a/tests/net/Android.bp b/tests/net/Android.bp index a7622198cec7..f6a2846c9b3c 100644 --- a/tests/net/Android.bp +++ b/tests/net/Android.bp @@ -70,4 +70,7 @@ android_test { "android.test.base", "android.test.mock", ], + jni_libs: [ + "libservice-connectivity", + ], } diff --git a/tests/net/common/java/android/net/CaptivePortalDataTest.kt b/tests/net/common/java/android/net/CaptivePortalDataTest.kt index bd1847b7c440..8710d23730b6 100644 --- a/tests/net/common/java/android/net/CaptivePortalDataTest.kt +++ b/tests/net/common/java/android/net/CaptivePortalDataTest.kt @@ -41,13 +41,14 @@ class CaptivePortalDataTest { .setBytesRemaining(456L) .setExpiryTime(789L) .setCaptive(true) + .setVenueFriendlyName("venue friendly name") .build() private fun makeBuilder() = CaptivePortalData.Builder(data) @Test fun testParcelUnparcel() { - assertParcelSane(data, fieldCount = 7) + assertParcelSane(data, fieldCount = 8) assertParcelingIsLossless(makeBuilder().setUserPortalUrl(null).build()) assertParcelingIsLossless(makeBuilder().setVenueInfoUrl(null).build()) @@ -66,6 +67,8 @@ class CaptivePortalDataTest { assertNotEqualsAfterChange { it.setBytesRemaining(789L) } assertNotEqualsAfterChange { it.setExpiryTime(12L) } assertNotEqualsAfterChange { it.setCaptive(false) } + assertNotEqualsAfterChange { it.setVenueFriendlyName("another friendly name") } + assertNotEqualsAfterChange { it.setVenueFriendlyName(null) } } @Test @@ -108,6 +111,11 @@ class CaptivePortalDataTest { assertFalse(makeBuilder().setCaptive(false).build().isCaptive) } + @Test + fun testVenueFriendlyName() { + assertEquals("venue friendly name", data.venueFriendlyName) + } + private fun CaptivePortalData.mutate(mutator: (CaptivePortalData.Builder) -> Unit) = CaptivePortalData.Builder(this).apply { mutator(this) }.build() diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt index 70f6386aa891..8e1875168a84 100644 --- a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt +++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt @@ -25,7 +25,6 @@ import android.content.ServiceConnection import android.net.ConnectivityManager import android.net.IDnsResolver import android.net.INetd -import android.net.INetworkPolicyManager import android.net.INetworkStatsService import android.net.LinkProperties import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL @@ -88,8 +87,6 @@ class ConnectivityServiceIntegrationTest { @Mock private lateinit var statsService: INetworkStatsService @Mock - private lateinit var policyManager: INetworkPolicyManager - @Mock private lateinit var log: IpConnectivityLog @Mock private lateinit var netd: INetd @@ -171,7 +168,7 @@ class ConnectivityServiceIntegrationTest { } private inner class TestConnectivityService(deps: Dependencies) : ConnectivityService( - context, netManager, statsService, policyManager, dnsResolver, log, netd, deps) + context, netManager, statsService, dnsResolver, log, netd, deps) private fun makeDependencies(): ConnectivityService.Dependencies { val deps = spy(ConnectivityService.Dependencies()) diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index a613e5e332fc..2e70be702b43 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -63,6 +63,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS; @@ -161,12 +162,10 @@ import android.net.DataStallReportParcelable; import android.net.EthernetManager; import android.net.IConnectivityDiagnosticsCallback; import android.net.IDnsResolver; -import android.net.IIpConnectivityMetrics; import android.net.INetd; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; -import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; import android.net.InetAddresses; import android.net.InterfaceConfigurationParcel; @@ -183,6 +182,7 @@ import android.net.NetworkCapabilities; import android.net.NetworkFactory; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; +import android.net.NetworkPolicyManager; import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.NetworkStack; @@ -299,6 +299,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; import java.util.function.Supplier; +import java.util.stream.Collectors; import kotlin.reflect.KClass; @@ -343,6 +344,11 @@ public class ConnectivityServiceTest { private static final String INTERFACE_NAME = "interface"; + private static final String TEST_VENUE_URL_NA = "https://android.com/"; + private static final String TEST_VENUE_URL_CAPPORT = "https://android.com/capport/"; + private static final String TEST_FRIENDLY_NAME = "Network friendly name"; + private static final String TEST_REDIRECT_URL = "http://example.com/firstPath"; + private MockContext mServiceContext; private HandlerThread mCsHandlerThread; private ConnectivityService.Dependencies mDeps; @@ -358,14 +364,12 @@ public class ConnectivityServiceTest { private HandlerThread mAlarmManagerThread; private TestNetIdManager mNetIdManager; - @Mock IIpConnectivityMetrics mIpConnectivityMetrics; @Mock IpConnectivityMetrics.Logger mMetricsService; @Mock DefaultNetworkMetrics mDefaultNetworkMetrics; @Mock DeviceIdleInternal mDeviceIdleInternal; @Mock INetworkManagementService mNetworkManagementService; @Mock INetworkStatsService mStatsService; @Mock IBatteryStats mBatteryStatsService; - @Mock INetworkPolicyManager mNpm; @Mock IDnsResolver mMockDnsResolver; @Mock INetd mMockNetd; @Mock NetworkStackClient mNetworkStack; @@ -380,6 +384,7 @@ public class ConnectivityServiceTest { @Mock TelephonyManager mTelephonyManager; @Mock MockableSystemProperties mSystemProperties; @Mock EthernetManager mEthernetManager; + @Mock NetworkPolicyManager mNetworkPolicyManager; private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor = ArgumentCaptor.forClass(ResolverParamsParcel.class); @@ -412,6 +417,7 @@ public class ConnectivityServiceTest { @Spy private Resources mResources; private final LinkedBlockingQueue<Intent> mStartedActivities = new LinkedBlockingQueue<>(); + // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant private final HashMap<String, Integer> mMockedPermissions = new HashMap<>(); @@ -477,6 +483,7 @@ public class ConnectivityServiceTest { if (Context.APP_OPS_SERVICE.equals(name)) return mAppOpsManager; if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager; if (Context.ETHERNET_SERVICE.equals(name)) return mEthernetManager; + if (Context.NETWORK_POLICY_SERVICE.equals(name)) return mNetworkPolicyManager; return super.getSystemService(name); } @@ -866,7 +873,7 @@ public class ConnectivityServiceTest { mProbesSucceeded = probesSucceeded; } - void notifyCaptivePortalDataChanged(CaptivePortalData data) { + void notifyCapportApiDataChanged(CaptivePortalData data) { try { mNmCallbacks.notifyCaptivePortalDataChanged(data); } catch (RemoteException e) { @@ -1196,6 +1203,8 @@ public class ConnectivityServiceTest { updateState(NetworkInfo.DetailedState.DISCONNECTED, "disconnect"); } mAgentRegistered = false; + setUids(null); + mInterface = null; } @Override @@ -1326,7 +1335,6 @@ public class ConnectivityServiceTest { mService = new ConnectivityService(mServiceContext, mNetworkManagementService, mStatsService, - mNpm, mMockDnsResolver, mock(IpConnectivityLog.class), mMockNetd, @@ -1336,7 +1344,7 @@ public class ConnectivityServiceTest { final ArgumentCaptor<INetworkPolicyListener> policyListenerCaptor = ArgumentCaptor.forClass(INetworkPolicyListener.class); - verify(mNpm).registerListener(policyListenerCaptor.capture()); + verify(mNetworkPolicyManager).registerListener(policyListenerCaptor.capture()); mPolicyListener = policyListenerCaptor.getValue(); // Create local CM before sending system ready so that we can answer @@ -1369,7 +1377,6 @@ public class ConnectivityServiceTest { doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any()); doReturn(mMetricsService).when(deps).getMetricsLogger(); doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt()); - doReturn(mIpConnectivityMetrics).when(deps).getIpConnectivityMetrics(); doReturn(mBatteryStatsService).when(deps).getBatteryStatsService(); doAnswer(inv -> { mPolicyTracker = new WrappedMultinetworkPolicyTracker( @@ -2003,7 +2010,7 @@ public class ConnectivityServiceTest { Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl())); final CaptivePortalData expectedCapportData = sanitized ? null : capportData; - mWiFiNetworkAgent.notifyCaptivePortalDataChanged(capportData); + mWiFiNetworkAgent.notifyCapportApiDataChanged(capportData); callback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> Objects.equals(expectedCapportData, lp.getCaptivePortalData())); defaultCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> @@ -3041,7 +3048,7 @@ public class ConnectivityServiceTest { .setBytesRemaining(12345L) .build(); - mWiFiNetworkAgent.notifyCaptivePortalDataChanged(testData); + mWiFiNetworkAgent.notifyCapportApiDataChanged(testData); captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> testData.equals(lp.getCaptivePortalData())); @@ -3054,6 +3061,136 @@ public class ConnectivityServiceTest { lp -> testData.equals(lp.getCaptivePortalData()) && lp.getMtu() == 1234); } + private TestNetworkCallback setupNetworkCallbackAndConnectToWifi() throws Exception { + // Grant NETWORK_SETTINGS permission to be able to receive LinkProperties change callbacks + // with sensitive (captive portal) data + mServiceContext.setPermission( + android.Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED); + + final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); + final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); + mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + + mWiFiNetworkAgent.connectWithCaptivePortal(TEST_REDIRECT_URL, false /* isStrictMode */); + captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + return captivePortalCallback; + } + + private class CaptivePortalTestData { + CaptivePortalTestData(CaptivePortalData naData, CaptivePortalData capportData, + CaptivePortalData expectedMergedData) { + mNaData = naData; + mCapportData = capportData; + mExpectedMergedData = expectedMergedData; + } + + public final CaptivePortalData mNaData; + public final CaptivePortalData mCapportData; + public final CaptivePortalData mExpectedMergedData; + } + + private CaptivePortalTestData setupCaptivePortalData() { + final CaptivePortalData capportData = new CaptivePortalData.Builder() + .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL)) + .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_CAPPORT)) + .setExpiryTime(1000000L) + .setBytesRemaining(12345L) + .build(); + + final CaptivePortalData naData = new CaptivePortalData.Builder() + .setBytesRemaining(80802L) + .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA)) + .setVenueFriendlyName(TEST_FRIENDLY_NAME).build(); + + final CaptivePortalData expectedMergedData = new CaptivePortalData.Builder() + .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL)) + .setBytesRemaining(12345L) + .setExpiryTime(1000000L) + .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA)) + .setVenueFriendlyName(TEST_FRIENDLY_NAME).build(); + + return new CaptivePortalTestData(naData, capportData, expectedMergedData); + } + + @Test + public void testMergeCaptivePortalApiWithFriendlyNameAndVenueUrl() throws Exception { + final TestNetworkCallback captivePortalCallback = setupNetworkCallbackAndConnectToWifi(); + final CaptivePortalTestData captivePortalTestData = setupCaptivePortalData(); + + // Baseline capport data + mWiFiNetworkAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData); + + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData())); + + // Venue URL and friendly name from Network agent, confirm that API data gets precedence + // on the bytes remaining. + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setCaptivePortalData(captivePortalTestData.mNaData); + mWiFiNetworkAgent.sendLinkProperties(linkProperties); + + // Make sure that the capport data is merged + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> captivePortalTestData.mExpectedMergedData.equals(lp.getCaptivePortalData())); + + // Create a new LP with no Network agent capport data + final LinkProperties newLps = new LinkProperties(); + newLps.setMtu(1234); + mWiFiNetworkAgent.sendLinkProperties(newLps); + // CaptivePortalData is not lost and has the original values when LPs are received from the + // NetworkAgent + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData()) + && lp.getMtu() == 1234); + + // Now send capport data only from the Network agent + mWiFiNetworkAgent.notifyCapportApiDataChanged(null); + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> lp.getCaptivePortalData() == null); + + newLps.setCaptivePortalData(captivePortalTestData.mNaData); + mWiFiNetworkAgent.sendLinkProperties(newLps); + + // Make sure that only the network agent capport data is available + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> captivePortalTestData.mNaData.equals(lp.getCaptivePortalData())); + } + + @Test + public void testMergeCaptivePortalDataFromNetworkAgentFirstThenCapport() throws Exception { + final TestNetworkCallback captivePortalCallback = setupNetworkCallbackAndConnectToWifi(); + final CaptivePortalTestData captivePortalTestData = setupCaptivePortalData(); + + // Venue URL and friendly name from Network agent, confirm that API data gets precedence + // on the bytes remaining. + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setCaptivePortalData(captivePortalTestData.mNaData); + mWiFiNetworkAgent.sendLinkProperties(linkProperties); + + // Make sure that the data is saved correctly + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> captivePortalTestData.mNaData.equals(lp.getCaptivePortalData())); + + // Expected merged data: Network agent data is preferred, and values that are not used by + // it are merged from capport data + mWiFiNetworkAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData); + + // Make sure that the Capport data is merged correctly + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> captivePortalTestData.mExpectedMergedData.equals(lp.getCaptivePortalData())); + + // Now set the naData to null + linkProperties.setCaptivePortalData(null); + mWiFiNetworkAgent.sendLinkProperties(linkProperties); + + // Make sure that the Capport data is retained correctly + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData())); + } + private NetworkRequest.Builder newWifiRequestBuilder() { return new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI); } @@ -3359,6 +3496,7 @@ public class ConnectivityServiceTest { assertEquals(null, mCm.getActiveNetwork()); mMockVpn.establishForMyUid(); + assertUidRangesUpdatedForMyUid(true); defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); @@ -3622,51 +3760,55 @@ public class ConnectivityServiceTest { // Register the factory and expect it to start looking for a network. testFactory.expectAddRequestsWithScores(0); // Score 0 as the request is not served yet. testFactory.register(); - testFactory.waitForNetworkRequests(1); - assertTrue(testFactory.getMyStartRequested()); - - // Bring up wifi. The factory stops looking for a network. - mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); - // Score 60 - 40 penalty for not validated yet, then 60 when it validates - testFactory.expectAddRequestsWithScores(20, 60); - mWiFiNetworkAgent.connect(true); - testFactory.waitForRequests(); - assertFalse(testFactory.getMyStartRequested()); - - ContentResolver cr = mServiceContext.getContentResolver(); - - // Turn on mobile data always on. The factory starts looking again. - testFactory.expectAddRequestsWithScores(0); // Always on requests comes up with score 0 - setAlwaysOnNetworks(true); - testFactory.waitForNetworkRequests(2); - assertTrue(testFactory.getMyStartRequested()); - // Bring up cell data and check that the factory stops looking. - assertLength(1, mCm.getAllNetworks()); - mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); - testFactory.expectAddRequestsWithScores(10, 50); // Unvalidated, then validated - mCellNetworkAgent.connect(true); - cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); - testFactory.waitForNetworkRequests(2); - assertFalse(testFactory.getMyStartRequested()); // Because the cell network outscores us. + try { + testFactory.waitForNetworkRequests(1); + assertTrue(testFactory.getMyStartRequested()); - // Check that cell data stays up. - waitForIdle(); - verifyActiveNetwork(TRANSPORT_WIFI); - assertLength(2, mCm.getAllNetworks()); + // Bring up wifi. The factory stops looking for a network. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + // Score 60 - 40 penalty for not validated yet, then 60 when it validates + testFactory.expectAddRequestsWithScores(20, 60); + mWiFiNetworkAgent.connect(true); + testFactory.waitForRequests(); + assertFalse(testFactory.getMyStartRequested()); - // Turn off mobile data always on and expect the request to disappear... - testFactory.expectRemoveRequests(1); - setAlwaysOnNetworks(false); - testFactory.waitForNetworkRequests(1); + ContentResolver cr = mServiceContext.getContentResolver(); + + // Turn on mobile data always on. The factory starts looking again. + testFactory.expectAddRequestsWithScores(0); // Always on requests comes up with score 0 + setAlwaysOnNetworks(true); + testFactory.waitForNetworkRequests(2); + assertTrue(testFactory.getMyStartRequested()); + + // Bring up cell data and check that the factory stops looking. + assertLength(1, mCm.getAllNetworks()); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + testFactory.expectAddRequestsWithScores(10, 50); // Unvalidated, then validated + mCellNetworkAgent.connect(true); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + testFactory.waitForNetworkRequests(2); + assertFalse( + testFactory.getMyStartRequested()); // Because the cell network outscores us. + + // Check that cell data stays up. + waitForIdle(); + verifyActiveNetwork(TRANSPORT_WIFI); + assertLength(2, mCm.getAllNetworks()); - // ... and cell data to be torn down. - cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); - assertLength(1, mCm.getAllNetworks()); + // Turn off mobile data always on and expect the request to disappear... + testFactory.expectRemoveRequests(1); + setAlwaysOnNetworks(false); + testFactory.waitForNetworkRequests(1); - testFactory.terminate(); - mCm.unregisterNetworkCallback(cellNetworkCallback); - handlerThread.quit(); + // ... and cell data to be torn down. + cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + assertLength(1, mCm.getAllNetworks()); + } finally { + testFactory.terminate(); + mCm.unregisterNetworkCallback(cellNetworkCallback); + handlerThread.quit(); + } } @Test @@ -5045,6 +5187,7 @@ public class ConnectivityServiceTest { lp.setInterfaceName(VPN_IFNAME); mMockVpn.establishForMyUid(lp); + assertUidRangesUpdatedForMyUid(true); final Network[] cellAndVpn = new Network[] { mCellNetworkAgent.getNetwork(), mMockVpn.getNetwork()}; @@ -5630,6 +5773,7 @@ public class ConnectivityServiceTest { // (and doing so is difficult without using reflection) but it's good to test that the code // behaves approximately correctly. mMockVpn.establishForMyUid(false, true, false); + assertUidRangesUpdatedForMyUid(true); final Network wifiNetwork = new Network(mNetIdManager.peekNextNetId()); mService.setUnderlyingNetworksForVpn(new Network[]{wifiNetwork}); callback.expectAvailableCallbacksUnvalidated(mMockVpn); @@ -5787,6 +5931,7 @@ public class ConnectivityServiceTest { mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */, false /* isStrictMode */); + assertUidRangesUpdatedForMyUid(true); defaultCallback.assertNoCallback(); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); @@ -5812,6 +5957,7 @@ public class ConnectivityServiceTest { mMockVpn.establishForMyUid(true /* validated */, true /* hasInternet */, false /* isStrictMode */); + assertUidRangesUpdatedForMyUid(true); defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); @@ -5837,6 +5983,7 @@ public class ConnectivityServiceTest { // Bring up a VPN that has the INTERNET capability, initially unvalidated. mMockVpn.establishForMyUid(false /* validated */, true /* hasInternet */, false /* isStrictMode */); + assertUidRangesUpdatedForMyUid(true); // Even though the VPN is unvalidated, it becomes the default network for our app. callback.expectAvailableCallbacksUnvalidated(mMockVpn); @@ -5888,6 +6035,7 @@ public class ConnectivityServiceTest { mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */, false /* isStrictMode */); + assertUidRangesUpdatedForMyUid(true); vpnNetworkCallback.expectAvailableCallbacks(mMockVpn.getNetwork(), false /* suspended */, false /* validated */, false /* blocked */, TIMEOUT_MS); @@ -5929,6 +6077,7 @@ public class ConnectivityServiceTest { mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */, false /* isStrictMode */); + assertUidRangesUpdatedForMyUid(true); vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); @@ -6096,6 +6245,7 @@ public class ConnectivityServiceTest { mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */, false /* isStrictMode */); + assertUidRangesUpdatedForMyUid(true); vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); @@ -6154,6 +6304,7 @@ public class ConnectivityServiceTest { // Bring up a VPN mMockVpn.establishForMyUid(); + assertUidRangesUpdatedForMyUid(true); callback.expectAvailableThenValidatedCallbacks(mMockVpn); callback.assertNoCallback(); @@ -6174,11 +6325,15 @@ public class ConnectivityServiceTest { // Create a fake restricted profile whose parent is our user ID. final int userId = UserHandle.getUserId(uid); + when(mUserManager.canHaveRestrictedProfile(userId)).thenReturn(true); final int restrictedUserId = userId + 1; final UserInfo info = new UserInfo(restrictedUserId, "user", UserInfo.FLAG_RESTRICTED); info.restrictedProfileParentId = userId; assertTrue(info.isRestricted()); when(mUserManager.getUserInfo(restrictedUserId)).thenReturn(info); + when(mPackageManager.getPackageUidAsUser(ALWAYS_ON_PACKAGE, restrictedUserId)) + .thenReturn(UserHandle.getUid(restrictedUserId, VPN_UID)); + final Intent addedIntent = new Intent(ACTION_USER_ADDED); addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, restrictedUserId); @@ -6218,6 +6373,54 @@ public class ConnectivityServiceTest { && caps.getUids().contains(new UidRange(uid, uid)) && caps.hasTransport(TRANSPORT_VPN) && !caps.hasTransport(TRANSPORT_WIFI)); + + // Test lockdown with restricted profiles. + mServiceContext.setPermission( + Manifest.permission.CONTROL_ALWAYS_ON_VPN, PERMISSION_GRANTED); + mServiceContext.setPermission( + Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED); + mServiceContext.setPermission( + Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED); + + // Connect wifi and check that UIDs in the main and restricted profiles have network access. + mMockVpn.disconnect(); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true /* validated */); + final int restrictedUid = UserHandle.getUid(restrictedUserId, 42 /* appId */); + assertNotNull(mCm.getActiveNetworkForUid(uid)); + assertNotNull(mCm.getActiveNetworkForUid(restrictedUid)); + + // Enable always-on VPN lockdown. The main user loses network access because no VPN is up. + final ArrayList<String> allowList = new ArrayList<>(); + mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList); + waitForIdle(); + assertNull(mCm.getActiveNetworkForUid(uid)); + assertNotNull(mCm.getActiveNetworkForUid(restrictedUid)); + + // Start the restricted profile, and check that the UID within it loses network access. + when(mUserManager.getAliveUsers()).thenReturn( + Arrays.asList(new UserInfo[] { + new UserInfo(userId, "", 0), + info + })); + // TODO: check that VPN app within restricted profile still has access, etc. + handler.post(() -> mServiceContext.sendBroadcast(addedIntent)); + waitForIdle(); + assertNull(mCm.getActiveNetworkForUid(uid)); + assertNull(mCm.getActiveNetworkForUid(restrictedUid)); + + // Stop the restricted profile, and check that the UID within it has network access again. + when(mUserManager.getAliveUsers()).thenReturn( + Arrays.asList(new UserInfo[] { + new UserInfo(userId, "", 0), + })); + handler.post(() -> mServiceContext.sendBroadcast(removedIntent)); + waitForIdle(); + assertNull(mCm.getActiveNetworkForUid(uid)); + assertNotNull(mCm.getActiveNetworkForUid(restrictedUid)); + + mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + waitForIdle(); } @Test @@ -6256,6 +6459,7 @@ public class ConnectivityServiceTest { // Connect VPN network. By default it is using current default network (Cell). mMockVpn.establishForMyUid(); + assertUidRangesUpdatedForMyUid(true); // Ensure VPN is now the active network. assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); @@ -6308,6 +6512,7 @@ public class ConnectivityServiceTest { // Connect VPN network. mMockVpn.establishForMyUid(); + assertUidRangesUpdatedForMyUid(true); // Ensure VPN is now the active network. assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); @@ -6509,6 +6714,26 @@ public class ConnectivityServiceTest { checkNetworkInfo(mCm.getNetworkInfo(type), type, state); } + // Checks that each of the |agents| receive a blocked status change callback with the specified + // |blocked| value, in any order. This is needed because when an event affects multiple + // networks, ConnectivityService does not guarantee the order in which callbacks are fired. + private void assertBlockedCallbackInAnyOrder(TestNetworkCallback callback, boolean blocked, + TestNetworkAgentWrapper... agents) { + final List<Network> expectedNetworks = Arrays.asList(agents).stream() + .map((agent) -> agent.getNetwork()) + .collect(Collectors.toList()); + + // Expect exactly one blocked callback for each agent. + for (int i = 0; i < agents.length; i++) { + CallbackEntry e = callback.expectCallbackThat(TIMEOUT_MS, (c) -> + c instanceof CallbackEntry.BlockedStatus + && ((CallbackEntry.BlockedStatus) c).getBlocked() == blocked); + Network network = e.getNetwork(); + assertTrue("Received unexpected blocked callback for network " + network, + expectedNetworks.remove(network)); + } + } + @Test public void testNetworkBlockedStatusAlwaysOnVpn() throws Exception { mServiceContext.setPermission( @@ -6555,9 +6780,10 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); // Disable lockdown, expect to see the network unblocked. - // There are no callbacks because they are not implemented yet. mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); expectNetworkRejectNonSecureVpn(inOrder, false, firstHalf, secondHalf); + callback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); + defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); vpnUidCallback.assertNoCallback(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); @@ -6605,6 +6831,8 @@ public class ConnectivityServiceTest { allowList.clear(); mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList); expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf); + defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent); + assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent); vpnUidCallback.assertNoCallback(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertNull(mCm.getActiveNetwork()); @@ -6614,6 +6842,8 @@ public class ConnectivityServiceTest { // Disable lockdown. Everything is unblocked. mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); + assertBlockedCallbackInAnyOrder(callback, false, mWiFiNetworkAgent, mCellNetworkAgent); vpnUidCallback.assertNoCallback(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); @@ -6647,6 +6877,8 @@ public class ConnectivityServiceTest { // Enable lockdown and connect a VPN. The VPN is not blocked. mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList); + defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent); + assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent); vpnUidCallback.assertNoCallback(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertNull(mCm.getActiveNetwork()); @@ -6655,10 +6887,11 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); mMockVpn.establishForMyUid(); + assertUidRangesUpdatedForMyUid(true); defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); vpnUidCallback.assertNoCallback(); // vpnUidCallback has NOT_VPN capability. assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); - assertEquals(null, mCm.getActiveNetworkForUid(VPN_UID)); // BUG? + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); @@ -6674,36 +6907,58 @@ public class ConnectivityServiceTest { mCm.unregisterNetworkCallback(vpnUidCallback); } + /** + * Test mutable and requestable network capabilities such as + * {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED} and + * {@link NetworkCapabilities#NET_CAPABILITY_NOT_VCN_MANAGED}. Verify that the + * {@code ConnectivityService} re-assign the networks accordingly. + */ @Test - public final void testLoseTrusted() throws Exception { - final NetworkRequest trustedRequest = new NetworkRequest.Builder() - .addCapability(NET_CAPABILITY_TRUSTED) - .build(); - final TestNetworkCallback trustedCallback = new TestNetworkCallback(); - mCm.requestNetwork(trustedRequest, trustedCallback); - - mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); - mCellNetworkAgent.connect(true); - trustedCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); - verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); - reset(mMockNetd); - - mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); - mWiFiNetworkAgent.connect(true); - trustedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); - verify(mMockNetd).networkSetDefault(eq(mWiFiNetworkAgent.getNetwork().netId)); - reset(mMockNetd); - - mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_TRUSTED); - trustedCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); - verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); - reset(mMockNetd); - - mCellNetworkAgent.removeCapability(NET_CAPABILITY_TRUSTED); - trustedCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); - verify(mMockNetd).networkClearDefault(); - - mCm.unregisterNetworkCallback(trustedCallback); + public final void testLoseMutableAndRequestableCaps() throws Exception { + final int[] testCaps = new int [] { + NET_CAPABILITY_TRUSTED, + NET_CAPABILITY_NOT_VCN_MANAGED + }; + for (final int testCap : testCaps) { + // Create requests with and without the testing capability. + final TestNetworkCallback callbackWithCap = new TestNetworkCallback(); + final TestNetworkCallback callbackWithoutCap = new TestNetworkCallback(); + mCm.requestNetwork(new NetworkRequest.Builder().addCapability(testCap).build(), + callbackWithCap); + mCm.requestNetwork(new NetworkRequest.Builder().removeCapability(testCap).build(), + callbackWithoutCap); + + // Setup networks with testing capability and verify the default network changes. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + callbackWithCap.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + callbackWithoutCap.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); + reset(mMockNetd); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + callbackWithCap.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + callbackWithoutCap.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + verify(mMockNetd).networkSetDefault(eq(mWiFiNetworkAgent.getNetwork().netId)); + reset(mMockNetd); + + // Remove the testing capability on wifi, verify the callback and default network + // changes back to cellular. + mWiFiNetworkAgent.removeCapability(testCap); + callbackWithCap.expectAvailableCallbacksValidated(mCellNetworkAgent); + callbackWithoutCap.expectCapabilitiesWithout(testCap, mWiFiNetworkAgent); + verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); + reset(mMockNetd); + + mCellNetworkAgent.removeCapability(testCap); + callbackWithCap.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + callbackWithoutCap.assertNoCallback(); + verify(mMockNetd).networkClearDefault(); + + mCm.unregisterNetworkCallback(callbackWithCap); + mCm.unregisterNetworkCallback(callbackWithoutCap); + } } @Ignore // 40%+ flakiness : figure out why and re-enable. @@ -7173,7 +7428,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent.connect(true); networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); verify(mNetworkManagementService, times(1)).addIdleTimer(eq(MOBILE_IFNAME), anyInt(), - eq(ConnectivityManager.TYPE_MOBILE)); + eq(NetworkCapabilities.TRANSPORT_CELLULAR)); mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); final LinkProperties wifiLp = new LinkProperties(); @@ -7187,7 +7442,7 @@ public class ConnectivityServiceTest { networkCallback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); verify(mNetworkManagementService, times(1)).addIdleTimer(eq(WIFI_IFNAME), anyInt(), - eq(ConnectivityManager.TYPE_WIFI)); + eq(NetworkCapabilities.TRANSPORT_WIFI)); verify(mNetworkManagementService, times(1)).removeIdleTimer(eq(MOBILE_IFNAME)); // Disconnect wifi and switch back to cell @@ -7197,7 +7452,7 @@ public class ConnectivityServiceTest { assertNoCallbacks(networkCallback); verify(mNetworkManagementService, times(1)).removeIdleTimer(eq(WIFI_IFNAME)); verify(mNetworkManagementService, times(1)).addIdleTimer(eq(MOBILE_IFNAME), anyInt(), - eq(ConnectivityManager.TYPE_MOBILE)); + eq(NetworkCapabilities.TRANSPORT_CELLULAR)); // reconnect wifi mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); @@ -7312,6 +7567,7 @@ public class ConnectivityServiceTest { LinkProperties testLinkProperties = new LinkProperties(); testLinkProperties.setHttpProxy(testProxyInfo); mMockVpn.establishForMyUid(testLinkProperties); + assertUidRangesUpdatedForMyUid(true); // Test that the VPN network returns a proxy, and the WiFi does not. assertEquals(testProxyInfo, mService.getProxyForNetwork(mMockVpn.getNetwork())); @@ -7349,6 +7605,7 @@ public class ConnectivityServiceTest { // The uid range needs to cover the test app so the network is visible to it. final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); mMockVpn.establish(lp, VPN_UID, vpnRange); + assertVpnUidRangesUpdated(true, vpnRange, VPN_UID); // A connected VPN should have interface rules set up. There are two expected invocations, // one during the VPN initial connection, one during the VPN LinkProperties update. @@ -7376,6 +7633,7 @@ public class ConnectivityServiceTest { // The uid range needs to cover the test app so the network is visible to it. final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange); + assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID); // Legacy VPN should not have interface rules set up verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any()); @@ -7391,6 +7649,7 @@ public class ConnectivityServiceTest { // The uid range needs to cover the test app so the network is visible to it. final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange); + assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID); // IPv6 unreachable route should not be misinterpreted as a default route verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any()); @@ -7405,6 +7664,7 @@ public class ConnectivityServiceTest { // The uid range needs to cover the test app so the network is visible to it. final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); mMockVpn.establish(lp, VPN_UID, vpnRange); + assertVpnUidRangesUpdated(true, vpnRange, VPN_UID); // Connected VPN should have interface rules set up. There are two expected invocations, // one during VPN uid update, one during VPN LinkProperties update @@ -7455,7 +7715,9 @@ public class ConnectivityServiceTest { lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); // The uid range needs to cover the test app so the network is visible to it. final UidRange vpnRange = UidRange.createForUser(VPN_USER); - mMockVpn.establish(lp, VPN_UID, Collections.singleton(vpnRange)); + final Set<UidRange> vpnRanges = Collections.singleton(vpnRange); + mMockVpn.establish(lp, VPN_UID, vpnRanges); + assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID); reset(mMockNetd); InOrder inOrder = inOrder(mMockNetd); @@ -7606,6 +7868,7 @@ public class ConnectivityServiceTest { throws Exception { final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); mMockVpn.establish(new LinkProperties(), vpnOwnerUid, vpnRange); + assertVpnUidRangesUpdated(true, vpnRange, vpnOwnerUid); mMockVpn.setVpnType(vpnType); final VpnInfo vpnInfo = new VpnInfo(); @@ -7810,8 +8073,7 @@ public class ConnectivityServiceTest { @Test public void testCheckConnectivityDiagnosticsPermissionsNetworkStack() throws Exception { final NetworkAgentInfo naiWithoutUid = - new NetworkAgentInfo( - null, null, null, null, null, new NetworkCapabilities(), 0, + new NetworkAgentInfo(null, null, null, null, new NetworkCapabilities(), 0, mServiceContext, null, null, mService, null, null, null, 0, INVALID_UID); mServiceContext.setPermission( @@ -7826,8 +8088,7 @@ public class ConnectivityServiceTest { @Test public void testCheckConnectivityDiagnosticsPermissionsWrongUidPackageName() throws Exception { final NetworkAgentInfo naiWithoutUid = - new NetworkAgentInfo( - null, null, null, null, null, new NetworkCapabilities(), 0, + new NetworkAgentInfo(null, null, null, null, new NetworkCapabilities(), 0, mServiceContext, null, null, mService, null, null, null, 0, INVALID_UID); mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); @@ -7842,8 +8103,7 @@ public class ConnectivityServiceTest { @Test public void testCheckConnectivityDiagnosticsPermissionsNoLocationPermission() throws Exception { final NetworkAgentInfo naiWithoutUid = - new NetworkAgentInfo( - null, null, null, null, null, new NetworkCapabilities(), 0, + new NetworkAgentInfo(null, null, null, null, new NetworkCapabilities(), 0, mServiceContext, null, null, mService, null, null, null, 0, INVALID_UID); mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); @@ -7859,14 +8119,14 @@ public class ConnectivityServiceTest { public void testCheckConnectivityDiagnosticsPermissionsActiveVpn() throws Exception { final Network network = new Network(NET_ID); final NetworkAgentInfo naiWithoutUid = - new NetworkAgentInfo( - null, null, network, null, null, new NetworkCapabilities(), 0, + new NetworkAgentInfo(null, network, null, null, new NetworkCapabilities(), 0, mServiceContext, null, null, mService, null, null, null, 0, INVALID_UID); setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION); mMockVpn.establishForMyUid(); + assertUidRangesUpdatedForMyUid(true); // Wait for networks to connect and broadcasts to be sent before removing permissions. waitForIdle(); @@ -7894,8 +8154,7 @@ public class ConnectivityServiceTest { final NetworkCapabilities nc = new NetworkCapabilities(); nc.setAdministratorUids(new int[] {Process.myUid()}); final NetworkAgentInfo naiWithUid = - new NetworkAgentInfo( - null, null, null, null, null, nc, 0, mServiceContext, null, null, + new NetworkAgentInfo(null, null, null, null, nc, 0, mServiceContext, null, null, mService, null, null, null, 0, INVALID_UID); setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, @@ -7914,8 +8173,7 @@ public class ConnectivityServiceTest { nc.setOwnerUid(Process.myUid()); nc.setAdministratorUids(new int[] {Process.myUid()}); final NetworkAgentInfo naiWithUid = - new NetworkAgentInfo( - null, null, null, null, null, nc, 0, mServiceContext, null, null, + new NetworkAgentInfo(null, null, null, null, nc, 0, mServiceContext, null, null, mService, null, null, null, 0, INVALID_UID); setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, @@ -8148,4 +8406,54 @@ public class ConnectivityServiceTest { assertTrue(isRequestIdInOrder); } } + + private void assertUidRangesUpdatedForMyUid(boolean add) throws Exception { + final int uid = Process.myUid(); + assertVpnUidRangesUpdated(add, uidRangesForUid(uid), uid); + } + + private void assertVpnUidRangesUpdated(boolean add, Set<UidRange> vpnRanges, int exemptUid) + throws Exception { + InOrder inOrder = inOrder(mMockNetd); + ArgumentCaptor<int[]> exemptUidCaptor = ArgumentCaptor.forClass(int[].class); + + inOrder.verify(mMockNetd, times(1)).socketDestroy(eq(toUidRangeStableParcels(vpnRanges)), + exemptUidCaptor.capture()); + assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid); + + if (add) { + inOrder.verify(mMockNetd, times(1)).networkAddUidRanges(eq(mMockVpn.getNetId()), + eq(toUidRangeStableParcels(vpnRanges))); + } else { + inOrder.verify(mMockNetd, times(1)).networkRemoveUidRanges(eq(mMockVpn.getNetId()), + eq(toUidRangeStableParcels(vpnRanges))); + } + + inOrder.verify(mMockNetd, times(1)).socketDestroy(eq(toUidRangeStableParcels(vpnRanges)), + exemptUidCaptor.capture()); + assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid); + } + + @Test + public void testVpnUidRangesUpdate() throws Exception { + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("tun0"); + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); + final UidRange vpnRange = UidRange.createForUser(VPN_USER); + Set<UidRange> vpnRanges = Collections.singleton(vpnRange); + mMockVpn.establish(lp, VPN_UID, vpnRanges); + assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID); + + reset(mMockNetd); + // Update to new range which is old range minus APP1, i.e. only APP2 + final Set<UidRange> newRanges = new HashSet<>(Arrays.asList( + new UidRange(vpnRange.start, APP1_UID - 1), + new UidRange(APP1_UID + 1, vpnRange.stop))); + mMockVpn.setUids(newRanges); + waitForIdle(); + + assertVpnUidRangesUpdated(true, newRanges, VPN_UID); + assertVpnUidRangesUpdated(false, vpnRanges, VPN_UID); + } } diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java index 3a071667a542..8c5d1d6d05e5 100644 --- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java +++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java @@ -124,6 +124,22 @@ public class IpConnectivityMetricsTest { assertEquals("", output2); } + private void logDefaultNetworkEvent(long timeMs, NetworkAgentInfo nai, + NetworkAgentInfo oldNai) { + final Network network = (nai != null) ? nai.network() : null; + final int score = (nai != null) ? nai.getCurrentScore() : 0; + final boolean validated = (nai != null) ? nai.lastValidated : false; + final LinkProperties lp = (nai != null) ? nai.linkProperties : null; + final NetworkCapabilities nc = (nai != null) ? nai.networkCapabilities : null; + + final Network prevNetwork = (oldNai != null) ? oldNai.network() : null; + final int prevScore = (oldNai != null) ? oldNai.getCurrentScore() : 0; + final LinkProperties prevLp = (oldNai != null) ? oldNai.linkProperties : null; + final NetworkCapabilities prevNc = (oldNai != null) ? oldNai.networkCapabilities : null; + + mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs, network, score, validated, + lp, nc, prevNetwork, prevScore, prevLp, prevNc); + } @Test public void testDefaultNetworkEvents() throws Exception { final long cell = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_CELLULAR}); @@ -147,7 +163,7 @@ public class IpConnectivityMetricsTest { for (NetworkAgentInfo[] pair : defaultNetworks) { timeMs += durationMs; durationMs += durationMs; - mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs, pair[1], pair[0]); + logDefaultNetworkEvent(timeMs, pair[1], pair[0]); } String want = String.join("\n", @@ -331,8 +347,8 @@ public class IpConnectivityMetricsTest { final long wifi = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_WIFI}); NetworkAgentInfo cellNai = makeNai(100, 50, false, true, cell); NetworkAgentInfo wifiNai = makeNai(101, 60, true, false, wifi); - mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs + 200, cellNai, null); - mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs + 300, wifiNai, cellNai); + logDefaultNetworkEvent(timeMs + 200L, cellNai, null); + logDefaultNetworkEvent(timeMs + 300L, wifiNai, cellNai); String want = String.join("\n", "dropped_events: 0", diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java index aafa18a532fa..96c56e32f156 100644 --- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java @@ -353,7 +353,7 @@ public class LingerMonitorTest { NetworkCapabilities caps = new NetworkCapabilities(); caps.addCapability(0); caps.addTransportType(transport); - NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null, + NetworkAgentInfo nai = new NetworkAgentInfo(null, new Network(netId), info, null, caps, 50, mCtx, null, null /* config */, mConnService, mNetd, mDnsResolver, mNMS, NetworkProvider.ID_NONE, Binder.getCallingUid()); nai.everValidated = true; diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index cc473175540c..02a2aadc4c79 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -27,7 +27,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.mockito.AdditionalMatchers.aryEq; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -89,6 +88,7 @@ import android.security.Credentials; import android.security.KeyStore; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Range; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -339,40 +339,26 @@ public class VpnTest { final Vpn vpn = createVpn(primaryUser.id); final UidRange user = PRI_USER_RANGE; - // Default state. - assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], - user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]); - // Set always-on without lockdown. assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null, mKeyStore)); - assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], - user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]); // Set always-on with lockdown. assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null, mKeyStore)); - verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(new UidRangeParcel[] { + verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1), new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop) })); - assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[2], - user.start + PKG_UIDS[3]); - assertUnblocked(vpn, user.start + PKG_UIDS[1]); - // Switch to another app. assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore)); - verify(mNetd).networkRejectNonSecureVpn(eq(false), aryEq(new UidRangeParcel[] { + verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1), new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop) })); - - verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(new UidRangeParcel[] { + verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start, user.start + PKG_UIDS[3] - 1), new UidRangeParcel(user.start + PKG_UIDS[3] + 1, user.stop) })); - assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], - user.start + PKG_UIDS[2]); - assertUnblocked(vpn, user.start + PKG_UIDS[3]); } @Test @@ -383,64 +369,53 @@ public class VpnTest { // Set always-on with lockdown and allow app PKGS[2] from lockdown. assertTrue(vpn.setAlwaysOnPackage( PKGS[1], true, Collections.singletonList(PKGS[2]), mKeyStore)); - verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(new UidRangeParcel[] { + verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1), new UidRangeParcel(user.start + PKG_UIDS[2] + 1, user.stop) })); - assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]); - assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]); // Change allowed app list to PKGS[3]. assertTrue(vpn.setAlwaysOnPackage( PKGS[1], true, Collections.singletonList(PKGS[3]), mKeyStore)); - verify(mNetd).networkRejectNonSecureVpn(eq(false), aryEq(new UidRangeParcel[] { + verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start + PKG_UIDS[2] + 1, user.stop) })); - verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(new UidRangeParcel[] { + verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1), new UidRangeParcel(user.start + PKG_UIDS[3] + 1, user.stop) })); - assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[2]); - assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[3]); // Change the VPN app. assertTrue(vpn.setAlwaysOnPackage( PKGS[0], true, Collections.singletonList(PKGS[3]), mKeyStore)); - verify(mNetd).networkRejectNonSecureVpn(eq(false), aryEq(new UidRangeParcel[] { + verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1), new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1) })); - verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(new UidRangeParcel[] { + verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start, user.start + PKG_UIDS[0] - 1), new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1) })); - assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]); - assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]); // Remove the list of allowed packages. assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null, mKeyStore)); - verify(mNetd).networkRejectNonSecureVpn(eq(false), aryEq(new UidRangeParcel[] { + verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1), new UidRangeParcel(user.start + PKG_UIDS[3] + 1, user.stop) })); - verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(new UidRangeParcel[] { + verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.stop), })); - assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], - user.start + PKG_UIDS[3]); - assertUnblocked(vpn, user.start + PKG_UIDS[0]); // Add the list of allowed packages. assertTrue(vpn.setAlwaysOnPackage( PKGS[0], true, Collections.singletonList(PKGS[1]), mKeyStore)); - verify(mNetd).networkRejectNonSecureVpn(eq(false), aryEq(new UidRangeParcel[] { + verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.stop) })); - verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(new UidRangeParcel[] { + verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1), new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop) })); - assertBlocked(vpn, user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]); - assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1]); // Try allowing a package with a comma, should be rejected. assertFalse(vpn.setAlwaysOnPackage( @@ -450,78 +425,46 @@ public class VpnTest { // allowed package should change from PGKS[1] to PKGS[2]. assertTrue(vpn.setAlwaysOnPackage( PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"), mKeyStore)); - verify(mNetd).networkRejectNonSecureVpn(eq(false), aryEq(new UidRangeParcel[]{ + verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1), new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop) })); - verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(new UidRangeParcel[]{ + verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[2] - 1), new UidRangeParcel(user.start + PKG_UIDS[2] + 1, user.stop) })); } @Test - public void testLockdownAddingAProfile() throws Exception { - final Vpn vpn = createVpn(primaryUser.id); - setMockedUsers(primaryUser); - - // Make a copy of the restricted profile, as we're going to mark it deleted halfway through. - final UserInfo tempProfile = new UserInfo(restrictedProfileA.id, restrictedProfileA.name, - restrictedProfileA.flags); - tempProfile.restrictedProfileParentId = primaryUser.id; - - final UidRange user = PRI_USER_RANGE; - final UidRange profile = UidRange.createForUser(tempProfile.id); - - // Set lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore)); - verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(new UidRangeParcel[] { - new UidRangeParcel(user.start, user.start + PKG_UIDS[3] - 1), - new UidRangeParcel(user.start + PKG_UIDS[3] + 1, user.stop) - })); - // Verify restricted user isn't affected at first. - assertUnblocked(vpn, profile.start + PKG_UIDS[0]); - - // Add the restricted user. - setMockedUsers(primaryUser, tempProfile); - vpn.onUserAdded(tempProfile.id); - verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(new UidRangeParcel[] { - new UidRangeParcel(profile.start, profile.start + PKG_UIDS[3] - 1), - new UidRangeParcel(profile.start + PKG_UIDS[3] + 1, profile.stop) - })); - - // Remove the restricted user. - tempProfile.partial = true; - vpn.onUserRemoved(tempProfile.id); - verify(mNetd).networkRejectNonSecureVpn(eq(false), aryEq(new UidRangeParcel[] { - new UidRangeParcel(profile.start, profile.start + PKG_UIDS[3] - 1), - new UidRangeParcel(profile.start + PKG_UIDS[3] + 1, profile.stop) - })); - } - - @Test public void testLockdownRuleRepeatability() throws Exception { final Vpn vpn = createVpn(primaryUser.id); final UidRangeParcel[] primaryUserRangeParcel = new UidRangeParcel[] { new UidRangeParcel(PRI_USER_RANGE.start, PRI_USER_RANGE.stop)}; // Given legacy lockdown is already enabled, vpn.setLockdown(true); - - verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(primaryUserRangeParcel)); + verify(mConnectivityManager, times(1)).setRequireVpnForUids(true, + toRanges(primaryUserRangeParcel)); // Enabling legacy lockdown twice should do nothing. vpn.setLockdown(true); - verify(mNetd, times(1)) - .networkRejectNonSecureVpn(anyBoolean(), any(UidRangeParcel[].class)); + verify(mConnectivityManager, times(1)).setRequireVpnForUids(anyBoolean(), any()); // And disabling should remove the rules exactly once. vpn.setLockdown(false); - verify(mNetd).networkRejectNonSecureVpn(eq(false), aryEq(primaryUserRangeParcel)); + verify(mConnectivityManager, times(1)).setRequireVpnForUids(false, + toRanges(primaryUserRangeParcel)); // Removing the lockdown again should have no effect. vpn.setLockdown(false); - verify(mNetd, times(2)).networkRejectNonSecureVpn( - anyBoolean(), any(UidRangeParcel[].class)); + verify(mConnectivityManager, times(2)).setRequireVpnForUids(anyBoolean(), any()); + } + + private ArrayList<Range<Integer>> toRanges(UidRangeParcel[] ranges) { + ArrayList<Range<Integer>> rangesArray = new ArrayList<>(ranges.length); + for (int i = 0; i < ranges.length; i++) { + rangesArray.add(new Range<>(ranges[i].start, ranges[i].stop)); + } + return rangesArray; } @Test @@ -535,21 +478,21 @@ public class VpnTest { new UidRangeParcel(entireUser[0].start + PKG_UIDS[0] + 1, entireUser[0].stop) }; - final InOrder order = inOrder(mNetd); + final InOrder order = inOrder(mConnectivityManager); // Given lockdown is enabled with no package (legacy VPN), vpn.setLockdown(true); - order.verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(entireUser)); + order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(entireUser)); // When a new VPN package is set the rules should change to cover that package. vpn.prepare(null, PKGS[0], VpnManager.TYPE_VPN_SERVICE); - order.verify(mNetd).networkRejectNonSecureVpn(eq(false), aryEq(entireUser)); - order.verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(exceptPkg0)); + order.verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(entireUser)); + order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(exceptPkg0)); // When that VPN package is unset, everything should be undone again in reverse. vpn.prepare(null, VpnConfig.LEGACY_VPN, VpnManager.TYPE_VPN_SERVICE); - order.verify(mNetd).networkRejectNonSecureVpn(eq(false), aryEq(exceptPkg0)); - order.verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(entireUser)); + order.verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(exceptPkg0)); + order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(entireUser)); } @Test @@ -1201,20 +1144,6 @@ public class VpnTest { return vpn; } - private static void assertBlocked(Vpn vpn, int... uids) { - for (int uid : uids) { - final boolean blocked = vpn.getLockdown() && vpn.isBlockingUid(uid); - assertTrue("Uid " + uid + " should be blocked", blocked); - } - } - - private static void assertUnblocked(Vpn vpn, int... uids) { - for (int uid : uids) { - final boolean blocked = vpn.getLockdown() && vpn.isBlockingUid(uid); - assertFalse("Uid " + uid + " should not be blocked", blocked); - } - } - /** * Populate {@link #mUserManager} with a list of fake users. */ diff --git a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java index 89146f945e1f..435c3c0af817 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java @@ -64,7 +64,6 @@ import org.junit.runner.RunWith; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; @@ -124,7 +123,7 @@ public class NetworkStatsCollectionTest { // now export into a unified format final ByteArrayOutputStream bos = new ByteArrayOutputStream(); - collection.write(new DataOutputStream(bos)); + collection.write(bos); // clear structure completely collection.reset(); @@ -152,7 +151,7 @@ public class NetworkStatsCollectionTest { // now export into a unified format final ByteArrayOutputStream bos = new ByteArrayOutputStream(); - collection.write(new DataOutputStream(bos)); + collection.write(bos); // clear structure completely collection.reset(); @@ -180,7 +179,7 @@ public class NetworkStatsCollectionTest { // now export into a unified format final ByteArrayOutputStream bos = new ByteArrayOutputStream(); - collection.write(new DataOutputStream(bos)); + collection.write(bos); // clear structure completely collection.reset(); diff --git a/tests/vcn/java/android/net/vcn/VcnConfigTest.java b/tests/vcn/java/android/net/vcn/VcnConfigTest.java index 77944deb26f1..c1ef350e5c4a 100644 --- a/tests/vcn/java/android/net/vcn/VcnConfigTest.java +++ b/tests/vcn/java/android/net/vcn/VcnConfigTest.java @@ -18,12 +18,17 @@ package android.net.vcn; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import android.annotation.NonNull; +import android.content.Context; import android.os.Parcel; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -33,12 +38,15 @@ import java.util.Set; @RunWith(AndroidJUnit4.class) @SmallTest public class VcnConfigTest { + private static final String TEST_PACKAGE_NAME = VcnConfigTest.class.getPackage().getName(); private static final Set<VcnGatewayConnectionConfig> GATEWAY_CONNECTION_CONFIGS = Collections.singleton(VcnGatewayConnectionConfigTest.buildTestConfig()); + private final Context mContext = mock(Context.class); + // Public visibility for VcnManagementServiceTest - public static VcnConfig buildTestConfig() { - VcnConfig.Builder builder = new VcnConfig.Builder(); + public static VcnConfig buildTestConfig(@NonNull Context context) { + VcnConfig.Builder builder = new VcnConfig.Builder(context); for (VcnGatewayConnectionConfig gatewayConnectionConfig : GATEWAY_CONNECTION_CONFIGS) { builder.addGatewayConnectionConfig(gatewayConnectionConfig); @@ -47,10 +55,24 @@ public class VcnConfigTest { return builder.build(); } + @Before + public void setUp() throws Exception { + doReturn(TEST_PACKAGE_NAME).when(mContext).getOpPackageName(); + } + + @Test + public void testBuilderConstructorRequiresContext() { + try { + new VcnConfig.Builder(null); + fail("Expected exception due to null context"); + } catch (NullPointerException e) { + } + } + @Test public void testBuilderRequiresGatewayConnectionConfig() { try { - new VcnConfig.Builder().build(); + new VcnConfig.Builder(mContext).build(); fail("Expected exception due to no VcnGatewayConnectionConfigs provided"); } catch (IllegalArgumentException e) { } @@ -58,21 +80,22 @@ public class VcnConfigTest { @Test public void testBuilderAndGetters() { - final VcnConfig config = buildTestConfig(); + final VcnConfig config = buildTestConfig(mContext); + assertEquals(TEST_PACKAGE_NAME, config.getProvisioningPackageName()); assertEquals(GATEWAY_CONNECTION_CONFIGS, config.getGatewayConnectionConfigs()); } @Test public void testPersistableBundle() { - final VcnConfig config = buildTestConfig(); + final VcnConfig config = buildTestConfig(mContext); assertEquals(config, new VcnConfig(config.toPersistableBundle())); } @Test public void testParceling() { - final VcnConfig config = buildTestConfig(); + final VcnConfig config = buildTestConfig(mContext); Parcel parcel = Parcel.obtain(); config.writeToParcel(parcel, 0); diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 1cc953239fed..696110f01869 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -16,16 +16,23 @@ package com.android.server; +import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; +import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.argThat; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import android.app.AppOpsManager; import android.content.Context; import android.net.ConnectivityManager; import android.net.vcn.VcnConfig; @@ -42,29 +49,47 @@ import android.telephony.TelephonyManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.vcn.TelephonySubscriptionTracker; +import com.android.server.vcn.Vcn; +import com.android.server.vcn.VcnContext; +import com.android.server.vcn.VcnNetworkProvider; import com.android.server.vcn.util.PersistableBundleUtils; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import java.io.FileNotFoundException; import java.util.Collections; import java.util.Map; +import java.util.Set; import java.util.UUID; /** Tests for {@link VcnManagementService}. */ @RunWith(AndroidJUnit4.class) @SmallTest public class VcnManagementServiceTest { + private static final String TEST_PACKAGE_NAME = + VcnManagementServiceTest.class.getPackage().getName(); private static final ParcelUuid TEST_UUID_1 = new ParcelUuid(new UUID(0, 0)); private static final ParcelUuid TEST_UUID_2 = new ParcelUuid(new UUID(1, 1)); - private static final VcnConfig TEST_VCN_CONFIG = VcnConfigTest.buildTestConfig(); + private static final VcnConfig TEST_VCN_CONFIG; + private static final int TEST_UID = Process.FIRST_APPLICATION_UID; + + static { + final Context mockConfigContext = mock(Context.class); + doReturn(TEST_PACKAGE_NAME).when(mockConfigContext).getOpPackageName(); + + TEST_VCN_CONFIG = VcnConfigTest.buildTestConfig(mockConfigContext); + } + private static final Map<ParcelUuid, VcnConfig> TEST_VCN_CONFIG_MAP = Collections.unmodifiableMap(Collections.singletonMap(TEST_UUID_1, TEST_VCN_CONFIG)); + private static final int TEST_SUBSCRIPTION_ID = 1; private static final SubscriptionInfo TEST_SUBSCRIPTION_INFO = new SubscriptionInfo( - 1 /* id */, + TEST_SUBSCRIPTION_ID /* id */, "" /* iccId */, 0 /* simSlotIndex */, "Carrier" /* displayName */, @@ -92,22 +117,48 @@ public class VcnManagementServiceTest { private final ConnectivityManager mConnMgr = mock(ConnectivityManager.class); private final TelephonyManager mTelMgr = mock(TelephonyManager.class); private final SubscriptionManager mSubMgr = mock(SubscriptionManager.class); - private final VcnManagementService mVcnMgmtSvc; + private final AppOpsManager mAppOpsMgr = mock(AppOpsManager.class); + private final VcnContext mVcnContext = mock(VcnContext.class); private final PersistableBundleUtils.LockingReadWriteHelper mConfigReadWriteHelper = mock(PersistableBundleUtils.LockingReadWriteHelper.class); + private final TelephonySubscriptionTracker mSubscriptionTracker = + mock(TelephonySubscriptionTracker.class); + + private final VcnManagementService mVcnMgmtSvc; public VcnManagementServiceTest() throws Exception { setupSystemService(mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); setupSystemService(mTelMgr, Context.TELEPHONY_SERVICE, TelephonyManager.class); setupSystemService( mSubMgr, Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class); + setupSystemService(mAppOpsMgr, Context.APP_OPS_SERVICE, AppOpsManager.class); + + doReturn(TEST_PACKAGE_NAME).when(mMockContext).getOpPackageName(); doReturn(mTestLooper.getLooper()).when(mMockDeps).getLooper(); - doReturn(Process.FIRST_APPLICATION_UID).when(mMockDeps).getBinderCallingUid(); + doReturn(TEST_UID).when(mMockDeps).getBinderCallingUid(); + doReturn(mVcnContext) + .when(mMockDeps) + .newVcnContext( + eq(mMockContext), + eq(mTestLooper.getLooper()), + any(VcnNetworkProvider.class)); + doReturn(mSubscriptionTracker) + .when(mMockDeps) + .newTelephonySubscriptionTracker( + eq(mMockContext), + eq(mTestLooper.getLooper()), + any(TelephonySubscriptionTrackerCallback.class)); doReturn(mConfigReadWriteHelper) .when(mMockDeps) .newPersistableBundleLockingReadWriteHelper(any()); + // Setup VCN instance generation + doAnswer((invocation) -> { + // Mock-within a doAnswer is safe, because it doesn't actually run nested. + return mock(Vcn.class); + }).when(mMockDeps).newVcn(any(), any(), any()); + final PersistableBundle bundle = PersistableBundleUtils.fromMap( TEST_VCN_CONFIG_MAP, @@ -117,6 +168,9 @@ public class VcnManagementServiceTest { setupMockedCarrierPrivilege(true); mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps); + + // Make sure the profiles are loaded. + mTestLooper.dispatchAll(); } private void setupSystemService(Object service, String name, Class<?> serviceClass) { @@ -137,8 +191,8 @@ public class VcnManagementServiceTest { public void testSystemReady() throws Exception { mVcnMgmtSvc.systemReady(); - verify(mConnMgr) - .registerNetworkProvider(any(VcnManagementService.VcnNetworkProvider.class)); + verify(mConnMgr).registerNetworkProvider(any(VcnNetworkProvider.class)); + verify(mSubscriptionTracker).register(); } @Test @@ -171,12 +225,110 @@ public class VcnManagementServiceTest { verify(mConfigReadWriteHelper).readFromDisk(); } + private void triggerSubscriptionTrackerCallback(Set<ParcelUuid> activeSubscriptionGroups) { + final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class); + doReturn(activeSubscriptionGroups).when(snapshot).getActiveSubscriptionGroups(); + + final Set<String> privilegedPackages = + (activeSubscriptionGroups == null || activeSubscriptionGroups.isEmpty()) + ? Collections.emptySet() + : Collections.singleton(TEST_PACKAGE_NAME); + doReturn(true) + .when(snapshot) + .packageHasPermissionsForSubscriptionGroup( + argThat(val -> activeSubscriptionGroups.contains(val)), + eq(TEST_PACKAGE_NAME)); + + final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); + cb.onNewSnapshot(snapshot); + } + + private TelephonySubscriptionTrackerCallback getTelephonySubscriptionTrackerCallback() { + final ArgumentCaptor<TelephonySubscriptionTrackerCallback> captor = + ArgumentCaptor.forClass(TelephonySubscriptionTrackerCallback.class); + verify(mMockDeps) + .newTelephonySubscriptionTracker( + eq(mMockContext), eq(mTestLooper.getLooper()), captor.capture()); + return captor.getValue(); + } + + private Vcn startAndGetVcnInstance(ParcelUuid uuid) { + mVcnMgmtSvc.setVcnConfig(uuid, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); + return mVcnMgmtSvc.getAllVcns().get(uuid); + } + + @Test + public void testTelephonyNetworkTrackerCallbackStartsInstances() throws Exception { + triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_1)); + verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG)); + } + + @Test + public void testTelephonyNetworkTrackerCallbackStopsInstances() throws Exception { + final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); + final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2); + + triggerSubscriptionTrackerCallback(Collections.emptySet()); + + // Verify teardown after delay + mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + mTestLooper.dispatchAll(); + verify(vcn).teardownAsynchronously(); + } + + @Test + public void testTelephonyNetworkTrackerCallbackSimSwitchesDoNotKillVcnInstances() + throws Exception { + final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); + final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2); + + // Simulate SIM unloaded + triggerSubscriptionTrackerCallback(Collections.emptySet()); + + // Simulate new SIM loaded right during teardown delay. + mTestLooper.moveTimeForward( + VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2); + mTestLooper.dispatchAll(); + triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_2)); + + // Verify that even after the full timeout duration, the VCN instance is not torn down + mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + mTestLooper.dispatchAll(); + verify(vcn, never()).teardownAsynchronously(); + } + + @Test + public void testTelephonyNetworkTrackerCallbackDoesNotKillNewVcnInstances() throws Exception { + final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); + final Vcn oldInstance = startAndGetVcnInstance(TEST_UUID_2); + + // Simulate SIM unloaded + triggerSubscriptionTrackerCallback(Collections.emptySet()); + + // Config cleared, SIM reloaded & config re-added right before teardown delay, staring new + // vcnInstance. + mTestLooper.moveTimeForward( + VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2); + mTestLooper.dispatchAll(); + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2); + final Vcn newInstance = startAndGetVcnInstance(TEST_UUID_2); + + // Verify that new instance was different, and the old one was torn down + assertTrue(oldInstance != newInstance); + verify(oldInstance).teardownAsynchronously(); + + // Verify that even after the full timeout duration, the new VCN instance is not torn down + mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + mTestLooper.dispatchAll(); + verify(newInstance, never()).teardownAsynchronously(); + } + @Test public void testSetVcnConfigRequiresNonSystemServer() throws Exception { doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid(); try { - mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, VcnConfigTest.buildTestConfig()); + mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); fail("Expected IllegalStateException exception for system server"); } catch (IllegalStateException expected) { } @@ -184,12 +336,12 @@ public class VcnManagementServiceTest { @Test public void testSetVcnConfigRequiresSystemUser() throws Exception { - doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, Process.FIRST_APPLICATION_UID)) + doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, TEST_UID)) .when(mMockDeps) .getBinderCallingUid(); try { - mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, VcnConfigTest.buildTestConfig()); + mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); fail("Expected security exception for non system user"); } catch (SecurityException expected) { } @@ -200,16 +352,25 @@ public class VcnManagementServiceTest { setupMockedCarrierPrivilege(false); try { - mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, VcnConfigTest.buildTestConfig()); + mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); fail("Expected security exception for missing carrier privileges"); } catch (SecurityException expected) { } } @Test + public void testSetVcnConfigMismatchedPackages() throws Exception { + try { + mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, "IncorrectPackage"); + fail("Expected exception due to mismatched packages in config and method call"); + } catch (IllegalArgumentException expected) { + } + } + + @Test public void testSetVcnConfig() throws Exception { // Use a different UUID to simulate a new VCN config. - mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG); + mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); assertEquals(TEST_VCN_CONFIG, mVcnMgmtSvc.getConfigs().get(TEST_UUID_2)); verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class)); } @@ -227,7 +388,7 @@ public class VcnManagementServiceTest { @Test public void testClearVcnConfigRequiresSystemUser() throws Exception { - doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, Process.FIRST_APPLICATION_UID)) + doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, TEST_UID)) .when(mMockDeps) .getBinderCallingUid(); @@ -255,4 +416,26 @@ public class VcnManagementServiceTest { assertTrue(mVcnMgmtSvc.getConfigs().isEmpty()); verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class)); } + + @Test + public void testSetVcnConfigClearVcnConfigStartsUpdatesAndTeardsDownVcns() throws Exception { + // Use a different UUID to simulate a new VCN config. + mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); + final Map<ParcelUuid, Vcn> vcnInstances = mVcnMgmtSvc.getAllVcns(); + final Vcn vcnInstance = vcnInstances.get(TEST_UUID_2); + assertEquals(1, vcnInstances.size()); + assertEquals(TEST_VCN_CONFIG, mVcnMgmtSvc.getConfigs().get(TEST_UUID_2)); + verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class)); + + // Verify Vcn is started + verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_2), eq(TEST_VCN_CONFIG)); + + // Verify Vcn is updated if it was previously started + mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); + verify(vcnInstance).updateConfig(TEST_VCN_CONFIG); + + // Verify Vcn is stopped if it was already started + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2); + verify(vcnInstance).teardownAsynchronously(); + } } diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java index 17b8f64a13fa..528f240b9912 100644 --- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java +++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java @@ -30,6 +30,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; @@ -37,6 +38,10 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; +import static java.util.Collections.singletonMap; + import android.annotation.NonNull; import android.content.Context; import android.content.Intent; @@ -49,6 +54,7 @@ import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; +import android.telephony.TelephonyManager; import android.util.ArraySet; import androidx.test.filters.SmallTest; @@ -63,6 +69,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -71,12 +78,16 @@ import java.util.UUID; @RunWith(AndroidJUnit4.class) @SmallTest public class TelephonySubscriptionTrackerTest { + private static final String PACKAGE_NAME = + TelephonySubscriptionTrackerTest.class.getPackage().getName(); private static final ParcelUuid TEST_PARCEL_UUID = new ParcelUuid(UUID.randomUUID()); private static final int TEST_SIM_SLOT_INDEX = 1; private static final int TEST_SUBSCRIPTION_ID_1 = 2; private static final SubscriptionInfo TEST_SUBINFO_1 = mock(SubscriptionInfo.class); private static final int TEST_SUBSCRIPTION_ID_2 = 3; private static final SubscriptionInfo TEST_SUBINFO_2 = mock(SubscriptionInfo.class); + private static final Map<ParcelUuid, Set<String>> TEST_PRIVILEGED_PACKAGES = + Collections.singletonMap(TEST_PARCEL_UUID, Collections.singleton(PACKAGE_NAME)); private static final Map<Integer, ParcelUuid> TEST_SUBID_TO_GROUP_MAP; static { @@ -91,6 +102,7 @@ public class TelephonySubscriptionTrackerTest { @NonNull private final Handler mHandler; @NonNull private final TelephonySubscriptionTracker.Dependencies mDeps; + @NonNull private final TelephonyManager mTelephonyManager; @NonNull private final SubscriptionManager mSubscriptionManager; @NonNull private final CarrierConfigManager mCarrierConfigManager; @@ -103,9 +115,15 @@ public class TelephonySubscriptionTrackerTest { mHandler = new Handler(mTestLooper.getLooper()); mDeps = mock(TelephonySubscriptionTracker.Dependencies.class); + mTelephonyManager = mock(TelephonyManager.class); mSubscriptionManager = mock(SubscriptionManager.class); mCarrierConfigManager = mock(CarrierConfigManager.class); + doReturn(Context.TELEPHONY_SERVICE) + .when(mContext) + .getSystemServiceName(TelephonyManager.class); + doReturn(mTelephonyManager).when(mContext).getSystemService(Context.TELEPHONY_SERVICE); + doReturn(Context.TELEPHONY_SUBSCRIPTION_SERVICE) .when(mContext) .getSystemServiceName(SubscriptionManager.class); @@ -140,6 +158,9 @@ public class TelephonySubscriptionTrackerTest { doReturn(Arrays.asList(TEST_SUBINFO_1, TEST_SUBINFO_2)) .when(mSubscriptionManager) .getAllSubscriptionInfoList(); + + doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt()); + setPrivilegedPackagesForMock(Collections.singletonList(PACKAGE_NAME)); } private IntentFilter getIntentFilter() { @@ -167,13 +188,15 @@ public class TelephonySubscriptionTrackerTest { return intent; } - private TelephonySubscriptionSnapshot buildExpectedSnapshot(Set<ParcelUuid> activeSubGroups) { - return buildExpectedSnapshot(TEST_SUBID_TO_GROUP_MAP, activeSubGroups); + private TelephonySubscriptionSnapshot buildExpectedSnapshot( + Map<ParcelUuid, Set<String>> privilegedPackages) { + return buildExpectedSnapshot(TEST_SUBID_TO_GROUP_MAP, privilegedPackages); } private TelephonySubscriptionSnapshot buildExpectedSnapshot( - Map<Integer, ParcelUuid> subIdToGroupMap, Set<ParcelUuid> activeSubGroups) { - return new TelephonySubscriptionSnapshot(subIdToGroupMap, activeSubGroups); + Map<Integer, ParcelUuid> subIdToGroupMap, + Map<ParcelUuid, Set<String>> privilegedPackages) { + return new TelephonySubscriptionSnapshot(subIdToGroupMap, privilegedPackages); } private void verifyNoActiveSubscriptions() { @@ -186,6 +209,10 @@ public class TelephonySubscriptionTrackerTest { Collections.singletonMap(TEST_SIM_SLOT_INDEX, TEST_SUBSCRIPTION_ID_1)); } + private void setPrivilegedPackagesForMock(@NonNull List<String> privilegedPackages) { + doReturn(privilegedPackages).when(mTelephonyManager).getPackagesWithCarrierPrivileges(); + } + @Test public void testRegister() throws Exception { verify(mContext) @@ -223,15 +250,30 @@ public class TelephonySubscriptionTrackerTest { } @Test - public void testOnSubscriptionsChangedFired_WithReadySubIds() throws Exception { + public void testOnSubscriptionsChangedFired_WithReadySubidsNoPrivilegedPackages() + throws Exception { setupReadySubIds(); + setPrivilegedPackagesForMock(Collections.emptyList()); final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener(); listener.onSubscriptionsChanged(); mTestLooper.dispatchAll(); - final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID); - verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups))); + final Map<ParcelUuid, Set<String>> privilegedPackages = + Collections.singletonMap(TEST_PARCEL_UUID, new ArraySet<>()); + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(privilegedPackages))); + } + + @Test + public void testOnSubscriptionsChangedFired_WithReadySubidsAndPrivilegedPackages() + throws Exception { + setupReadySubIds(); + + final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener(); + listener.onSubscriptionsChanged(); + mTestLooper.dispatchAll(); + + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); } @Test @@ -239,8 +281,7 @@ public class TelephonySubscriptionTrackerTest { mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); mTestLooper.dispatchAll(); - final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID); - verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups))); + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); } @Test @@ -253,8 +294,7 @@ public class TelephonySubscriptionTrackerTest { mTestLooper.dispatchAll(); // Expect an empty snapshot - verify(mCallback).onNewSnapshot( - eq(buildExpectedSnapshot(Collections.emptyMap(), Collections.emptySet()))); + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap(), emptyMap()))); } @Test @@ -281,41 +321,57 @@ public class TelephonySubscriptionTrackerTest { @Test public void testSubscriptionsClearedAfterValidTriggersCallbacks() throws Exception { - final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID); - mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); mTestLooper.dispatchAll(); - verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups))); + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); assertNotNull( mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX)); doReturn(Collections.emptyList()).when(mSubscriptionManager).getAllSubscriptionInfoList(); mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); mTestLooper.dispatchAll(); - verify(mCallback).onNewSnapshot( - eq(buildExpectedSnapshot(Collections.emptyMap(), Collections.emptySet()))); + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap(), emptyMap()))); } @Test public void testSlotClearedAfterValidTriggersCallbacks() throws Exception { - final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID); - mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); mTestLooper.dispatchAll(); - verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups))); + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); assertNotNull( mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX)); mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(false)); mTestLooper.dispatchAll(); - verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(Collections.emptySet()))); + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap()))); assertNull(mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX)); } @Test + public void testChangingPrivilegedPackagesAfterValidTriggersCallbacks() throws Exception { + setupReadySubIds(); + + // Setup initial "valid" state + final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener(); + listener.onSubscriptionsChanged(); + mTestLooper.dispatchAll(); + + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); + + // Simulate a loss of carrier privileges + setPrivilegedPackagesForMock(Collections.emptyList()); + listener.onSubscriptionsChanged(); + mTestLooper.dispatchAll(); + + verify(mCallback) + .onNewSnapshot( + eq(buildExpectedSnapshot(singletonMap(TEST_PARCEL_UUID, emptySet())))); + } + + @Test public void testTelephonySubscriptionSnapshotGetGroupForSubId() throws Exception { final TelephonySubscriptionSnapshot snapshot = - new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, Collections.emptySet()); + new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, emptyMap()); assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_1)); assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_2)); @@ -324,7 +380,7 @@ public class TelephonySubscriptionTrackerTest { @Test public void testTelephonySubscriptionSnapshotGetAllSubIdsInGroup() throws Exception { final TelephonySubscriptionSnapshot snapshot = - new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, Collections.emptySet()); + new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, emptyMap()); assertEquals( new ArraySet<>(Arrays.asList(TEST_SUBSCRIPTION_ID_1, TEST_SUBSCRIPTION_ID_2)), diff --git a/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java b/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java new file mode 100644 index 000000000000..c2c6200fd5f9 --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vcn; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.annotation.NonNull; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.os.test.TestLooper; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; + +/** Tests for TelephonySubscriptionTracker */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class VcnNetworkProviderTest { + private static final int TEST_SCORE_UNSATISFIED = 0; + private static final int TEST_SCORE_HIGH = 100; + private static final int TEST_PROVIDER_ID = 1; + private static final int TEST_LEGACY_TYPE = ConnectivityManager.TYPE_MOBILE; + private static final NetworkRequest.Type TEST_REQUEST_TYPE = NetworkRequest.Type.REQUEST; + + @NonNull private final Context mContext; + @NonNull private final TestLooper mTestLooper; + + @NonNull private VcnNetworkProvider mVcnNetworkProvider; + @NonNull private NetworkRequestListener mListener; + + public VcnNetworkProviderTest() { + mContext = mock(Context.class); + mTestLooper = new TestLooper(); + } + + @Before + public void setUp() throws Exception { + mVcnNetworkProvider = new VcnNetworkProvider(mContext, mTestLooper.getLooper()); + mListener = mock(NetworkRequestListener.class); + } + + @Test + public void testRequestsPassedToRegisteredListeners() throws Exception { + mVcnNetworkProvider.registerListener(mListener); + + final NetworkRequest request = mock(NetworkRequest.class); + mVcnNetworkProvider.onNetworkRequested(request, TEST_SCORE_UNSATISFIED, TEST_PROVIDER_ID); + verify(mListener).onNetworkRequested(request, TEST_SCORE_UNSATISFIED, TEST_PROVIDER_ID); + } + + @Test + public void testRequestsPassedToRegisteredListeners_satisfiedByHighScoringProvider() + throws Exception { + mVcnNetworkProvider.registerListener(mListener); + + final NetworkRequest request = mock(NetworkRequest.class); + mVcnNetworkProvider.onNetworkRequested(request, TEST_SCORE_HIGH, TEST_PROVIDER_ID); + verify(mListener).onNetworkRequested(request, TEST_SCORE_HIGH, TEST_PROVIDER_ID); + } + + @Test + public void testUnregisterListener() throws Exception { + mVcnNetworkProvider.registerListener(mListener); + mVcnNetworkProvider.unregisterListener(mListener); + + final NetworkRequest request = mock(NetworkRequest.class); + mVcnNetworkProvider.onNetworkRequested(request, TEST_SCORE_UNSATISFIED, TEST_PROVIDER_ID); + verifyNoMoreInteractions(mListener); + } + + @Test + public void testCachedRequestsPassedOnRegister() throws Exception { + final List<NetworkRequest> requests = new ArrayList<>(); + + for (int i = 0; i < 10; i++) { + final NetworkRequest request = + new NetworkRequest( + new NetworkCapabilities(), + TEST_LEGACY_TYPE, + i /* requestId */, + TEST_REQUEST_TYPE); + + requests.add(request); + mVcnNetworkProvider.onNetworkRequested(request, i, i + 1); + } + + mVcnNetworkProvider.registerListener(mListener); + for (int i = 0; i < requests.size(); i++) { + final NetworkRequest request = requests.get(i); + verify(mListener).onNetworkRequested(request, i, i + 1); + } + verifyNoMoreInteractions(mListener); + } +} diff --git a/wifi/MOVED.txt b/wifi/MOVED.txt new file mode 100644 index 000000000000..6ffb23cb0aaf --- /dev/null +++ b/wifi/MOVED.txt @@ -0,0 +1,8 @@ +Source code and tests for Wifi module APIs have moved to +packages/modules/Wifi/framework. + +- frameworks/base/wifi/java -> packages/modules/Wifi/framework/java +- frameworks/base/wifi/tests -> packages/modules/Wifi/framework/tests + +What remains in frameworks/base/wifi are Wifi APIs that +are not part of the Wifi module. |