diff options
329 files changed, 15512 insertions, 3657 deletions
diff --git a/Android.bp b/Android.bp index 0407c41a16a7..d58b48399a8f 100644 --- a/Android.bp +++ b/Android.bp @@ -104,7 +104,6 @@ java_defaults { "core/java/android/app/timedetector/ITimeDetectorService.aidl", "core/java/android/app/timezone/ICallback.aidl", "core/java/android/app/timezone/IRulesManager.aidl", - "core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl", "core/java/android/app/usage/ICacheQuotaService.aidl", "core/java/android/app/usage/IStorageStatsManager.aidl", "core/java/android/app/usage/IUsageStatsManager.aidl", @@ -187,6 +186,7 @@ java_defaults { "core/java/android/hardware/radio/ITunerCallback.aidl", "core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl", "core/java/android/hardware/usb/IUsbManager.aidl", + "core/java/android/net/ICaptivePortal.aidl", "core/java/android/net/IConnectivityManager.aidl", "core/java/android/net/IIpConnectivityMetrics.aidl", "core/java/android/net/IEthernetManager.aidl", @@ -221,6 +221,7 @@ java_defaults { "core/java/android/os/ICancellationSignal.aidl", "core/java/android/os/IDeviceIdentifiersPolicyService.aidl", "core/java/android/os/IDeviceIdleController.aidl", + "core/java/android/os/IDynamicAndroidService.aidl", "core/java/android/os/IHardwarePropertiesManager.aidl", ":libincident_aidl", "core/java/android/os/IMaintenanceActivityListener.aidl", @@ -601,6 +602,7 @@ java_defaults { ":storaged_aidl", ":vold_aidl", + ":gsiservice_aidl", ":installd_aidl", ":dumpstate_aidl", @@ -657,6 +659,7 @@ java_defaults { "frameworks/native/aidl/gui", "system/core/storaged/binder", "system/vold/binder", + "system/gsid/aidl", "system/bt/binder", "system/security/keystore/binder", ], @@ -678,7 +681,6 @@ java_defaults { static_libs: [ "apex_aidl_interface-java", - "networkstack-aidl-interfaces-java", "framework-protos", "android.hidl.base-V1.0-java", "android.hardware.cas-V1.0-java", @@ -702,8 +704,8 @@ java_defaults { "android.hardware.vibrator-V1.1-java", "android.hardware.vibrator-V1.2-java", "android.hardware.wifi-V1.0-java-constants", - "networkstack-aidl-interfaces-java", - "netd_aidl_interface-java", + "networkstack-aidl-framework-java", + "netd_aidl_parcelables-java", ], required: [ @@ -814,10 +816,8 @@ aidl_interface { srcs: [ "core/java/android/net/ApfCapabilitiesParcelable.aidl", "core/java/android/net/DhcpResultsParcelable.aidl", - "core/java/android/net/ICaptivePortal.aidl", "core/java/android/net/INetworkMonitor.aidl", "core/java/android/net/INetworkMonitorCallbacks.aidl", - "core/java/android/net/IIpMemoryStore.aidl", "core/java/android/net/INetworkStackConnector.aidl", "core/java/android/net/INetworkStackStatusCallback.aidl", "core/java/android/net/InitialConfigurationParcelable.aidl", @@ -830,11 +830,22 @@ aidl_interface { "core/java/android/net/ProxyInfoParcelable.aidl", "core/java/android/net/RouteInfoParcelable.aidl", "core/java/android/net/StaticIpConfigurationParcelable.aidl", + "core/java/android/net/TcpKeepalivePacketDataParcelable.aidl", "core/java/android/net/dhcp/DhcpServingParamsParcel.aidl", "core/java/android/net/dhcp/IDhcpServer.aidl", "core/java/android/net/dhcp/IDhcpServerCallbacks.aidl", "core/java/android/net/ip/IIpClient.aidl", "core/java/android/net/ip/IIpClientCallbacks.aidl", + ], + api_dir: "aidl/networkstack", +} + +aidl_interface { + name: "networkstack-aidl-framework", + local_include_dir: "core/java", + srcs: [ + "core/java/android/net/TcpKeepalivePacketDataParcelable.aidl", + "core/java/android/net/IIpMemoryStore.aidl", "core/java/android/net/ipmemorystore/**/*.aidl", ], api_dir: "aidl/networkstack", @@ -1107,58 +1118,21 @@ packages_to_document = [ "org/apache/http/params", ] -// The since flag (-since N.xml API_LEVEL) is used to add API Level information -// to the reference documentation. Must be in order of oldest to newest. -// -// Conscrypt (com.android.org.conscrypt) is an implementation detail and should -// not be referenced in the documentation. -framework_docs_args = "-android -manifest $(location core/res/AndroidManifest.xml) " + - "-hidePackage com.android.internal " + - "-hidePackage com.android.internal.util " + - "-hidePackage com.android.okhttp " + - "-hidePackage com.android.org.conscrypt " + - "-hidePackage com.android.server " + - "-since $(location 1/public/api/android.xml) 1 " + - "-since $(location 2/public/api/android.xml) 2 " + - "-since $(location 3/public/api/android.xml) 3 " + - "-since $(location 4/public/api/android.xml) 4 " + - "-since $(location 5/public/api/android.xml) 5 " + - "-since $(location 6/public/api/android.xml) 6 " + - "-since $(location 7/public/api/android.xml) 7 " + - "-since $(location 8/public/api/android.xml) 8 " + - "-since $(location 9/public/api/android.xml) 9 " + - "-since $(location 10/public/api/android.xml) 10 " + - "-since $(location 11/public/api/android.xml) 11 " + - "-since $(location 12/public/api/android.xml) 12 " + - "-since $(location 13/public/api/android.xml) 13 " + - "-since $(location 14/public/api/android.txt) 14 " + - "-since $(location 15/public/api/android.txt) 15 " + - "-since $(location 16/public/api/android.txt) 16 " + - "-since $(location 17/public/api/android.txt) 17 " + - "-since $(location 18/public/api/android.txt) 18 " + - "-since $(location 19/public/api/android.txt) 19 " + - "-since $(location 20/public/api/android.txt) 20 " + - "-since $(location 21/public/api/android.txt) 21 " + - "-since $(location 22/public/api/android.txt) 22 " + - "-since $(location 23/public/api/android.txt) 23 " + - "-since $(location 24/public/api/android.txt) 24 " + - "-since $(location 25/public/api/android.txt) 25 " + - "-since $(location 26/public/api/android.txt) 26 " + - "-since $(location 27/public/api/android.txt) 27 " + - "-since $(location 28/public/api/android.txt) 28 " + - "-since $(location api/current.txt) Q " + - "-werror -lerror -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 " + - "-overview $(location core/java/overview.html) " + - // Federate Support Library references against local API file. - "-federate SupportLib https://developer.android.com " + - "-federationapi SupportLib $(location current/support-api.txt) " +// Make the api/current.txt file available for use by modules in other +// directories. +filegroup { + name: "frameworks-base-api-current.txt", + srcs: [ + "api/current.txt", + ], +} framework_docs_only_args = " -android -manifest $(location core/res/AndroidManifest.xml) " + "-werror -lerror -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 " + "-overview $(location core/java/overview.html) " + // Federate Support Library references against local API file. "-federate SupportLib https://developer.android.com " + - "-federationapi SupportLib $(location current/support-api.txt) " + "-federationapi SupportLib $(location :current-support-api) " framework_docs_only_libs = [ "voip-common", diff --git a/Android.mk b/Android.mk index e4053452a9c5..c58f7af1d7d5 100644 --- a/Android.mk +++ b/Android.mk @@ -72,42 +72,17 @@ $(OUT_DOCS)/offline-sdk-timestamp: $(OUT_DOCS)/offline-sdk-docs-docs.zip $(hide) mkdir -p $(OUT_DOCS)/offline-sdk ( unzip -qo $< -d $(OUT_DOCS)/offline-sdk && touch -f $@ ) || exit 1 +.PHONY: docs offline-sdk-docs +docs offline-sdk-docs: $(OUT_DOCS)/offline-sdk-timestamp + # Run this for checkbuild checkbuild: doc-comment-check-docs -# Check comment when you are updating the API -update-api: doc-comment-check-docs # ==== hiddenapi lists ======================================= -.KATI_RESTAT: $(INTERNAL_PLATFORM_HIDDENAPI_FLAGS) -$(INTERNAL_PLATFORM_HIDDENAPI_FLAGS): \ - PRIVATE_FLAGS_INPUTS := $(PRIVATE_FLAGS_INPUTS) $(SOONG_HIDDENAPI_FLAGS) -$(INTERNAL_PLATFORM_HIDDENAPI_FLAGS): \ - frameworks/base/tools/hiddenapi/generate_hiddenapi_lists.py \ - frameworks/base/config/hiddenapi-greylist.txt \ - frameworks/base/config/hiddenapi-greylist-max-p.txt \ - frameworks/base/config/hiddenapi-greylist-max-o.txt \ - frameworks/base/config/hiddenapi-force-blacklist.txt \ - $(INTERNAL_PLATFORM_HIDDENAPI_STUB_FLAGS) \ - $(INTERNAL_PLATFORM_REMOVED_DEX_API_FILE) \ - $(SOONG_HIDDENAPI_FLAGS) - frameworks/base/tools/hiddenapi/generate_hiddenapi_lists.py \ - --csv $(INTERNAL_PLATFORM_HIDDENAPI_STUB_FLAGS) $(PRIVATE_FLAGS_INPUTS) \ - --greylist frameworks/base/config/hiddenapi-greylist.txt \ - --greylist-ignore-conflicts $(INTERNAL_PLATFORM_REMOVED_DEX_API_FILE) \ - --greylist-max-p frameworks/base/config/hiddenapi-greylist-max-p.txt \ - --greylist-max-o-ignore-conflicts \ - frameworks/base/config/hiddenapi-greylist-max-o.txt \ - --blacklist frameworks/base/config/hiddenapi-force-blacklist.txt \ - --output $@.tmp - $(call commit-change-for-toc,$@) - -$(INTERNAL_PLATFORM_HIDDENAPI_GREYLIST_METADATA): \ - frameworks/base/tools/hiddenapi/merge_csv.py \ - $(PRIVATE_METADATA_INPUTS) - frameworks/base/tools/hiddenapi/merge_csv.py $(PRIVATE_METADATA_INPUTS) > $@ - +ifneq ($(UNSAFE_DISABLE_HIDDENAPI_FLAGS),true) $(call dist-for-goals,droidcore,$(INTERNAL_PLATFORM_HIDDENAPI_FLAGS)) $(call dist-for-goals,droidcore,$(INTERNAL_PLATFORM_HIDDENAPI_GREYLIST_METADATA)) +endif # UNSAFE_DISABLE_HIDDENAPI_FLAGS # Include subdirectory makefiles # ============================================================ diff --git a/api/TEST_MAPPING b/api/TEST_MAPPING new file mode 100644 index 000000000000..8a676e994081 --- /dev/null +++ b/api/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "CtsCurrentApiSignatureTestCases" + } + ] +} diff --git a/api/current.txt b/api/current.txt index cb09edc7caf9..172140fd653f 100755 --- a/api/current.txt +++ b/api/current.txt @@ -18656,9 +18656,9 @@ package android.icu.text { method public static android.icu.text.BreakIterator getSentenceInstance(java.util.Locale); method public static android.icu.text.BreakIterator getSentenceInstance(android.icu.util.ULocale); method public abstract java.text.CharacterIterator getText(); - method public static android.icu.text.BreakIterator getTitleInstance(); - method public static android.icu.text.BreakIterator getTitleInstance(java.util.Locale); - method public static android.icu.text.BreakIterator getTitleInstance(android.icu.util.ULocale); + method @Deprecated public static android.icu.text.BreakIterator getTitleInstance(); + method @Deprecated public static android.icu.text.BreakIterator getTitleInstance(java.util.Locale); + method @Deprecated public static android.icu.text.BreakIterator getTitleInstance(android.icu.util.ULocale); method public static android.icu.text.BreakIterator getWordInstance(); method public static android.icu.text.BreakIterator getWordInstance(java.util.Locale); method public static android.icu.text.BreakIterator getWordInstance(android.icu.util.ULocale); @@ -18675,7 +18675,7 @@ package android.icu.text { field public static final int KIND_CHARACTER = 0; // 0x0 field public static final int KIND_LINE = 2; // 0x2 field public static final int KIND_SENTENCE = 3; // 0x3 - field public static final int KIND_TITLE = 4; // 0x4 + field @Deprecated public static final int KIND_TITLE = 4; // 0x4 field public static final int KIND_WORD = 1; // 0x1 field public static final int WORD_IDEO = 400; // 0x190 field public static final int WORD_IDEO_LIMIT = 500; // 0x1f4 @@ -27167,7 +27167,7 @@ package android.net { field public static final String EXTRA_NETWORK = "android.net.extra.NETWORK"; field @Deprecated public static final String EXTRA_NETWORK_INFO = "networkInfo"; field public static final String EXTRA_NETWORK_REQUEST = "android.net.extra.NETWORK_REQUEST"; - field public static final String EXTRA_NETWORK_TYPE = "networkType"; + field @Deprecated public static final String EXTRA_NETWORK_TYPE = "networkType"; field public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity"; field @Deprecated public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork"; field public static final String EXTRA_REASON = "reason"; @@ -27854,6 +27854,7 @@ package android.net { method public android.net.VpnService.Builder setBlocking(boolean); method public android.net.VpnService.Builder setConfigureIntent(android.app.PendingIntent); method public android.net.VpnService.Builder setHttpProxy(android.net.ProxyInfo); + method public android.net.VpnService.Builder setMetered(boolean); method public android.net.VpnService.Builder setMtu(int); method public android.net.VpnService.Builder setSession(String); method public android.net.VpnService.Builder setUnderlyingNetworks(android.net.Network[]); @@ -40409,6 +40410,7 @@ package android.system { method public static java.io.FileDescriptor accept(java.io.FileDescriptor, java.net.InetSocketAddress) throws android.system.ErrnoException, java.net.SocketException; method public static boolean access(String, int) throws android.system.ErrnoException; method public static void bind(java.io.FileDescriptor, java.net.InetAddress, int) throws android.system.ErrnoException, java.net.SocketException; + method public static void bind(java.io.FileDescriptor, java.net.SocketAddress) throws android.system.ErrnoException, java.net.SocketException; method public static void chmod(String, int) throws android.system.ErrnoException; method public static void chown(String, int, int) throws android.system.ErrnoException; method public static void close(java.io.FileDescriptor) throws android.system.ErrnoException; @@ -40477,6 +40479,7 @@ package android.system { method public static long sendfile(java.io.FileDescriptor, java.io.FileDescriptor, android.system.Int64Ref, long) throws android.system.ErrnoException; method public static int sendto(java.io.FileDescriptor, java.nio.ByteBuffer, int, java.net.InetAddress, int) throws android.system.ErrnoException, java.net.SocketException; method public static int sendto(java.io.FileDescriptor, byte[], int, int, int, java.net.InetAddress, int) throws android.system.ErrnoException, java.net.SocketException; + method public static int sendto(java.io.FileDescriptor, byte[], int, int, int, java.net.SocketAddress) throws android.system.ErrnoException, java.net.SocketException; method @Deprecated public static void setegid(int) throws android.system.ErrnoException; method public static void setenv(String, String, boolean) throws android.system.ErrnoException; method @Deprecated public static void seteuid(int) throws android.system.ErrnoException; @@ -41156,6 +41159,7 @@ package android.telecom { method public static String capabilitiesToString(int); method public android.telecom.PhoneAccountHandle getAccountHandle(); method public int getCallCapabilities(); + method public int getCallDirection(); method @Nullable public android.telecom.CallIdentification getCallIdentification(); method public int getCallProperties(); method public String getCallerDisplayName(); @@ -41192,6 +41196,9 @@ package android.telecom { field public static final int CAPABILITY_SUPPORT_DEFLECT = 16777216; // 0x1000000 field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2 field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8 + field public static final int DIRECTION_INCOMING = 0; // 0x0 + field public static final int DIRECTION_OUTGOING = 1; // 0x1 + field public static final int DIRECTION_UNKNOWN = -1; // 0xffffffff field public static final int PROPERTY_CONFERENCE = 1; // 0x1 field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 4; // 0x4 field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20 @@ -41280,6 +41287,15 @@ package android.telecom { method public abstract void onScreenCall(@NonNull android.telecom.Call.Details); method public final void provideCallIdentification(@NonNull android.telecom.Call.Details, @NonNull android.telecom.CallIdentification); method public final void respondToCall(@NonNull android.telecom.Call.Details, @NonNull android.telecom.CallScreeningService.CallResponse); + field public static final String ACTION_NUISANCE_CALL_STATUS_CHANGED = "android.telecom.action.NUISANCE_CALL_STATUS_CHANGED"; + field public static final int CALL_DURATION_LONG = 4; // 0x4 + field public static final int CALL_DURATION_MEDIUM = 3; // 0x3 + field public static final int CALL_DURATION_SHORT = 2; // 0x2 + field public static final int CALL_DURATION_VERY_SHORT = 1; // 0x1 + field public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION"; + field public static final String EXTRA_CALL_HANDLE = "android.telecom.extra.CALL_HANDLE"; + field public static final String EXTRA_CALL_TYPE = "android.telecom.extra.CALL_TYPE"; + field public static final String EXTRA_IS_NUISANCE = "android.telecom.extra.IS_NUISANCE"; field public static final String SERVICE_INTERFACE = "android.telecom.CallScreeningService"; } @@ -41858,12 +41874,12 @@ package android.telecom { public class TelecomManager { method public void acceptHandover(android.net.Uri, int, android.telecom.PhoneAccountHandle); - method @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall(); - method @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall(int); + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall(); + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall(int); method public void addNewIncomingCall(android.telecom.PhoneAccountHandle, android.os.Bundle); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void cancelMissedCallsNotification(); method public android.content.Intent createManageBlockedNumbersIntent(); - method @RequiresPermission(android.Manifest.permission.ANSWER_PHONE_CALLS) public boolean endCall(); + method @Deprecated @RequiresPermission(android.Manifest.permission.ANSWER_PHONE_CALLS) public boolean endCall(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.net.Uri getAdnUriForPhoneAccount(android.telecom.PhoneAccountHandle); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts(); method public String getDefaultDialerPackage(); @@ -41885,6 +41901,7 @@ package android.telecom { method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, String); method @RequiresPermission(anyOf={android.Manifest.permission.CALL_PHONE, android.Manifest.permission.MANAGE_OWN_CALLS}) public void placeCall(android.net.Uri, android.os.Bundle); method public void registerPhoneAccount(android.telecom.PhoneAccount); + method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void reportNuisanceCallStatus(@NonNull android.net.Uri, boolean); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void showInCallScreen(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void silenceRinger(); method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle); @@ -42078,7 +42095,7 @@ package android.telephony { } public final class AvailableNetworkInfo implements android.os.Parcelable { - ctor public AvailableNetworkInfo(int, int, java.util.ArrayList<java.lang.String>); + ctor public AvailableNetworkInfo(int, int, java.util.List<java.lang.String>); method public int describeContents(); method public java.util.List<java.lang.String> getMccMncs(); method public int getPriority(); @@ -42319,6 +42336,7 @@ package android.telephony { method public int getChannelNumber(); method public String getMccString(); method public String getMncString(); + method public long getNci(); method public int getPci(); method public int getTac(); method public void writeToParcel(android.os.Parcel, int); @@ -42332,6 +42350,7 @@ package android.telephony { method public String getMccString(); method public String getMncString(); method @Nullable public String getMobileNetworkOperator(); + method public int getUarfcn(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityTdscdma> CREATOR; } @@ -42999,12 +43018,12 @@ package android.telephony { method public boolean canChangeDtmfToneLength(); method @Nullable public android.telephony.TelephonyManager createForPhoneAccountHandle(android.telecom.PhoneAccountHandle); method public android.telephony.TelephonyManager createForSubscriptionId(int); - method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public java.util.List<android.telephony.CellInfo> getAllCellInfo(); + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public java.util.List<android.telephony.CellInfo> getAllCellInfo(); method public int getCallState(); method public int getCardIdForDefaultEuicc(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @WorkerThread public android.os.PersistableBundle getCarrierConfig(); method public int getCarrierIdFromSimMccMnc(); - method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.CellLocation getCellLocation(); + method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public android.telephony.CellLocation getCellLocation(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @Nullable public java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>> getCurrentEmergencyNumberList(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @Nullable public java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>> getCurrentEmergencyNumberList(int); method public int getDataActivity(); @@ -43033,8 +43052,8 @@ package android.telephony { method public int getNetworkType(); method public int getPhoneCount(); method public int getPhoneType(); - method public int getPreferredOpportunisticDataSubscription(); - method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telephony.ServiceState getServiceState(); + method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription(); + method @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState(); method @Nullable public android.telephony.SignalStrength getSignalStrength(); method public int getSimCarrierId(); method @Nullable public CharSequence getSimCarrierIdName(); @@ -43069,14 +43088,15 @@ package android.telephony { method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataRoamingEnabled(); method public boolean isHearingAidCompatibilitySupported(); method public boolean isNetworkRoaming(); + method public boolean isRttSupported(); method public boolean isSmsCapable(); method @Deprecated public boolean isTtyModeSupported(); method public boolean isVoiceCapable(); method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle); method public boolean isWorldPhone(); method public void listen(android.telephony.PhoneStateListener, int); - method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback); + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); + method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback); method public void sendDialerSpecialCode(String); method public String sendEnvelopeWithStatus(String); method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler); @@ -43092,6 +43112,7 @@ package android.telephony { method public boolean setVoiceMailNumber(String, String); method @Deprecated public void setVoicemailRingtoneUri(android.telecom.PhoneAccountHandle, android.net.Uri); method @Deprecated public void setVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle, boolean); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void switchMultiSimConfig(int); method public boolean updateAvailableNetworks(java.util.List<android.telephony.AvailableNetworkInfo>); field public static final String ACTION_CONFIGURE_VOICEMAIL = "android.telephony.action.CONFIGURE_VOICEMAIL"; field public static final String ACTION_NETWORK_COUNTRY_CHANGED = "android.telephony.action.NETWORK_COUNTRY_CHANGED"; @@ -43143,7 +43164,6 @@ package android.telephony { field public static final String EXTRA_STATE_RINGING; field public static final String EXTRA_SUBSCRIPTION_ID = "android.telephony.extra.SUBSCRIPTION_ID"; field public static final String EXTRA_VOICEMAIL_NUMBER = "android.telephony.extra.VOICEMAIL_NUMBER"; - field public static final int INVALID_CARD_ID = -1; // 0xffffffff field public static final String METADATA_HIDE_VOICEMAIL_SETTINGS_MENU = "android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU"; field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7 field public static final int NETWORK_TYPE_CDMA = 4; // 0x4 @@ -43179,7 +43199,9 @@ package android.telephony { field public static final int SIM_STATE_PUK_REQUIRED = 3; // 0x3 field public static final int SIM_STATE_READY = 5; // 0x5 field public static final int SIM_STATE_UNKNOWN = 0; // 0x0 + field public static final int UNINITIALIZED_CARD_ID = -2; // 0xfffffffe field public static final int UNKNOWN_CARRIER_ID = -1; // 0xffffffff + field public static final int UNSUPPORTED_CARD_ID = -1; // 0xffffffff field public static final int USSD_ERROR_SERVICE_UNAVAIL = -2; // 0xfffffffe field public static final int USSD_RETURN_FAILURE = -1; // 0xffffffff field public static final String VVM_TYPE_CVVM = "vvm_type_cvvm"; @@ -43533,6 +43555,483 @@ package android.telephony.gsm { } +package android.telephony.ims { + + public class Rcs1To1Thread extends android.telephony.ims.RcsThread { + method @WorkerThread public long getFallbackThreadId() throws android.telephony.ims.RcsMessageStoreException; + method @NonNull @WorkerThread public android.telephony.ims.RcsParticipant getRecipient() throws android.telephony.ims.RcsMessageStoreException; + method public boolean isGroup(); + method @WorkerThread public void setFallbackThreadId(long) throws android.telephony.ims.RcsMessageStoreException; + } + + public abstract class RcsEvent { + ctor protected RcsEvent(long); + method public long getTimestamp(); + } + + public final class RcsEventQueryParams implements android.os.Parcelable { + method public int describeContents(); + method @android.telephony.ims.RcsEventQueryParams.EventType public int getEventType(); + method public int getLimit(); + method public boolean getSortDirection(); + method public int getSortingProperty(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int ALL_EVENTS = -1; // 0xffffffff + field public static final int ALL_GROUP_THREAD_EVENTS = 0; // 0x0 + field public static final android.os.Parcelable.Creator<android.telephony.ims.RcsEventQueryParams> CREATOR; + field public static final int GROUP_THREAD_ICON_CHANGED_EVENT = 8; // 0x8 + field public static final int GROUP_THREAD_NAME_CHANGED_EVENT = 16; // 0x10 + field public static final int GROUP_THREAD_PARTICIPANT_JOINED_EVENT = 2; // 0x2 + field public static final int GROUP_THREAD_PARTICIPANT_LEFT_EVENT = 4; // 0x4 + field public static final int PARTICIPANT_ALIAS_CHANGED_EVENT = 1; // 0x1 + field public static final int SORT_BY_CREATION_ORDER = 0; // 0x0 + field public static final int SORT_BY_TIMESTAMP = 1; // 0x1 + } + + public static class RcsEventQueryParams.Builder { + ctor public RcsEventQueryParams.Builder(); + method public android.telephony.ims.RcsEventQueryParams build(); + method @CheckResult public android.telephony.ims.RcsEventQueryParams.Builder setEventType(@android.telephony.ims.RcsEventQueryParams.EventType int); + method @CheckResult public android.telephony.ims.RcsEventQueryParams.Builder setGroupThread(@NonNull android.telephony.ims.RcsGroupThread); + method @CheckResult public android.telephony.ims.RcsEventQueryParams.Builder setResultLimit(@IntRange(from=0) int) throws java.security.InvalidParameterException; + method @CheckResult public android.telephony.ims.RcsEventQueryParams.Builder setSortDirection(boolean); + method @CheckResult public android.telephony.ims.RcsEventQueryParams.Builder setSortProperty(@android.telephony.ims.RcsEventQueryParams.SortingProperty int); + } + + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({android.telephony.ims.RcsEventQueryParams.ALL_EVENTS, android.telephony.ims.RcsEventQueryParams.ALL_GROUP_THREAD_EVENTS, android.telephony.ims.RcsEventQueryParams.PARTICIPANT_ALIAS_CHANGED_EVENT, android.telephony.ims.RcsEventQueryParams.GROUP_THREAD_PARTICIPANT_JOINED_EVENT, android.telephony.ims.RcsEventQueryParams.GROUP_THREAD_PARTICIPANT_LEFT_EVENT, android.telephony.ims.RcsEventQueryParams.GROUP_THREAD_NAME_CHANGED_EVENT, android.telephony.ims.RcsEventQueryParams.GROUP_THREAD_ICON_CHANGED_EVENT}) public static @interface RcsEventQueryParams.EventType { + } + + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({android.telephony.ims.RcsEventQueryParams.SORT_BY_CREATION_ORDER, android.telephony.ims.RcsEventQueryParams.SORT_BY_TIMESTAMP}) public static @interface RcsEventQueryParams.SortingProperty { + } + + public final class RcsEventQueryResult implements android.os.Parcelable { + method public int describeContents(); + method public android.telephony.ims.RcsQueryContinuationToken getContinuationToken(); + method public java.util.List<android.telephony.ims.RcsEvent> getEvents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.RcsEventQueryResult> CREATOR; + } + + public final class RcsFileTransferCreationParams implements android.os.Parcelable { + method public int describeContents(); + method public String getContentMimeType(); + method public android.net.Uri getContentUri(); + method public long getFileSize(); + method @android.telephony.ims.RcsFileTransferPart.RcsFileTransferStatus public int getFileTransferStatus(); + method public int getHeight(); + method public long getMediaDuration(); + method public String getPreviewMimeType(); + method public android.net.Uri getPreviewUri(); + method public String getRcsFileTransferSessionId(); + method public long getTransferOffset(); + method public int getWidth(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.RcsFileTransferCreationParams> CREATOR; + } + + public class RcsFileTransferCreationParams.Builder { + ctor public RcsFileTransferCreationParams.Builder(); + method public android.telephony.ims.RcsFileTransferCreationParams build(); + method @CheckResult public android.telephony.ims.RcsFileTransferCreationParams.Builder setContentMimeType(String); + method @CheckResult public android.telephony.ims.RcsFileTransferCreationParams.Builder setContentUri(android.net.Uri); + method @CheckResult public android.telephony.ims.RcsFileTransferCreationParams.Builder setFileSize(long); + method @CheckResult public android.telephony.ims.RcsFileTransferCreationParams.Builder setFileTransferSessionId(String); + method @CheckResult public android.telephony.ims.RcsFileTransferCreationParams.Builder setFileTransferStatus(@android.telephony.ims.RcsFileTransferPart.RcsFileTransferStatus int); + method @CheckResult public android.telephony.ims.RcsFileTransferCreationParams.Builder setHeight(int); + method @CheckResult public android.telephony.ims.RcsFileTransferCreationParams.Builder setMediaDuration(long); + method @CheckResult public android.telephony.ims.RcsFileTransferCreationParams.Builder setPreviewMimeType(String); + method @CheckResult public android.telephony.ims.RcsFileTransferCreationParams.Builder setPreviewUri(android.net.Uri); + method @CheckResult public android.telephony.ims.RcsFileTransferCreationParams.Builder setTransferOffset(long); + method @CheckResult public android.telephony.ims.RcsFileTransferCreationParams.Builder setWidth(int); + } + + public class RcsFileTransferPart { + method @WorkerThread @Nullable public String getContentMimeType() throws android.telephony.ims.RcsMessageStoreException; + method @Nullable @WorkerThread public android.net.Uri getContentUri() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public long getFileSize() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public String getFileTransferSessionId() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread @android.telephony.ims.RcsFileTransferPart.RcsFileTransferStatus public int getFileTransferStatus() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public int getHeight() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public long getLength() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public String getPreviewMimeType() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public android.net.Uri getPreviewUri() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public long getTransferOffset() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public int getWidth() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setContentMimeType(String) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setContentUri(android.net.Uri) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setFileSize(long) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setFileTransferSessionId(String) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setFileTransferStatus(@android.telephony.ims.RcsFileTransferPart.RcsFileTransferStatus int) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setHeight(int) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setLength(long) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setPreviewMimeType(String) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setPreviewUri(android.net.Uri) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setTransferOffset(long) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setWidth(int) throws android.telephony.ims.RcsMessageStoreException; + field public static final int DOWNLOADING = 6; // 0x6 + field public static final int DOWNLOADING_CANCELLED = 9; // 0x9 + field public static final int DOWNLOADING_FAILED = 8; // 0x8 + field public static final int DOWNLOADING_PAUSED = 7; // 0x7 + field public static final int DRAFT = 1; // 0x1 + field public static final int NOT_SET = 0; // 0x0 + field public static final int SENDING = 2; // 0x2 + field public static final int SENDING_CANCELLED = 5; // 0x5 + field public static final int SENDING_FAILED = 4; // 0x4 + field public static final int SENDING_PAUSED = 3; // 0x3 + field public static final int SUCCEEDED = 10; // 0xa + } + + @IntDef({android.telephony.ims.RcsFileTransferPart.DRAFT, android.telephony.ims.RcsFileTransferPart.SENDING, android.telephony.ims.RcsFileTransferPart.SENDING_PAUSED, android.telephony.ims.RcsFileTransferPart.SENDING_FAILED, android.telephony.ims.RcsFileTransferPart.SENDING_CANCELLED, android.telephony.ims.RcsFileTransferPart.DOWNLOADING, android.telephony.ims.RcsFileTransferPart.DOWNLOADING_PAUSED, android.telephony.ims.RcsFileTransferPart.DOWNLOADING_FAILED, android.telephony.ims.RcsFileTransferPart.DOWNLOADING_CANCELLED, android.telephony.ims.RcsFileTransferPart.SUCCEEDED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RcsFileTransferPart.RcsFileTransferStatus { + } + + public class RcsGroupThread extends android.telephony.ims.RcsThread { + method @WorkerThread public void addParticipant(@NonNull android.telephony.ims.RcsParticipant) throws android.telephony.ims.RcsMessageStoreException; + method @Nullable @WorkerThread public android.net.Uri getConferenceUri() throws android.telephony.ims.RcsMessageStoreException; + method @Nullable public android.net.Uri getGroupIcon() throws android.telephony.ims.RcsMessageStoreException; + method @Nullable @WorkerThread public String getGroupName() throws android.telephony.ims.RcsMessageStoreException; + method @Nullable @WorkerThread public android.telephony.ims.RcsParticipant getOwner() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread @NonNull public java.util.Set<android.telephony.ims.RcsParticipant> getParticipants() throws android.telephony.ims.RcsMessageStoreException; + method public boolean isGroup(); + method @WorkerThread public void removeParticipant(@NonNull android.telephony.ims.RcsParticipant) throws android.telephony.ims.RcsMessageStoreException; + method @Nullable @WorkerThread public void setConferenceUri(android.net.Uri) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setGroupIcon(@Nullable android.net.Uri) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setGroupName(String) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setOwner(@Nullable android.telephony.ims.RcsParticipant) throws android.telephony.ims.RcsMessageStoreException; + } + + public abstract class RcsGroupThreadEvent extends android.telephony.ims.RcsEvent { + method @NonNull public android.telephony.ims.RcsParticipant getOriginatingParticipant(); + method @NonNull public android.telephony.ims.RcsGroupThread getRcsGroupThread(); + } + + public final class RcsGroupThreadIconChangedEvent extends android.telephony.ims.RcsGroupThreadEvent implements android.os.Parcelable { + ctor public RcsGroupThreadIconChangedEvent(long, @NonNull android.telephony.ims.RcsGroupThread, @NonNull android.telephony.ims.RcsParticipant, @Nullable android.net.Uri); + method public int describeContents(); + method @Nullable public android.net.Uri getNewIcon(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.RcsGroupThreadIconChangedEvent> CREATOR; + } + + public final class RcsGroupThreadNameChangedEvent extends android.telephony.ims.RcsGroupThreadEvent implements android.os.Parcelable { + ctor public RcsGroupThreadNameChangedEvent(long, @NonNull android.telephony.ims.RcsGroupThread, @NonNull android.telephony.ims.RcsParticipant, @Nullable String); + method public int describeContents(); + method @Nullable public String getNewName(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.RcsGroupThreadNameChangedEvent> CREATOR; + } + + public final class RcsGroupThreadParticipantJoinedEvent extends android.telephony.ims.RcsGroupThreadEvent implements android.os.Parcelable { + ctor public RcsGroupThreadParticipantJoinedEvent(long, @NonNull android.telephony.ims.RcsGroupThread, @NonNull android.telephony.ims.RcsParticipant, @NonNull android.telephony.ims.RcsParticipant); + method public int describeContents(); + method public android.telephony.ims.RcsParticipant getJoinedParticipant(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.RcsGroupThreadParticipantJoinedEvent> CREATOR; + } + + public final class RcsGroupThreadParticipantLeftEvent extends android.telephony.ims.RcsGroupThreadEvent implements android.os.Parcelable { + ctor public RcsGroupThreadParticipantLeftEvent(long, @NonNull android.telephony.ims.RcsGroupThread, @NonNull android.telephony.ims.RcsParticipant, @NonNull android.telephony.ims.RcsParticipant); + method public int describeContents(); + method @NonNull public android.telephony.ims.RcsParticipant getLeavingParticipantId(); + method public void persist() throws android.telephony.ims.RcsMessageStoreException; + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.RcsGroupThreadParticipantLeftEvent> CREATOR; + } + + public class RcsIncomingMessage extends android.telephony.ims.RcsMessage { + method @WorkerThread public long getArrivalTimestamp() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public long getSeenTimestamp() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public android.telephony.ims.RcsParticipant getSenderParticipant() throws android.telephony.ims.RcsMessageStoreException; + method public boolean isIncoming(); + method @WorkerThread public void setArrivalTimestamp(long) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setSeenTimestamp(long) throws android.telephony.ims.RcsMessageStoreException; + } + + public final class RcsIncomingMessageCreationParams extends android.telephony.ims.RcsMessageCreationParams implements android.os.Parcelable { + method public int describeContents(); + method public long getArrivalTimestamp(); + method public long getSeenTimestamp(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.RcsIncomingMessageCreationParams> CREATOR; + } + + public static class RcsIncomingMessageCreationParams.Builder extends android.telephony.ims.RcsMessageCreationParams.Builder { + ctor public RcsIncomingMessageCreationParams.Builder(long, long, int); + method public android.telephony.ims.RcsIncomingMessageCreationParams build(); + method @CheckResult public android.telephony.ims.RcsIncomingMessageCreationParams.Builder setArrivalTimestamp(long); + method @CheckResult public android.telephony.ims.RcsIncomingMessageCreationParams.Builder setSeenTimestamp(long); + method @CheckResult public android.telephony.ims.RcsIncomingMessageCreationParams.Builder setSenderParticipant(android.telephony.ims.RcsParticipant); + } + + public class RcsManager { + method public android.telephony.ims.RcsMessageStore getRcsMessageStore(); + } + + public abstract class RcsMessage { + method @NonNull @WorkerThread public java.util.Set<android.telephony.ims.RcsFileTransferPart> getFileTransferParts() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public double getLatitude() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public double getLongitude() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public long getOriginationTimestamp() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public String getRcsMessageId() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread @android.telephony.ims.RcsMessage.RcsMessageStatus public int getStatus() throws android.telephony.ims.RcsMessageStoreException; + method public int getSubscriptionId() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public String getText() throws android.telephony.ims.RcsMessageStoreException; + method @NonNull @WorkerThread public android.telephony.ims.RcsFileTransferPart insertFileTransfer(android.telephony.ims.RcsFileTransferCreationParams) throws android.telephony.ims.RcsMessageStoreException; + method public abstract boolean isIncoming(); + method @WorkerThread public void removeFileTransferPart(@NonNull android.telephony.ims.RcsFileTransferPart) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setLatitude(double) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setLongitude(double) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setOriginationTimestamp(long) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setRcsMessageId(String) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setStatus(@android.telephony.ims.RcsMessage.RcsMessageStatus int) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setSubscriptionId(int) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setText(String) throws android.telephony.ims.RcsMessageStoreException; + field public static final int DRAFT = 1; // 0x1 + field public static final int FAILED = 6; // 0x6 + field public static final double LOCATION_NOT_SET = 4.9E-324; + field public static final int NOT_SET = 0; // 0x0 + field public static final int QUEUED = 2; // 0x2 + field public static final int RECEIVED = 7; // 0x7 + field public static final int RETRYING = 5; // 0x5 + field public static final int SEEN = 9; // 0x9 + field public static final int SENDING = 3; // 0x3 + field public static final int SENT = 4; // 0x4 + } + + @IntDef({android.telephony.ims.RcsMessage.DRAFT, android.telephony.ims.RcsMessage.QUEUED, android.telephony.ims.RcsMessage.SENDING, android.telephony.ims.RcsMessage.SENT, android.telephony.ims.RcsMessage.RETRYING, android.telephony.ims.RcsMessage.FAILED, android.telephony.ims.RcsMessage.RECEIVED, android.telephony.ims.RcsMessage.SEEN}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RcsMessage.RcsMessageStatus { + } + + public class RcsMessageCreationParams { + ctor protected RcsMessageCreationParams(android.telephony.ims.RcsMessageCreationParams.Builder); + method public double getLatitude(); + method public double getLongitude(); + method public int getMessageStatus(); + method public long getOriginationTimestamp(); + method @Nullable public String getRcsMessageGlobalId(); + method public int getSubId(); + method @Nullable public String getText(); + } + + public static class RcsMessageCreationParams.Builder { + method public android.telephony.ims.RcsMessageCreationParams build(); + method @CheckResult public android.telephony.ims.RcsMessageCreationParams.Builder setLatitude(double); + method @CheckResult public android.telephony.ims.RcsMessageCreationParams.Builder setLongitude(double); + method @CheckResult public android.telephony.ims.RcsMessageCreationParams.Builder setRcsMessageId(String); + method @CheckResult public android.telephony.ims.RcsMessageCreationParams.Builder setStatus(@android.telephony.ims.RcsMessage.RcsMessageStatus int); + method @CheckResult public android.telephony.ims.RcsMessageCreationParams.Builder setText(String); + } + + public final class RcsMessageQueryParams implements android.os.Parcelable { + method public int describeContents(); + method public int getFileTransferPresence(); + method public int getLimit(); + method public String getMessageLike(); + method public int getMessageType(); + method public boolean getSortDirection(); + method @android.telephony.ims.RcsMessageQueryParams.SortingProperty public int getSortingProperty(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.RcsMessageQueryParams> CREATOR; + field public static final int MESSAGES_WITHOUT_FILE_TRANSFERS = 8; // 0x8 + field public static final int MESSAGES_WITH_FILE_TRANSFERS = 4; // 0x4 + field public static final int MESSAGE_TYPE_INCOMING = 1; // 0x1 + field public static final int MESSAGE_TYPE_OUTGOING = 2; // 0x2 + field public static final int SORT_BY_CREATION_ORDER = 0; // 0x0 + field public static final int SORT_BY_TIMESTAMP = 1; // 0x1 + } + + public static class RcsMessageQueryParams.Builder { + ctor public RcsMessageQueryParams.Builder(); + method public android.telephony.ims.RcsMessageQueryParams build(); + method @CheckResult public android.telephony.ims.RcsMessageQueryParams.Builder setFileTransferPresence(int); + method @CheckResult public android.telephony.ims.RcsMessageQueryParams.Builder setMessageLike(String); + method @CheckResult public android.telephony.ims.RcsMessageQueryParams.Builder setMessageType(int); + method @CheckResult public android.telephony.ims.RcsMessageQueryParams.Builder setResultLimit(@IntRange(from=0) int) throws java.security.InvalidParameterException; + method @CheckResult public android.telephony.ims.RcsMessageQueryParams.Builder setSortDirection(boolean); + method @CheckResult public android.telephony.ims.RcsMessageQueryParams.Builder setSortProperty(@android.telephony.ims.RcsMessageQueryParams.SortingProperty int); + method @CheckResult public android.telephony.ims.RcsMessageQueryParams.Builder setThread(@Nullable android.telephony.ims.RcsThread); + } + + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({android.telephony.ims.RcsMessageQueryParams.SORT_BY_CREATION_ORDER, android.telephony.ims.RcsMessageQueryParams.SORT_BY_TIMESTAMP}) public static @interface RcsMessageQueryParams.SortingProperty { + } + + public final class RcsMessageQueryResult implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.telephony.ims.RcsQueryContinuationToken getContinuationToken(); + method @NonNull public java.util.List<android.telephony.ims.RcsMessage> getMessages(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.RcsMessageQueryResult> CREATOR; + } + + public final class RcsMessageSnippet implements android.os.Parcelable { + method public int describeContents(); + method @android.telephony.ims.RcsMessage.RcsMessageStatus public int getSnippetStatus(); + method @Nullable public String getSnippetText(); + method public long getSnippetTimestamp(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.RcsMessageSnippet> CREATOR; + } + + public class RcsMessageStore { + ctor public RcsMessageStore(); + method @WorkerThread @NonNull public android.telephony.ims.RcsGroupThread createGroupThread(@Nullable java.util.List<android.telephony.ims.RcsParticipant>, @Nullable String, @Nullable android.net.Uri) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread @NonNull public android.telephony.ims.Rcs1To1Thread createRcs1To1Thread(@NonNull android.telephony.ims.RcsParticipant) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread @NonNull public android.telephony.ims.RcsParticipant createRcsParticipant(String, @Nullable String) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void deleteThread(@NonNull android.telephony.ims.RcsThread) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread @NonNull public android.telephony.ims.RcsEventQueryResult getRcsEvents(@Nullable android.telephony.ims.RcsEventQueryParams) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread @NonNull public android.telephony.ims.RcsEventQueryResult getRcsEvents(@NonNull android.telephony.ims.RcsQueryContinuationToken) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread @NonNull public android.telephony.ims.RcsMessageQueryResult getRcsMessages(@Nullable android.telephony.ims.RcsMessageQueryParams) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread @NonNull public android.telephony.ims.RcsMessageQueryResult getRcsMessages(@NonNull android.telephony.ims.RcsQueryContinuationToken) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread @NonNull public android.telephony.ims.RcsParticipantQueryResult getRcsParticipants(@Nullable android.telephony.ims.RcsParticipantQueryParams) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread @NonNull public android.telephony.ims.RcsParticipantQueryResult getRcsParticipants(@NonNull android.telephony.ims.RcsQueryContinuationToken) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread @NonNull public android.telephony.ims.RcsThreadQueryResult getRcsThreads(@Nullable android.telephony.ims.RcsThreadQueryParams) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread @NonNull public android.telephony.ims.RcsThreadQueryResult getRcsThreads(@NonNull android.telephony.ims.RcsQueryContinuationToken) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread @NonNull public void persistRcsEvent(android.telephony.ims.RcsEvent) throws android.telephony.ims.RcsMessageStoreException; + } + + public class RcsMessageStoreException extends java.lang.Exception { + ctor public RcsMessageStoreException(String); + } + + public class RcsOutgoingMessage extends android.telephony.ims.RcsMessage { + method @NonNull @WorkerThread public java.util.List<android.telephony.ims.RcsOutgoingMessageDelivery> getOutgoingDeliveries() throws android.telephony.ims.RcsMessageStoreException; + method public boolean isIncoming(); + } + + public final class RcsOutgoingMessageCreationParams extends android.telephony.ims.RcsMessageCreationParams implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.RcsOutgoingMessageCreationParams> CREATOR; + } + + public static class RcsOutgoingMessageCreationParams.Builder extends android.telephony.ims.RcsMessageCreationParams.Builder { + ctor public RcsOutgoingMessageCreationParams.Builder(long, int); + method public android.telephony.ims.RcsOutgoingMessageCreationParams build(); + } + + public class RcsOutgoingMessageDelivery { + method @WorkerThread public long getDeliveredTimestamp() throws android.telephony.ims.RcsMessageStoreException; + method @NonNull public android.telephony.ims.RcsOutgoingMessage getMessage(); + method @NonNull public android.telephony.ims.RcsParticipant getRecipient(); + method @WorkerThread public long getSeenTimestamp() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread @android.telephony.ims.RcsMessage.RcsMessageStatus public int getStatus() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setDeliveredTimestamp(long) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setSeenTimestamp(long) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setStatus(@android.telephony.ims.RcsMessage.RcsMessageStatus int) throws android.telephony.ims.RcsMessageStoreException; + } + + public class RcsParticipant { + method @Nullable @WorkerThread public String getAlias() throws android.telephony.ims.RcsMessageStoreException; + method @Nullable @WorkerThread public String getCanonicalAddress() throws android.telephony.ims.RcsMessageStoreException; + method @Nullable @WorkerThread public String getContactId() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setAlias(String) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void setContactId(String) throws android.telephony.ims.RcsMessageStoreException; + } + + public final class RcsParticipantAliasChangedEvent extends android.telephony.ims.RcsEvent implements android.os.Parcelable { + ctor public RcsParticipantAliasChangedEvent(long, @NonNull android.telephony.ims.RcsParticipant, @Nullable String); + method public int describeContents(); + method @Nullable public String getNewAlias(); + method @NonNull public android.telephony.ims.RcsParticipant getParticipantId(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.RcsParticipantAliasChangedEvent> CREATOR; + } + + public final class RcsParticipantQueryParams implements android.os.Parcelable { + method public int describeContents(); + method public String getAliasLike(); + method public String getCanonicalAddressLike(); + method public int getLimit(); + method public boolean getSortDirection(); + method public int getSortingProperty(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.RcsParticipantQueryParams> CREATOR; + field public static final int SORT_BY_ALIAS = 1; // 0x1 + field public static final int SORT_BY_CANONICAL_ADDRESS = 2; // 0x2 + field public static final int SORT_BY_CREATION_ORDER = 0; // 0x0 + } + + public static class RcsParticipantQueryParams.Builder { + ctor public RcsParticipantQueryParams.Builder(); + method public android.telephony.ims.RcsParticipantQueryParams build(); + method @CheckResult public android.telephony.ims.RcsParticipantQueryParams.Builder setAliasLike(String); + method @CheckResult public android.telephony.ims.RcsParticipantQueryParams.Builder setCanonicalAddressLike(String); + method @CheckResult public android.telephony.ims.RcsParticipantQueryParams.Builder setResultLimit(@IntRange(from=0) int) throws java.security.InvalidParameterException; + method @CheckResult public android.telephony.ims.RcsParticipantQueryParams.Builder setSortDirection(boolean); + method @CheckResult public android.telephony.ims.RcsParticipantQueryParams.Builder setSortProperty(@android.telephony.ims.RcsParticipantQueryParams.SortingProperty int); + method @CheckResult public android.telephony.ims.RcsParticipantQueryParams.Builder setThread(android.telephony.ims.RcsThread); + } + + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({android.telephony.ims.RcsParticipantQueryParams.SORT_BY_CREATION_ORDER, android.telephony.ims.RcsParticipantQueryParams.SORT_BY_ALIAS, android.telephony.ims.RcsParticipantQueryParams.SORT_BY_CANONICAL_ADDRESS}) public static @interface RcsParticipantQueryParams.SortingProperty { + } + + public final class RcsParticipantQueryResult implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.telephony.ims.RcsQueryContinuationToken getContinuationToken(); + method @NonNull public java.util.List<android.telephony.ims.RcsParticipant> getParticipants(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.RcsParticipantQueryResult> CREATOR; + } + + public final class RcsQueryContinuationToken implements android.os.Parcelable { + method public int describeContents(); + method @android.telephony.ims.RcsQueryContinuationToken.ContinuationTokenType public int getQueryType(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.RcsQueryContinuationToken> CREATOR; + field public static final int EVENT_QUERY_CONTINUATION_TOKEN_TYPE = 0; // 0x0 + field public static final int MESSAGE_QUERY_CONTINUATION_TOKEN_TYPE = 1; // 0x1 + field public static final int PARTICIPANT_QUERY_CONTINUATION_TOKEN_TYPE = 2; // 0x2 + field public static final int THREAD_QUERY_CONTINUATION_TOKEN_TYPE = 3; // 0x3 + } + + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({android.telephony.ims.RcsQueryContinuationToken.EVENT_QUERY_CONTINUATION_TOKEN_TYPE, android.telephony.ims.RcsQueryContinuationToken.MESSAGE_QUERY_CONTINUATION_TOKEN_TYPE, android.telephony.ims.RcsQueryContinuationToken.PARTICIPANT_QUERY_CONTINUATION_TOKEN_TYPE, android.telephony.ims.RcsQueryContinuationToken.THREAD_QUERY_CONTINUATION_TOKEN_TYPE}) public static @interface RcsQueryContinuationToken.ContinuationTokenType { + } + + public abstract class RcsThread { + method @WorkerThread @NonNull public android.telephony.ims.RcsIncomingMessage addIncomingMessage(@NonNull android.telephony.ims.RcsIncomingMessageCreationParams) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread @NonNull public android.telephony.ims.RcsOutgoingMessage addOutgoingMessage(@NonNull android.telephony.ims.RcsOutgoingMessageCreationParams) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread public void deleteMessage(@NonNull android.telephony.ims.RcsMessage) throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread @NonNull public android.telephony.ims.RcsMessageQueryResult getMessages() throws android.telephony.ims.RcsMessageStoreException; + method @WorkerThread @NonNull public android.telephony.ims.RcsMessageSnippet getSnippet() throws android.telephony.ims.RcsMessageStoreException; + method public abstract boolean isGroup(); + } + + public final class RcsThreadQueryParams implements android.os.Parcelable { + method public int describeContents(); + method public int getLimit(); + method public boolean getSortDirection(); + method @android.telephony.ims.RcsThreadQueryParams.SortingProperty public int getSortingProperty(); + method public int getThreadType(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.RcsThreadQueryParams> CREATOR; + field public static final int SORT_BY_CREATION_ORDER = 0; // 0x0 + field public static final int SORT_BY_TIMESTAMP = 1; // 0x1 + field public static final int THREAD_TYPE_1_TO_1 = 2; // 0x2 + field public static final int THREAD_TYPE_GROUP = 1; // 0x1 + } + + public static class RcsThreadQueryParams.Builder { + ctor public RcsThreadQueryParams.Builder(); + method public android.telephony.ims.RcsThreadQueryParams build(); + method @CheckResult public android.telephony.ims.RcsThreadQueryParams.Builder setParticipant(@NonNull android.telephony.ims.RcsParticipant); + method @CheckResult public android.telephony.ims.RcsThreadQueryParams.Builder setParticipants(@NonNull java.util.List<android.telephony.ims.RcsParticipant>); + method @CheckResult public android.telephony.ims.RcsThreadQueryParams.Builder setResultLimit(@IntRange(from=0) int) throws java.security.InvalidParameterException; + method @CheckResult public android.telephony.ims.RcsThreadQueryParams.Builder setSortDirection(boolean); + method @CheckResult public android.telephony.ims.RcsThreadQueryParams.Builder setSortProperty(@android.telephony.ims.RcsThreadQueryParams.SortingProperty int); + method @CheckResult public android.telephony.ims.RcsThreadQueryParams.Builder setThreadType(int); + } + + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({android.telephony.ims.RcsThreadQueryParams.SORT_BY_CREATION_ORDER, android.telephony.ims.RcsThreadQueryParams.SORT_BY_TIMESTAMP}) public static @interface RcsThreadQueryParams.SortingProperty { + } + + public final class RcsThreadQueryResult implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.telephony.ims.RcsQueryContinuationToken getContinuationToken(); + method @NonNull public java.util.List<android.telephony.ims.RcsThread> getThreads(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.RcsThreadQueryResult> CREATOR; + } + +} + package android.telephony.mbms { public class DownloadProgressListener { @@ -43599,9 +44098,9 @@ package android.telephony.mbms { } public interface GroupCallCallback { - method public void onBroadcastSignalStrengthUpdated(@IntRange(from=0xffffffff, to=4) int); - method public void onError(int, @Nullable String); - method public void onGroupCallStateChanged(int, int); + method public default void onBroadcastSignalStrengthUpdated(@IntRange(from=0xffffffff, to=4) int); + method public default void onError(int, @Nullable String); + method public default void onGroupCallStateChanged(int, int); field public static final int SIGNAL_STRENGTH_UNAVAILABLE = -1; // 0xffffffff } @@ -43659,10 +44158,10 @@ package android.telephony.mbms { } public interface MbmsGroupCallSessionCallback { - method public void onAvailableSaisUpdated(@NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<java.util.List<java.lang.Integer>>); - method public void onError(int, @Nullable String); - method public void onMiddlewareReady(); - method public void onServiceInterfaceAvailable(@NonNull String, int); + method public default void onAvailableSaisUpdated(@NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<java.util.List<java.lang.Integer>>); + method public default void onError(int, @Nullable String); + method public default void onMiddlewareReady(); + method public default void onServiceInterfaceAvailable(@NonNull String, int); } public class MbmsStreamingSessionCallback { @@ -58466,9 +58965,9 @@ package java.lang.invoke { method public String getName(); method public int getReferenceKind(); method public default boolean isVarArgs(); - method public static boolean refKindIsField(int); - method public static boolean refKindIsValid(int); - method public static String refKindName(int); + method @Deprecated public static boolean refKindIsField(int); + method @Deprecated public static boolean refKindIsValid(int); + method @Deprecated public static String refKindName(int); method public static String referenceKindToString(int); method public <T extends java.lang.reflect.Member> T reflectAs(Class<T>, java.lang.invoke.MethodHandles.Lookup); method public static String toString(int, Class<?>, String, java.lang.invoke.MethodType); diff --git a/api/removed.txt b/api/removed.txt index 05d52d4e40c0..72202ad9712a 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -540,7 +540,7 @@ package android.telephony { public class TelephonyManager { method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public java.util.List<android.telephony.NeighboringCellInfo> getNeighboringCellInfo(); - method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, android.telephony.TelephonyScanManager.NetworkScanCallback); + method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, android.telephony.TelephonyScanManager.NetworkScanCallback); } } diff --git a/api/system-current.txt b/api/system-current.txt index 9cfe60428fe1..9c45fb4ee5c4 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -101,6 +101,7 @@ package android { field public static final String OBSERVE_APP_USAGE = "android.permission.OBSERVE_APP_USAGE"; field public static final String OVERRIDE_WIFI_CONFIG = "android.permission.OVERRIDE_WIFI_CONFIG"; field public static final String PACKAGE_VERIFICATION_AGENT = "android.permission.PACKAGE_VERIFICATION_AGENT"; + field public static final String PACKET_KEEPALIVE_OFFLOAD = "android.permission.PACKET_KEEPALIVE_OFFLOAD"; field public static final String PEERS_MAC_ADDRESS = "android.permission.PEERS_MAC_ADDRESS"; field public static final String PERFORM_CDMA_PROVISIONING = "android.permission.PERFORM_CDMA_PROVISIONING"; field public static final String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION"; @@ -837,6 +838,7 @@ package android.content { method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void startActivityAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle); field public static final String BACKUP_SERVICE = "backup"; field public static final String CONTEXTHUB_SERVICE = "contexthub"; + field public static final String DYNAMIC_ANDROID_SERVICE = "dynamic_android"; field public static final String EUICC_CARD_SERVICE = "euicc_card"; field public static final String HDMI_CONTROL_SERVICE = "hdmi_control"; field public static final String NETD_SERVICE = "netd"; @@ -860,6 +862,32 @@ package android.content { method public void sendOrderedBroadcast(android.content.Intent, String, android.os.Bundle, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle); } + public class DynamicAndroidClient { + ctor public DynamicAndroidClient(@NonNull android.content.Context); + method public void bind(); + method public void setOnStatusChangedListener(@NonNull android.content.DynamicAndroidClient.OnStatusChangedListener, @NonNull java.util.concurrent.Executor); + method public void setOnStatusChangedListener(@NonNull android.content.DynamicAndroidClient.OnStatusChangedListener); + method public void start(String, long); + method public void start(String, long, long); + method public void unbind(); + field public static final int CAUSE_ERROR_EXCEPTION = 6; // 0x6 + field public static final int CAUSE_ERROR_INVALID_URL = 4; // 0x4 + field public static final int CAUSE_ERROR_IO = 3; // 0x3 + field public static final int CAUSE_ERROR_IPC = 5; // 0x5 + field public static final int CAUSE_INSTALL_CANCELLED = 2; // 0x2 + field public static final int CAUSE_INSTALL_COMPLETED = 1; // 0x1 + field public static final int CAUSE_NOT_SPECIFIED = 0; // 0x0 + field public static final int STATUS_IN_PROGRESS = 2; // 0x2 + field public static final int STATUS_IN_USE = 4; // 0x4 + field public static final int STATUS_NOT_STARTED = 1; // 0x1 + field public static final int STATUS_READY = 3; // 0x3 + field public static final int STATUS_UNKNOWN = 0; // 0x0 + } + + public static interface DynamicAndroidClient.OnStatusChangedListener { + method public void onStatusChanged(int, int, long); + } + public class Intent implements java.lang.Cloneable android.os.Parcelable { field public static final String ACTION_BATTERY_LEVEL_CHANGED = "android.intent.action.BATTERY_LEVEL_CHANGED"; field public static final String ACTION_CALL_EMERGENCY = "android.intent.action.CALL_EMERGENCY"; @@ -3066,11 +3094,14 @@ package android.net { } public class ConnectivityManager { - method @RequiresPermission("android.permission.PACKET_KEEPALIVE_OFFLOAD") public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull java.io.FileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); + method @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull java.io.FileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); + method @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull java.net.Socket, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); method public boolean getAvoidBadWifi(); method @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS) public String getCaptivePortalServerUrl(); + method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementValue(int, boolean, @NonNull android.net.ConnectivityManager.TetheringEntitlementValueListener, @Nullable android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void setAirplaneMode(boolean); + method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(android.net.Network, android.os.Bundle); method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback); method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler); method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int); @@ -3079,6 +3110,9 @@ package android.net { field public static final int TETHERING_BLUETOOTH = 2; // 0x2 field public static final int TETHERING_USB = 1; // 0x1 field public static final int TETHERING_WIFI = 0; // 0x0 + field public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd + field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0 + field public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb } public abstract static class ConnectivityManager.OnStartTetheringCallback { @@ -3087,6 +3121,11 @@ package android.net { method public void onTetheringStarted(); } + public abstract static class ConnectivityManager.TetheringEntitlementValueListener { + ctor public ConnectivityManager.TetheringEntitlementValueListener(); + method public void onEntitlementResult(int); + } + public final class IpPrefix implements android.os.Parcelable { ctor public IpPrefix(java.net.InetAddress, int); ctor public IpPrefix(String); @@ -3186,6 +3225,10 @@ package android.net { field public static final String EXTRA_PACKAGE_NAME = "packageName"; } + public class NetworkStack { + field public static final String PERMISSION_MAINLINE_NETWORK_STACK = "android.permission.MAINLINE_NETWORK_STACK"; + } + public final class RouteInfo implements android.os.Parcelable { ctor public RouteInfo(android.net.IpPrefix, java.net.InetAddress, String, int); method public int getType(); @@ -4304,6 +4347,7 @@ package android.os { } public final class UserHandle implements android.os.Parcelable { + method public static int getAppId(int); method public int getIdentifier(); method @Deprecated public boolean isOwner(); method public boolean isSystem(); @@ -5448,6 +5492,7 @@ package android.telecom { field public static final String EXTRA_CALL_BACK_INTENT = "android.telecom.extra.CALL_BACK_INTENT"; field public static final String EXTRA_CLEAR_MISSED_CALLS_INTENT = "android.telecom.extra.CLEAR_MISSED_CALLS_INTENT"; field public static final String EXTRA_CONNECTION_SERVICE = "android.telecom.extra.CONNECTION_SERVICE"; + field public static final String EXTRA_IS_USER_INTENT_EMERGENCY_CALL = "android.telecom.extra.IS_USER_INTENT_EMERGENCY_CALL"; field public static final int TTY_MODE_FULL = 1; // 0x1 field public static final int TTY_MODE_HCO = 2; // 0x2 field public static final int TTY_MODE_OFF = 0; // 0x0 @@ -5511,6 +5556,7 @@ package android.telephony { method @NonNull public java.util.List<android.service.carrier.CarrierIdentifier> getExcludedCarriers(); method public int getMultiSimPolicy(); method public boolean isAllCarriersAllowed(); + method public java.util.List<java.lang.Boolean> isCarrierIdentifiersAllowed(@NonNull java.util.List<android.service.carrier.CarrierIdentifier>); method public void writeToParcel(android.os.Parcel, int); field public static final int CARRIER_RESTRICTION_DEFAULT_ALLOWED = 1; // 0x1 field public static final int CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED = 0; // 0x0 @@ -6234,10 +6280,13 @@ package android.telephony { public class SubscriptionManager { method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList(); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEnabledSubscriptionId(int); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSubscriptionEnabled(int); method public void requestEmbeddedSubscriptionInfoListRefresh(); method public void requestEmbeddedSubscriptionInfoListRefresh(int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultDataSubId(int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultSmsSubId(int); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setSubscriptionEnabled(int, boolean); field public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI; field public static final int PROFILE_CLASS_DEFAULT = -1; // 0xffffffff field public static final int PROFILE_CLASS_OPERATIONAL = 2; // 0x2 @@ -6304,12 +6353,13 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst(); - method @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public int getPreferredNetworkType(int); + method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.util.Pair<java.lang.Integer,java.lang.Integer>> getLogicalToPhysicalSlotMapping(); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getPreferredNetworkTypeBitmap(); method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public int getRadioPowerState(); method public int getSimApplicationState(); method public int getSimCardState(); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getSimLocale(); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSupportedRadioAccessFamily(); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getSupportedRadioAccessFamily(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.UiccSlotInfo[] getUiccSlotsInfo(); method @Nullable public android.os.Bundle getVisualVoicemailSettings(); @@ -6322,12 +6372,13 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMultisimCarrierRestricted(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRadioOn(); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isRebootRequiredForModemConfigChange(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging(); method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isVideoCallingEnabled(); method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle); method public boolean needsOtaServiceProvisioning(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio(); - method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); + method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestNumberVerification(@NonNull android.telephony.PhoneNumberRange, long, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.NumberVerificationCallback); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean resetRadioConfig(); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>); @@ -6337,6 +6388,7 @@ package android.telephony { method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultisimCarrierRestriction(boolean); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmap(long); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadio(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadioPower(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerState(int); @@ -6350,6 +6402,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean switchSlots(int[]); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void toggleRadioOnOff(); method public void updateServiceLocation(); + field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final String ACTION_DEBUG_EVENT = "android.telephony.action.DEBUG_EVENT"; field public static final String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.telephony.action.SIM_APPLICATION_STATE_CHANGED"; field public static final String ACTION_SIM_CARD_STATE_CHANGED = "android.telephony.action.SIM_CARD_STATE_CHANGED"; field public static final String ACTION_SIM_SLOT_STATUS_CHANGED = "android.telephony.action.SIM_SLOT_STATUS_CHANGED"; @@ -6357,52 +6410,32 @@ package android.telephony { field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1 field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0 field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff + field public static final String EXTRA_DEBUG_EVENT_DESCRIPTION = "android.telephony.extra.DEBUG_EVENT_DESCRIPTION"; + field public static final String EXTRA_DEBUG_EVENT_ID = "android.telephony.extra.DEBUG_EVENT_ID"; field public static final String EXTRA_SIM_STATE = "android.telephony.extra.SIM_STATE"; field public static final String EXTRA_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL = "android.telephony.extra.VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL"; field public static final String EXTRA_VOICEMAIL_SCRAMBLED_PIN_STRING = "android.telephony.extra.VOICEMAIL_SCRAMBLED_PIN_STRING"; field public static final long MAX_NUMBER_VERIFICATION_TIMEOUT_MILLIS = 60000L; // 0xea60L - field public static final int NETWORK_MODE_CDMA_EVDO = 4; // 0x4 - field public static final int NETWORK_MODE_CDMA_NO_EVDO = 5; // 0x5 - field public static final int NETWORK_MODE_EVDO_NO_CDMA = 6; // 0x6 - field public static final int NETWORK_MODE_GLOBAL = 7; // 0x7 - field public static final int NETWORK_MODE_GSM_ONLY = 1; // 0x1 - field public static final int NETWORK_MODE_GSM_UMTS = 3; // 0x3 - field public static final int NETWORK_MODE_LTE_CDMA_EVDO = 8; // 0x8 - field public static final int NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA = 10; // 0xa - field public static final int NETWORK_MODE_LTE_GSM_WCDMA = 9; // 0x9 - field public static final int NETWORK_MODE_LTE_ONLY = 11; // 0xb - field public static final int NETWORK_MODE_LTE_TDSCDMA = 15; // 0xf - field public static final int NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = 22; // 0x16 - field public static final int NETWORK_MODE_LTE_TDSCDMA_GSM = 17; // 0x11 - field public static final int NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA = 20; // 0x14 - field public static final int NETWORK_MODE_LTE_TDSCDMA_WCDMA = 19; // 0x13 - field public static final int NETWORK_MODE_LTE_WCDMA = 12; // 0xc - field public static final int NETWORK_MODE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = 21; // 0x15 - field public static final int NETWORK_MODE_TDSCDMA_GSM = 16; // 0x10 - field public static final int NETWORK_MODE_TDSCDMA_GSM_WCDMA = 18; // 0x12 - field public static final int NETWORK_MODE_TDSCDMA_ONLY = 13; // 0xd - field public static final int NETWORK_MODE_TDSCDMA_WCDMA = 14; // 0xe - field public static final int NETWORK_MODE_WCDMA_ONLY = 2; // 0x2 - field public static final int NETWORK_MODE_WCDMA_PREF = 0; // 0x0 - field public static final int NETWORK_TYPE_BITMASK_1xRTT = 128; // 0x80 - field public static final int NETWORK_TYPE_BITMASK_CDMA = 16; // 0x10 - field public static final int NETWORK_TYPE_BITMASK_EDGE = 4; // 0x4 - field public static final int NETWORK_TYPE_BITMASK_EHRPD = 16384; // 0x4000 - field public static final int NETWORK_TYPE_BITMASK_EVDO_0 = 32; // 0x20 - field public static final int NETWORK_TYPE_BITMASK_EVDO_A = 64; // 0x40 - field public static final int NETWORK_TYPE_BITMASK_EVDO_B = 4096; // 0x1000 - field public static final int NETWORK_TYPE_BITMASK_GPRS = 2; // 0x2 - field public static final int NETWORK_TYPE_BITMASK_GSM = 65536; // 0x10000 - field public static final int NETWORK_TYPE_BITMASK_HSDPA = 256; // 0x100 - field public static final int NETWORK_TYPE_BITMASK_HSPA = 1024; // 0x400 - field public static final int NETWORK_TYPE_BITMASK_HSPAP = 32768; // 0x8000 - field public static final int NETWORK_TYPE_BITMASK_HSUPA = 512; // 0x200 - field public static final int NETWORK_TYPE_BITMASK_LTE = 8192; // 0x2000 - field public static final int NETWORK_TYPE_BITMASK_LTE_CA = 524288; // 0x80000 - field public static final int NETWORK_TYPE_BITMASK_NR = 1048576; // 0x100000 - field public static final int NETWORK_TYPE_BITMASK_TD_SCDMA = 131072; // 0x20000 - field public static final int NETWORK_TYPE_BITMASK_UMTS = 8; // 0x8 - field public static final int NETWORK_TYPE_BITMASK_UNKNOWN = 1; // 0x1 + field public static final long NETWORK_TYPE_BITMASK_1xRTT = 64L; // 0x40L + field public static final long NETWORK_TYPE_BITMASK_CDMA = 8L; // 0x8L + field public static final long NETWORK_TYPE_BITMASK_EDGE = 2L; // 0x2L + field public static final long NETWORK_TYPE_BITMASK_EHRPD = 8192L; // 0x2000L + field public static final long NETWORK_TYPE_BITMASK_EVDO_0 = 16L; // 0x10L + field public static final long NETWORK_TYPE_BITMASK_EVDO_A = 32L; // 0x20L + field public static final long NETWORK_TYPE_BITMASK_EVDO_B = 2048L; // 0x800L + field public static final long NETWORK_TYPE_BITMASK_GPRS = 1L; // 0x1L + field public static final long NETWORK_TYPE_BITMASK_GSM = 32768L; // 0x8000L + field public static final long NETWORK_TYPE_BITMASK_HSDPA = 128L; // 0x80L + field public static final long NETWORK_TYPE_BITMASK_HSPA = 512L; // 0x200L + field public static final long NETWORK_TYPE_BITMASK_HSPAP = 16384L; // 0x4000L + field public static final long NETWORK_TYPE_BITMASK_HSUPA = 256L; // 0x100L + field public static final long NETWORK_TYPE_BITMASK_IWLAN = 131072L; // 0x20000L + field public static final long NETWORK_TYPE_BITMASK_LTE = 4096L; // 0x1000L + field public static final long NETWORK_TYPE_BITMASK_LTE_CA = 262144L; // 0x40000L + field public static final long NETWORK_TYPE_BITMASK_NR = 524288L; // 0x80000L + field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L + field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L + field public static final long NETWORK_TYPE_BITMASK_UNKNOWN = 0L; // 0x0L field public static final int RADIO_POWER_OFF = 0; // 0x0 field public static final int RADIO_POWER_ON = 1; // 0x1 field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2 @@ -6721,6 +6754,7 @@ package android.telephony.ims { method public int getServiceType(); method public static int getVideoStateFromCallType(int); method public static int getVideoStateFromImsCallProfile(android.telephony.ims.ImsCallProfile); + method public boolean hasKnownUserIntentEmergency(); method public boolean isEmergencyCallTesting(); method public boolean isVideoCall(); method public boolean isVideoPaused(); @@ -6733,6 +6767,7 @@ package android.telephony.ims { method public void setEmergencyCallTesting(boolean); method public void setEmergencyServiceCategories(int); method public void setEmergencyUrns(java.util.List<java.lang.String>); + method public void setHasKnownUserIntentEmergency(boolean); method public void updateCallExtras(android.telephony.ims.ImsCallProfile); method public void updateCallType(android.telephony.ims.ImsCallProfile); method public void updateMediaProfile(android.telephony.ims.ImsCallProfile); @@ -6844,6 +6879,16 @@ package android.telephony.ims { field public final java.util.HashMap<java.lang.String,android.os.Bundle> mParticipants; } + public class ImsException extends java.lang.Exception { + ctor public ImsException(@Nullable String); + ctor public ImsException(@Nullable String, int); + ctor public ImsException(@Nullable String, int, Throwable); + method public int getCode(); + field public static final int CODE_ERROR_SERVICE_UNAVAILABLE = 1; // 0x1 + field public static final int CODE_ERROR_UNSPECIFIED = 0; // 0x0 + field public static final int CODE_ERROR_UNSUPPORTED_OPERATION = 2; // 0x2 + } + public final class ImsExternalCallState implements android.os.Parcelable { ctor public ImsExternalCallState(String, android.net.Uri, android.net.Uri, boolean, int, int, boolean); method public int describeContents(); @@ -6861,7 +6906,7 @@ package android.telephony.ims { } public class ImsMmTelManager { - method public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(android.content.Context, int); + method public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiModeSetting(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiRoamingModeSetting(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAdvancedCallingSettingEnabled(); @@ -6870,8 +6915,8 @@ package android.telephony.ims { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVoWiFiRoamingSettingEnabled(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVoWiFiSettingEnabled(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVtSettingEnabled(); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback) throws android.telephony.ims.ImsException; + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setAdvancedCallingSetting(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setRttCapabilitySetting(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoWiFiModeSetting(int); @@ -7306,11 +7351,11 @@ package android.telephony.ims { } public class ProvisioningManager { - method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(android.content.Context, int); + method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(int); method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int); method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getProvisioningStringValue(int); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, String); diff --git a/api/test-current.txt b/api/test-current.txt index 1384f0b7b41e..c9d176979a65 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -608,6 +608,7 @@ package android.net { } public class ConnectivityManager { + method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(android.net.Network, android.os.Bundle); field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC"; field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; } @@ -667,6 +668,11 @@ package android.net { method public int[] getCapabilities(); method public int[] getTransportTypes(); method public boolean satisfiedByNetworkCapabilities(android.net.NetworkCapabilities); + field public static final int TRANSPORT_TEST = 7; // 0x7 + } + + public class NetworkStack { + field public static final String PERMISSION_MAINLINE_NETWORK_STACK = "android.permission.MAINLINE_NETWORK_STACK"; } public final class RouteInfo implements android.os.Parcelable { @@ -1473,7 +1479,6 @@ package android.telephony { public class TelephonyManager { method public int getCarrierIdListVersion(); - method public boolean isRttSupported(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile(); method public void setCarrierTestOverride(String, String, String, String, String, String, String); field public static final int UNKNOWN_CARRIER_ID_LIST_VERSION = -1; // 0xffffffff diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index c04e61b77274..ca4a184407b4 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -174,6 +174,8 @@ public class Am extends BaseCommand { instrument.noWindowAnimation = true; } else if (opt.equals("--no-hidden-api-checks")) { instrument.disableHiddenApiChecks = true; + } else if (opt.equals("--no-isolated-storage")) { + // NOTE: currently a no-op in this branch } else if (opt.equals("--user")) { instrument.userId = parseUserArg(nextArgRequired()); } else if (opt.equals("--abi")) { diff --git a/cmds/bootanimation/bootanim.rc b/cmds/bootanimation/bootanim.rc index 1b3c32b20503..469c9646a4aa 100644 --- a/cmds/bootanimation/bootanim.rc +++ b/cmds/bootanimation/bootanim.rc @@ -2,7 +2,6 @@ service bootanim /system/bin/bootanimation class core animation user graphics group graphics audio - updatable disabled oneshot writepid /dev/stune/top-app/tasks diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 5fd148e5d5af..1d629da2f250 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -161,6 +161,8 @@ message Atom { BluetoothBondStateChanged bluetooth_bond_state_changed = 165; BluetoothClassicPairingEventReported bluetooth_classic_pairing_event_reported = 166; BluetoothSmpPairingEventReported bluetooth_smp_pairing_event_reported = 167; + ProcessStartTime process_start_time = 169; + BluetoothSocketConnectionStateChanged bluetooth_socket_connection_state_changed = 171; } // Pulled events will start at field 10000. @@ -1719,6 +1721,44 @@ message BluetoothSmpPairingEventReported { } /** + * Logs when a Bluetooth socket’s connection state changed + * + * Logged from: + * system/bt + */ +message BluetoothSocketConnectionStateChanged { + // An identifier that can be used to match events for this device. + // Currently, this is a salted hash of the MAC address of this Bluetooth device. + // Salt: Randomly generated 256 bit value + // Hash algorithm: HMAC-SHA256 + // Size: 32 byte + // Default: null or empty if this is a server listener socket + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Temporary port of this socket for the current connection or session only + // Default 0 when unknown or don't care + optional int32 port = 2; + // Socket type as mentioned in + // frameworks/base/core/java/android/bluetooth/BluetoothSocket.java + // Default: SOCKET_TYPE_UNKNOWN + optional android.bluetooth.SocketTypeEnum type = 3; + // Socket connection state + // Default: SOCKET_CONNECTION_STATE_UNKNOWN + optional android.bluetooth.SocketConnectionstateEnum state = 4; + // Number of bytes sent to remote device during this connection + optional int64 tx_bytes = 5; + // Number of bytes received from remote device during this connection + optional int64 rx_bytes = 6; + // Socket owner's UID + optional int32 uid = 7 [(is_uid) = true]; + // Server port of this socket, if any. When both |server_port| and |port| fields are populated, + // |port| must be spawned by |server_port| + // Default 0 when unknown or don't care + optional int32 server_port = 8; + // Whether this is a server listener socket + optional android.bluetooth.SocketRoleEnum is_server = 9; +} + +/** * Logs when something is plugged into or removed from the USB-C connector. * * Logged from: @@ -2929,3 +2969,59 @@ message SeOmapiReported { optional string package_name = 3; } + +/* +* Logs number of milliseconds it takes to start a process. +* The definition of app process start time is from the app launch time to +* the time that Zygote finished forking the app process and loaded the +* application package's java classes. + +* This metric is different from AppStartOccurred which is for foreground +* activity only. + +* ProcessStartTime can report all processes (both foreground and background) +* start time. +* +* Logged from: +* frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java +*/ +message ProcessStartTime { + // The uid of the ProcessRecord. + optional int32 uid = 1 [(is_uid) = true]; + + // The process pid. + optional int32 pid = 2; + + // The process name. + // Usually package name, "system" for system server. + // Provided by ActivityManagerService. + optional string process_name = 3; + + enum StartType { + UNKNOWN = 0; + WARM = 1; + HOT = 2; + COLD = 3; + } + + // The start type. + optional StartType type = 4; + + // The elapsed realtime at the start of the process. + optional int64 process_start_time_millis = 5; + + // Number of milliseconds it takes to reach bind application. + optional int32 bind_application_delay_millis = 6; + + // Number of milliseconds it takes to finish start of the process. + optional int32 process_start_delay_millis = 7; + + // hostingType field in ProcessRecord, the component type such as "activity", + // "service", "content provider", "broadcast" or other strings. + optional string hosting_type = 8; + + // hostingNameStr field in ProcessRecord. The component class name that runs + // in this process. + optional string hosting_name = 9; +} + diff --git a/config/hiddenapi-force-blacklist.txt b/config/hiddenapi-force-blacklist.txt index dca3b525c06a..b328f2ac1955 100644 --- a/config/hiddenapi-force-blacklist.txt +++ b/config/hiddenapi-force-blacklist.txt @@ -1,4 +1,6 @@ Ldalvik/system/VMRuntime;->setHiddenApiExemptions([Ljava/lang/String;)V +Ldalvik/system/VMRuntime;->setTargetSdkVersion(I)V +Ldalvik/system/VMRuntime;->setTargetSdkVersionNative(I)V Ljava/lang/invoke/MethodHandles$Lookup;->IMPL_LOOKUP:Ljava/lang/invoke/MethodHandles$Lookup; Ljava/lang/invoke/VarHandle;->acquireFence()V Ljava/lang/invoke/VarHandle;->compareAndExchange([Ljava/lang/Object;)Ljava/lang/Object; diff --git a/config/hiddenapi-greylist-max-o.txt b/config/hiddenapi-greylist-max-o.txt index 4b6cc0ef7795..d9c1cd0313fc 100644 --- a/config/hiddenapi-greylist-max-o.txt +++ b/config/hiddenapi-greylist-max-o.txt @@ -109777,7 +109777,6 @@ Ldalvik/system/VMRuntime;->setHiddenApiAccessLogSamplingRate(I)V Ldalvik/system/VMRuntime;->setNonSdkApiUsageConsumer(Ljava/util/function/Consumer;)V Ldalvik/system/VMRuntime;->setProcessPackageName(Ljava/lang/String;)V Ldalvik/system/VMRuntime;->setSystemDaemonThreadPriority()V -Ldalvik/system/VMRuntime;->setTargetSdkVersionNative(I)V Ldalvik/system/VMRuntime;->startHeapTaskProcessor()V Ldalvik/system/VMRuntime;->startJitCompilation()V Ldalvik/system/VMRuntime;->stopHeapTaskProcessor()V diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt index 3d5b32a62ca5..e166c31d578a 100644 --- a/config/hiddenapi-greylist.txt +++ b/config/hiddenapi-greylist.txt @@ -603,8 +603,6 @@ Landroid/net/IConnectivityManager;->getTetherableWifiRegexs()[Ljava/lang/String; Landroid/net/IConnectivityManager;->getTetheredIfaces()[Ljava/lang/String; Landroid/net/IConnectivityManager;->getTetheringErroredIfaces()[Ljava/lang/String; Landroid/net/IConnectivityManager;->startLegacyVpn(Lcom/android/internal/net/VpnProfile;)V -Landroid/net/INetd$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/INetd; -Landroid/net/INetd;->interfaceAddAddress(Ljava/lang/String;Ljava/lang/String;I)V Landroid/net/INetworkManagementEventObserver$Stub;-><init>()V Landroid/net/INetworkPolicyListener$Stub;-><init>()V Landroid/net/INetworkPolicyManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/INetworkPolicyManager; @@ -2546,7 +2544,6 @@ Lcom/android/internal/telephony/cat/CatService;->isStkAppInstalled()Z Lcom/android/internal/telephony/cat/CatService;->mCmdIf:Lcom/android/internal/telephony/CommandsInterface; Lcom/android/internal/telephony/cat/CatService;->mContext:Landroid/content/Context; Lcom/android/internal/telephony/cat/CatService;->mCurrntCmd:Lcom/android/internal/telephony/cat/CatCmdMessage; -Lcom/android/internal/telephony/cat/CatService;->mHandlerThread:Landroid/os/HandlerThread; Lcom/android/internal/telephony/cat/CatService;->mMenuCmd:Lcom/android/internal/telephony/cat/CatCmdMessage; Lcom/android/internal/telephony/cat/CatService;->mMsgDecoder:Lcom/android/internal/telephony/cat/RilMessageDecoder; Lcom/android/internal/telephony/cat/CatService;->mSlotId:I @@ -2933,7 +2930,6 @@ Lcom/android/internal/telephony/gsm/UsimPhoneBookManager;->reset()V Lcom/android/internal/telephony/GsmAlphabet$TextEncodingDetails;-><init>()V Lcom/android/internal/telephony/GsmCdmaCall;->attachFake(Lcom/android/internal/telephony/Connection;Lcom/android/internal/telephony/Call$State;)V Lcom/android/internal/telephony/GsmCdmaCallTracker;->clearDisconnected()V -Lcom/android/internal/telephony/GsmCdmaCallTracker;->dialThreeWay(Ljava/lang/String;)Lcom/android/internal/telephony/Connection; Lcom/android/internal/telephony/GsmCdmaCallTracker;->disableDataCallInEmergencyCall(Ljava/lang/String;)V Lcom/android/internal/telephony/GsmCdmaCallTracker;->fakeHoldForegroundBeforeDial()V Lcom/android/internal/telephony/GsmCdmaCallTracker;->getPhone()Lcom/android/internal/telephony/GsmCdmaPhone; diff --git a/core/java/android/accessibilityservice/OWNERS b/core/java/android/accessibilityservice/OWNERS new file mode 100644 index 000000000000..265674a74b7e --- /dev/null +++ b/core/java/android/accessibilityservice/OWNERS @@ -0,0 +1,3 @@ +svetoslavganov@google.com +pweaver@google.com +rhedjao@google.com diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 0d3110cfded8..ac33c169288b 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -850,8 +850,9 @@ public final class LoadedApk { } } - // /vendor/lib, /odm/lib and /product/lib are added to the native lib search - // paths of the classloader. Note that this is done AFTER the classloader is + // /aepx/com.android.runtime/lib, /vendor/lib, /odm/lib and /product/lib + // are added to the native lib search paths of the classloader. + // Note that this is done AFTER the classloader is // created by ApplicationLoaders.getDefault().getClassLoader(...). The // reason is because if we have added the paths when creating the classloader // above, the paths are also added to the search path of the linker namespace @@ -868,8 +869,11 @@ public final class LoadedApk { // System.loadLibrary(). In order to prevent the problem, we explicitly // add the paths only to the classloader, and not to the native loader // (linker namespace). - List<String> extraLibPaths = new ArrayList<>(3); + List<String> extraLibPaths = new ArrayList<>(4); String abiSuffix = VMRuntime.getRuntime().is64Bit() ? "64" : ""; + if (!defaultSearchPaths.contains("/apex/com.android.runtime/lib")) { + extraLibPaths.add("/apex/com.android.runtime/lib" + abiSuffix); + } if (!defaultSearchPaths.contains("/vendor/lib")) { extraLibPaths.add("/vendor/lib" + abiSuffix); } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index b2951df63ebd..e92efde236c4 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -26,7 +26,6 @@ import android.app.job.JobScheduler; import android.app.slice.SliceManager; import android.app.timedetector.TimeDetector; import android.app.timezone.RulesManager; -import android.app.timezonedetector.TimeZoneDetector; import android.app.trust.TrustManager; import android.app.usage.IStorageStatsManager; import android.app.usage.IUsageStatsManager; @@ -84,13 +83,11 @@ import android.net.IConnectivityManager; import android.net.IEthernetManager; import android.net.IIpMemoryStore; import android.net.IIpSecService; -import android.net.INetd; import android.net.INetworkPolicyManager; import android.net.IpMemoryStore; import android.net.IpSecManager; import android.net.NetworkPolicyManager; import android.net.NetworkScoreManager; -import android.net.NetworkStack; import android.net.NetworkWatchlistManager; import android.net.lowpan.ILowpanManager; import android.net.lowpan.LowpanManager; @@ -114,11 +111,13 @@ import android.os.BugreportManager; import android.os.Build; import android.os.DeviceIdleManager; import android.os.DropBoxManager; +import android.os.DynamicAndroidManager; import android.os.HardwarePropertiesManager; import android.os.IBatteryPropertiesRegistrar; import android.os.IBinder; import android.os.IDeviceIdleController; import android.os.IDumpstate; +import android.os.IDynamicAndroidService; import android.os.IHardwarePropertiesManager; import android.os.IPowerManager; import android.os.IRecoverySystem; @@ -289,21 +288,13 @@ final class SystemServiceRegistry { return new ConnectivityManager(context, service); }}); - registerService(Context.NETD_SERVICE, INetd.class, new StaticServiceFetcher<INetd>() { + registerService(Context.NETD_SERVICE, IBinder.class, new StaticServiceFetcher<IBinder>() { @Override - public INetd createService() throws ServiceNotFoundException { - return INetd.Stub.asInterface( - ServiceManager.getServiceOrThrow(Context.NETD_SERVICE)); + public IBinder createService() throws ServiceNotFoundException { + return ServiceManager.getServiceOrThrow(Context.NETD_SERVICE); } }); - registerService(Context.NETWORK_STACK_SERVICE, NetworkStack.class, - new StaticServiceFetcher<NetworkStack>() { - @Override - public NetworkStack createService() { - return new NetworkStack(); - }}); - registerService(Context.IP_MEMORY_STORE_SERVICE, IpMemoryStore.class, new CachedServiceFetcher<IpMemoryStore>() { @Override @@ -1058,12 +1049,15 @@ final class SystemServiceRegistry { throws ServiceNotFoundException { return new TimeDetector(); }}); - registerService(Context.TIME_ZONE_DETECTOR_SERVICE, TimeZoneDetector.class, - new CachedServiceFetcher<TimeZoneDetector>() { + registerService(Context.DYNAMIC_ANDROID_SERVICE, DynamicAndroidManager.class, + new CachedServiceFetcher<DynamicAndroidManager>() { @Override - public TimeZoneDetector createService(ContextImpl ctx) + public DynamicAndroidManager createService(ContextImpl ctx) throws ServiceNotFoundException { - return new TimeZoneDetector(); + IBinder b = ServiceManager.getServiceOrThrow( + Context.DYNAMIC_ANDROID_SERVICE); + return new DynamicAndroidManager( + IDynamicAndroidService.Stub.asInterface(b)); }}); } diff --git a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl deleted file mode 100644 index ef2cbab137dc..000000000000 --- a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.app.timezonedetector; - -/** - * System private API to comunicate with time zone detector service. - * - * <p>Used by parts of the Android system with signals associated with the device's time zone to - * provide information to the Time Zone Detector Service. - * - * <p>Use the {@link android.app.timezonedetector.TimeZoneDetector} class rather than going through - * this Binder interface directly. See {@link android.app.timezonedetector.TimeZoneDetectorService} - * for more complete documentation. - * - * - * {@hide} - */ -interface ITimeZoneDetectorService { - void stubbedCall(); -} diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java deleted file mode 100644 index be3c7649a486..000000000000 --- a/core/java/android/app/timezonedetector/TimeZoneDetector.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.app.timezonedetector; - -import android.annotation.SystemService; -import android.content.Context; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.ServiceManager.ServiceNotFoundException; -import android.util.Log; - -/** - * The interface through which system components can send signals to the TimeZoneDetectorService. - * @hide - */ -@SystemService(Context.TIME_ZONE_DETECTOR_SERVICE) -public final class TimeZoneDetector { - - private static final String TAG = "timezonedetector.TimeZoneDetector"; - private static final boolean DEBUG = false; - - private final ITimeZoneDetectorService mITimeZoneDetectorService; - - public TimeZoneDetector() throws ServiceNotFoundException { - mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface( - ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE)); - - } - /** - * Does nothing. - * TODO: Remove this when the service implementation is built out. - */ - public void stubbedCall() { - if (DEBUG) { - Log.d(TAG, "stubbedCall called"); - } - try { - mITimeZoneDetectorService.stubbedCall(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } -} diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 2803856fb3bc..23f29041487d 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -2175,8 +2175,7 @@ public final class BluetoothDevice implements Parcelable { * encrypted. * <p> Use this socket if an authenticated socket link is possible. Authentication refers * to the authentication of the link key to prevent man-in-the-middle type of attacks. When a - * secure socket connection is not possible, use {#link createInsecureLeL2capCocSocket(int, - * int)}. + * secure socket connection is not possible, use {#link createInsecureL2capChannel(int)}. * * @param psm dynamic PSM value from remote device * @return a CoC #BluetoothSocket ready for an outgoing connection diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 8959540ff7c3..b072740ccfdf 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -49,7 +49,6 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; -import android.net.NetworkStack; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -3087,7 +3086,6 @@ public abstract class Context { CROSS_PROFILE_APPS_SERVICE, //@hide: SYSTEM_UPDATE_SERVICE, //@hide: TIME_DETECTOR_SERVICE, - //@hide: TIME_ZONE_DETECTOR_SERVICE, }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} @@ -3515,11 +3513,10 @@ public abstract class Context { public static final String NETD_SERVICE = "netd"; /** - * Use with {@link #getSystemService(String)} to retrieve a - * {@link NetworkStack} for communicating with the network stack + * Use with {@link android.os.ServiceManager.getService()} to retrieve a + * {@link NetworkStackClient} IBinder for communicating with the network stack * @hide - * @see #getSystemService(String) - * @see NetworkStack + * @see NetworkStackClient */ public static final String NETWORK_STACK_SERVICE = "network_stack"; @@ -4290,19 +4287,18 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve an - * {@link android.app.timezonedetector.ITimeZoneDetectorService}. + * {@link android.telephony.ims.RcsManager}. * @hide - * - * @see #getSystemService(String) */ - public static final String TIME_ZONE_DETECTOR_SERVICE = "time_zone_detector"; + public static final String TELEPHONY_RCS_SERVICE = "ircs"; - /** + /** * Use with {@link #getSystemService(String)} to retrieve an - * {@link android.telephony.ims.RcsManager}. + * {@link android.os.DynamicAndroidManager}. * @hide */ - public static final String TELEPHONY_RCS_SERVICE = "ircs"; + @SystemApi + public static final String DYNAMIC_ANDROID_SERVICE = "dynamic_android"; /** * Determine whether the given permission is allowed for a particular diff --git a/core/java/android/content/DynamicAndroidClient.java b/core/java/android/content/DynamicAndroidClient.java new file mode 100644 index 000000000000..571cba429ea9 --- /dev/null +++ b/core/java/android/content/DynamicAndroidClient.java @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2019 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.content; + +import android.annotation.CallbackExecutor; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.util.Slog; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; +import java.util.concurrent.Executor; + +/** + * This class contains methods and constants used to start DynamicAndroid + * installation, and a listener for progress update. + * @hide + */ +@SystemApi +public class DynamicAndroidClient { + /** @hide */ + @IntDef(prefix = { "STATUS_" }, value = { + STATUS_UNKNOWN, + STATUS_NOT_STARTED, + STATUS_IN_PROGRESS, + STATUS_READY, + STATUS_IN_USE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface InstallationStatus {} + + /** @hide */ + @IntDef(prefix = { "CAUSE_" }, value = { + CAUSE_NOT_SPECIFIED, + CAUSE_INSTALL_COMPLETED, + CAUSE_INSTALL_CANCELLED, + CAUSE_ERROR_IO, + CAUSE_ERROR_INVALID_URL, + CAUSE_ERROR_IPC, + CAUSE_ERROR_EXCEPTION, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StatusChangedCause {} + + private static final String TAG = "DynAndroidClient"; + + private static final long DEFAULT_USERDATA_SIZE = (10L << 30); + + + /** Listener for installation status update. */ + public interface OnStatusChangedListener { + /** + * This callback is called when installation status is changed, and when the + * client is {@link #bind} to DynamicAndroid installation service. + * + * @param status status code, also defined in {@code DynamicAndroidClient}. + * @param cause cause code, also defined in {@code DynamicAndroidClient}. + * @param progress number of bytes installed. + */ + void onStatusChanged(@InstallationStatus int status, @StatusChangedCause int cause, + long progress); + } + + /* + * Status codes + */ + /** We are bound to installation service, but failed to get its status */ + public static final int STATUS_UNKNOWN = 0; + + /** Installation is not started yet. */ + public static final int STATUS_NOT_STARTED = 1; + + /** Installation is in progress. */ + public static final int STATUS_IN_PROGRESS = 2; + + /** Installation is finished but the user has not launched it. */ + public static final int STATUS_READY = 3; + + /** Device is running in Dynamic Android. */ + public static final int STATUS_IN_USE = 4; + + /* + * Causes + */ + /** Cause is not specified. This means the status is not changed. */ + public static final int CAUSE_NOT_SPECIFIED = 0; + + /** Status changed because installation is completed. */ + public static final int CAUSE_INSTALL_COMPLETED = 1; + + /** Status changed because installation is cancelled. */ + public static final int CAUSE_INSTALL_CANCELLED = 2; + + /** Installation failed due to IOException. */ + public static final int CAUSE_ERROR_IO = 3; + + /** Installation failed because the image URL source is not supported. */ + public static final int CAUSE_ERROR_INVALID_URL = 4; + + /** Installation failed due to IPC error. */ + public static final int CAUSE_ERROR_IPC = 5; + + /** Installation failed due to unhandled exception. */ + public static final int CAUSE_ERROR_EXCEPTION = 6; + + /* + * IPC Messages + */ + /** + * Message to register listener. + * @hide + */ + public static final int MSG_REGISTER_LISTENER = 1; + + /** + * Message to unregister listener. + * @hide + */ + public static final int MSG_UNREGISTER_LISTENER = 2; + + /** + * Message for status update. + * @hide + */ + public static final int MSG_POST_STATUS = 3; + + /* + * Messages keys + */ + /** + * Message key, for progress update. + * @hide + */ + public static final String KEY_INSTALLED_SIZE = "KEY_INSTALLED_SIZE"; + + /* + * Intent Actions + */ + /** + * Intent action: start installation. + * @hide + */ + public static final String ACTION_START_INSTALL = + "android.content.action.START_INSTALL"; + + /** + * Intent action: notify user if we are currently running in Dynamic Android. + * @hide + */ + public static final String ACTION_NOTIFY_IF_IN_USE = + "android.content.action.NOTIFY_IF_IN_USE"; + + /* + * Intent Keys + */ + /** + * Intent key: URL to system image. + * @hide + */ + public static final String KEY_SYSTEM_URL = "KEY_SYSTEM_URL"; + + /** + * Intent key: Size of system image, in bytes. + * @hide + */ + public static final String KEY_SYSTEM_SIZE = "KEY_SYSTEM_SIZE"; + + /** + * Intent key: Number of bytes to reserve for userdata. + * @hide + */ + public static final String KEY_USERDATA_SIZE = "KEY_USERDATA_SIZE"; + + + private static class IncomingHandler extends Handler { + private final WeakReference<DynamicAndroidClient> mWeakClient; + + IncomingHandler(DynamicAndroidClient service) { + super(Looper.getMainLooper()); + mWeakClient = new WeakReference<>(service); + } + + @Override + public void handleMessage(Message msg) { + DynamicAndroidClient service = mWeakClient.get(); + + if (service != null) { + service.handleMessage(msg); + } + } + } + + private class DynAndroidServiceConnection implements ServiceConnection { + public void onServiceConnected(ComponentName className, IBinder service) { + Slog.v(TAG, "DynAndroidService connected"); + + mService = new Messenger(service); + + try { + Message msg = Message.obtain(null, MSG_REGISTER_LISTENER); + msg.replyTo = mMessenger; + + mService.send(msg); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to get status from installation service"); + mExecutor.execute(() -> { + mListener.onStatusChanged(STATUS_UNKNOWN, CAUSE_ERROR_IPC, 0); + }); + } + } + + public void onServiceDisconnected(ComponentName className) { + Slog.v(TAG, "DynAndroidService disconnected"); + mService = null; + } + } + + private final Context mContext; + private final DynAndroidServiceConnection mConnection; + private final Messenger mMessenger; + + private boolean mBound; + private Executor mExecutor; + private OnStatusChangedListener mListener; + private Messenger mService; + + /** + * @hide + */ + @SystemApi + public DynamicAndroidClient(@NonNull Context context) { + mContext = context; + mConnection = new DynAndroidServiceConnection(); + mMessenger = new Messenger(new IncomingHandler(this)); + } + + /** + * This method register a listener for status change. The listener is called using + * the executor. + */ + public void setOnStatusChangedListener( + @NonNull OnStatusChangedListener listener, + @NonNull @CallbackExecutor Executor executor) { + mListener = listener; + mExecutor = executor; + } + + /** + * This method register a listener for status change. The listener is called in main + * thread. + */ + public void setOnStatusChangedListener( + @NonNull OnStatusChangedListener listener) { + mListener = listener; + mExecutor = null; + } + + /** + * Bind to DynamicAndroidInstallationService. + */ + public void bind() { + Intent intent = new Intent(); + intent.setClassName("com.android.dynandroid", + "com.android.dynandroid.DynamicAndroidInstallationService"); + + mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + + mBound = true; + } + + /** + * Unbind from DynamicAndroidInstallationService. + */ + public void unbind() { + if (!mBound) { + return; + } + + if (mService != null) { + try { + Message msg = Message.obtain(null, MSG_UNREGISTER_LISTENER); + msg.replyTo = mMessenger; + mService.send(msg); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to unregister from installation service"); + } + } + + // Detach our existing connection. + mContext.unbindService(mConnection); + + mBound = false; + } + + /** + * Start installing DynamicAndroid from URL with default userdata size. + * + * @param systemUrl A network URL or a file URL to system image. + * @param systemSize size of system image. + */ + public void start(String systemUrl, long systemSize) { + start(systemUrl, systemSize, DEFAULT_USERDATA_SIZE); + } + + /** + * Start installing DynamicAndroid from URL. + * + * @param systemUrl A network URL or a file URL to system image. + * @param systemSize size of system image. + * @param userdataSize bytes reserved for userdata. + */ + public void start(String systemUrl, long systemSize, long userdataSize) { + Intent intent = new Intent(); + + intent.setClassName("com.android.dynandroid", + "com.android.dynandroid.VerificationActivity"); + + intent.setAction(ACTION_START_INSTALL); + + intent.putExtra(KEY_SYSTEM_URL, systemUrl); + intent.putExtra(KEY_SYSTEM_SIZE, systemSize); + intent.putExtra(KEY_USERDATA_SIZE, userdataSize); + + mContext.startActivity(intent); + } + + private void handleMessage(Message msg) { + switch (msg.what) { + case MSG_POST_STATUS: + int status = msg.arg1; + int cause = msg.arg2; + // obj is non-null + long progress = ((Bundle) msg.obj).getLong(KEY_INSTALLED_SIZE); + + if (mExecutor != null) { + mExecutor.execute(() -> { + mListener.onStatusChanged(status, cause, progress); + }); + } else { + mListener.onStatusChanged(status, cause, progress); + } + break; + default: + // do nothing + + } + } +} diff --git a/core/java/android/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java index b2288fc4492d..87568e857d6d 100644 --- a/core/java/android/ddm/DdmHandleHello.java +++ b/core/java/android/ddm/DdmHandleHello.java @@ -16,14 +16,16 @@ package android.ddm; -import org.apache.harmony.dalvik.ddmc.Chunk; -import org.apache.harmony.dalvik.ddmc.ChunkHandler; -import org.apache.harmony.dalvik.ddmc.DdmServer; -import android.util.Log; import android.os.Debug; import android.os.UserHandle; +import android.util.Log; + import dalvik.system.VMRuntime; +import org.apache.harmony.dalvik.ddmc.Chunk; +import org.apache.harmony.dalvik.ddmc.ChunkHandler; +import org.apache.harmony.dalvik.ddmc.DdmServer; + import java.nio.ByteBuffer; /** @@ -35,6 +37,8 @@ public class DdmHandleHello extends ChunkHandler { public static final int CHUNK_WAIT = type("WAIT"); public static final int CHUNK_FEAT = type("FEAT"); + private static final int CLIENT_PROTOCOL_VERSION = 1; + private static DdmHandleHello mInstance = new DdmHandleHello(); private static final String[] FRAMEWORK_FEATURES = new String[] { @@ -145,7 +149,7 @@ public class DdmHandleHello extends ChunkHandler { + vmFlags.length() * 2 + 1); out.order(ChunkHandler.CHUNK_ORDER); - out.putInt(DdmServer.CLIENT_PROTOCOL_VERSION); + out.putInt(CLIENT_PROTOCOL_VERSION); out.putInt(android.os.Process.myPid()); out.putInt(vmIdent.length()); out.putInt(appName.length()); diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index f47ada6ae557..92b30a440d69 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -68,6 +68,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.Socket; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -177,10 +178,10 @@ public class ConnectivityManager { * The lookup key for a {@link NetworkInfo} object. Retrieve with * {@link android.content.Intent#getParcelableExtra(String)}. * - * @deprecated Since {@link NetworkInfo} can vary based on UID, applications - * should always obtain network information through - * {@link #getActiveNetworkInfo()}. - * @see #EXTRA_NETWORK_TYPE + * @deprecated The {@link NetworkInfo} object is deprecated, as many of its properties + * can't accurately represent modern network characteristics. + * Please obtain information about networks from the {@link NetworkCapabilities} + * or {@link LinkProperties} objects instead. */ @Deprecated public static final String EXTRA_NETWORK_INFO = "networkInfo"; @@ -189,7 +190,11 @@ public class ConnectivityManager { * Network type which triggered a {@link #CONNECTIVITY_ACTION} broadcast. * * @see android.content.Intent#getIntExtra(String, int) + * @deprecated The network type is not rich enough to represent the characteristics + * of modern networks. Please use {@link NetworkCapabilities} instead, + * in particular the transports. */ + @Deprecated public static final String EXTRA_NETWORK_TYPE = "networkType"; /** @@ -673,11 +678,20 @@ public class ConnectivityManager { @Deprecated public static final int TYPE_VPN = 17; + /** + * A network that is exclusively meant to be used for testing + * + * @deprecated Use {@link NetworkCapabilities} instead. + * @hide + */ + @Deprecated + public static final int TYPE_TEST = 18; // TODO: Remove this once NetworkTypes are unused. + /** {@hide} */ - public static final int MAX_RADIO_TYPE = TYPE_VPN; + public static final int MAX_RADIO_TYPE = TYPE_TEST; /** {@hide} */ - public static final int MAX_NETWORK_TYPE = TYPE_VPN; + public static final int MAX_NETWORK_TYPE = TYPE_TEST; private static final int MIN_NETWORK_TYPE = TYPE_MOBILE; @@ -1243,9 +1257,13 @@ public class ConnectivityManager { * is no current default network. * * {@hide} + * @deprecated please use {@link #getLinkProperties(Network)} on the return + * value of {@link #getActiveNetwork()} instead. In particular, + * this method will return non-null LinkProperties even if the + * app is blocked by policy from using this network. */ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 109783091) public LinkProperties getActiveLinkProperties() { try { return mService.getActiveLinkProperties(); @@ -1816,7 +1834,7 @@ public class ConnectivityManager { @Override public void handleMessage(Message message) { switch (message.what) { - case NetworkAgent.EVENT_PACKET_KEEPALIVE: + case NetworkAgent.EVENT_SOCKET_KEEPALIVE: int error = message.arg2; try { if (error == SUCCESS) { @@ -1881,7 +1899,8 @@ public class ConnectivityManager { * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive * changes. Must be extended by applications that use this API. * - * @return A {@link SocketKeepalive} object, which can be used to control this keepalive object. + * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the + * given socket. **/ public SocketKeepalive createSocketKeepalive(@NonNull Network network, @NonNull UdpEncapsulationSocket socket, @@ -1910,6 +1929,8 @@ public class ConnectivityManager { * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive * changes. Must be extended by applications that use this API. * + * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the + * given socket. * @hide */ @SystemApi @@ -1925,6 +1946,34 @@ public class ConnectivityManager { } /** + * Request that keepalives be started on a TCP socket. + * The socket must be established. + * + * @param network The {@link Network} the socket is on. + * @param socket The socket that needs to be kept alive. + * @param executor The executor on which callback will be invoked. This implementation assumes + * the provided {@link Executor} runs the callbacks in sequence with no + * concurrency. Failing this, no guarantee of correctness can be made. It is + * the responsibility of the caller to ensure the executor provides this + * guarantee. A simple way of creating such an executor is with the standard + * tool {@code Executors.newSingleThreadExecutor}. + * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive + * changes. Must be extended by applications that use this API. + * + * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the + * given socket. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) + public SocketKeepalive createSocketKeepalive(@NonNull Network network, + @NonNull Socket socket, + @NonNull Executor executor, + @NonNull Callback callback) { + return new TcpSocketKeepalive(mService, network, socket, executor, callback); + } + + /** * Ensure that a network route exists to deliver traffic to the specified * host via the specified network interface. An attempt to add a route that * already exists is ignored, but treated as successful. @@ -2581,6 +2630,7 @@ public class ConnectivityManager { } /** {@hide} */ + @SystemApi public static final int TETHER_ERROR_NO_ERROR = 0; /** {@hide} */ public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; @@ -2603,9 +2653,13 @@ public class ConnectivityManager { /** {@hide} */ public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; /** {@hide} */ + @SystemApi public static final int TETHER_ERROR_PROVISION_FAILED = 11; /** {@hide} */ public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; + /** {@hide} */ + @SystemApi + public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; /** * Get a more detailed error code after a Tethering or Untethering @@ -2628,6 +2682,65 @@ public class ConnectivityManager { } /** + * Callback for use with {@link #getLatestTetheringEntitlementValue} to find out whether + * entitlement succeeded. + * @hide + */ + @SystemApi + public abstract static class TetheringEntitlementValueListener { + /** + * Called to notify entitlement result. + * + * @param resultCode a int value of entitlement result. It may be one of + * {@link #TETHER_ERROR_NO_ERROR}, + * {@link #TETHER_ERROR_PROVISION_FAILED}, or + * {@link #TETHER_ERROR_ENTITLEMENT_UNKONWN}. + */ + public void onEntitlementResult(int resultCode) {} + } + + /** + * Get the last value of the entitlement check on this downstream. If the cached value is + * {@link #TETHER_ERROR_NO_ERROR} or showEntitlementUi argument is false, it just return the + * cached value. Otherwise, a UI-based entitlement check would be performed. It is not + * guaranteed that the UI-based entitlement check will complete in any specific time period + * and may in fact never complete. Any successful entitlement check the platform performs for + * any reason will update the cached value. + * + * @param type the downstream type of tethering. Must be one of + * {@link #TETHERING_WIFI}, + * {@link #TETHERING_USB}, or + * {@link #TETHERING_BLUETOOTH}. + * @param showEntitlementUi a boolean indicating whether to run UI-based entitlement check. + * @param listener an {@link TetheringEntitlementValueListener} which will be called to notify + * the caller of the result of entitlement check. The listener may be called zero or + * one time. + * @param handler {@link Handler} to specify the thread upon which the listener will be invoked. + * {@hide} + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void getLatestTetheringEntitlementValue(int type, boolean showEntitlementUi, + @NonNull final TetheringEntitlementValueListener listener, @Nullable Handler handler) { + Preconditions.checkNotNull(listener, "TetheringEntitlementValueListener cannot be null."); + ResultReceiver wrappedListener = new ResultReceiver(handler) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + listener.onEntitlementResult(resultCode); + } + }; + + try { + String pkgName = mContext.getOpPackageName(); + Log.i(TAG, "getLatestTetheringEntitlementValue:" + pkgName); + mService.getLatestTetheringEntitlementValue(type, wrappedListener, + showEntitlementUi, pkgName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Report network connectivity status. This is currently used only * to alter status bar UI. * <p>This method requires the caller to hold the permission @@ -3803,6 +3916,26 @@ public class ConnectivityManager { } /** + * Requests that the system open the captive portal app with the specified extras. + * + * <p>This endpoint is exclusively for use by the NetworkStack and is protected by the + * corresponding permission. + * @param network Network on which the captive portal was detected. + * @param appExtras Extras to include in the app start intent. + * @hide + */ + @SystemApi + @TestApi + @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) + public void startCaptivePortalApp(Network network, Bundle appExtras) { + try { + mService.startCaptivePortalAppInternal(network, appExtras); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Determine whether the device is configured to avoid bad wifi. * @hide */ @@ -3942,10 +4075,17 @@ public class ConnectivityManager { @Deprecated public static boolean setProcessDefaultNetwork(@Nullable Network network) { int netId = (network == null) ? NETID_UNSET : network.netId; - if (netId == NetworkUtils.getBoundNetworkForProcess()) { - return true; + boolean isSameNetId = (netId == NetworkUtils.getBoundNetworkForProcess()); + + if (netId != NETID_UNSET) { + netId = network.getNetIdForResolv(); } - if (NetworkUtils.bindProcessToNetwork(netId)) { + + if (!NetworkUtils.bindProcessToNetwork(netId)) { + return false; + } + + if (!isSameNetId) { // Set HTTP proxy system properties to match network. // TODO: Deprecate this static method and replace it with a non-static version. try { @@ -3959,10 +4099,9 @@ public class ConnectivityManager { // Must flush socket pool as idle sockets will be bound to previous network and may // cause subsequent fetches to be performed on old network. NetworkEventDispatcher.getInstance().onNetworkConfigurationChanged(); - return true; - } else { - return false; } + + return true; } /** diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index fd7360fd4c17..83bb3a0c8c15 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -27,6 +27,7 @@ import android.net.NetworkQuotaInfo; import android.net.NetworkRequest; import android.net.NetworkState; import android.net.ProxyInfo; +import android.os.Bundle; import android.os.IBinder; import android.os.Messenger; import android.os.ParcelFileDescriptor; @@ -167,6 +168,7 @@ interface IConnectivityManager void setAcceptUnvalidated(in Network network, boolean accept, boolean always); void setAvoidUnvalidated(in Network network); void startCaptivePortalApp(in Network network); + void startCaptivePortalAppInternal(in Network network, in Bundle appExtras); boolean getAvoidBadWifi(); int getMultipathPreference(in Network Network); @@ -188,6 +190,9 @@ interface IConnectivityManager int intervalSeconds, in Messenger messenger, in IBinder binder, String srcAddr, String dstAddr); + void startTcpKeepalive(in Network network, in FileDescriptor fd, int intervalSeconds, + in Messenger messenger, in IBinder binder); + void stopKeepalive(in Network network, int slot); String getCaptivePortalServerUrl(); @@ -197,4 +202,7 @@ interface IConnectivityManager int getConnectionOwnerUid(in ConnectionInfo connectionInfo); boolean isCallerCurrentAlwaysOnVpnApp(); boolean isCallerCurrentAlwaysOnVpnLockdownApp(); + + void getLatestTetheringEntitlementValue(int type, in ResultReceiver receiver, + boolean showEntitlementUi, String callerPkg); } diff --git a/core/java/android/net/INetworkMonitor.aidl b/core/java/android/net/INetworkMonitor.aidl index 41f969acad6f..c94cdde15aa8 100644 --- a/core/java/android/net/INetworkMonitor.aidl +++ b/core/java/android/net/INetworkMonitor.aidl @@ -34,6 +34,7 @@ oneway interface INetworkMonitor { void start(); void launchCaptivePortalApp(); + void notifyCaptivePortalAppFinished(int response); void forceReevaluation(int uid); void notifyPrivateDnsChanged(in PrivateDnsConfigParcel config); void notifyDnsResponse(int returnCode); diff --git a/core/java/android/net/INetworkMonitorCallbacks.aidl b/core/java/android/net/INetworkMonitorCallbacks.aidl index a8682f9ddd3b..2c61511feb72 100644 --- a/core/java/android/net/INetworkMonitorCallbacks.aidl +++ b/core/java/android/net/INetworkMonitorCallbacks.aidl @@ -24,7 +24,6 @@ oneway interface INetworkMonitorCallbacks { void onNetworkMonitorCreated(in INetworkMonitor networkMonitor); void notifyNetworkTested(int testResult, @nullable String redirectUrl); void notifyPrivateDnsConfigResolved(in PrivateDnsConfigParcel config); - void showProvisioningNotification(String action); + void showProvisioningNotification(String action, String packageName); void hideProvisioningNotification(); - void logCaptivePortalLoginEvent(int eventId, String packageName); }
\ No newline at end of file diff --git a/core/java/android/net/InterfaceConfiguration.java b/core/java/android/net/InterfaceConfiguration.java index 62cf7d7ceb25..b9d49c14f6c6 100644 --- a/core/java/android/net/InterfaceConfiguration.java +++ b/core/java/android/net/InterfaceConfiguration.java @@ -36,8 +36,9 @@ public class InterfaceConfiguration implements Parcelable { private LinkAddress mAddr; private HashSet<String> mFlags = Sets.newHashSet(); - private static final String FLAG_UP = INetd.IF_STATE_UP; - private static final String FLAG_DOWN = INetd.IF_STATE_DOWN; + // Must be kept in sync with constant in INetd.aidl + private static final String FLAG_UP = "up"; + private static final String FLAG_DOWN = "down"; private static final String[] EMPTY_STRING_ARRAY = new String[0]; diff --git a/core/java/android/net/KeepalivePacketData.java b/core/java/android/net/KeepalivePacketData.java index 7436ad08789e..18726f748d30 100644 --- a/core/java/android/net/KeepalivePacketData.java +++ b/core/java/android/net/KeepalivePacketData.java @@ -16,22 +16,20 @@ package android.net; -import static android.net.ConnectivityManager.PacketKeepalive.*; +import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS; +import static android.net.SocketKeepalive.ERROR_INVALID_PORT; +import android.net.SocketKeepalive.InvalidPacketException; import android.net.util.IpUtils; import android.os.Parcel; import android.os.Parcelable; -import android.system.OsConstants; import android.util.Log; -import java.net.Inet4Address; import java.net.InetAddress; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; /** * Represents the actual packets that are sent by the - * {@link android.net.ConnectivityManager.PacketKeepalive} API. + * {@link android.net.SocketKeepalive} API. * * @hide */ @@ -53,8 +51,8 @@ public class KeepalivePacketData implements Parcelable { /** Packet data. A raw byte string of packet data, not including the link-layer header. */ private final byte[] mPacket; - private static final int IPV4_HEADER_LENGTH = 20; - private static final int UDP_HEADER_LENGTH = 8; + protected static final int IPV4_HEADER_LENGTH = 20; + protected static final int UDP_HEADER_LENGTH = 8; // This should only be constructed via static factory methods, such as // nattKeepalivePacket @@ -80,53 +78,10 @@ public class KeepalivePacketData implements Parcelable { } } - public static class InvalidPacketException extends Exception { - public final int error; - public InvalidPacketException(int error) { - this.error = error; - } - } - public byte[] getPacket() { return mPacket.clone(); } - public static KeepalivePacketData nattKeepalivePacket( - InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort) - throws InvalidPacketException { - - if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) { - throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); - } - - if (dstPort != NATT_PORT) { - throw new InvalidPacketException(ERROR_INVALID_PORT); - } - - int length = IPV4_HEADER_LENGTH + UDP_HEADER_LENGTH + 1; - ByteBuffer buf = ByteBuffer.allocate(length); - buf.order(ByteOrder.BIG_ENDIAN); - buf.putShort((short) 0x4500); // IP version and TOS - buf.putShort((short) length); - buf.putInt(0); // ID, flags, offset - buf.put((byte) 64); // TTL - buf.put((byte) OsConstants.IPPROTO_UDP); - int ipChecksumOffset = buf.position(); - buf.putShort((short) 0); // IP checksum - buf.put(srcAddress.getAddress()); - buf.put(dstAddress.getAddress()); - buf.putShort((short) srcPort); - buf.putShort((short) dstPort); - buf.putShort((short) (length - 20)); // UDP length - int udpChecksumOffset = buf.position(); - buf.putShort((short) 0); // UDP checksum - buf.put((byte) 0xff); // NAT-T keepalive - buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0)); - buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV4_HEADER_LENGTH)); - - return new KeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array()); - } - /* Parcelable Implementation */ public int describeContents() { return 0; @@ -141,7 +96,7 @@ public class KeepalivePacketData implements Parcelable { out.writeByteArray(mPacket); } - private KeepalivePacketData(Parcel in) { + protected KeepalivePacketData(Parcel in) { srcAddress = NetworkUtils.numericToInetAddress(in.readString()); dstAddress = NetworkUtils.numericToInetAddress(in.readString()); srcPort = in.readInt(); diff --git a/core/java/android/net/NattKeepalivePacketData.java b/core/java/android/net/NattKeepalivePacketData.java new file mode 100644 index 000000000000..bdb246f516a2 --- /dev/null +++ b/core/java/android/net/NattKeepalivePacketData.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS; +import static android.net.SocketKeepalive.ERROR_INVALID_PORT; + +import android.net.SocketKeepalive.InvalidPacketException; +import android.net.util.IpUtils; +import android.system.OsConstants; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** @hide */ +public final class NattKeepalivePacketData extends KeepalivePacketData { + + // This should only be constructed via static factory methods, such as + // nattKeepalivePacket + private NattKeepalivePacketData(InetAddress srcAddress, int srcPort, + InetAddress dstAddress, int dstPort, byte[] data) throws + InvalidPacketException { + super(srcAddress, srcPort, dstAddress, dstPort, data); + } + + /** + * Factory method to create Nat-T keepalive packet structure. + */ + public static NattKeepalivePacketData nattKeepalivePacket( + InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort) + throws InvalidPacketException { + + if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) { + throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); + } + + if (dstPort != NattSocketKeepalive.NATT_PORT) { + throw new InvalidPacketException(ERROR_INVALID_PORT); + } + + int length = IPV4_HEADER_LENGTH + UDP_HEADER_LENGTH + 1; + ByteBuffer buf = ByteBuffer.allocate(length); + buf.order(ByteOrder.BIG_ENDIAN); + buf.putShort((short) 0x4500); // IP version and TOS + buf.putShort((short) length); + buf.putInt(0); // ID, flags, offset + buf.put((byte) 64); // TTL + buf.put((byte) OsConstants.IPPROTO_UDP); + int ipChecksumOffset = buf.position(); + buf.putShort((short) 0); // IP checksum + buf.put(srcAddress.getAddress()); + buf.put(dstAddress.getAddress()); + buf.putShort((short) srcPort); + buf.putShort((short) dstPort); + buf.putShort((short) (length - 20)); // UDP length + int udpChecksumOffset = buf.position(); + buf.putShort((short) 0); // UDP checksum + buf.put((byte) 0xff); // NAT-T keepalive + buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0)); + buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV4_HEADER_LENGTH)); + + return new NattKeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array()); + } +} diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 99bfc140f140..7bef69012bf7 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -18,7 +18,6 @@ package android.net; import android.annotation.UnsupportedAppUsage; import android.content.Context; -import android.net.ConnectivityManager.PacketKeepalive; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -154,7 +153,7 @@ public abstract class NetworkAgent extends Handler { * * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics. */ - public static final int CMD_START_PACKET_KEEPALIVE = BASE + 11; + public static final int CMD_START_SOCKET_KEEPALIVE = BASE + 11; /** * Requests that the specified keepalive packet be stopped. @@ -163,20 +162,40 @@ public abstract class NetworkAgent extends Handler { * * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics. */ - public static final int CMD_STOP_PACKET_KEEPALIVE = BASE + 12; + public static final int CMD_STOP_SOCKET_KEEPALIVE = BASE + 12; /** - * Sent by the NetworkAgent to ConnectivityService to provide status on a packet keepalive - * request. This may either be the reply to a CMD_START_PACKET_KEEPALIVE, or an asynchronous + * Sent by the NetworkAgent to ConnectivityService to provide status on a socket keepalive + * request. This may either be the reply to a CMD_START_SOCKET_KEEPALIVE, or an asynchronous * error notification. * - * This is also sent by KeepaliveTracker to the app's ConnectivityManager.PacketKeepalive to - * so that the app's PacketKeepaliveCallback methods can be called. + * This is also sent by KeepaliveTracker to the app's {@link SocketKeepalive}, + * so that the app's {@link SocketKeepalive.Callback} methods can be called. * * arg1 = slot number of the keepalive * arg2 = error code */ - public static final int EVENT_PACKET_KEEPALIVE = BASE + 13; + public static final int EVENT_SOCKET_KEEPALIVE = BASE + 13; + + // TODO: move the above 2 constants down so they are in order once merge conflicts are resolved + /** + * Sent by the KeepaliveTracker to NetworkAgent to add a packet filter. + * + * For TCP keepalive offloads, keepalive packets are sent by the firmware. However, because the + * remote site will send ACK packets in response to the keepalive packets, the firmware also + * needs to be configured to properly filter the ACKs to prevent the system from waking up. + * This does not happen with UDP, so this message is TCP-specific. + * arg1 = slot number of the keepalive to filter for. + * obj = the keepalive packet to send repeatedly. + */ + public static final int CMD_ADD_KEEPALIVE_PACKET_FILTER = BASE + 16; + + /** + * Sent by the KeepaliveTracker to NetworkAgent to remove a packet filter. See + * {@link #CMD_ADD_KEEPALIVE_PACKET_FILTER}. + * arg1 = slot number of the keepalive packet filter to remove. + */ + public static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER = BASE + 17; /** * Sent by ConnectivityService to inform this network transport of signal strength thresholds @@ -288,12 +307,12 @@ public abstract class NetworkAgent extends Handler { saveAcceptUnvalidated(msg.arg1 != 0); break; } - case CMD_START_PACKET_KEEPALIVE: { - startPacketKeepalive(msg); + case CMD_START_SOCKET_KEEPALIVE: { + startSocketKeepalive(msg); break; } - case CMD_STOP_PACKET_KEEPALIVE: { - stopPacketKeepalive(msg); + case CMD_STOP_SOCKET_KEEPALIVE: { + stopSocketKeepalive(msg); break; } @@ -313,6 +332,14 @@ public abstract class NetworkAgent extends Handler { preventAutomaticReconnect(); break; } + case CMD_ADD_KEEPALIVE_PACKET_FILTER: { + addKeepalivePacketFilter(msg); + break; + } + case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: { + removeKeepalivePacketFilter(msg); + break; + } } } @@ -443,22 +470,40 @@ public abstract class NetworkAgent extends Handler { /** * Requests that the network hardware send the specified packet at the specified interval. */ - protected void startPacketKeepalive(Message msg) { - onPacketKeepaliveEvent(msg.arg1, PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED); + protected void startSocketKeepalive(Message msg) { + onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED); } /** - * Requests that the network hardware send the specified packet at the specified interval. + * Requests that the network hardware stops sending keepalive packets. + */ + protected void stopSocketKeepalive(Message msg) { + onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED); + } + + /** + * Called by the network when a socket keepalive event occurs. + */ + public void onSocketKeepaliveEvent(int slot, int reason) { + queueOrSendMessage(EVENT_SOCKET_KEEPALIVE, slot, reason); + } + + /** + * Called by ConnectivityService to add specific packet filter to network hardware to block + * ACKs matching the sent keepalive packets. Implementations that support this feature must + * override this method. */ - protected void stopPacketKeepalive(Message msg) { - onPacketKeepaliveEvent(msg.arg1, PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED); + protected void addKeepalivePacketFilter(Message msg) { + onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED); } /** - * Called by the network when a packet keepalive event occurs. + * Called by ConnectivityService to remove a packet filter installed with + * {@link #addKeepalivePacketFilter(Message)}. Implementations that support this feature + * must override this method. */ - public void onPacketKeepaliveEvent(int slot, int reason) { - queueOrSendMessage(EVENT_PACKET_KEEPALIVE, slot, reason); + protected void removeKeepalivePacketFilter(Message msg) { + onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED); } /** diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 7e9bda14b199..1d2d81dc4fbe 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -597,6 +597,7 @@ public final class NetworkCapabilities implements Parcelable { TRANSPORT_VPN, TRANSPORT_WIFI_AWARE, TRANSPORT_LOWPAN, + TRANSPORT_TEST, }) public @interface Transport { } @@ -635,10 +636,18 @@ public final class NetworkCapabilities implements Parcelable { */ public static final int TRANSPORT_LOWPAN = 6; + /** + * Indicates this network uses a Test-only virtual interface as a transport. + * + * @hide + */ + @TestApi + public static final int TRANSPORT_TEST = 7; + /** @hide */ public static final int MIN_TRANSPORT = TRANSPORT_CELLULAR; /** @hide */ - public static final int MAX_TRANSPORT = TRANSPORT_LOWPAN; + public static final int MAX_TRANSPORT = TRANSPORT_TEST; /** @hide */ public static boolean isValidTransport(@Transport int transportType) { @@ -652,7 +661,8 @@ public final class NetworkCapabilities implements Parcelable { "ETHERNET", "VPN", "WIFI_AWARE", - "LOWPAN" + "LOWPAN", + "TEST" }; /** diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 5ab34e9aa6e8..bf272625e713 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -92,16 +92,6 @@ public class NetworkPolicyManager { public static final int MASK_ALL_NETWORKS = 0b11110000; public static final int FIREWALL_RULE_DEFAULT = 0; - public static final int FIREWALL_RULE_ALLOW = INetd.FIREWALL_RULE_ALLOW; - public static final int FIREWALL_RULE_DENY = INetd.FIREWALL_RULE_DENY; - - public static final int FIREWALL_TYPE_WHITELIST = INetd.FIREWALL_WHITELIST; - public static final int FIREWALL_TYPE_BLACKLIST = INetd.FIREWALL_BLACKLIST; - - public static final int FIREWALL_CHAIN_NONE = INetd.FIREWALL_CHAIN_NONE; - public static final int FIREWALL_CHAIN_DOZABLE = INetd.FIREWALL_CHAIN_DOZABLE; - public static final int FIREWALL_CHAIN_STANDBY = INetd.FIREWALL_CHAIN_STANDBY; - public static final int FIREWALL_CHAIN_POWERSAVE = INetd.FIREWALL_CHAIN_POWERSAVE; public static final String FIREWALL_CHAIN_NAME_NONE = "none"; public static final String FIREWALL_CHAIN_NAME_DOZABLE = "dozable"; diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java index ac6bff029e8c..dbb894f92f55 100644 --- a/core/java/android/net/NetworkStack.java +++ b/core/java/android/net/NetworkStack.java @@ -15,240 +15,26 @@ */ package android.net; -import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH; -import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemService; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.net.dhcp.DhcpServingParamsParcel; -import android.net.dhcp.IDhcpServerCallbacks; -import android.net.ip.IIpClientCallbacks; -import android.os.Binder; -import android.os.IBinder; -import android.os.Process; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.UserHandle; -import android.util.Slog; - -import com.android.internal.annotations.GuardedBy; - -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; +import android.annotation.SystemApi; +import android.annotation.TestApi; /** - * Service used to communicate with the network stack, which is running in a separate module. + * + * Constants for client code communicating with the network stack service. * @hide */ -@SystemService(Context.NETWORK_STACK_SERVICE) +@SystemApi +@TestApi public class NetworkStack { - private static final String TAG = NetworkStack.class.getSimpleName(); - - public static final String NETWORKSTACK_PACKAGE_NAME = "com.android.mainline.networkstack"; - - private static final int NETWORKSTACK_TIMEOUT_MS = 10_000; - - @NonNull - @GuardedBy("mPendingNetStackRequests") - private final ArrayList<NetworkStackCallback> mPendingNetStackRequests = new ArrayList<>(); - @Nullable - @GuardedBy("mPendingNetStackRequests") - private INetworkStackConnector mConnector; - - private volatile boolean mNetworkStackStartRequested = false; - - private interface NetworkStackCallback { - void onNetworkStackConnected(INetworkStackConnector connector); - } - - public NetworkStack() { } - - /** - * Create a DHCP server according to the specified parameters. - * - * <p>The server will be returned asynchronously through the provided callbacks. - */ - public void makeDhcpServer(final String ifName, final DhcpServingParamsParcel params, - final IDhcpServerCallbacks cb) { - requestConnector(connector -> { - try { - connector.makeDhcpServer(ifName, params, cb); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - }); - } - /** - * Create an IpClient on the specified interface. - * - * <p>The IpClient will be returned asynchronously through the provided callbacks. + * Permission granted only to the NetworkStack APK, defined in NetworkStackStub with signature + * protection level. + * @hide */ - public void makeIpClient(String ifName, IIpClientCallbacks cb) { - requestConnector(connector -> { - try { - connector.makeIpClient(ifName, cb); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - }); - } - - /** - * Create a NetworkMonitor. - * - * <p>The INetworkMonitor will be returned asynchronously through the provided callbacks. - */ - public void makeNetworkMonitor( - NetworkParcelable network, String name, INetworkMonitorCallbacks cb) { - requestConnector(connector -> { - try { - connector.makeNetworkMonitor(network, name, cb); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - }); - } - - private class NetworkStackConnection implements ServiceConnection { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - registerNetworkStackService(service); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - // TODO: crash/reboot the system ? - Slog.wtf(TAG, "Lost network stack connector"); - } - }; - - private void registerNetworkStackService(@NonNull IBinder service) { - final INetworkStackConnector connector = INetworkStackConnector.Stub.asInterface(service); - - ServiceManager.addService(Context.NETWORK_STACK_SERVICE, service, false /* allowIsolated */, - DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL); - - final ArrayList<NetworkStackCallback> requests; - synchronized (mPendingNetStackRequests) { - requests = new ArrayList<>(mPendingNetStackRequests); - mPendingNetStackRequests.clear(); - mConnector = connector; - } - - for (NetworkStackCallback r : requests) { - r.onNetworkStackConnected(connector); - } - } - - /** - * Start the network stack. Should be called only once on device startup. - * - * <p>This method will start the network stack either in the network stack process, or inside - * the system server on devices that do not support the network stack module. The network stack - * connector will then be delivered asynchronously to clients that requested it before it was - * started. - */ - public void start(Context context) { - mNetworkStackStartRequested = true; - // Try to bind in-process if the library is available - IBinder connector = null; - try { - final Class service = Class.forName( - "com.android.server.NetworkStackService", - true /* initialize */, - context.getClassLoader()); - connector = (IBinder) service.getMethod("makeConnector", Context.class) - .invoke(null, context); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - Slog.wtf(TAG, "Could not create network stack connector from NetworkStackService"); - // TODO: crash/reboot system here ? - return; - } catch (ClassNotFoundException e) { - // Normal behavior if stack is provided by the app: fall through - } - - // In-process network stack. Add the service to the service manager here. - if (connector != null) { - registerNetworkStackService(connector); - return; - } - // Start the network stack process. The service will be added to the service manager in - // NetworkStackConnection.onServiceConnected(). - final Intent intent = new Intent(INetworkStackConnector.class.getName()); - final ComponentName comp = intent.resolveSystemService(context.getPackageManager(), 0); - intent.setComponent(comp); - - if (comp == null || !context.bindServiceAsUser(intent, new NetworkStackConnection(), - Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM)) { - Slog.wtf(TAG, - "Could not bind to network stack in-process, or in app with " + intent); - // TODO: crash/reboot system server if no network stack after a timeout ? - } - } - - /** - * For non-system server clients, get the connector registered by the system server. - */ - private INetworkStackConnector getRemoteConnector() { - // Block until the NetworkStack connector is registered in ServiceManager. - // <p>This is only useful for non-system processes that do not have a way to be notified of - // registration completion. Adding a callback system would be too heavy weight considering - // that the connector is registered on boot, so it is unlikely that a client would request - // it before it is registered. - // TODO: consider blocking boot on registration and simplify much of the logic in this class - IBinder connector; - try { - final long before = System.currentTimeMillis(); - while ((connector = ServiceManager.getService(Context.NETWORK_STACK_SERVICE)) == null) { - Thread.sleep(20); - if (System.currentTimeMillis() - before > NETWORKSTACK_TIMEOUT_MS) { - Slog.e(TAG, "Timeout waiting for NetworkStack connector"); - return null; - } - } - } catch (InterruptedException e) { - Slog.e(TAG, "Error waiting for NetworkStack connector", e); - return null; - } - - return INetworkStackConnector.Stub.asInterface(connector); - } - - private void requestConnector(@NonNull NetworkStackCallback request) { - // TODO: PID check. - final int caller = Binder.getCallingUid(); - if (caller != Process.SYSTEM_UID && caller != Process.BLUETOOTH_UID) { - // Don't even attempt to obtain the connector and give a nice error message - throw new SecurityException( - "Only the system server should try to bind to the network stack."); - } - - if (!mNetworkStackStartRequested) { - // The network stack is not being started in this process, e.g. this process is not - // the system server. Get a remote connector registered by the system server. - final INetworkStackConnector connector = getRemoteConnector(); - synchronized (mPendingNetStackRequests) { - mConnector = connector; - } - request.onNetworkStackConnected(connector); - return; - } - - final INetworkStackConnector connector; - synchronized (mPendingNetStackRequests) { - connector = mConnector; - if (connector == null) { - mPendingNetStackRequests.add(request); - return; - } - } + @SystemApi + @TestApi + public static final String PERMISSION_MAINLINE_NETWORK_STACK = + "android.permission.MAINLINE_NETWORK_STACK"; - request.onNetworkStackConnected(connector); - } + private NetworkStack() {} } diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index 07668a9e3a82..0ae29b125149 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -71,6 +71,18 @@ public class NetworkUtils { throws SocketException; /** + * Attaches a socket filter that drops all of incoming packets. + * @param fd the socket's {@link FileDescriptor}. + */ + public static native void attachDropAllBPFFilter(FileDescriptor fd) throws SocketException; + + /** + * Detaches a socket filter. + * @param fd the socket's {@link FileDescriptor}. + */ + public static native void detachBPFFilter(FileDescriptor fd) throws SocketException; + + /** * Configures a socket for receiving ICMPv6 router solicitations and sending advertisements. * @param fd the socket's {@link FileDescriptor}. * @param ifIndex the interface index. @@ -170,6 +182,16 @@ public class NetworkUtils { private static native void addArpEntry(byte[] ethAddr, byte[] netAddr, String ifname, FileDescriptor fd) throws IOException; + + /** + * Get the tcp repair window associated with the {@code fd}. + * + * @param fd the tcp socket's {@link FileDescriptor}. + * @return a {@link TcpRepairWindow} object indicates tcp window size. + */ + public static native TcpRepairWindow getTcpRepairWindow(FileDescriptor fd) + throws ErrnoException; + /** * @see Inet4AddressUtils#intToInet4AddressHTL(int) * @deprecated Use either {@link Inet4AddressUtils#intToInet4AddressHTH(int)} diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java index 97d50f4bac05..07728beb9c64 100644 --- a/core/java/android/net/SocketKeepalive.java +++ b/core/java/android/net/SocketKeepalive.java @@ -19,6 +19,7 @@ package android.net; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -109,13 +110,53 @@ public abstract class SocketKeepalive implements AutoCloseable { **/ public static final int MAX_INTERVAL_SEC = 3600; + /** + * An exception that embarks an error code. + * @hide + */ + public static class ErrorCodeException extends Exception { + public final int error; + public ErrorCodeException(final int error, final Throwable e) { + super(e); + this.error = error; + } + public ErrorCodeException(final int error) { + this.error = error; + } + } + + /** + * This socket is invalid. + * See the error code for details, and the optional cause. + * @hide + */ + public static class InvalidSocketException extends ErrorCodeException { + public InvalidSocketException(final int error, final Throwable e) { + super(error, e); + } + public InvalidSocketException(final int error) { + super(error); + } + } + + /** + * This packet is invalid. + * See the error code for details. + * @hide + */ + public static class InvalidPacketException extends ErrorCodeException { + public InvalidPacketException(final int error) { + super(error); + } + } + @NonNull final IConnectivityManager mService; @NonNull final Network mNetwork; @NonNull private final Executor mExecutor; @NonNull private final SocketKeepalive.Callback mCallback; @NonNull private final Looper mLooper; @NonNull final Messenger mMessenger; - @NonNull Integer mSlot; + @Nullable Integer mSlot; SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network, @NonNull Executor executor, @NonNull Callback callback) { @@ -135,7 +176,7 @@ public abstract class SocketKeepalive implements AutoCloseable { @Override public void handleMessage(Message message) { switch (message.what) { - case NetworkAgent.EVENT_PACKET_KEEPALIVE: + case NetworkAgent.EVENT_SOCKET_KEEPALIVE: final int status = message.arg2; try { if (status == SUCCESS) { diff --git a/core/java/android/net/TcpKeepalivePacketData.java b/core/java/android/net/TcpKeepalivePacketData.java new file mode 100644 index 000000000000..f07dfb64cd01 --- /dev/null +++ b/core/java/android/net/TcpKeepalivePacketData.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net; + +import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.SocketKeepalive.InvalidPacketException; +import android.net.util.IpUtils; +import android.os.Parcel; +import android.os.Parcelable; +import android.system.OsConstants; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Objects; + +/** + * Represents the actual tcp keep alive packets which will be used for hardware offload. + * @hide + */ +public class TcpKeepalivePacketData extends KeepalivePacketData implements Parcelable { + private static final String TAG = "TcpKeepalivePacketData"; + + /** TCP sequence number. */ + public final int tcpSeq; + + /** TCP ACK number. */ + public final int tcpAck; + + /** TCP RCV window. */ + public final int tcpWnd; + + /** TCP RCV window scale. */ + public final int tcpWndScale; + + private static final int IPV4_HEADER_LENGTH = 20; + private static final int IPV6_HEADER_LENGTH = 40; + private static final int TCP_HEADER_LENGTH = 20; + + // This should only be constructed via static factory methods, such as + // tcpKeepalivePacket. + private TcpKeepalivePacketData(TcpSocketInfo tcpDetails, byte[] data) + throws InvalidPacketException { + super(tcpDetails.srcAddress, tcpDetails.srcPort, tcpDetails.dstAddress, + tcpDetails.dstPort, data); + tcpSeq = tcpDetails.seq; + tcpAck = tcpDetails.ack; + // In the packet, the window is shifted right by the window scale. + tcpWnd = tcpDetails.rcvWnd; + tcpWndScale = tcpDetails.rcvWndScale; + } + + /** + * Factory method to create tcp keepalive packet structure. + */ + public static TcpKeepalivePacketData tcpKeepalivePacket( + TcpSocketInfo tcpDetails) throws InvalidPacketException { + final byte[] packet; + if ((tcpDetails.srcAddress instanceof Inet4Address) + && (tcpDetails.dstAddress instanceof Inet4Address)) { + packet = buildV4Packet(tcpDetails); + } else { + // TODO: support ipv6 + throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); + } + + return new TcpKeepalivePacketData(tcpDetails, packet); + } + + /** + * Build ipv4 tcp keepalive packet, not including the link-layer header. + */ + // TODO : if this code is ever moved to the network stack, factorize constants with the ones + // over there. + private static byte[] buildV4Packet(TcpSocketInfo tcpDetails) { + final int length = IPV4_HEADER_LENGTH + TCP_HEADER_LENGTH; + ByteBuffer buf = ByteBuffer.allocate(length); + buf.order(ByteOrder.BIG_ENDIAN); + // IP version and TOS. TODO : fetch this from getsockopt(SOL_IP, IP_TOS) + buf.putShort((short) 0x4500); + buf.putShort((short) length); + buf.putInt(0x4000); // ID, flags=DF, offset + // TODO : fetch TTL from getsockopt(SOL_IP, IP_TTL) + buf.put((byte) 64); + buf.put((byte) OsConstants.IPPROTO_TCP); + final int ipChecksumOffset = buf.position(); + buf.putShort((short) 0); // IP checksum + buf.put(tcpDetails.srcAddress.getAddress()); + buf.put(tcpDetails.dstAddress.getAddress()); + buf.putShort((short) tcpDetails.srcPort); + buf.putShort((short) tcpDetails.dstPort); + buf.putInt(tcpDetails.seq); // Sequence Number + buf.putInt(tcpDetails.ack); // ACK + buf.putShort((short) 0x5010); // TCP length=5, flags=ACK + buf.putShort((short) (tcpDetails.rcvWnd >> tcpDetails.rcvWndScale)); // Window size + final int tcpChecksumOffset = buf.position(); + buf.putShort((short) 0); // TCP checksum + // URG is not set therefore the urgent pointer is not included + buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0)); + buf.putShort(tcpChecksumOffset, IpUtils.tcpChecksum( + buf, 0, IPV4_HEADER_LENGTH, TCP_HEADER_LENGTH)); + + return buf.array(); + } + + // TODO: add buildV6Packet. + + /** Represents tcp/ip information. */ + // TODO: Replace TcpSocketInfo with TcpKeepalivePacketDataParcelable. + public static class TcpSocketInfo { + public final InetAddress srcAddress; + public final InetAddress dstAddress; + public final int srcPort; + public final int dstPort; + public final int seq; + public final int ack; + public final int rcvWnd; + public final int rcvWndScale; + + public TcpSocketInfo(InetAddress sAddr, int sPort, InetAddress dAddr, + int dPort, int writeSeq, int readSeq, int rWnd, int rWndScale) { + srcAddress = sAddr; + dstAddress = dAddr; + srcPort = sPort; + dstPort = dPort; + seq = writeSeq; + ack = readSeq; + rcvWnd = rWnd; + rcvWndScale = rWndScale; + } + } + + @Override + public boolean equals(@Nullable final Object o) { + if (!(o instanceof TcpKeepalivePacketData)) return false; + final TcpKeepalivePacketData other = (TcpKeepalivePacketData) o; + return this.srcAddress.equals(other.srcAddress) + && this.dstAddress.equals(other.dstAddress) + && this.srcPort == other.srcPort + && this.dstPort == other.dstPort + && this.tcpAck == other.tcpAck + && this.tcpSeq == other.tcpSeq + && this.tcpWnd == other.tcpWnd + && this.tcpWndScale == other.tcpWndScale; + } + + @Override + public int hashCode() { + return Objects.hash(srcAddress, dstAddress, srcPort, dstPort, tcpAck, tcpSeq, tcpWnd, + tcpWndScale); + } + + /* Parcelable Implementation. */ + /* Note that this object implements parcelable (and needs to keep doing this as it inherits + * from a class that does), but should usually be parceled as a stable parcelable using + * the toStableParcelable() and fromStableParcelable() methods. + */ + public int describeContents() { + return 0; + } + + /** Write to parcel. */ + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeInt(tcpSeq); + out.writeInt(tcpAck); + out.writeInt(tcpWnd); + out.writeInt(tcpWndScale); + } + + private TcpKeepalivePacketData(Parcel in) { + super(in); + tcpSeq = in.readInt(); + tcpAck = in.readInt(); + tcpWnd = in.readInt(); + tcpWndScale = in.readInt(); + } + + /** Parcelable Creator. */ + public static final Parcelable.Creator<TcpKeepalivePacketData> CREATOR = + new Parcelable.Creator<TcpKeepalivePacketData>() { + public TcpKeepalivePacketData createFromParcel(Parcel in) { + return new TcpKeepalivePacketData(in); + } + + public TcpKeepalivePacketData[] newArray(int size) { + return new TcpKeepalivePacketData[size]; + } + }; + + /** + * Convert this TcpKeepalivePacketData to a TcpKeepalivePacketDataParcelable. + */ + @NonNull + public TcpKeepalivePacketDataParcelable toStableParcelable() { + final TcpKeepalivePacketDataParcelable parcel = new TcpKeepalivePacketDataParcelable(); + parcel.srcAddress = srcAddress.getAddress(); + parcel.srcPort = srcPort; + parcel.dstAddress = dstAddress.getAddress(); + parcel.dstPort = dstPort; + parcel.seq = tcpSeq; + parcel.ack = tcpAck; + return parcel; + } + + @Override + public String toString() { + return "saddr: " + srcAddress + + " daddr: " + dstAddress + + " sport: " + srcPort + + " dport: " + dstPort + + " seq: " + tcpSeq + + " ack: " + tcpAck + + " wnd: " + tcpWnd + + " wndScale: " + tcpWndScale; + } +} diff --git a/telephony/java/android/telephony/ims/RcsPart.java b/core/java/android/net/TcpKeepalivePacketDataParcelable.aidl index da501738a0bf..7329c63b09be 100644 --- a/telephony/java/android/telephony/ims/RcsPart.java +++ b/core/java/android/net/TcpKeepalivePacketDataParcelable.aidl @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.telephony.ims; -import android.os.Parcelable; +package android.net; -/** - * A part of a composite {@link RcsMessage}. - * @hide - TODO(sahinc) make this public - */ -public abstract class RcsPart implements Parcelable { +parcelable TcpKeepalivePacketDataParcelable { + byte[] srcAddress; + int srcPort; + byte[] dstAddress; + int dstPort; + int seq; + int ack; } diff --git a/core/java/android/net/TcpRepairWindow.java b/core/java/android/net/TcpRepairWindow.java new file mode 100644 index 000000000000..86034f0a76ed --- /dev/null +++ b/core/java/android/net/TcpRepairWindow.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +/** + * Corresponds to C's {@code struct tcp_repair_window} from + * include/uapi/linux/tcp.h + * + * @hide + */ +public final class TcpRepairWindow { + public final int sndWl1; + public final int sndWnd; + public final int maxWindow; + public final int rcvWnd; + public final int rcvWup; + public final int rcvWndScale; + + /** + * Constructs an instance with the given field values. + */ + public TcpRepairWindow(final int sndWl1, final int sndWnd, final int maxWindow, + final int rcvWnd, final int rcvWup, final int rcvWndScale) { + this.sndWl1 = sndWl1; + this.sndWnd = sndWnd; + this.maxWindow = maxWindow; + this.rcvWnd = rcvWnd; + this.rcvWup = rcvWup; + this.rcvWndScale = rcvWndScale; + } +} diff --git a/core/java/android/net/TcpSocketKeepalive.java b/core/java/android/net/TcpSocketKeepalive.java new file mode 100644 index 000000000000..8f6ee7bf2950 --- /dev/null +++ b/core/java/android/net/TcpSocketKeepalive.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.NonNull; +import android.os.Binder; +import android.os.RemoteException; +import android.util.Log; + +import java.io.FileDescriptor; +import java.net.Socket; +import java.util.concurrent.Executor; + +/** @hide */ +final class TcpSocketKeepalive extends SocketKeepalive { + + private final Socket mSocket; + + TcpSocketKeepalive(@NonNull IConnectivityManager service, + @NonNull Network network, + @NonNull Socket socket, + @NonNull Executor executor, + @NonNull Callback callback) { + super(service, network, executor, callback); + mSocket = socket; + } + + /** + * Starts keepalives. {@code mSocket} must be a connected TCP socket. + * + * - The application must not write to or read from the socket after calling this method, until + * onDataReceived, onStopped, or onError are called. If it does, the keepalive will fail + * with {@link #ERROR_SOCKET_NOT_IDLE}, or {@code #ERROR_INVALID_SOCKET} if the socket + * experienced an error (as in poll(2) returned POLLERR); if this happens, the data received + * from the socket may be invalid, and the socket can't be recovered. + * - If the socket has data in the send or receive buffer, then this call will fail with + * {@link #ERROR_SOCKET_NOT_IDLE} and can be retried after the data has been processed. + * An app could ensure this by using an application-layer protocol where it can receive + * acknowledgement that it will go into keepalive mode. It could then go into keepalive + * mode after having read the acknowledgement, draining the socket. + */ + @Override + void startImpl(int intervalSec) { + try { + final FileDescriptor fd = mSocket.getFileDescriptor$(); + mService.startTcpKeepalive(mNetwork, fd, intervalSec, mMessenger, new Binder()); + } catch (RemoteException e) { + Log.e(TAG, "Error starting packet keepalive: ", e); + stopLooper(); + } + } + + @Override + void stopImpl() { + try { + if (mSlot != null) { + mService.stopKeepalive(mNetwork, mSlot); + } + } catch (RemoteException e) { + Log.e(TAG, "Error stopping packet keepalive: ", e); + stopLooper(); + } + } +} diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index dc099a46aa2a..784f23311103 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -791,6 +791,27 @@ public class VpnService extends Service { } /** + * Marks the VPN network as metered. A VPN network is classified as metered when the user is + * sensitive to heavy data usage due to monetary costs and/or data limitations. In such + * cases, you should set this to {@code true} so that apps on the system can avoid doing + * large data transfers. Otherwise, set this to {@code false}. Doing so would cause VPN + * network to inherit its meteredness from its underlying networks. + * + * <p>VPN apps targeting {@link android.os.Build.VERSION_CODES#Q} or above will be + * considered metered by default. + * + * @param isMetered {@code true} if VPN network should be treated as metered regardless of + * underlying network meteredness + * @return this {@link Builder} object to facilitate chaining method calls + * @see #setUnderlyingNetworks(Networks[]) + * @see ConnectivityManager#isActiveNetworkMetered() + */ + public Builder setMetered(boolean isMetered) { + mConfig.isMetered = isMetered; + return this; + } + + /** * Create a VPN interface using the parameters supplied to this * builder. The interface works on IP packets, and a file descriptor * is returned for the application to access them. Each read diff --git a/core/java/android/net/ip/IIpClient.aidl b/core/java/android/net/ip/IIpClient.aidl index 7769ec2b65ac..a4a80e1efe6f 100644 --- a/core/java/android/net/ip/IIpClient.aidl +++ b/core/java/android/net/ip/IIpClient.aidl @@ -17,6 +17,7 @@ package android.net.ip; import android.net.ProxyInfoParcelable; import android.net.ProvisioningConfigurationParcelable; +import android.net.TcpKeepalivePacketDataParcelable; /** @hide */ oneway interface IIpClient { @@ -29,4 +30,6 @@ oneway interface IIpClient { void setTcpBufferSizes(in String tcpBufferSizes); void setHttpProxy(in ProxyInfoParcelable proxyInfo); void setMulticastFilter(boolean enabled); + void addKeepalivePacketFilter(int slot, in TcpKeepalivePacketDataParcelable pkt); + void removeKeepalivePacketFilter(int slot); } diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java index 141d33bc4145..a851e04e78ec 100644 --- a/core/java/android/os/AsyncTask.java +++ b/core/java/android/os/AsyncTask.java @@ -19,14 +19,16 @@ package android.os; import android.annotation.MainThread; import android.annotation.Nullable; import android.annotation.WorkerThread; + import java.util.ArrayDeque; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.FutureTask; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -70,7 +72,7 @@ import java.util.concurrent.atomic.AtomicInteger; * protected Long doInBackground(URL... urls) { * int count = urls.length; * long totalSize = 0; - * for (int i = 0; i < count; i++) { + * for (int i = 0; i < count; i++) { * totalSize += Downloader.downloadFile(urls[i]); * publishProgress((int) ((i / (float) count) * 100)); * // Escape early if cancel() is called @@ -158,13 +160,22 @@ import java.util.concurrent.atomic.AtomicInteger; * </ul> * * <h2>Memory observability</h2> - * <p>AsyncTask guarantees that all callback calls are synchronized in such a way that the following - * operations are safe without explicit synchronizations.</p> + * <p>AsyncTask guarantees that all callback calls are synchronized to ensure the following + * without explicit synchronizations.</p> * <ul> - * <li>Set member fields in the constructor or {@link #onPreExecute}, and refer to them - * in {@link #doInBackground}. - * <li>Set member fields in {@link #doInBackground}, and refer to them in - * {@link #onProgressUpdate} and {@link #onPostExecute}. + * <li>The memory effects of {@link #onPreExecute}, and anything else + * executed before the call to {@link #execute}, including the construction + * of the AsyncTask object, are visible to {@link #doInBackground}. + * <li>The memory effects of {@link #doInBackground} are visible to + * {@link #onPostExecute}. + * <li>Any memory effects of {@link #doInBackground} preceding a call + * to {@link #publishProgress} are visible to the corresponding + * {@link #onProgressUpdate} call. (But {@link #doInBackground} continues to + * run, and care needs to be taken that later updates in {@link #doInBackground} + * do not interfere with an in-progress {@link #onProgressUpdate} call.) + * <li>Any memory effects preceding a call to {@link #cancel} are visible + * after a call to {@link #isCancelled} that returns true as a result, or + * during and after a resulting call to {@link #onCancelled}. * </ul> * * <h2>Order of execution</h2> @@ -180,13 +191,19 @@ import java.util.concurrent.atomic.AtomicInteger; public abstract class AsyncTask<Params, Progress, Result> { private static final String LOG_TAG = "AsyncTask"; - private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); - // We want at least 2 threads and at most 4 threads in the core pool, - // preferring to have 1 less than the CPU count to avoid saturating - // the CPU with background work - private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); - private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; - private static final int KEEP_ALIVE_SECONDS = 30; + // We keep only a single pool thread around all the time. + // We let the pool grow to a fairly large number of threads if necessary, + // but let them time out quickly. In the unlikely case that we run out of threads, + // we fall back to a simple unbounded-queue executor. + // This combination ensures that: + // 1. We normally keep few threads (1) around. + // 2. We queue only after launching a significantly larger, but still bounded, set of threads. + // 3. We keep the total number of threads bounded, but still allow an unbounded set + // of tasks to be queued. + private static final int CORE_POOL_SIZE = 1; + private static final int MAXIMUM_POOL_SIZE = 20; + private static final int BACKUP_POOL_SIZE = 5; + private static final int KEEP_ALIVE_SECONDS = 3; private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); @@ -196,8 +213,29 @@ public abstract class AsyncTask<Params, Progress, Result> { } }; - private static final BlockingQueue<Runnable> sPoolWorkQueue = - new LinkedBlockingQueue<Runnable>(128); + // Used only for rejected executions. + // Initialization protected by sRunOnSerialPolicy lock. + private static ThreadPoolExecutor sBackupExecutor; + private static LinkedBlockingQueue<Runnable> sBackupExecutorQueue; + + private static final RejectedExecutionHandler sRunOnSerialPolicy = + new RejectedExecutionHandler() { + public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { + android.util.Log.w(LOG_TAG, "Exceeded ThreadPoolExecutor pool size"); + // As a last ditch fallback, run it on an executor with an unbounded queue. + // Create this executor lazily, hopefully almost never. + synchronized (this) { + if (sBackupExecutor == null) { + sBackupExecutorQueue = new LinkedBlockingQueue<Runnable>(); + sBackupExecutor = new ThreadPoolExecutor( + BACKUP_POOL_SIZE, BACKUP_POOL_SIZE, KEEP_ALIVE_SECONDS, + TimeUnit.SECONDS, sBackupExecutorQueue, sThreadFactory); + sBackupExecutor.allowCoreThreadTimeOut(true); + } + } + sBackupExecutor.execute(r); + } + }; /** * An {@link Executor} that can be used to execute tasks in parallel. @@ -207,8 +245,8 @@ public abstract class AsyncTask<Params, Progress, Result> { static { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, - sPoolWorkQueue, sThreadFactory); - threadPoolExecutor.allowCoreThreadTimeOut(true); + new SynchronousQueue<Runnable>(), sThreadFactory); + threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy); THREAD_POOL_EXECUTOR = threadPoolExecutor; } @@ -388,6 +426,10 @@ public abstract class AsyncTask<Params, Progress, Result> { * specified parameters are the parameters passed to {@link #execute} * by the caller of this task. * + * This will normally run on a background thread. But to better + * support testing frameworks, it is recommended that this also tolerates + * direct execution on the foreground thread, as part of the {@link #execute} call. + * * This method can call {@link #publishProgress} to publish updates * on the UI thread. * @@ -404,6 +446,8 @@ public abstract class AsyncTask<Params, Progress, Result> { /** * Runs on the UI thread before {@link #doInBackground}. + * Invoked directly by {@link #execute} or {@link #executeOnExecutor}. + * The default version does nothing. * * @see #onPostExecute * @see #doInBackground @@ -414,7 +458,10 @@ public abstract class AsyncTask<Params, Progress, Result> { /** * <p>Runs on the UI thread after {@link #doInBackground}. The - * specified result is the value returned by {@link #doInBackground}.</p> + * specified result is the value returned by {@link #doInBackground}. + * To better support testing frameworks, it is recommended that this be + * written to tolerate direct execution as part of the execute() call. + * The default version does nothing.</p> * * <p>This method won't be invoked if the task was cancelled.</p> * @@ -432,6 +479,7 @@ public abstract class AsyncTask<Params, Progress, Result> { /** * Runs on the UI thread after {@link #publishProgress} is invoked. * The specified values are the values passed to {@link #publishProgress}. + * The default version does nothing. * * @param values The values indicating progress. * @@ -466,7 +514,8 @@ public abstract class AsyncTask<Params, Progress, Result> { /** * <p>Applications should preferably override {@link #onCancelled(Object)}. * This method is invoked by the default implementation of - * {@link #onCancelled(Object)}.</p> + * {@link #onCancelled(Object)}. + * The default version does nothing.</p> * * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and * {@link #doInBackground(Object[])} has finished.</p> @@ -504,12 +553,16 @@ public abstract class AsyncTask<Params, Progress, Result> { * an attempt to stop the task.</p> * * <p>Calling this method will result in {@link #onCancelled(Object)} being - * invoked on the UI thread after {@link #doInBackground(Object[])} - * returns. Calling this method guarantees that {@link #onPostExecute(Object)} - * is never invoked. After invoking this method, you should check the - * value returned by {@link #isCancelled()} periodically from - * {@link #doInBackground(Object[])} to finish the task as early as - * possible.</p> + * invoked on the UI thread after {@link #doInBackground(Object[])} returns. + * Calling this method guarantees that onPostExecute(Object) is never + * subsequently invoked, even if <tt>cancel</tt> returns false, but + * {@link #onPostExecute} has not yet run. To finish the + * task as early as possible, check {@link #isCancelled()} periodically from + * {@link #doInBackground(Object[])}.</p> + * + * <p>This only requests cancellation. It never waits for a running + * background task to terminate, even if <tt>mayInterruptIfRunning</tt> is + * true.</p> * * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this * task should be interrupted; otherwise, in-progress tasks are allowed diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java index 3a5b8a86204e..27f7e2296e7f 100644 --- a/core/java/android/os/BugreportManager.java +++ b/core/java/android/os/BugreportManager.java @@ -24,7 +24,8 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; -import android.os.IBinder.DeathRecipient; + +import com.android.internal.util.Preconditions; import java.io.FileDescriptor; import java.lang.annotation.Retention; @@ -127,13 +128,16 @@ public class BugreportManager { @NonNull BugreportParams params, @NonNull @CallbackExecutor Executor executor, @NonNull BugreportCallback callback) { - // TODO(b/111441001): Enforce android.Manifest.permission.DUMP if necessary. + Preconditions.checkNotNull(bugreportFd); + Preconditions.checkNotNull(params); + Preconditions.checkNotNull(executor); + Preconditions.checkNotNull(callback); DumpstateListener dsListener = new DumpstateListener(executor, callback); try { // Note: mBinder can get callingUid from the binder transaction. mBinder.startBugreport(-1 /* callingUid */, mContext.getOpPackageName(), - (bugreportFd != null ? bugreportFd.getFileDescriptor() : new FileDescriptor()), + bugreportFd.getFileDescriptor(), (screenshotFd != null ? screenshotFd.getFileDescriptor() : new FileDescriptor()), params.getMode(), dsListener); @@ -154,8 +158,7 @@ public class BugreportManager { } } - private final class DumpstateListener extends IDumpstateListener.Stub - implements DeathRecipient { + private final class DumpstateListener extends IDumpstateListener.Stub { private final Executor mExecutor; private final BugreportCallback mCallback; @@ -165,11 +168,6 @@ public class BugreportManager { } @Override - public void binderDied() { - // TODO(b/111441001): implement - } - - @Override public void onProgress(int progress) throws RemoteException { final long identity = Binder.clearCallingIdentity(); try { diff --git a/core/java/android/os/DynamicAndroidManager.java b/core/java/android/os/DynamicAndroidManager.java new file mode 100644 index 000000000000..5238896016ee --- /dev/null +++ b/core/java/android/os/DynamicAndroidManager.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.annotation.RequiresPermission; +import android.annotation.SystemService; +import android.content.Context; +import android.gsi.GsiProgress; + +/** + * The DynamicAndroidManager offers a mechanism to use a new Android image temporarily. After the + * installation, the device can reboot into this image with a new created /data. This image will + * last until the next reboot and then the device will go back to the original image. However the + * installed image and the new created /data are not deleted but disabled. Thus the application can + * either re-enable the installed image by calling {@link #toggle} or use the {@link #remove} to + * delete it completely. In other words, there are three device states: no installation, installed + * and running. The procedure to install a DynamicAndroid starts with a {@link #startInstallation}, + * followed by a series of {@link #write} and ends with a {@link commit}. Once the installation is + * complete, the device state changes from no installation to the installed state and a followed + * reboot will change its state to running. Note one instance of dynamic android can exist on a + * given device thus the {@link #startInstallation} will fail if the device is currently running a + * DynamicAndroid. + * + * @hide + */ +@SystemService(Context.DYNAMIC_ANDROID_SERVICE) +public class DynamicAndroidManager { + private static final String TAG = "DynamicAndroidManager"; + + private final IDynamicAndroidService mService; + + /** {@hide} */ + public DynamicAndroidManager(IDynamicAndroidService service) { + mService = service; + } + + /** The DynamicAndroidManager.Session represents a started session for the installation. */ + public class Session { + private Session() {} + /** + * Write a chunk of the DynamicAndroid system image + * + * @return {@code true} if the call succeeds. {@code false} if there is any native runtime + * error. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_ANDROID) + public boolean write(byte[] buf) { + try { + return mService.write(buf); + } catch (RemoteException e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Finish write and make device to boot into the it after reboot. + * + * @return {@code true} if the call succeeds. {@code false} if there is any native runtime + * error. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_ANDROID) + public boolean commit() { + try { + return mService.commit(); + } catch (RemoteException e) { + throw new RuntimeException(e.toString()); + } + } + } + /** + * Start DynamicAndroid installation. This call may take an unbounded amount of time. The caller + * may use another thread to call the getStartProgress() to get the progress. + * + * @param systemSize system size in bytes + * @param userdataSize userdata size in bytes + * @return {@code true} if the call succeeds. {@code false} either the device does not contain + * enough space or a DynamicAndroid is currently in use where the {@link #isInUse} would be + * true. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_ANDROID) + public Session startInstallation(long systemSize, long userdataSize) { + try { + if (mService.startInstallation(systemSize, userdataSize)) { + return new Session(); + } else { + return null; + } + } catch (RemoteException e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Query the progress of the current installation operation. This can be called while the + * installation is in progress. + * + * @return GsiProgress GsiProgress { int status; long bytes_processed; long total_bytes; } The + * status field can be IGsiService.STATUS_NO_OPERATION, IGsiService.STATUS_WORKING or + * IGsiService.STATUS_COMPLETE. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_ANDROID) + public GsiProgress getInstallationProgress() { + try { + return mService.getInstallationProgress(); + } catch (RemoteException e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Abort the installation process. Note this method must be called in a thread other than the + * one calling the startInstallation method as the startInstallation method will not return + * until it is finished. + * + * @return {@code true} if the call succeeds. {@code false} if there is no installation + * currently. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_ANDROID) + public boolean abort() { + try { + return mService.abort(); + } catch (RemoteException e) { + throw new RuntimeException(e.toString()); + } + } + + /** @return {@code true} if the device is running a dynamic android */ + @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_ANDROID) + public boolean isInUse() { + try { + return mService.isInUse(); + } catch (RemoteException e) { + throw new RuntimeException(e.toString()); + } + } + + /** @return {@code true} if the device has a dynamic android installed */ + @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_ANDROID) + public boolean isInstalled() { + try { + return mService.isInstalled(); + } catch (RemoteException e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Remove DynamicAndroid installation if present + * + * @return {@code true} if the call succeeds. {@code false} if there is no installed image. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_ANDROID) + public boolean remove() { + try { + return mService.remove(); + } catch (RemoteException e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Enable DynamicAndroid when it's not enabled, otherwise, disable it. + * + * @return {@code true} if the call succeeds. {@code false} if there is no installed image. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_ANDROID) + public boolean toggle() { + try { + return mService.toggle(); + } catch (RemoteException e) { + throw new RuntimeException(e.toString()); + } + } +} diff --git a/core/java/android/os/IDynamicAndroidService.aidl b/core/java/android/os/IDynamicAndroidService.aidl new file mode 100644 index 000000000000..0b28799c8dd0 --- /dev/null +++ b/core/java/android/os/IDynamicAndroidService.aidl @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.os; + +import android.gsi.GsiProgress; + +/** {@hide} */ +interface IDynamicAndroidService +{ + /** + * Start DynamicAndroid installation. This call may take 60~90 seconds. The caller + * may use another thread to call the getStartProgress() to get the progress. + * + * @param systemSize system size in bytes + * @param userdataSize userdata size in bytes + * @return true if the call succeeds + */ + boolean startInstallation(long systemSize, long userdataSize); + + /** + * Query the progress of the current installation operation. This can be called while + * the installation is in progress. + * + * @return GsiProgress + */ + GsiProgress getInstallationProgress(); + + /** + * Abort the installation process. Note this method must be called in a thread other + * than the one calling the startInstallation method as the startInstallation + * method will not return until it is finished. + * + * @return true if the call succeeds + */ + boolean abort(); + + /** + * @return true if the device is running an DynamicAnroid image + */ + boolean isInUse(); + + /** + * @return true if the device has an DynamicAndroid image installed + */ + boolean isInstalled(); + + /** + * Remove DynamicAndroid installation if present + * + * @return true if the call succeeds + */ + boolean remove(); + + /** + * Enable DynamicAndroid when it's not enabled, otherwise, disable it. + * + * @return true if the call succeeds + */ + boolean toggle(); + + /** + * Write a chunk of the DynamicAndroid system image + * + * @return true if the call succeeds + */ + boolean write(in byte[] buf); + + /** + * Finish write and make device to boot into the it after reboot. + * + * @return true if the call succeeds + */ + boolean commit(); +} diff --git a/core/java/android/os/ParcelFileDescriptor.aidl b/core/java/android/os/ParcelFileDescriptor.aidl deleted file mode 100644 index c07b98055d5a..000000000000 --- a/core/java/android/os/ParcelFileDescriptor.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* //device/java/android/android/os/ParcelFileDescriptor.aidl -** -** Copyright 2007, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -package android.os; - -parcelable ParcelFileDescriptor cpp_header "binder/ParcelFileDescriptor.h"; diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index 8492b0cf58e2..3ee54ceebaa9 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -32,6 +32,7 @@ import android.content.pm.PackageManager; import android.provider.Settings; import android.telephony.euicc.EuiccManager; import android.text.TextUtils; +import android.text.format.DateFormat; import android.util.Log; import android.view.Display; import android.view.WindowManager; @@ -762,7 +763,8 @@ public class RecoverySystem { String reasonArg = null; if (!TextUtils.isEmpty(reason)) { - reasonArg = "--reason=" + sanitizeArg(reason); + String timeStamp = DateFormat.format("yyyy-MM-ddTHH:mm:ssZ", System.currentTimeMillis()).toString(); + reasonArg = "--reason=" + sanitizeArg(reason + "," + timeStamp); } final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ; diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java index 648c022d8673..de41ce2e08c5 100644 --- a/core/java/android/os/UserHandle.java +++ b/core/java/android/os/UserHandle.java @@ -226,6 +226,7 @@ public final class UserHandle implements Parcelable { * @hide */ @TestApi + @SystemApi public static @AppIdInt int getAppId(int uid) { return uid % PER_USER_RANGE; } diff --git a/core/java/android/os/connectivity/CellularBatteryStats.java b/core/java/android/os/connectivity/CellularBatteryStats.java index 2593c85cff12..c99ecb32d418 100644 --- a/core/java/android/os/connectivity/CellularBatteryStats.java +++ b/core/java/android/os/connectivity/CellularBatteryStats.java @@ -44,6 +44,7 @@ public final class CellularBatteryStats implements Parcelable { private long[] mTimeInRatMs; private long[] mTimeInRxSignalStrengthLevelMs; private long[] mTxTimeMs; + private long mMonitoredRailChargeConsumedMaMs; public static final Parcelable.Creator<CellularBatteryStats> CREATOR = new Parcelable.Creator<CellularBatteryStats>() { @@ -74,6 +75,7 @@ public final class CellularBatteryStats implements Parcelable { out.writeLongArray(mTimeInRatMs); out.writeLongArray(mTimeInRxSignalStrengthLevelMs); out.writeLongArray(mTxTimeMs); + out.writeLong(mMonitoredRailChargeConsumedMaMs); } public void readFromParcel(Parcel in) { @@ -90,6 +92,7 @@ public final class CellularBatteryStats implements Parcelable { in.readLongArray(mTimeInRatMs); in.readLongArray(mTimeInRxSignalStrengthLevelMs); in.readLongArray(mTxTimeMs); + mMonitoredRailChargeConsumedMaMs = in.readLong(); } public long getLoggingDurationMs() { @@ -144,6 +147,10 @@ public final class CellularBatteryStats implements Parcelable { return mTxTimeMs; } + public long getMonitoredRailChargeConsumedMaMs() { + return mMonitoredRailChargeConsumedMaMs; + } + public void setLoggingDurationMs(long t) { mLoggingDurationMs = t; return; @@ -211,6 +218,11 @@ public final class CellularBatteryStats implements Parcelable { return; } + public void setMonitoredRailChargeConsumedMaMs(long monitoredRailEnergyConsumedMaMs) { + mMonitoredRailChargeConsumedMaMs = monitoredRailEnergyConsumedMaMs; + return; + } + public int describeContents() { return 0; } @@ -237,6 +249,7 @@ public final class CellularBatteryStats implements Parcelable { Arrays.fill(mTimeInRxSignalStrengthLevelMs, 0); mTxTimeMs = new long[ModemActivityInfo.TX_POWER_LEVELS]; Arrays.fill(mTxTimeMs, 0); + mMonitoredRailChargeConsumedMaMs = 0; return; } }
\ No newline at end of file diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index de54a8aa5548..f63c0adbdf4b 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -772,19 +772,6 @@ public class CallLog { * @param callBlockReason The reason why the call is blocked. * @param callScreeningAppName The call screening application name which block the call. * @param callScreeningComponentName The call screening component name which block the call. - * @param callIdPackageName The package name of the - * {@link android.telecom.CallScreeningService} which provided - * {@link CallIdentification}. - * @param callIdAppName The app name of the {@link android.telecom.CallScreeningService} - * which provided {@link CallIdentification}. - * @param callIdName The caller name provided by the - * {@link android.telecom.CallScreeningService}. - * @param callIdDescription The caller description provided by the - * {@link android.telecom.CallScreeningService}. - * @param callIdDetails The caller details provided by the - * {@link android.telecom.CallScreeningService}. - * @param callIdCallType The caller type provided by the - * {@link android.telecom.CallScreeningService}. * * @result The URI of the call log entry belonging to the user that made or received this * call. This could be of the shadow provider. Do not return it to non-system apps, @@ -803,37 +790,10 @@ public class CallLog { number, userToBeInsertedTo, addForAllUsers)); } final ContentResolver resolver = context.getContentResolver(); - int numberPresentation = PRESENTATION_ALLOWED; - TelecomManager tm = null; - try { - tm = TelecomManager.from(context); - } catch (UnsupportedOperationException e) {} - - String accountAddress = null; - if (tm != null && accountHandle != null) { - PhoneAccount account = tm.getPhoneAccount(accountHandle); - if (account != null) { - Uri address = account.getSubscriptionAddress(); - if (address != null) { - accountAddress = address.getSchemeSpecificPart(); - } - } - } + String accountAddress = getLogAccountAddress(context, accountHandle); - // Remap network specified number presentation types - // PhoneConstants.PRESENTATION_xxx to calllog number presentation types - // Calls.PRESENTATION_xxx, in order to insulate the persistent calllog - // from any future radio changes. - // If the number field is empty set the presentation type to Unknown. - if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) { - numberPresentation = PRESENTATION_RESTRICTED; - } else if (presentation == PhoneConstants.PRESENTATION_PAYPHONE) { - numberPresentation = PRESENTATION_PAYPHONE; - } else if (TextUtils.isEmpty(number) - || presentation == PhoneConstants.PRESENTATION_UNKNOWN) { - numberPresentation = PRESENTATION_UNKNOWN; - } + int numberPresentation = getLogNumberPresentation(number, presentation); if (numberPresentation != PRESENTATION_ALLOWED) { number = ""; if (ci != null) { @@ -1138,8 +1098,7 @@ public class CallLog { if (TextUtils.isEmpty(countryIso)) { return; } - final String normalizedNumber = PhoneNumberUtils.formatNumberToE164(number, - getCurrentCountryIso(context)); + final String normalizedNumber = PhoneNumberUtils.formatNumberToE164(number, countryIso); if (TextUtils.isEmpty(normalizedNumber)) { return; } @@ -1148,6 +1107,54 @@ public class CallLog { resolver.update(Data.CONTENT_URI, values, Data._ID + "=?", new String[] {dataId}); } + /** + * Remap network specified number presentation types + * PhoneConstants.PRESENTATION_xxx to calllog number presentation types + * Calls.PRESENTATION_xxx, in order to insulate the persistent calllog + * from any future radio changes. + * If the number field is empty set the presentation type to Unknown. + */ + private static int getLogNumberPresentation(String number, int presentation) { + if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) { + return presentation; + } + + if (presentation == PhoneConstants.PRESENTATION_PAYPHONE) { + return presentation; + } + + if (TextUtils.isEmpty(number) + || presentation == PhoneConstants.PRESENTATION_UNKNOWN) { + return PRESENTATION_UNKNOWN; + } + + return PRESENTATION_ALLOWED; + } + + private static String getLogAccountAddress(Context context, + PhoneAccountHandle accountHandle) { + TelecomManager tm = null; + try { + tm = TelecomManager.from(context); + } catch (UnsupportedOperationException e) { + if (VERBOSE_LOG) { + Log.v(LOG_TAG, "No TelecomManager found to get account address."); + } + } + + String accountAddress = null; + if (tm != null && accountHandle != null) { + PhoneAccount account = tm.getPhoneAccount(accountHandle); + if (account != null) { + Uri address = account.getSubscriptionAddress(); + if (address != null) { + accountAddress = address.getSchemeSpecificPart(); + } + } + } + return accountAddress; + } + private static String getCurrentCountryIso(Context context) { String countryIso = null; final CountryDetector detector = (CountryDetector) context.getSystemService( diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e904b0713e24..abd7c89a0301 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -12862,6 +12862,19 @@ public final class Settings { "user_preferred_sub2","user_preferred_sub3"}; /** + * Which subscription is enabled for a physical slot. + * @hide + */ + public static final String ENABLED_SUBSCRIPTION_FOR_SLOT = "enabled_subscription_for_slot"; + + /** + * Whether corresponding logical modem is enabled for a physical slot. + * The value 1 - enable, 0 - disable + * @hide + */ + public static final String MODEM_STACK_ENABLED_FOR_SLOT = "modem_stack_enabled_for_slot"; + + /** * Whether to enable new contacts aggregator or not. * The value 1 - enable, 0 - disable * @hide diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java index b84e556cc552..53cc25b14285 100644 --- a/core/java/android/service/euicc/EuiccService.java +++ b/core/java/android/service/euicc/EuiccService.java @@ -76,6 +76,11 @@ import java.util.concurrent.atomic.AtomicInteger; * filter with the appropriate action, the {@link #CATEGORY_EUICC_UI} category, and a non-zero * priority. * + * <p>Old implementations of EuiccService may support passing in slot IDs equal to + * {@link android.telephony.SubscriptionManager#INVALID_SIM_SLOT_INDEX}, which allows the LPA to + * decide which eUICC to target when there are multiple eUICCs. This behavior is not supported in + * Android Q or later. + * * @hide */ @SystemApi @@ -520,7 +525,7 @@ public abstract class EuiccService extends Service { int resultCode = EuiccService.this.onDownloadSubscription( slotId, subscription, switchAfterDownload, forceDeactivateSim); result = new DownloadSubscriptionResult(resultCode, - 0 /* resolvableErrors */, TelephonyManager.INVALID_CARD_ID); + 0 /* resolvableErrors */, TelephonyManager.UNSUPPORTED_CARD_ID); } try { callback.onComplete(result); diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index fda0a3ee4147..15c14e7bd1ef 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -30,8 +30,7 @@ import android.icu.text.Edits; import android.icu.util.ULocale; import android.os.Parcel; import android.os.Parcelable; -import android.os.SystemProperties; -import android.provider.Settings; +import android.sysprop.DisplayProperties; import android.text.style.AbsoluteSizeSpan; import android.text.style.AccessibilityClickableSpan; import android.text.style.AccessibilityURLSpan; @@ -2001,7 +2000,7 @@ public class TextUtils { return ((locale != null && !locale.equals(Locale.ROOT) && ULocale.forLocale(locale).isRightToLeft()) // If forcing into RTL layout mode, return RTL as default - || SystemProperties.getBoolean(Settings.Global.DEVELOPMENT_FORCE_RTL, false)) + || DisplayProperties.debug_force_rtl().orElse(false)) ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR; } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index f228773aebf2..efff6d617376 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -74,8 +74,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.os.SystemClock; -import android.os.SystemProperties; import android.os.Trace; +import android.sysprop.DisplayProperties; import android.text.InputType; import android.text.TextUtils; import android.util.AttributeSet; @@ -790,14 +790,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, protected static final String VIEW_LOG_TAG = "View"; /** - * When set to true, apps will draw debugging information about their layouts. - * - * @hide - */ - @UnsupportedAppUsage - public static final String DEBUG_LAYOUT_PROPERTY = "debug.layout"; - - /** * When set to true, this view will save its attribute data. * * @hide @@ -26855,7 +26847,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Show where the margins, bounds and layout bounds are for each view. */ - boolean mDebugLayout = SystemProperties.getBoolean(DEBUG_LAYOUT_PROPERTY, false); + boolean mDebugLayout = DisplayProperties.debug_layout().orElse(false); /** * Point used to compute visible regions. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index fd11ef13f9ac..798401d87b5a 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -70,6 +70,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; +import android.sysprop.DisplayProperties; import android.util.AndroidRuntimeException; import android.util.DisplayMetrics; import android.util.Log; @@ -6829,7 +6830,7 @@ public final class ViewRootImpl implements ViewParent, } // Layout debugging - boolean layout = SystemProperties.getBoolean(View.DEBUG_LAYOUT_PROPERTY, false); + boolean layout = DisplayProperties.debug_layout().orElse(false); if (layout != mAttachInfo.mDebugLayout) { mAttachInfo.mDebugLayout = layout; if (!mHandler.hasMessages(MSG_INVALIDATE_WORLD)) { diff --git a/core/java/android/view/accessibility/OWNERS b/core/java/android/view/accessibility/OWNERS new file mode 100644 index 000000000000..265674a74b7e --- /dev/null +++ b/core/java/android/view/accessibility/OWNERS @@ -0,0 +1,3 @@ +svetoslavganov@google.com +pweaver@google.com +rhedjao@google.com diff --git a/core/java/android/webkit/OWNERS b/core/java/android/webkit/OWNERS index 00e540a46ab2..b33da57c42e3 100644 --- a/core/java/android/webkit/OWNERS +++ b/core/java/android/webkit/OWNERS @@ -1,3 +1,4 @@ changwan@google.com +ntfschr@google.com tobiasjs@google.com torne@google.com diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index da8605e645b4..65b974ba8b42 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -104,6 +104,7 @@ public class VpnConfig implements Parcelable { public boolean allowBypass; public boolean allowIPv4; public boolean allowIPv6; + public boolean isMetered = true; public Network[] underlyingNetworks; public ProxyInfo proxyInfo; @@ -165,6 +166,7 @@ public class VpnConfig implements Parcelable { out.writeInt(allowBypass ? 1 : 0); out.writeInt(allowIPv4 ? 1 : 0); out.writeInt(allowIPv6 ? 1 : 0); + out.writeInt(isMetered ? 1 : 0); out.writeTypedArray(underlyingNetworks, flags); out.writeParcelable(proxyInfo, flags); } @@ -191,6 +193,7 @@ public class VpnConfig implements Parcelable { config.allowBypass = in.readInt() != 0; config.allowIPv4 = in.readInt() != 0; config.allowIPv6 = in.readInt() != 0; + config.isMetered = in.readInt() != 0; config.underlyingNetworks = in.createTypedArray(Network.CREATOR); config.proxyInfo = in.readParcelable(null); return config; diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 1048cb4e6e3a..56eb128558e8 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -93,6 +93,11 @@ public final class Zygote { */ public static final int PROFILE_SYSTEM_SERVER = 1 << 14; + /* + * Enable using the ART app image startup cache + */ + public static final int USE_APP_IMAGE_STARTUP_CACHE = 1 << 16; + /** No external storage should be mounted. */ public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE; /** Default external storage should be mounted. */ diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 2bb075989f35..d067ae792e2d 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -315,9 +315,14 @@ class ZygoteConnection { } } - private class HiddenApiUsageLogger implements VMRuntime.HiddenApiUsageLogger { + private static class HiddenApiUsageLogger implements VMRuntime.HiddenApiUsageLogger { private final MetricsLogger mMetricsLogger = new MetricsLogger(); + private static HiddenApiUsageLogger sInstance = new HiddenApiUsageLogger(); + + public static HiddenApiUsageLogger getInstance() { + return HiddenApiUsageLogger.sInstance; + } public void hiddenApiUsed(String packageName, String signature, int accessMethod, boolean accessDenied) { @@ -351,7 +356,7 @@ class ZygoteConnection { private void handleHiddenApiAccessLogSampleRate(int samplingRate) { try { ZygoteInit.setHiddenApiAccessLogSampleRate(samplingRate); - ZygoteInit.setHiddenApiUsageLogger(new HiddenApiUsageLogger()); + ZygoteInit.setHiddenApiUsageLogger(HiddenApiUsageLogger.getInstance()); mSocketOutStream.writeInt(0); } catch (IOException ioe) { throw new IllegalStateException("Error writing to command socket", ioe); diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 9f23797d6ccc..e132abd7e4cb 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -41,6 +41,7 @@ import android.system.OsConstants; import android.system.StructCapUserData; import android.system.StructCapUserHeader; import android.text.Hyphenator; +import android.text.TextUtils; import android.util.EventLog; import android.util.Log; import android.util.Slog; @@ -84,6 +85,8 @@ public class ZygoteInit { private static final String PROPERTY_DISABLE_OPENGL_PRELOADING = "ro.zygote.disable_gl_preload"; private static final String PROPERTY_GFX_DRIVER = "ro.gfx.driver.0"; + private static final String PROPERTY_USE_APP_IMAGE_STARTUP_CACHE = + "persist.device_config.runtime_native.use_app_image_startup_cache"; private static final int LOG_BOOT_PROGRESS_PRELOAD_START = 3020; private static final int LOG_BOOT_PROGRESS_PRELOAD_END = 3030; @@ -705,6 +708,13 @@ public class ZygoteInit { parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER; } + String use_app_image_cache = SystemProperties.get( + PROPERTY_USE_APP_IMAGE_STARTUP_CACHE, ""); + // Property defaults to true currently. + if (!TextUtils.isEmpty(use_app_image_cache) && !use_app_image_cache.equals("false")) { + parsedArgs.mRuntimeFlags |= Zygote.USE_APP_IMAGE_STARTUP_CACHE; + } + /* Request to fork the system server process */ pid = Zygote.forkSystemServer( parsedArgs.mUid, parsedArgs.mGid, diff --git a/core/jni/Android.bp b/core/jni/Android.bp index c81a77d4c16e..c385ca11611c 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -41,7 +41,7 @@ cc_library_shared { "com_google_android_gles_jni_EGLImpl.cpp", "com_google_android_gles_jni_GLImpl.cpp", // TODO: .arm "android_app_Activity.cpp", - "android_app_ActivityThread.cpp", + "android_app_ActivityThread.cpp", "android_app_NativeActivity.cpp", "android_app_admin_SecurityLog.cpp", "android_opengl_EGL14.cpp", @@ -225,6 +225,7 @@ cc_library_shared { ], static_libs: [ + "libasync_safe", "libgif", "libseccomp_policy", "libgrallocusage", @@ -278,8 +279,8 @@ cc_library_shared { "libsoundtrigger", "libminikin", "libprocessgroup", - "libnativebridge", - "libnativeloader", + "libnativebridge_lazy", + "libnativeloader_lazy", "libmemunreachable", "libhidlbase", "libhidltransport", diff --git a/core/jni/OWNERS b/core/jni/OWNERS index 86342c47c455..7ff15f2e182d 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -3,7 +3,7 @@ per-file *Camera*,*camera* = cychen@google.com, epeev@google.com, etalvala@googl per-file *Camera*,*camera* = shuzhenwang@google.com, yinchiayeh@google.com, zhijunhe@google.com # Connectivity -per-file android_net_* = ek@google.com, lorenzo@google.com, satk@google.com +per-file android_net_* = codewiz@google.com, jchalard@google.com, lorenzo@google.com, reminv@google.com, satk@google.com # Zygote -per-file com_android_inernal_os_Zygote.*,fd_utils.* = chriswailes@google.com, ngeoffray@google.com, sehr@google.com, narayan@google.com, maco@google.com +per-file com_android_internal_os_Zygote.*,fd_utils.* = chriswailes@google.com, ngeoffray@google.com, sehr@google.com, narayan@google.com, maco@google.com diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index 7eddcfe425d3..cfb2dd199f39 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -29,6 +29,7 @@ #include <net/if.h> #include <linux/filter.h> #include <linux/if_arp.h> +#include <linux/tcp.h> #include <netinet/ether.h> #include <netinet/icmp6.h> #include <netinet/ip.h> @@ -226,6 +227,34 @@ static void android_net_utils_attachControlPacketFilter( } } +static void android_net_utils_attachDropAllBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd) +{ + struct sock_filter filter_code[] = { + // Reject all. + BPF_STMT(BPF_RET | BPF_K, 0) + }; + struct sock_fprog filter = { + sizeof(filter_code) / sizeof(filter_code[0]), + filter_code, + }; + + int fd = jniGetFDFromFileDescriptor(env, javaFd); + if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) { + jniThrowExceptionFmt(env, "java/net/SocketException", + "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno)); + } +} + +static void android_net_utils_detachBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd) +{ + int dummy = 0; + int fd = jniGetFDFromFileDescriptor(env, javaFd); + if (setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, &dummy, sizeof(dummy)) != 0) { + jniThrowExceptionFmt(env, "java/net/SocketException", + "setsockopt(SO_DETACH_FILTER): %s", strerror(errno)); + } + +} static void android_net_utils_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd, jint ifIndex) { @@ -458,6 +487,41 @@ static jbyteArray android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, return answer; } +static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) { + if (javaFd == NULL) { + jniThrowNullPointerException(env, NULL); + return NULL; + } + + int fd = jniGetFDFromFileDescriptor(env, javaFd); + struct tcp_repair_window trw = {}; + socklen_t size = sizeof(trw); + + // Obtain the parameters of the TCP repair window. + int rc = getsockopt(fd, IPPROTO_TCP, TCP_REPAIR_WINDOW, &trw, &size); + if (rc == -1) { + throwErrnoException(env, "getsockopt : TCP_REPAIR_WINDOW", errno); + return NULL; + } + + struct tcp_info tcpinfo = {}; + socklen_t tcpinfo_size = sizeof(tcp_info); + + // Obtain the window scale from the tcp info structure. This contains a scale factor that + // should be applied to the window size. + rc = getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcpinfo, &tcpinfo_size); + if (rc == -1) { + throwErrnoException(env, "getsockopt : TCP_INFO", errno); + return NULL; + } + + jclass class_TcpRepairWindow = env->FindClass("android/net/TcpRepairWindow"); + jmethodID ctor = env->GetMethodID(class_TcpRepairWindow, "<init>", "(IIIIII)V"); + + return env->NewObject(class_TcpRepairWindow, ctor, trw.snd_wl1, trw.snd_wnd, trw.max_window, + trw.rcv_wnd, trw.rcv_wup, tcpinfo.tcpi_rcv_wscale); +} + // ---------------------------------------------------------------------------- /* @@ -475,6 +539,9 @@ static const JNINativeMethod gNetworkUtilMethods[] = { { "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter }, { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter }, { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter }, + { "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter }, + { "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter }, + { "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow }, { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket }, { "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend }, { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery }, diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index 978254175da4..32ddad1791f2 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -346,30 +346,34 @@ static void parse_cpuset_cpus(char *cpus, cpu_set_t *cpu_set) { static void get_cpuset_cores_for_policy(SchedPolicy policy, cpu_set_t *cpu_set) { FILE *file; - const char *filename; + std::string filename; CPU_ZERO(cpu_set); switch (policy) { case SP_BACKGROUND: - filename = "/dev/cpuset/background/cpus"; + if (!CgroupGetAttributePath("LowCapacityCPUs", &filename)) { + return; + } break; case SP_FOREGROUND: case SP_AUDIO_APP: case SP_AUDIO_SYS: case SP_RT_APP: - filename = "/dev/cpuset/foreground/cpus"; + if (!CgroupGetAttributePath("HighCapacityCPUs", &filename)) { + return; + } break; case SP_TOP_APP: - filename = "/dev/cpuset/top-app/cpus"; + if (!CgroupGetAttributePath("MaxCapacityCPUs", &filename)) { + return; + } break; default: - filename = NULL; + return; } - if (!filename) return; - - file = fopen(filename, "re"); + file = fopen(filename.c_str(), "re"); if (file != NULL) { // Parse cpus string char *line = NULL; @@ -379,7 +383,7 @@ static void get_cpuset_cores_for_policy(SchedPolicy policy, cpu_set_t *cpu_set) if (num_read > 0) { parse_cpuset_cpus(line, cpu_set); } else { - ALOGE("Failed to read %s", filename); + ALOGE("Failed to read %s", filename.c_str()); } free(line); } diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 1448d7b97eb1..bc1332adacbc 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -25,6 +25,8 @@ #define LOG_TAG "Zygote" +#include <async_safe/log.h> + // sys/mount.h has to come before linux/fs.h due to redefinition of MS_RDONLY, MS_BIND, etc #include <sys/mount.h> #include <linux/fs.h> @@ -296,27 +298,23 @@ static void SigChldHandler(int /*signal_number*/) { int saved_errno = errno; while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { - // Log process-death status that we care about. In general it is - // not safe to call LOG(...) from a signal handler because of - // possible reentrancy. However, we know a priori that the - // current implementation of LOG() is safe to call from a SIGCHLD - // handler in the zygote process. If the LOG() implementation - // changes its locking strategy or its use of syscalls within the - // lazy-init critical section, its use here may become unsafe. + // Log process-death status that we care about. if (WIFEXITED(status)) { - ALOGI("Process %d exited cleanly (%d)", pid, WEXITSTATUS(status)); + async_safe_format_log(ANDROID_LOG_INFO, LOG_TAG, + "Process %d exited cleanly (%d)", pid, WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { - ALOGI("Process %d exited due to signal (%d)", pid, WTERMSIG(status)); - if (WCOREDUMP(status)) { - ALOGI("Process %d dumped core.", pid); - } + async_safe_format_log(ANDROID_LOG_INFO, LOG_TAG, + "Process %d exited due to signal %d (%s)%s", pid, + WTERMSIG(status), strsignal(WTERMSIG(status)), + WCOREDUMP(status) ? "; core dumped" : ""); } // If the just-crashed process is the system_server, bring down zygote // so that it is restarted by init and system server will be restarted // from there. if (pid == gSystemServerPid) { - ALOGE("Exit zygote because system server (%d) has terminated", pid); + async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, + "Exit zygote because system server (pid %d) has terminated", pid); kill(getpid(), SIGKILL); } @@ -329,14 +327,17 @@ static void SigChldHandler(int /*signal_number*/) { // Note that we shouldn't consider ECHILD an error because // the secondary zygote might have no children left to wait for. if (pid < 0 && errno != ECHILD) { - ALOGW("Zygote SIGCHLD error in waitpid: %s", strerror(errno)); + async_safe_format_log(ANDROID_LOG_WARN, LOG_TAG, + "Zygote SIGCHLD error in waitpid: %s", strerror(errno)); } if (blastulas_removed > 0) { if (write(gBlastulaPoolEventFD, &blastulas_removed, sizeof(blastulas_removed)) == -1) { // If this write fails something went terribly wrong. We will now kill // the zygote and let the system bring it back up. - ALOGE("Zygote failed to write to blastula pool event FD: %s", strerror(errno)); + async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, + "Zygote failed to write to blastula pool event FD: %s", + strerror(errno)); kill(getpid(), SIGKILL); } } @@ -1115,8 +1116,8 @@ static jlong CalculateCapabilities(JNIEnv* env, jint uid, jint gid, jintArray gi RuntimeAbort(env, __LINE__, "Bad gids array"); } - for (int gid_index = gids_num; --gids_num >= 0;) { - if (native_gid_proxy[gid_index] == AID_WAKELOCK) { + for (int gids_index = 0; gids_index < gids_num; ++gids_index) { + if (native_gid_proxy[gids_index] == AID_WAKELOCK) { gid_wakelock_found = true; break; } @@ -1305,15 +1306,12 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( RuntimeAbort(env, __LINE__, "System server process has died. Restarting Zygote!"); } - bool low_ram_device = GetBoolProperty("ro.config.low_ram", false); - bool per_app_memcg = GetBoolProperty("ro.config.per_app_memcg", low_ram_device); - if (per_app_memcg) { + if (UsePerAppMemcg()) { // Assign system_server to the correct memory cgroup. - // Not all devices mount /dev/memcg so check for the file first + // Not all devices mount memcg so check if it is mounted first // to avoid unnecessarily printing errors and denials in the logs. - if (!access("/dev/memcg/system/tasks", F_OK) && - !WriteStringToFile(StringPrintf("%d", pid), "/dev/memcg/system/tasks")) { - ALOGE("couldn't write %d to /dev/memcg/system/tasks", pid); + if (!SetTaskProfiles(pid, std::vector<std::string>{"SystemMemoryProcess"})) { + ALOGE("couldn't add process %d into system memcg group", pid); } } } diff --git a/core/proto/android/bluetooth/enums.proto b/core/proto/android/bluetooth/enums.proto index a88a06cf091c..b4f3d1ea5ae4 100644 --- a/core/proto/android/bluetooth/enums.proto +++ b/core/proto/android/bluetooth/enums.proto @@ -110,3 +110,31 @@ enum UnbondReasonEnum { UNBOND_REASON_REMOTE_AUTH_CANCELED = 8; UNBOND_REASON_REMOVED = 9; } + +enum SocketTypeEnum { + SOCKET_TYPE_UNKNOWN = 0; + SOCKET_TYPE_RFCOMM = 1; + SOCKET_TYPE_SCO = 2; + SOCKET_TYPE_L2CAP_BREDR = 3; + SOCKET_TYPE_L2CAP_LE = 4; +} + +enum SocketConnectionstateEnum { + SOCKET_CONNECTION_STATE_UNKNOWN = 0; + // Socket acts as a server waiting for connection + SOCKET_CONNECTION_STATE_LISTENING = 1; + // Socket acts as a client trying to connect + SOCKET_CONNECTION_STATE_CONNECTING = 2; + // Socket is connected + SOCKET_CONNECTION_STATE_CONNECTED = 3; + // Socket tries to disconnect from remote + SOCKET_CONNECTION_STATE_DISCONNECTING = 4; + // This socket is closed + SOCKET_CONNECTION_STATE_DISCONNECTED = 5; +} + +enum SocketRoleEnum { + SOCKET_ROLE_UNKNOWN = 0; + SOCKET_ROLE_LISTEN = 1; + SOCKET_ROLE_CONNECTION = 2; +} diff --git a/core/proto/android/server/connectivity/Android.bp b/core/proto/android/server/connectivity/Android.bp new file mode 100644 index 000000000000..c0ac2cb8f800 --- /dev/null +++ b/core/proto/android/server/connectivity/Android.bp @@ -0,0 +1,25 @@ +// Copyright (C) 2019 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. + +java_library_static { + name: "datastallprotosnano", + proto: { + type: "nano", + }, + srcs: [ + "data_stall_event.proto", + ], + sdk_version: "system_current", + no_framework_libs: true, +}
\ No newline at end of file diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 2f3c1db4f775..7590675a9f9d 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -481,6 +481,7 @@ <protected-broadcast android:name="android.security.action.TRUST_STORE_CHANGED" /> <protected-broadcast android:name="android.security.action.KEYCHAIN_CHANGED" /> <protected-broadcast android:name="android.security.action.KEY_ACCESS_CHANGED" /> + <protected-broadcast android:name="android.telecom.action.NUISANCE_CALL_STATUS_CHANGED" /> <protected-broadcast android:name="android.telecom.action.PHONE_ACCOUNT_REGISTERED" /> <protected-broadcast android:name="android.telecom.action.PHONE_ACCOUNT_UNREGISTERED" /> <protected-broadcast android:name="android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION" /> @@ -1554,7 +1555,7 @@ <permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" android:protectionLevel="signature|privileged" /> - <!-- Allows a system application to access hardware packet offload capabilities. + <!-- @SystemApi Allows a system application to access hardware packet offload capabilities. @hide --> <permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" android:protectionLevel="signature|privileged" /> @@ -1681,6 +1682,10 @@ <permission android:name="android.permission.HARDWARE_TEST" android:protectionLevel="signature" /> + <!-- @hide Allows an application to manage DynamicAndroid image --> + <permission android:name="android.permission.MANAGE_DYNAMIC_ANDROID" + android:protectionLevel="signature" /> + <!-- @SystemApi Allows access to Broadcast Radio @hide This is not a third-party API (intended for system apps).--> <permission android:name="android.permission.ACCESS_BROADCAST_RADIO" diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 212c7235f27b..94ba172fc676 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -505,7 +505,9 @@ public class SettingsBackupTest { Settings.Global.OVERRIDE_SETTINGS_PROVIDER_RESTORE_ANY_VERSION, Settings.Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED, Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS, - Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS); + Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS, + Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT, + Settings.Global.MODEM_STACK_ENABLED_FOR_SLOT); private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS = newHashSet( Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, diff --git a/data/etc/Android.bp b/data/etc/Android.bp index 035ee108c6da..bb4765835890 100644 --- a/data/etc/Android.bp +++ b/data/etc/Android.bp @@ -58,6 +58,14 @@ prebuilt_etc { } prebuilt_etc { + name: "privapp_whitelist_com.android.dialer", + product_specific: true, + sub_dir: "permissions", + src: "com.android.dialer.xml", + filename_from_src: true, +} + +prebuilt_etc { name: "privapp_whitelist_com.android.launcher3", product_specific: true, sub_dir: "permissions", diff --git a/data/etc/com.android.dialer.xml b/data/etc/com.android.dialer.xml new file mode 100644 index 000000000000..ccdb21fa5040 --- /dev/null +++ b/data/etc/com.android.dialer.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 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 + --> +<permissions> + <privapp-permissions package="com.android.dialer"> + <permission name="android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK"/> + <permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/> + <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/> + <permission name="android.permission.MODIFY_PHONE_STATE"/> + <permission name="android.permission.STATUS_BAR"/> + <permission name="android.permission.STOP_APP_SWITCHES"/> + <permission name="com.android.voicemail.permission.READ_VOICEMAIL"/> + <permission name="com.android.voicemail.permission.WRITE_VOICEMAIL"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index c9f0f108ab44..78bbcf137f2a 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -48,17 +48,6 @@ applications that come with the platform <permission name="android.permission.WRITE_MEDIA_STORAGE"/> </privapp-permissions> - <privapp-permissions package="com.android.dialer"> - <permission name="android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK"/> - <permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/> - <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/> - <permission name="android.permission.MODIFY_PHONE_STATE"/> - <permission name="android.permission.STATUS_BAR"/> - <permission name="android.permission.STOP_APP_SWITCHES"/> - <permission name="com.android.voicemail.permission.READ_VOICEMAIL"/> - <permission name="com.android.voicemail.permission.WRITE_VOICEMAIL"/> - </privapp-permissions> - <privapp-permissions package="com.android.emergency"> <!-- Required to place emergency calls from emergency info screen. --> <permission name="android.permission.CALL_PRIVILEGED"/> @@ -169,6 +158,7 @@ applications that come with the platform <permission name="android.permission.WRITE_APN_SETTINGS"/> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> <permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"/> + <permission name="android.permission.READ_PRECISE_PHONE_STATE"/> <permission name="com.android.voicemail.permission.READ_VOICEMAIL"/> <permission name="com.android.voicemail.permission.WRITE_VOICEMAIL"/> </privapp-permissions> @@ -210,23 +200,16 @@ applications that come with the platform <permission name="android.permission.USE_RESERVED_DISK"/> </privapp-permissions> - <privapp-permissions package="com.android.mainline.networkstack"> + <privapp-permissions package="com.android.networkstack"> <permission name="android.permission.ACCESS_NETWORK_CONDITIONS"/> - <permission name="android.permission.CHANGE_BACKGROUND_DATA_SETTING"/> <permission name="android.permission.CONNECTIVITY_INTERNAL"/> <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/> <permission name="android.permission.CONTROL_VPN"/> + <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.LOCAL_MAC_ADDRESS"/> - <permission name="android.permission.MANAGE_IPSEC_TUNNELS"/> - <permission name="android.permission.MANAGE_NETWORK_POLICY"/> <permission name="android.permission.MANAGE_SUBSCRIPTION_PLANS"/> <permission name="android.permission.MANAGE_USB"/> - <permission name="android.permission.NETWORK_BYPASS_PRIVATE_DNS"/> - <permission name="android.permission.NETWORK_SETTINGS"/> - <permission name="android.permission.NETWORK_STACK" /> - <permission name="android.permission.NET_TUNNELING"/> <permission name="android.permission.PACKET_KEEPALIVE_OFFLOAD"/> - <permission name="android.permission.PEERS_MAC_ADDRESS"/> <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/> <permission name="android.permission.READ_PRECISE_PHONE_STATE"/> <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> @@ -264,6 +247,7 @@ applications that come with the platform <permission name="android.permission.CHANGE_LOWPAN_STATE"/> <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/> <permission name="android.permission.CLEAR_APP_CACHE"/> + <permission name="android.permission.ACCESS_INSTANT_APPS" /> <permission name="android.permission.CONNECTIVITY_INTERNAL"/> <permission name="android.permission.DELETE_CACHE_FILES"/> <permission name="android.permission.DELETE_PACKAGES"/> diff --git a/keystore/java/android/security/OWNERS b/keystore/java/android/security/OWNERS new file mode 100644 index 000000000000..ed30587418dd --- /dev/null +++ b/keystore/java/android/security/OWNERS @@ -0,0 +1 @@ +per-file *.java,*.aidl = eranm@google.com,pgrafov@google.com,rubinxu@google.com diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java index aa2917484a05..3dc884eb38ad 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java @@ -17,7 +17,6 @@ package android.security.keystore; import android.security.Credentials; -import android.security.GateKeeper; import android.security.KeyStore; import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterArguments; @@ -204,7 +203,12 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { } } } - + if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_3DES) { + if (mKeySizeBits != 168) { + throw new InvalidAlgorithmParameterException( + "3DES key size must be 168 bits."); + } + } if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) { if (mKeySizeBits < 64) { throw new InvalidAlgorithmParameterException( diff --git a/keystore/tests/OWNERS b/keystore/tests/OWNERS new file mode 100644 index 000000000000..9e65f88b3366 --- /dev/null +++ b/keystore/tests/OWNERS @@ -0,0 +1,5 @@ +# Android Enterprise security team +eranm@google.com +irinaid@google.com +pgrafov@google.com +rubinxu@google.com diff --git a/libs/hwui/FrameMetricsObserver.h b/libs/hwui/FrameMetricsObserver.h index ba72e937095f..0b9ae5cb4b7b 100644 --- a/libs/hwui/FrameMetricsObserver.h +++ b/libs/hwui/FrameMetricsObserver.h @@ -23,7 +23,7 @@ namespace uirenderer { class FrameMetricsObserver : public VirtualLightRefBase { public: - virtual void notify(const int64_t* buffer); + virtual void notify(const int64_t* buffer) = 0; }; }; // namespace uirenderer diff --git a/media/OWNERS b/media/OWNERS index 03b751c07c6c..eb26367d3d29 100644 --- a/media/OWNERS +++ b/media/OWNERS @@ -11,3 +11,6 @@ lajos@google.com marcone@google.com sungsoo@google.com wjia@google.com + +# For maintaining sync with AndroidX code +per-file ExifInterface.java = jinpark@google.com, sungsoo@google.com diff --git a/media/java/android/media/MediaHTTPConnection.java b/media/java/android/media/MediaHTTPConnection.java index ad25a06fabe2..5f324f7f97ed 100644 --- a/media/java/android/media/MediaHTTPConnection.java +++ b/media/java/android/media/MediaHTTPConnection.java @@ -16,6 +16,8 @@ package android.media; +import static android.media.MediaPlayer.MEDIA_ERROR_UNSUPPORTED; + import android.annotation.UnsupportedAppUsage; import android.net.NetworkUtils; import android.os.IBinder; @@ -23,21 +25,19 @@ import android.os.StrictMode; import android.util.Log; import java.io.BufferedInputStream; -import java.io.InputStream; import java.io.IOException; +import java.io.InputStream; import java.net.CookieHandler; -import java.net.CookieManager; -import java.net.Proxy; -import java.net.URL; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.NoRouteToHostException; import java.net.ProtocolException; +import java.net.Proxy; +import java.net.URL; import java.net.UnknownServiceException; import java.util.HashMap; import java.util.Map; - -import static android.media.MediaPlayer.MEDIA_ERROR_UNSUPPORTED; +import java.util.concurrent.atomic.AtomicBoolean; /** @hide */ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { @@ -67,6 +67,7 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { // from com.squareup.okhttp.internal.http private final static int HTTP_TEMP_REDIRECT = 307; private final static int MAX_REDIRECTS = 20; + private AtomicBoolean mIsConnected = new AtomicBoolean(false); @UnsupportedAppUsage public MediaHTTPConnection() { @@ -90,6 +91,7 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { mAllowCrossDomainRedirect = true; mURL = new URL(uri); mHeaders = convertHeaderStringToMap(headers); + mIsConnected.set(true); } catch (MalformedURLException e) { return null; } @@ -140,7 +142,14 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { @Override @UnsupportedAppUsage public void disconnect() { - teardownConnection(); + if (mIsConnected.getAndSet(false)) { + (new Thread() { + @Override + public void run() { + teardownConnection(); + } + }).start(); + } mHeaders = null; mURL = null; } @@ -325,7 +334,14 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { @Override @UnsupportedAppUsage public int readAt(long offset, int size) { - return native_readAt(offset, size); + if (!mIsConnected.get()) { + return -1; + } + int result = native_readAt(offset, size); + if (!mIsConnected.get()) { + return -1; + } + return result; } private int readAt(long offset, byte[] data, int size) { diff --git a/media/jni/Android.bp b/media/jni/Android.bp index 4c563dbbe979..25c7b5cc5cd6 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -51,6 +51,7 @@ cc_library_shared { "libmtp", "libexif", "libpiex", + "libprocessgroup", "libandroidfw", "libhidlallocatorutils", "libhidlbase", @@ -131,6 +132,7 @@ cc_library_shared { "libcutils", "libdexfile", "liblzma", + "libjsoncpp", "libmedia_helper", "libmedia_player2_util", "libmediadrm", @@ -140,6 +142,7 @@ cc_library_shared { "libmediautils", "libnativehelper", "libnetd_client", + "libprocessgroup", "libstagefright_esds", "libstagefright_foundation", "libstagefright_httplive", diff --git a/packages/CaptivePortalLogin/Android.bp b/packages/CaptivePortalLogin/Android.bp new file mode 100644 index 000000000000..9c31b4d4374f --- /dev/null +++ b/packages/CaptivePortalLogin/Android.bp @@ -0,0 +1,27 @@ +// +// Copyright (C) 2019 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. +// + +android_app { + name: "CaptivePortalLogin", + srcs: ["src/**/*.java"], + sdk_version: "system_current", + certificate: "networkstack", + static_libs: [ + "android-support-v4", + "metrics-constants-protos", + ], + manifest: "AndroidManifest.xml", +} diff --git a/packages/CaptivePortalLogin/Android.mk b/packages/CaptivePortalLogin/Android.mk deleted file mode 100644 index 7dc23ff8e8b8..000000000000 --- a/packages/CaptivePortalLogin/Android.mk +++ /dev/null @@ -1,13 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := optional -LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 - -LOCAL_SRC_FILES := $(call all-java-files-under, src) - -LOCAL_PACKAGE_NAME := CaptivePortalLogin -LOCAL_PRIVATE_PLATFORM_APIS := true -LOCAL_CERTIFICATE := platform - -include $(BUILD_PACKAGE) diff --git a/packages/CaptivePortalLogin/AndroidManifest.xml b/packages/CaptivePortalLogin/AndroidManifest.xml index c84f3ec68178..0894ee576a2d 100644 --- a/packages/CaptivePortalLogin/AndroidManifest.xml +++ b/packages/CaptivePortalLogin/AndroidManifest.xml @@ -21,9 +21,10 @@ <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> - <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" /> - <uses-permission android:name="android.permission.NETWORK_SETTINGS" /> + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.NETWORK_BYPASS_PRIVATE_DNS" /> + <uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK" /> <application android:label="@string/app_name" android:usesCleartextTraffic="true" diff --git a/packages/CaptivePortalLogin/res/layout/ssl_error_msg.xml b/packages/CaptivePortalLogin/res/layout/ssl_error_msg.xml deleted file mode 100644 index d460041e59ae..000000000000 --- a/packages/CaptivePortalLogin/res/layout/ssl_error_msg.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2018 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<TextView - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/ssl_error_msg" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceSmall" - android:layout_marginStart="20dip" - android:layout_marginEnd="20dip" - android:gravity="center_vertical" - android:layout_marginBottom="4dip" - android:layout_marginTop="4dip" /> - diff --git a/packages/CaptivePortalLogin/res/layout/ssl_warning.xml b/packages/CaptivePortalLogin/res/layout/ssl_warning.xml index ffd57a430662..ce05e78757a1 100644 --- a/packages/CaptivePortalLogin/res/layout/ssl_warning.xml +++ b/packages/CaptivePortalLogin/res/layout/ssl_warning.xml @@ -78,7 +78,18 @@ android:id="@+id/certificate_layout" android:layout_width="match_parent" android:layout_height="wrap_content" + android:orientation="vertical" android:layout_marginBottom="16dip" > + <TextView + android:id="@+id/ssl_error_msg" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall" + android:layout_marginStart="20dip" + android:layout_marginEnd="20dip" + android:gravity="center_vertical" + android:layout_marginBottom="4dip" + android:layout_marginTop="16dip" /> </LinearLayout> </ScrollView> diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java index 08b22c7d518a..2c9b6eb72b4d 100644 --- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java +++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java @@ -20,7 +20,7 @@ import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC; import android.app.Activity; import android.app.AlertDialog; -import android.app.LoadedApk; +import android.app.Application; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -37,8 +37,9 @@ import android.net.captiveportal.CaptivePortalProbeSpec; import android.net.http.SslCertificate; import android.net.http.SslError; import android.net.wifi.WifiInfo; -import android.os.Build; +import android.net.wifi.WifiManager; import android.os.Bundle; +import android.os.SystemProperties; import android.support.v4.widget.SwipeRefreshLayout; import android.text.TextUtils; import android.util.ArrayMap; @@ -95,6 +96,7 @@ public class CaptivePortalLoginActivity extends Activity { private CaptivePortal mCaptivePortal; private NetworkCallback mNetworkCallback; private ConnectivityManager mCm; + private WifiManager mWifiManager; private boolean mLaunchBrowser = false; private MyWebViewClient mWebViewClient; private SwipeRefreshLayout mSwipeRefreshLayout; @@ -108,7 +110,8 @@ public class CaptivePortalLoginActivity extends Activity { mCaptivePortal = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL); logMetricsEvent(MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY); - mCm = ConnectivityManager.from(this); + mCm = getSystemService(ConnectivityManager.class); + mWifiManager = getSystemService(WifiManager.class); mNetwork = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_NETWORK); mUserAgent = getIntent().getStringExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT); @@ -150,7 +153,6 @@ public class CaptivePortalLoginActivity extends Activity { // Also initializes proxy system properties. mNetwork = mNetwork.getPrivateDnsBypassingCopy(); mCm.bindProcessToNetwork(mNetwork); - mCm.setProcessDefaultNetworkForHostResolution(mNetwork); // Proxy system properties must be initialized before setContentView is called because // setContentView initializes the WebView logic which in turn reads the system properties. @@ -172,6 +174,7 @@ public class CaptivePortalLoginActivity extends Activity { webSettings.setSupportZoom(true); webSettings.setBuiltInZoomControls(true); webSettings.setDisplayZoomControls(false); + webSettings.setDomStorageEnabled(true); mWebViewClient = new MyWebViewClient(); webview.setWebViewClient(mWebViewClient); webview.setWebChromeClient(new MyWebChromeClient()); @@ -189,9 +192,12 @@ public class CaptivePortalLoginActivity extends Activity { // Find WebView's proxy BroadcastReceiver and prompt it to read proxy system properties. private void setWebViewProxy() { - LoadedApk loadedApk = getApplication().mLoadedApk; + // TODO: migrate to androidx WebView proxy setting API as soon as it is finalized try { - Field receiversField = LoadedApk.class.getDeclaredField("mReceivers"); + final Field loadedApkField = Application.class.getDeclaredField("mLoadedApk"); + final Class<?> loadedApkClass = loadedApkField.getType(); + final Object loadedApk = loadedApkField.get(getApplication()); + Field receiversField = loadedApkClass.getDeclaredField("mReceivers"); receiversField.setAccessible(true); ArrayMap receivers = (ArrayMap) receiversField.get(loadedApk); for (Object receiverMap : receivers.values()) { @@ -332,7 +338,11 @@ public class CaptivePortalLoginActivity extends Activity { private static String sanitizeURL(URL url) { // In non-Debug build, only show host to avoid leaking private info. - return Build.IS_DEBUGGABLE ? Objects.toString(url) : host(url); + return isDebuggable() ? Objects.toString(url) : host(url); + } + + private static boolean isDebuggable() { + return SystemProperties.getInt("ro.debuggable", 0) == 1; } private void testForCaptivePortal() { @@ -585,19 +595,18 @@ public class CaptivePortalLoginActivity extends Activity { } private void setViewSecurityCertificate(LinearLayout certificateLayout, SslError error) { + ((TextView) certificateLayout.findViewById(R.id.ssl_error_msg)) + .setText(sslErrorMessage(error)); SslCertificate cert = error.getCertificate(); - - View certificateView = cert.inflateCertificateView(CaptivePortalLoginActivity.this); - final LinearLayout placeholder = (LinearLayout) certificateView - .findViewById(com.android.internal.R.id.placeholder); - LayoutInflater factory = LayoutInflater.from(CaptivePortalLoginActivity.this); - - TextView textView = (TextView) factory.inflate( - R.layout.ssl_error_msg, placeholder, false); - textView.setText(sslErrorMessage(error)); - placeholder.addView(textView); - - certificateLayout.addView(certificateView); + // TODO: call the method directly once inflateCertificateView is @SystemApi + try { + final View certificateView = (View) SslCertificate.class.getMethod( + "inflateCertificateView", Context.class) + .invoke(cert, CaptivePortalLoginActivity.this); + certificateLayout.addView(certificateView); + } catch (ReflectiveOperationException | SecurityException e) { + Log.e(TAG, "Could not create certificate view", e); + } } } @@ -618,11 +627,30 @@ public class CaptivePortalLoginActivity extends Activity { private String getHeaderTitle() { NetworkCapabilities nc = mCm.getNetworkCapabilities(mNetwork); - if (nc == null || TextUtils.isEmpty(nc.getSSID()) - || !nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + final String ssid = getSsid(); + if (TextUtils.isEmpty(ssid) + || nc == null || !nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { return getString(R.string.action_bar_label); } - return getString(R.string.action_bar_title, WifiInfo.removeDoubleQuotes(nc.getSSID())); + return getString(R.string.action_bar_title, ssid); + } + + // TODO: remove once SSID is obtained from NetworkCapabilities + private String getSsid() { + if (mWifiManager == null) { + return null; + } + final WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); + return removeDoubleQuotes(wifiInfo.getSSID()); + } + + private static String removeDoubleQuotes(String string) { + if (string == null) return null; + final int length = string.length(); + if ((length > 1) && (string.charAt(0) == '"') && (string.charAt(length - 1) == '"')) { + return string.substring(1, length - 1); + } + return string; } private String getHeaderSubtitle(URL url) { diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java index 4f67350b5adc..31549fa0d1bb 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java @@ -106,6 +106,7 @@ public class CaptivePortalLoginActivity extends Activity { webSettings.setLoadWithOverviewMode(true); webSettings.setSupportZoom(true); webSettings.setBuiltInZoomControls(true); + webSettings.setDomStorageEnabled(true); mWebViewClient = new MyWebViewClient(); mWebView.setWebViewClient(mWebViewClient); mWebView.setWebChromeClient(new MyWebChromeClient()); diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp index b0522f2a99a0..b700bf324817 100644 --- a/packages/NetworkStack/Android.bp +++ b/packages/NetworkStack/Android.bp @@ -35,10 +35,12 @@ java_library { android_app { name: "NetworkStack", sdk_version: "system_current", - certificate: "platform", + certificate: "networkstack", privileged: true, static_libs: [ "NetworkStackLib" ], + jarjar_rules: "jarjar-rules-shared.txt", manifest: "AndroidManifest.xml", + required: ["NetworkStackPermissionStub"], }
\ No newline at end of file diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml index 5ab833bda66d..52c209e5f247 100644 --- a/packages/NetworkStack/AndroidManifest.xml +++ b/packages/NetworkStack/AndroidManifest.xml @@ -17,18 +17,19 @@ */ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.mainline.networkstack" + package="com.android.networkstack" android:sharedUserId="android.uid.networkstack"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" /> - <uses-permission android:name="android.permission.NETWORK_SETTINGS" /> - <!-- Launch captive portal app as specific user --> - <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> - <uses-permission android:name="android.permission.NETWORK_STACK" /> + <!-- Signature permission defined in NetworkStackStub --> + <uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK" /> + <!-- Send latency broadcast as current user --> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> + <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" /> <application android:label="NetworkStack" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/NetworkStack/OWNERS b/packages/NetworkStack/OWNERS new file mode 100644 index 000000000000..a395465e5f21 --- /dev/null +++ b/packages/NetworkStack/OWNERS @@ -0,0 +1,5 @@ +codewiz@google.com +jchalard@google.com +lorenzo@google.com +reminv@google.com +satk@google.com diff --git a/packages/NetworkStack/jarjar-rules-shared.txt b/packages/NetworkStack/jarjar-rules-shared.txt new file mode 100644 index 000000000000..a8c712a3336d --- /dev/null +++ b/packages/NetworkStack/jarjar-rules-shared.txt @@ -0,0 +1,19 @@ +rule com.android.internal.util.** android.net.networkstack.util.@1 + +rule android.net.shared.Inet4AddressUtils* android.net.networkstack.shared.Inet4AddressUtils@1 +rule android.net.shared.InetAddressUtils* android.net.networkstack.shared.InetAddressUtils@1 + +# Ignore DhcpResultsParcelable, but jarjar DhcpResults +# TODO: move DhcpResults into services.net and delete from here +rule android.net.DhcpResultsParcelable* @0 +rule android.net.DhcpResults* android.net.networkstack.DhcpResults@1 +rule android.net.LocalLog* android.net.networkstack.LocalLog@1 + +# TODO: remove from framework dependencies, then remove here +rule android.net.InterfaceConfigurationParcel* android.net.networkstack.InterfaceConfigurationParcel@1 +rule android.net.TetherStatsParcel* android.net.networkstack.TetherStatsParcel@1 + +# Used by UidRange, which is used by framework classes such as NetworkCapabilities. +rule android.net.UidRangeParcel* android.net.networkstack.UidRangeParcel@1 +# TODO: move TcpKeepalivePacketData to services.net and delete +rule android.net.TcpKeepalivePacketDataParcelable* android.net.networkstack.TcpKeepalivePacketDataParcelable@1
\ No newline at end of file diff --git a/packages/NetworkStack/src/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java index 4fa7d6462092..923f162c92a6 100644 --- a/packages/NetworkStack/src/android/net/apf/ApfFilter.java +++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java @@ -23,6 +23,7 @@ import static android.system.OsConstants.ETH_P_ARP; import static android.system.OsConstants.ETH_P_IP; import static android.system.OsConstants.ETH_P_IPV6; import static android.system.OsConstants.IPPROTO_ICMPV6; +import static android.system.OsConstants.IPPROTO_TCP; import static android.system.OsConstants.IPPROTO_UDP; import static android.system.OsConstants.SOCK_RAW; @@ -38,6 +39,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.TcpKeepalivePacketDataParcelable; import android.net.apf.ApfGenerator.IllegalInstructionException; import android.net.apf.ApfGenerator.Register; import android.net.ip.IpClient.IpClientCallbacksWrapper; @@ -55,6 +57,7 @@ import android.system.Os; import android.text.format.DateUtils; import android.util.Log; import android.util.Pair; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -149,7 +152,9 @@ public class ApfFilter { DROPPED_IPV6_NON_ICMP_MULTICAST, DROPPED_802_3_FRAME, DROPPED_ETHERTYPE_BLACKLISTED, - DROPPED_ARP_REPLY_SPA_NO_HOST; + DROPPED_ARP_REPLY_SPA_NO_HOST, + DROPPED_IPV4_KEEPALIVE_ACK, + DROPPED_IPV6_KEEPALIVE_ACK; // Returns the negative byte offset from the end of the APF data segment for // a given counter. @@ -285,6 +290,7 @@ public class ApfFilter { private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16; private static final int IPV4_ANY_HOST_ADDRESS = 0; private static final int IPV4_BROADCAST_ADDRESS = -1; // 255.255.255.255 + private static final int IPV4_HEADER_LEN = 20; // Without options // Traffic class and Flow label are not byte aligned. Luckily we // don't care about either value so we'll consider bytes 1-3 of the @@ -305,6 +311,8 @@ public class ApfFilter { private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 2; private static final int UDP_HEADER_LEN = 8; + private static final int TCP_HEADER_SIZE_OFFSET = 12; + private static final int DHCP_CLIENT_PORT = 68; // NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 28; @@ -788,7 +796,7 @@ public class ApfFilter { boolean isExpired() { // TODO: We may want to handle 0 lifetime RAs differently, if they are common. We'll - // have to calculte the filter lifetime specially as a fraction of 0 is still 0. + // have to calculate the filter lifetime specially as a fraction of 0 is still 0. return currentLifetime() <= 0; } @@ -847,11 +855,147 @@ public class ApfFilter { } } + // A class to hold keepalive ack information. + private abstract static class TcpKeepaliveAck { + // Note that the offset starts from IP header. + // These must be added ether header length when generating program. + static final int IP_HEADER_OFFSET = 0; + + protected static class TcpKeepaliveAckData { + public final byte[] srcAddress; + public final int srcPort; + public final byte[] dstAddress; + public final int dstPort; + public final int seq; + public final int ack; + // Create the characteristics of the ack packet from the sent keepalive packet. + TcpKeepaliveAckData(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { + srcAddress = sentKeepalivePacket.dstAddress; + srcPort = sentKeepalivePacket.dstPort; + dstAddress = sentKeepalivePacket.srcAddress; + dstPort = sentKeepalivePacket.srcPort; + seq = sentKeepalivePacket.ack; + ack = sentKeepalivePacket.seq + 1; + } + } + + protected final TcpKeepaliveAckData mPacket; + protected final byte[] mSrcDstAddr; + + TcpKeepaliveAck(final TcpKeepaliveAckData packet, final byte[] srcDstAddr) { + mPacket = packet; + mSrcDstAddr = srcDstAddr; + } + + static byte[] concatArrays(final byte[]... arr) { + int size = 0; + for (byte[] a : arr) { + size += a.length; + } + final byte[] result = new byte[size]; + int offset = 0; + for (byte[] a : arr) { + System.arraycopy(a, 0, result, offset, a.length); + offset += a.length; + } + return result; + } + + public String toString() { + return String.format("%s(%d) -> %s(%d), seq=%d, ack=%d", + mPacket.srcAddress, + mPacket.srcPort, + mPacket.dstAddress, + mPacket.dstPort, + mPacket.seq, + mPacket.ack); + } + + // Append a filter for this keepalive ack to {@code gen}. + // Jump to drop if it matches the keepalive ack. + // Jump to the next filter if packet doesn't match the keepalive ack. + abstract void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException; + } + + private class TcpKeepaliveAckV4 extends TcpKeepaliveAck { + private static final int IPV4_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 12; + private static final int IPV4_TCP_SRC_PORT_OFFSET = 0; + private static final int IPV4_TCP_DST_PORT_OFFSET = 2; + private static final int IPV4_TCP_SEQ_OFFSET = 4; + private static final int IPV4_TCP_ACK_OFFSET = 8; + + TcpKeepaliveAckV4(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { + this(new TcpKeepaliveAckData(sentKeepalivePacket)); + } + TcpKeepaliveAckV4(final TcpKeepaliveAckData packet) { + super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */); + } + + @Override + void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { + final String nextFilterLabel = "keepalive_ack" + getUniqueNumberLocked(); + gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET); + gen.addJumpIfR0NotEquals(IPPROTO_TCP, nextFilterLabel); + gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); + gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel); + + // Pass the packet if it's not zero-sized : + // Load the IP header size into R1 + gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); + // Load the TCP header size into R0 (it's indexed by R1) + gen.addLoad8Indexed(Register.R0, ETH_HEADER_LEN + TCP_HEADER_SIZE_OFFSET); + // Size offset is in the top nibble, but it must be multiplied by 4, and the two + // top bits of the low nibble are guaranteed to be zeroes. Right-shift R0 by 2. + gen.addRightShift(2); + // R0 += R1 -> R0 contains TCP + IP headers lenght + gen.addAddR1(); + // Add the Ethernet header length to R0. + gen.addLoadImmediate(Register.R1, ETH_HEADER_LEN); + gen.addAddR1(); + // Compare total length of headers to the size of the packet. + gen.addLoadFromMemory(Register.R1, gen.PACKET_SIZE_MEMORY_SLOT); + gen.addNeg(Register.R0); + gen.addAddR1(); + gen.addJumpIfR0NotEquals(0, nextFilterLabel); + + // Add IPv4 header length + gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); + gen.addLoad16Indexed(Register.R0, ETH_HEADER_LEN + IPV4_TCP_SRC_PORT_OFFSET); + gen.addJumpIfR0NotEquals(mPacket.srcPort, nextFilterLabel); + gen.addLoad16Indexed(Register.R0, ETH_HEADER_LEN + IPV4_TCP_DST_PORT_OFFSET); + gen.addJumpIfR0NotEquals(mPacket.dstPort, nextFilterLabel); + gen.addLoad32Indexed(Register.R0, ETH_HEADER_LEN + IPV4_TCP_SEQ_OFFSET); + gen.addJumpIfR0NotEquals(mPacket.seq, nextFilterLabel); + gen.addLoad32Indexed(Register.R0, ETH_HEADER_LEN + IPV4_TCP_ACK_OFFSET); + gen.addJumpIfR0NotEquals(mPacket.ack, nextFilterLabel); + + maybeSetupCounter(gen, Counter.DROPPED_IPV4_KEEPALIVE_ACK); + gen.addJump(mCountAndDropLabel); + gen.defineLabel(nextFilterLabel); + } + } + + private class TcpKeepaliveAckV6 extends TcpKeepaliveAck { + TcpKeepaliveAckV6(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { + this(new TcpKeepaliveAckData(sentKeepalivePacket)); + } + TcpKeepaliveAckV6(final TcpKeepaliveAckData packet) { + super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */); + } + + @Override + void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { + throw new UnsupportedOperationException("IPv6 Keepalive is not supported yet"); + } + } + // Maximum number of RAs to filter for. private static final int MAX_RAS = 10; @GuardedBy("this") - private ArrayList<Ra> mRas = new ArrayList<Ra>(); + private ArrayList<Ra> mRas = new ArrayList<>(); + @GuardedBy("this") + private SparseArray<TcpKeepaliveAck> mKeepaliveAcks = new SparseArray<>(); // There is always some marginal benefit to updating the installed APF program when an RA is // seen because we can extend the program's lifetime slightly, but there is some cost to @@ -980,6 +1124,8 @@ public class ApfFilter { // drop // if it's IPv4 broadcast: // drop + // if keepalive ack + // drop // pass if (mMulticastFilter) { @@ -1023,6 +1169,9 @@ public class ApfFilter { gen.addJumpIfR0Equals(broadcastAddr, mCountAndDropLabel); } + // If any keepalive filters, + generateKeepaliveFilter(gen); + // If L2 broadcast packet, drop. // TODO: can we invert this condition to fall through to the common pass case below? maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST); @@ -1030,6 +1179,8 @@ public class ApfFilter { gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel); maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST); gen.addJump(mCountAndDropLabel); + } else { + generateKeepaliveFilter(gen); } // Otherwise, pass @@ -1037,6 +1188,13 @@ public class ApfFilter { gen.addJump(mCountAndPassLabel); } + private void generateKeepaliveFilter(ApfGenerator gen) throws IllegalInstructionException { + // Drop IPv4 Keepalive acks + for (int i = 0; i < mKeepaliveAcks.size(); ++i) { + final TcpKeepaliveAck ack = mKeepaliveAcks.valueAt(i); + if (ack instanceof TcpKeepaliveAckV4) ack.generateFilterLocked(gen); + } + } /** * Generate filter code to process IPv6 packets. Execution of this code ends in either the @@ -1057,6 +1215,8 @@ public class ApfFilter { // drop // if it's ICMPv6 NA to ff02::1: // drop + // if keepalive ack + // drop gen.addLoad8(Register.R0, IPV6_NEXT_HEADER_OFFSET); @@ -1112,6 +1272,12 @@ public class ApfFilter { maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA); gen.addJump(mCountAndDropLabel); gen.defineLabel(skipUnsolicitedMulticastNALabel); + + // Drop IPv6 Keepalive acks + for (int i = 0; i < mKeepaliveAcks.size(); ++i) { + final TcpKeepaliveAck ack = mKeepaliveAcks.valueAt(i); + if (ack instanceof TcpKeepaliveAckV6) ack.generateFilterLocked(gen); + } } /** @@ -1489,6 +1655,36 @@ public class ApfFilter { installNewProgramLocked(); } + /** + * Add keepalive ack packet filter. + * This will add a filter to drop acks to the keepalive packet passed as an argument. + * + * @param slot The index used to access the filter. + * @param sentKeepalivePacket The attributes of the sent keepalive packet. + */ + public synchronized void addKeepalivePacketFilter(final int slot, + final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { + log("Adding keepalive ack(" + slot + ")"); + if (null != mKeepaliveAcks.get(slot)) { + throw new IllegalArgumentException("Keepalive slot " + slot + " is occupied"); + } + final int ipVersion = sentKeepalivePacket.srcAddress.length == 4 ? 4 : 6; + mKeepaliveAcks.put(slot, (ipVersion == 4) + ? new TcpKeepaliveAckV4(sentKeepalivePacket) + : new TcpKeepaliveAckV6(sentKeepalivePacket)); + installNewProgramLocked(); + } + + /** + * Remove keepalive packet filter. + * + * @param slot The index used to access the filter. + */ + public synchronized void removeKeepalivePacketFilter(int slot) { + mKeepaliveAcks.remove(slot); + installNewProgramLocked(); + } + static public long counterValue(byte[] data, Counter counter) throws ArrayIndexOutOfBoundsException { // Follow the same wrap-around addressing scheme of the interpreter. @@ -1541,6 +1737,17 @@ public class ApfFilter { } pw.decreaseIndent(); + pw.println("Keepalive filter:"); + pw.increaseIndent(); + for (int i = 0; i < mKeepaliveAcks.size(); ++i) { + final TcpKeepaliveAck keepaliveAck = mKeepaliveAcks.valueAt(i); + pw.print("Slot "); + pw.print(mKeepaliveAcks.keyAt(i)); + pw.print(" : "); + pw.println(keepaliveAck); + } + pw.decreaseIndent(); + if (DBG) { pw.println("Last program:"); pw.increaseIndent(); diff --git a/packages/NetworkStack/src/android/net/apf/ApfGenerator.java b/packages/NetworkStack/src/android/net/apf/ApfGenerator.java index 87a1b5ea8b4d..809327a0b79d 100644 --- a/packages/NetworkStack/src/android/net/apf/ApfGenerator.java +++ b/packages/NetworkStack/src/android/net/apf/ApfGenerator.java @@ -476,7 +476,7 @@ public class ApfGenerator { /** * Add an instruction to the end of the program to load 16-bits from the packet into - * {@code register}. The offset of the loaded 16-bits from the begining of the packet is + * {@code register}. The offset of the loaded 16-bits from the beginning of the packet is * the sum of {@code offset} and the value in register R1. */ public ApfGenerator addLoad16Indexed(Register register, int offset) { @@ -488,7 +488,7 @@ public class ApfGenerator { /** * Add an instruction to the end of the program to load 32-bits from the packet into - * {@code register}. The offset of the loaded 32-bits from the begining of the packet is + * {@code register}. The offset of the loaded 32-bits from the beginning of the packet is * the sum of {@code offset} and the value in register R1. */ public ApfGenerator addLoad32Indexed(Register register, int offset) { diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpPacketListener.java b/packages/NetworkStack/src/android/net/dhcp/DhcpPacketListener.java index 96d1a287ef09..97d26c7c9c1f 100644 --- a/packages/NetworkStack/src/android/net/dhcp/DhcpPacketListener.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpPacketListener.java @@ -18,7 +18,7 @@ package android.net.dhcp; import android.annotation.NonNull; import android.annotation.Nullable; -import android.net.shared.FdEventsReader; +import android.net.util.FdEventsReader; import android.os.Handler; import android.system.Os; diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java index 12fe8c507db4..b1f6d246563e 100644 --- a/packages/NetworkStack/src/android/net/ip/IpClient.java +++ b/packages/NetworkStack/src/android/net/ip/IpClient.java @@ -23,6 +23,7 @@ import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission; +import android.annotation.NonNull; import android.content.Context; import android.net.ConnectivityManager; import android.net.DhcpResults; @@ -34,6 +35,7 @@ import android.net.ProvisioningConfigurationParcelable; import android.net.ProxyInfo; import android.net.ProxyInfoParcelable; import android.net.RouteInfo; +import android.net.TcpKeepalivePacketDataParcelable; import android.net.apf.ApfCapabilities; import android.net.apf.ApfFilter; import android.net.dhcp.DhcpClient; @@ -44,6 +46,7 @@ import android.net.shared.ProvisioningConfiguration; import android.net.util.InterfaceParams; import android.net.util.SharedLog; import android.os.ConditionVariable; +import android.os.IBinder; import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; @@ -292,6 +295,8 @@ public class IpClient extends StateMachine { private static final int EVENT_PROVISIONING_TIMEOUT = 10; private static final int EVENT_DHCPACTION_TIMEOUT = 11; private static final int EVENT_READ_PACKET_FILTER_COMPLETE = 12; + private static final int CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF = 13; + private static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF = 14; // Internal commands to use instead of trying to call transitionTo() inside // a given State's enter() method. Calling transitionTo() from enter/exit @@ -376,6 +381,13 @@ public class IpClient extends StateMachine { public InterfaceParams getInterfaceParams(String ifname) { return InterfaceParams.getByName(ifname); } + + /** + * Get a INetd connector. + */ + public INetd getNetd(Context context) { + return INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)); + } } public IpClient(Context context, String ifName, IIpClientCallbacks callback, @@ -409,7 +421,7 @@ public class IpClient extends StateMachine { // TODO: Consider creating, constructing, and passing in some kind of // InterfaceController.Dependencies class. - mNetd = mContext.getSystemService(INetd.class); + mNetd = deps.getNetd(mContext); mInterfaceCtrl = new InterfaceController(mInterfaceName, mNetd, mLog); mLinkObserver = new IpClientLinkObserver( @@ -522,6 +534,16 @@ public class IpClient extends StateMachine { checkNetworkStackCallingPermission(); IpClient.this.setMulticastFilter(enabled); } + @Override + public void addKeepalivePacketFilter(int slot, TcpKeepalivePacketDataParcelable pkt) { + checkNetworkStackCallingPermission(); + IpClient.this.addKeepalivePacketFilter(slot, pkt); + } + @Override + public void removeKeepalivePacketFilter(int slot) { + checkNetworkStackCallingPermission(); + IpClient.this.removeKeepalivePacketFilter(slot); + } } public String getInterfaceName() { @@ -644,6 +666,22 @@ public class IpClient extends StateMachine { } /** + * Called by WifiStateMachine to add keepalive packet filter before setting up + * keepalive offload. + */ + public void addKeepalivePacketFilter(int slot, @NonNull TcpKeepalivePacketDataParcelable pkt) { + sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, 0 /* Unused */, pkt); + } + + /** + * Called by WifiStateMachine to remove keepalive packet filter after stopping keepalive + * offload. + */ + public void removeKeepalivePacketFilter(int slot) { + sendMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF, slot, 0 /* Unused */); + } + + /** * Dump logs of this IpClient. */ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { @@ -1512,6 +1550,23 @@ public class IpClient extends StateMachine { break; } + case CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF: { + final int slot = msg.arg1; + if (mApfFilter != null) { + mApfFilter.addKeepalivePacketFilter(slot, + (TcpKeepalivePacketDataParcelable) msg.obj); + } + break; + } + + case CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF: { + final int slot = msg.arg1; + if (mApfFilter != null) { + mApfFilter.removeKeepalivePacketFilter(slot); + } + break; + } + case EVENT_DHCPACTION_TIMEOUT: stopDhcpAction(); break; diff --git a/core/java/android/net/shared/FdEventsReader.java b/packages/NetworkStack/src/android/net/util/FdEventsReader.java index bffbfb115886..1380ea720d40 100644 --- a/core/java/android/net/shared/FdEventsReader.java +++ b/packages/NetworkStack/src/android/net/util/FdEventsReader.java @@ -14,14 +14,13 @@ * limitations under the License. */ -package android.net.shared; +package android.net.util; import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; import android.annotation.NonNull; import android.annotation.Nullable; -import android.net.util.SocketUtils; import android.os.Handler; import android.os.Looper; import android.os.MessageQueue; diff --git a/packages/NetworkStack/src/android/net/util/PacketReader.java b/packages/NetworkStack/src/android/net/util/PacketReader.java index 94b1e9f2e14e..4aec6b6753a6 100644 --- a/packages/NetworkStack/src/android/net/util/PacketReader.java +++ b/packages/NetworkStack/src/android/net/util/PacketReader.java @@ -18,7 +18,6 @@ package android.net.util; import static java.lang.Math.max; -import android.net.shared.FdEventsReader; import android.os.Handler; import android.system.Os; diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java index cedcb84e9d08..90db207c9902 100644 --- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java +++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java @@ -114,7 +114,8 @@ public class NetworkStackService extends Service { NetworkStackConnector(Context context) { mContext = context; - mNetd = (INetd) context.getSystemService(Context.NETD_SERVICE); + mNetd = INetd.Stub.asInterface( + (IBinder) context.getSystemService(Context.NETD_SERVICE)); mObserverRegistry = new NetworkObserverRegistry(); mCm = context.getSystemService(ConnectivityManager.class); @@ -246,6 +247,12 @@ public class NetworkStackService extends Service { } @Override + public void notifyCaptivePortalAppFinished(int response) { + checkNetworkStackCallingPermission(); + mNm.notifyCaptivePortalAppFinished(response); + } + + @Override public void forceReevaluation(int uid) { checkNetworkStackCallingPermission(); mNm.forceReevaluation(uid); diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java index b34efc46f3b4..ec4a47930fad 100644 --- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java +++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java @@ -39,9 +39,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.net.CaptivePortal; import android.net.ConnectivityManager; -import android.net.ICaptivePortal; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.LinkProperties; @@ -61,20 +59,15 @@ import android.net.util.SharedLog; import android.net.util.Stopwatch; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; +import android.os.Bundle; import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; -import android.telephony.CellIdentityCdma; -import android.telephony.CellIdentityGsm; -import android.telephony.CellIdentityLte; -import android.telephony.CellIdentityWcdma; -import android.telephony.CellInfo; -import android.telephony.CellInfoCdma; -import android.telephony.CellInfoGsm; -import android.telephony.CellInfoLte; -import android.telephony.CellInfoWcdma; +import android.telephony.AccessNetworkConstants; +import android.telephony.NetworkRegistrationState; +import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; @@ -109,6 +102,8 @@ public class NetworkMonitor extends StateMachine { private static final boolean DBG = true; private static final boolean VDBG = false; private static final boolean VDBG_STALL = Log.isLoggable(TAG, Log.DEBUG); + // TODO: use another permission for CaptivePortalLoginActivity once it has its own certificate + private static final String PERMISSION_NETWORK_SETTINGS = "android.permission.NETWORK_SETTINGS"; // Default configuration values for captive portal detection probes. // TODO: append a random length parameter to the default HTTPS url. // TODO: randomize browser version ids in the default User-Agent String. @@ -463,6 +458,13 @@ public class NetworkMonitor extends StateMachine { sendMessage(CMD_LAUNCH_CAPTIVE_PORTAL_APP); } + /** + * Notify that the captive portal app was closed with the provided response code. + */ + public void notifyCaptivePortalAppFinished(int response) { + sendMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, response); + } + @Override protected void log(String s) { if (DBG) Log.d(TAG + "/" + mNetwork.toString(), s); @@ -497,7 +499,7 @@ public class NetworkMonitor extends StateMachine { private void showProvisioningNotification(String action) { try { - mCallback.showProvisioningNotification(action); + mCallback.showProvisioningNotification(action, mContext.getPackageName()); } catch (RemoteException e) { Log.e(TAG, "Error showing provisioning notification", e); } @@ -672,42 +674,19 @@ public class NetworkMonitor extends StateMachine { public boolean processMessage(Message message) { switch (message.what) { case CMD_LAUNCH_CAPTIVE_PORTAL_APP: - final Intent intent = new Intent( - ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN); + final Bundle appExtras = new Bundle(); // OneAddressPerFamilyNetwork is not parcelable across processes. - intent.putExtra(ConnectivityManager.EXTRA_NETWORK, new Network(mNetwork)); - intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL, - new CaptivePortal(new ICaptivePortal.Stub() { - @Override - public void appResponse(int response) { - if (response == APP_RETURN_WANTED_AS_IS) { - mContext.enforceCallingPermission( - android.Manifest.permission.CONNECTIVITY_INTERNAL, - "CaptivePortal"); - } - sendMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, response); - } - - @Override - public void logEvent(int eventId, String packageName) - throws RemoteException { - mContext.enforceCallingPermission( - android.Manifest.permission.CONNECTIVITY_INTERNAL, - "CaptivePortal"); - mCallback.logCaptivePortalLoginEvent(eventId, packageName); - } - })); + final Network network = new Network(mNetwork); + appExtras.putParcelable(ConnectivityManager.EXTRA_NETWORK, network); final CaptivePortalProbeResult probeRes = mLastPortalProbeResult; - intent.putExtra(EXTRA_CAPTIVE_PORTAL_URL, probeRes.detectUrl); + appExtras.putString(EXTRA_CAPTIVE_PORTAL_URL, probeRes.detectUrl); if (probeRes.probeSpec != null) { final String encodedSpec = probeRes.probeSpec.getEncodedSpec(); - intent.putExtra(EXTRA_CAPTIVE_PORTAL_PROBE_SPEC, encodedSpec); + appExtras.putString(EXTRA_CAPTIVE_PORTAL_PROBE_SPEC, encodedSpec); } - intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT, + appExtras.putString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT, mCaptivePortalUserAgent); - intent.setFlags( - Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivityAsUser(intent, UserHandle.CURRENT); + mCm.startCaptivePortalApp(network, appExtras); return HANDLED; default: return NOT_HANDLED; @@ -1311,6 +1290,7 @@ public class NetworkMonitor extends StateMachine { urlConnection.setInstanceFollowRedirects(probeType == ValidationProbeEvent.PROBE_PAC); urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); + urlConnection.setRequestProperty("Connection", "close"); urlConnection.setUseCaches(false); if (mCaptivePortalUserAgent != null) { urlConnection.setRequestProperty("User-Agent", mCaptivePortalUserAgent); @@ -1484,10 +1464,6 @@ public class NetworkMonitor extends StateMachine { */ private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal, long requestTimestampMs, long responseTimestampMs) { - if (!mWifiManager.isScanAlwaysAvailable()) { - return; - } - if (!mSystemReady) { return; } @@ -1495,6 +1471,10 @@ public class NetworkMonitor extends StateMachine { Intent latencyBroadcast = new Intent(NetworkMonitorUtils.ACTION_NETWORK_CONDITIONS_MEASURED); if (mNetworkCapabilities.hasTransport(TRANSPORT_WIFI)) { + if (!mWifiManager.isScanAlwaysAvailable()) { + return; + } + WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo(); if (currentWifiInfo != null) { // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not @@ -1514,39 +1494,21 @@ public class NetworkMonitor extends StateMachine { } latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_WIFI); } else if (mNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) { + // TODO(b/123893112): Support multi-sim. latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_NETWORK_TYPE, mTelephonyManager.getNetworkType()); - List<CellInfo> info = mTelephonyManager.getAllCellInfo(); - if (info == null) return; - int numRegisteredCellInfo = 0; - for (CellInfo cellInfo : info) { - if (cellInfo.isRegistered()) { - numRegisteredCellInfo++; - if (numRegisteredCellInfo > 1) { - if (VDBG) { - logw("more than one registered CellInfo." - + " Can't tell which is active. Bailing."); - } - return; - } - if (cellInfo instanceof CellInfoCdma) { - CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity(); - latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId); - } else if (cellInfo instanceof CellInfoGsm) { - CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity(); - latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId); - } else if (cellInfo instanceof CellInfoLte) { - CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity(); - latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId); - } else if (cellInfo instanceof CellInfoWcdma) { - CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity(); - latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId); - } else { - if (VDBG) logw("Registered cellinfo is unrecognized"); - return; - } - } + final ServiceState dataSs = mTelephonyManager.getServiceState(); + if (dataSs == null) { + logw("failed to retrieve ServiceState"); + return; } + // See if the data sub is registered for PS services on cell. + final NetworkRegistrationState nrs = dataSs.getNetworkRegistrationState( + NetworkRegistrationState.DOMAIN_PS, + AccessNetworkConstants.TransportType.WWAN); + latencyBroadcast.putExtra( + NetworkMonitorUtils.EXTRA_CELL_ID, + nrs == null ? null : nrs.getCellIdentity()); latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_MOBILE); } else { return; diff --git a/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java b/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java index 82bf038073c7..f6eb900c4910 100644 --- a/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java +++ b/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java @@ -19,6 +19,7 @@ package com.android.server.util; import static android.os.Binder.getCallingUid; import android.os.Process; +import android.os.UserHandle; /** * Utility class to check calling permissions on the network stack. @@ -32,7 +33,7 @@ public final class PermissionUtil { public static void checkNetworkStackCallingPermission() { // TODO: check that the calling PID is the system server. final int caller = getCallingUid(); - if (caller != Process.SYSTEM_UID && caller != Process.BLUETOOTH_UID) { + if (caller != Process.SYSTEM_UID && UserHandle.getAppId(caller) != Process.BLUETOOTH_UID) { throw new SecurityException("Invalid caller: " + caller); } } diff --git a/packages/NetworkStack/tests/Android.bp b/packages/NetworkStack/tests/Android.bp index 45fa2dc2f383..5c7b514834cb 100644 --- a/packages/NetworkStack/tests/Android.bp +++ b/packages/NetworkStack/tests/Android.bp @@ -18,6 +18,7 @@ android_test { name: "NetworkStackTests", certificate: "platform", srcs: ["src/**/*.java"], + test_suites: ["device-tests"], resource_dirs: ["res"], static_libs: [ "android-support-test", @@ -49,6 +50,7 @@ android_test { "libhidlbase", "libhidltransport", "libhwbinder", + "libjsoncpp", "liblog", "liblzma", "libnativehelper", diff --git a/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java index f76e41217c2a..3414397d73c3 100644 --- a/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java +++ b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java @@ -22,6 +22,7 @@ import static android.system.OsConstants.ETH_P_ARP; import static android.system.OsConstants.ETH_P_IP; import static android.system.OsConstants.ETH_P_IPV6; import static android.system.OsConstants.IPPROTO_ICMPV6; +import static android.system.OsConstants.IPPROTO_TCP; import static android.system.OsConstants.IPPROTO_UDP; import static android.system.OsConstants.SOCK_STREAM; @@ -39,13 +40,12 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.TcpKeepalivePacketDataParcelable; import android.net.apf.ApfFilter.ApfConfiguration; import android.net.apf.ApfGenerator.IllegalInstructionException; import android.net.apf.ApfGenerator.Register; import android.net.ip.IIpClientCallbacks; -import android.net.ip.IpClient; import android.net.ip.IpClient.IpClientCallbacksWrapper; -import android.net.ip.IpClientCallbacks; import android.net.metrics.IpConnectivityLog; import android.net.metrics.RaEvent; import android.net.util.InterfaceParams; @@ -1003,15 +1003,32 @@ public class ApfTest { private static final byte[] ETH_BROADCAST_MAC_ADDRESS = {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }; + private static final int IPV4_HEADER_LEN = 20; private static final int IPV4_VERSION_IHL_OFFSET = ETH_HEADER_LEN + 0; + private static final int IPV4_TOTAL_LENGTH_OFFSET = ETH_HEADER_LEN + 2; private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9; + private static final int IPV4_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 12; private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16; + private static final int IPV4_TCP_HEADER_LEN = 20; + private static final int IPV4_TCP_HEADER_OFFSET = ETH_HEADER_LEN + IPV4_HEADER_LEN; + private static final int IPV4_TCP_SRC_PORT_OFFSET = IPV4_TCP_HEADER_OFFSET + 0; + private static final int IPV4_TCP_DEST_PORT_OFFSET = IPV4_TCP_HEADER_OFFSET + 2; + private static final int IPV4_TCP_SEQ_NUM_OFFSET = IPV4_TCP_HEADER_OFFSET + 4; + private static final int IPV4_TCP_ACK_NUM_OFFSET = IPV4_TCP_HEADER_OFFSET + 8; + private static final int IPV4_TCP_HEADER_LENGTH_OFFSET = IPV4_TCP_HEADER_OFFSET + 12; + private static final int IPV4_TCP_HEADER_FLAG_OFFSET = IPV4_TCP_HEADER_OFFSET + 13; private static final byte[] IPV4_BROADCAST_ADDRESS = {(byte) 255, (byte) 255, (byte) 255, (byte) 255}; private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6; private static final int IPV6_HEADER_LEN = 40; + private static final int IPV6_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 8; private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24; + private static final int IPV6_TCP_HEADER_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN; + private static final int IPV6_TCP_SRC_PORT_OFFSET = IPV6_TCP_HEADER_OFFSET + 0; + private static final int IPV6_TCP_DEST_PORT_OFFSET = IPV6_TCP_HEADER_OFFSET + 2; + private static final int IPV6_TCP_SEQ_NUM_OFFSET = IPV6_TCP_HEADER_OFFSET + 4; + private static final int IPV6_TCP_ACK_NUM_OFFSET = IPV6_TCP_HEADER_OFFSET + 8; // The IPv6 all nodes address ff02::1 private static final byte[] IPV6_ALL_NODES_ADDRESS = { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; @@ -1491,6 +1508,211 @@ public class ApfTest { return packet.array(); } + private static final byte[] IPV4_KEEPALIVE_SRC_ADDR = {10, 0, 0, 5}; + private static final byte[] IPV4_KEEPALIVE_DST_ADDR = {10, 0, 0, 6}; + private static final byte[] IPV4_ANOTHER_ADDR = {10, 0 , 0, 7}; + private static final byte[] IPV6_KEEPALIVE_SRC_ADDR = + {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf1}; + private static final byte[] IPV6_KEEPALIVE_DST_ADDR = + {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf2}; + private static final byte[] IPV6_ANOTHER_ADDR = + {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf5}; + + @Test + public void testApfFilterKeepaliveAck() throws Exception { + final MockIpClientCallback cb = new MockIpClientCallback(); + final ApfConfiguration config = getDefaultConfig(); + config.multicastFilter = DROP_MULTICAST; + config.ieee802_3Filter = DROP_802_3_FRAMES; + final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog); + byte[] program; + final int srcPort = 12345; + final int dstPort = 54321; + final int seqNum = 2123456789; + final int ackNum = 1234567890; + final int anotherSrcPort = 23456; + final int anotherDstPort = 65432; + final int anotherSeqNum = 2123456780; + final int anotherAckNum = 1123456789; + final int slot1 = 1; + final int slot2 = 2; + final int window = 14480; + final int windowScale = 4; + + // src: 10.0.0.5, port: 12345 + // dst: 10.0.0.6, port: 54321 + InetAddress srcAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_SRC_ADDR); + InetAddress dstAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_DST_ADDR); + + final TcpKeepalivePacketDataParcelable parcel = new TcpKeepalivePacketDataParcelable(); + parcel.srcAddress = srcAddr.getAddress(); + parcel.srcPort = srcPort; + parcel.dstAddress = dstAddr.getAddress(); + parcel.dstPort = dstPort; + parcel.seq = seqNum; + parcel.ack = ackNum; + + apfFilter.addKeepalivePacketFilter(slot1, parcel); + program = cb.getApfProgram(); + + // Verify IPv4 keepalive ack packet is dropped + // src: 10.0.0.6, port: 54321 + // dst: 10.0.0.5, port: 12345 + assertDrop(program, + ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, + dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */)); + // Verify IPv4 non-keepalive ack packet from the same source address is passed + assertPass(program, + ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, + dstPort, srcPort, ackNum + 100, seqNum, 0 /* dataLength */)); + assertPass(program, + ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, + dstPort, srcPort, ackNum, seqNum + 1, 10 /* dataLength */)); + // Verify IPv4 packet from another address is passed + assertPass(program, + ipv4Packet(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, anotherSrcPort, + anotherDstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */)); + + // Remove IPv4 keepalive filter + apfFilter.removeKeepalivePacketFilter(slot1); + + try { + // src: 2404:0:0:0:0:0:faf1, port: 12345 + // dst: 2404:0:0:0:0:0:faf2, port: 54321 + srcAddr = InetAddress.getByAddress(IPV6_KEEPALIVE_SRC_ADDR); + dstAddr = InetAddress.getByAddress(IPV6_KEEPALIVE_DST_ADDR); + + final TcpKeepalivePacketDataParcelable ipv6Parcel = + new TcpKeepalivePacketDataParcelable(); + ipv6Parcel.srcAddress = srcAddr.getAddress(); + ipv6Parcel.srcPort = srcPort; + ipv6Parcel.dstAddress = dstAddr.getAddress(); + ipv6Parcel.dstPort = dstPort; + ipv6Parcel.seq = seqNum; + ipv6Parcel.ack = ackNum; + + apfFilter.addKeepalivePacketFilter(slot1, ipv6Parcel); + program = cb.getApfProgram(); + + // Verify IPv6 keepalive ack packet is dropped + // src: 2404:0:0:0:0:0:faf2, port: 54321 + // dst: 2404:0:0:0:0:0:faf1, port: 12345 + assertDrop(program, + ipv6Packet(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR, + dstPort, srcPort, ackNum, seqNum + 1)); + // Verify IPv6 non-keepalive ack packet from the same source address is passed + assertPass(program, + ipv6Packet(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR, + dstPort, srcPort, ackNum + 100, seqNum)); + // Verify IPv6 packet from another address is passed + assertPass(program, + ipv6Packet(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, anotherSrcPort, + anotherDstPort, anotherSeqNum, anotherAckNum)); + + // Remove IPv6 keepalive filter + apfFilter.removeKeepalivePacketFilter(slot1); + + // Verify multiple filters + apfFilter.addKeepalivePacketFilter(slot1, parcel); + apfFilter.addKeepalivePacketFilter(slot2, ipv6Parcel); + program = cb.getApfProgram(); + + // Verify IPv4 keepalive ack packet is dropped + // src: 10.0.0.6, port: 54321 + // dst: 10.0.0.5, port: 12345 + assertDrop(program, + ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, + dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */)); + // Verify IPv4 non-keepalive ack packet from the same source address is passed + assertPass(program, + ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, + dstPort, srcPort, ackNum + 100, seqNum, 0 /* dataLength */)); + // Verify IPv4 packet from another address is passed + assertPass(program, + ipv4Packet(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, anotherSrcPort, + anotherDstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */)); + + // Verify IPv6 keepalive ack packet is dropped + // src: 2404:0:0:0:0:0:faf2, port: 54321 + // dst: 2404:0:0:0:0:0:faf1, port: 12345 + assertDrop(program, + ipv6Packet(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR, + dstPort, srcPort, ackNum, seqNum + 1)); + // Verify IPv6 non-keepalive ack packet from the same source address is passed + assertPass(program, + ipv6Packet(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR, + dstPort, srcPort, ackNum + 100, seqNum)); + // Verify IPv6 packet from another address is passed + assertPass(program, + ipv6Packet(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, anotherSrcPort, + anotherDstPort, anotherSeqNum, anotherAckNum)); + + // Remove keepalive filters + apfFilter.removeKeepalivePacketFilter(slot1); + apfFilter.removeKeepalivePacketFilter(slot2); + } catch (UnsupportedOperationException e) { + // TODO: support V6 packets + } + + program = cb.getApfProgram(); + + // Verify IPv4, IPv6 packets are passed + assertPass(program, + ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, + dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */)); + assertPass(program, + ipv6Packet(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR, + dstPort, srcPort, ackNum, seqNum + 1)); + assertPass(program, + ipv4Packet(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, srcPort, + dstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */)); + assertPass(program, + ipv6Packet(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, srcPort, + dstPort, anotherSeqNum, anotherAckNum)); + + apfFilter.shutdown(); + } + + private static byte[] ipv4Packet(byte[] sip, byte[] dip, int sport, + int dport, int seq, int ack, int dataLength) { + final int totalLength = dataLength + IPV4_HEADER_LEN + IPV4_TCP_HEADER_LEN; + + ByteBuffer packet = ByteBuffer.wrap(new byte[totalLength + ETH_HEADER_LEN]); + + // ether type + packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IP); + + // IPv4 header + packet.put(IPV4_VERSION_IHL_OFFSET, (byte) 0x45); + packet.putShort(IPV4_TOTAL_LENGTH_OFFSET, (short) totalLength); + packet.put(IPV4_PROTOCOL_OFFSET, (byte) IPPROTO_TCP); + put(packet, IPV4_SRC_ADDR_OFFSET, sip); + put(packet, IPV4_DEST_ADDR_OFFSET, dip); + packet.putShort(IPV4_TCP_SRC_PORT_OFFSET, (short) sport); + packet.putShort(IPV4_TCP_DEST_PORT_OFFSET, (short) dport); + packet.putInt(IPV4_TCP_SEQ_NUM_OFFSET, seq); + packet.putInt(IPV4_TCP_ACK_NUM_OFFSET, ack); + + // TCP header length 5(20 bytes), reserved 3 bits, NS=0 + packet.put(IPV4_TCP_HEADER_LENGTH_OFFSET, (byte) 0x50); + // TCP flags: ACK set + packet.put(IPV4_TCP_HEADER_FLAG_OFFSET, (byte) 0x10); + return packet.array(); + } + + private static byte[] ipv6Packet(byte[] sip, byte[] tip, int sport, + int dport, int seq, int ack) { + ByteBuffer packet = ByteBuffer.wrap(new byte[100]); + packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IPV6); + put(packet, IPV6_SRC_ADDR_OFFSET, sip); + put(packet, IPV6_DEST_ADDR_OFFSET, tip); + packet.putShort(IPV6_TCP_SRC_PORT_OFFSET, (short) sport); + packet.putShort(IPV6_TCP_DEST_PORT_OFFSET, (short) dport); + packet.putInt(IPV6_TCP_SEQ_NUM_OFFSET, seq); + packet.putInt(IPV6_TCP_ACK_NUM_OFFSET, ack); + return packet.array(); + } + // Verify that the last program pushed to the IpClient.Callback properly filters the // given packet for the given lifetime. private void verifyRaLifetime(byte[] program, ByteBuffer packet, int lifetime) { diff --git a/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java index 7e57d1eb00b0..aaaff0279fed 100644 --- a/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java +++ b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java @@ -104,8 +104,8 @@ public class IpClientTest { when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm); when(mContext.getSystemService(eq(ConnectivityManager.class))).thenReturn(mCm); - when(mContext.getSystemService(INetd.class)).thenReturn(mNetd); when(mContext.getResources()).thenReturn(mResources); + when(mDependencies.getNetd(any())).thenReturn(mNetd); when(mResources.getInteger(R.integer.config_networkAvoidBadWifi)) .thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE); diff --git a/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java index d11bb64213c3..9a16bb77182e 100644 --- a/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java +++ b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java @@ -16,8 +16,7 @@ package com.android.server.connectivity; -import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN; -import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL; +import static android.net.CaptivePortal.APP_RETURN_DISMISSED; import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID; import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; @@ -41,8 +40,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; -import android.content.Intent; -import android.net.CaptivePortal; import android.net.ConnectivityManager; import android.net.INetworkMonitorCallbacks; import android.net.InetAddresses; @@ -54,10 +51,10 @@ import android.net.captiveportal.CaptivePortalProbeResult; import android.net.metrics.IpConnectivityLog; import android.net.util.SharedLog; import android.net.wifi.WifiManager; +import android.os.Bundle; import android.os.ConditionVariable; import android.os.Handler; import android.os.SystemClock; -import android.os.UserHandle; import android.provider.Settings; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -482,24 +479,28 @@ public class NetworkMonitorTest { nm.notifyNetworkConnected(); verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) - .showProvisioningNotification(any()); + .showProvisioningNotification(any(), any()); // Check that startCaptivePortalApp sends the expected intent. nm.launchCaptivePortalApp(); - final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); - verify(mContext, timeout(HANDLER_TIMEOUT_MS).times(1)) - .startActivityAsUser(intentCaptor.capture(), eq(UserHandle.CURRENT)); - final Intent intent = intentCaptor.getValue(); - assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, intent.getAction()); - final Network network = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK); - assertEquals(TEST_NETID, network.netId); + final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + final ArgumentCaptor<Network> networkCaptor = ArgumentCaptor.forClass(Network.class); + verify(mCm, timeout(HANDLER_TIMEOUT_MS).times(1)) + .startCaptivePortalApp(networkCaptor.capture(), bundleCaptor.capture()); + final Bundle bundle = bundleCaptor.getValue(); + final Network bundleNetwork = bundle.getParcelable(ConnectivityManager.EXTRA_NETWORK); + assertEquals(TEST_NETID, bundleNetwork.netId); + // network is passed both in bundle and as parameter, as the bundle is opaque to the + // framework and only intended for the captive portal app, but the framework needs + // the network to identify the right NetworkMonitor. + assertEquals(TEST_NETID, networkCaptor.getValue().netId); // Have the app report that the captive portal is dismissed, and check that we revalidate. setStatus(mHttpsConnection, 204); setStatus(mHttpConnection, 204); - final CaptivePortal captivePortal = intent.getParcelableExtra(EXTRA_CAPTIVE_PORTAL); - captivePortal.reportCaptivePortalDismissed(); + + nm.notifyCaptivePortalAppFinished(APP_RETURN_DISMISSED); verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null); } diff --git a/packages/NetworkStackPermissionStub/Android.bp b/packages/NetworkStackPermissionStub/Android.bp new file mode 100644 index 000000000000..dd70cf56b51b --- /dev/null +++ b/packages/NetworkStackPermissionStub/Android.bp @@ -0,0 +1,27 @@ +// +// Copyright (C) 2019 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. +// + +// Stub APK to define permissions for NetworkStack +android_app { + name: "NetworkStackPermissionStub", + // TODO: mark app as hasCode=false in manifest once soong stops complaining about apps without + // a classes.dex. + srcs: ["src/**/*.java"], + platform_apis: true, + certificate: "networkstack", + privileged: true, + manifest: "AndroidManifest.xml", +} diff --git a/packages/NetworkStackPermissionStub/AndroidManifest.xml b/packages/NetworkStackPermissionStub/AndroidManifest.xml new file mode 100644 index 000000000000..a8742d7ab34f --- /dev/null +++ b/packages/NetworkStackPermissionStub/AndroidManifest.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.networkstack.permissionstub" + android:sharedUserId="android.uid.networkstack"> + <!-- + This package only exists to define the below permissions, and enforce that they are only + granted to apps sharing the same signature. + Permissions defined here are intended to be used only by the NetworkStack: both + NetworkStack and this stub APK are to be signed with a dedicated certificate to ensure + that, with the below permissions being signature permissions. + + This APK *must* be installed, even if the NetworkStack app is not installed, because otherwise, + any application will be able to define this permission and the system will give that application + full access to the network stack. + --> + <permission android:name="android.permission.MAINLINE_NETWORK_STACK" + android:protectionLevel="signature"/> + + <application android:name="com.android.server.NetworkStackPermissionStub"/> +</manifest>
\ No newline at end of file diff --git a/telephony/java/android/telephony/ims/RcsParticipantEvent.java b/packages/NetworkStackPermissionStub/src/com/android/server/NetworkStackPermissionStub.java index 371b8b723d0a..01e59d28d995 100644 --- a/telephony/java/android/telephony/ims/RcsParticipantEvent.java +++ b/packages/NetworkStackPermissionStub/src/com/android/server/NetworkStackPermissionStub.java @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.telephony.ims; -import android.os.Parcelable; +package com.android.server; + +import android.app.Application; /** - * An event that is associated with an {@link RcsParticipant} - * @hide - TODO(sahinc) make this public + * Empty application for NetworkStackStub that only exists because soong builds complain if APKs + * have no source file. */ -public abstract class RcsParticipantEvent implements Parcelable { +public class NetworkStackPermissionStub extends Application { } diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 4791517d9273..0cae894e89a6 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -22,4 +22,20 @@ android_library { } +// NOTE: Keep this module in sync with ./common.mk +java_defaults { + name: "SettingsLibDefaults", + static_libs: [ + "androidx.annotation_annotation", + "androidx.lifecycle_lifecycle-common", + "androidx.legacy_legacy-support-v4", + "androidx.lifecycle_lifecycle-runtime", + "androidx.recyclerview_recyclerview", + "androidx.preference_preference", + "androidx.appcompat_appcompat", + "androidx.legacy_legacy-preference-v14", + "SettingsLib", + ], +} + // For the test package. diff --git a/packages/SettingsLib/common.mk b/packages/SettingsLib/common.mk index 834b83b49ada..8c309ff97370 100644 --- a/packages/SettingsLib/common.mk +++ b/packages/SettingsLib/common.mk @@ -12,6 +12,11 @@ # # include frameworks/base/packages/SettingsLib/common.mk # +# During the conversion to Soong bluprint files, the equivalent +# functionality is provided by adding +# defaults: ["SettingsLibDefaults"], +# to the corresponding module. +# NOTE: keep this file and ./Android.bp in sync. LOCAL_STATIC_JAVA_LIBRARIES += \ androidx.annotation_annotation \ diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 76a216d1be48..23e13c231c20 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -191,9 +191,9 @@ <string name="bluetooth_profile_a2dp_high_quality_unknown_codec">HD audio</string> <!-- Bluetooth settings. The user-visible string that is used whenever referring to the Hearing Aid profile. --> - <string name="bluetooth_profile_hearing_aid">Hearing Aid</string> + <string name="bluetooth_profile_hearing_aid">Hearing Aids</string> <!-- Bluetooth settings. Connection options screen. The summary for the Hearing Aid checkbox preference when Hearing Aid is connected. --> - <string name="bluetooth_hearing_aid_profile_summary_connected">Connected to Hearing Aid</string> + <string name="bluetooth_hearing_aid_profile_summary_connected">Connected to Hearing Aids</string> <!-- Bluetooth settings. Connection options screen. The summary for the A2DP checkbox preference when A2DP is connected. --> <string name="bluetooth_a2dp_profile_summary_connected">Connected to media audio</string> @@ -233,7 +233,7 @@ will set the HID profile as preferred. --> <string name="bluetooth_hid_profile_summary_use_for">Use for input</string> <!-- Bluetooth settings. Connection options screen. The summary for the Hearing Aid checkbox preference that describes how checking it will set the Hearing Aid profile as preferred. --> - <string name="bluetooth_hearing_aid_profile_summary_use_for">Use for Hearing Aid</string> + <string name="bluetooth_hearing_aid_profile_summary_use_for">Use for Hearing Aids</string> <!-- Button text for accepting an incoming pairing request. [CHAR LIMIT=20] --> <string name="bluetooth_pairing_accept">Pair</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 9f3401ea7463..6cd2f45213e3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -16,6 +16,7 @@ package com.android.settingslib.bluetooth; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHearingAid; @@ -28,7 +29,7 @@ import android.os.ParcelUuid; import android.os.SystemClock; import android.text.TextUtils; import android.util.Log; -import android.bluetooth.BluetoothAdapter; + import androidx.annotation.VisibleForTesting; import com.android.settingslib.R; @@ -205,7 +206,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> // This is to ensure all the profiles are disconnected as some CK/Hs do not // disconnect PBAP connection when HF connection is brought down PbapServerProfile PbapProfile = mProfileManager.getPbapProfile(); - if (PbapProfile.getConnectionStatus(mDevice) == BluetoothProfile.STATE_CONNECTED) + if (PbapProfile != null && isConnectedProfile(PbapProfile)) { PbapProfile.disconnect(mDevice); } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 9306219415cd..b277666411f9 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -78,6 +78,7 @@ <uses-permission android:name="android.permission.MOVE_PACKAGE" /> <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" /> <uses-permission android:name="android.permission.CLEAR_APP_CACHE" /> + <uses-permission android:name="android.permission.ACCESS_INSTANT_APPS" /> <uses-permission android:name="android.permission.DELETE_CACHE_FILES" /> <uses-permission android:name="android.permission.DELETE_PACKAGES" /> <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 5b3164e3c1fd..7d0291f643cf 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -78,6 +78,7 @@ android_app { static_libs: [ "SystemUI-core", ], + resource_dirs: [], platform_apis: true, product_specific: true, diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java index 8450b749910c..7a64f5ac4ef8 100755..100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java @@ -35,6 +35,7 @@ import android.media.session.PlaybackState; import android.os.Debug; import android.os.Handler; import android.os.RemoteException; +import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -242,7 +243,8 @@ public class PipManager implements BasePipManager { ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED); - mContext.registerReceiver(mBroadcastReceiver, intentFilter); + mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, intentFilter, + null, null); if (sSettingsPackageAndClassNamePairList == null) { String[] settings = mContext.getResources().getStringArray( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index be46d2c7e4ae..be7403a1ab87 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -99,7 +99,6 @@ public class CarStatusBar extends StatusBar implements Log.d(TAG, "Connecting to HVAC service"); Dependency.get(HvacController.class).connectToCarService(); } - mCarFacetButtonController = Dependency.get(CarFacetButtonController.class); mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); mDeviceIsProvisioned = mDeviceProvisionedController.isDeviceProvisioned(); if (!mDeviceIsProvisioned) { @@ -117,7 +116,7 @@ public class CarStatusBar extends StatusBar implements /** * Remove all content from navbars and rebuild them. Used to allow for different nav bars - * before and after the device is provisioned + * before and after the device is provisioned. Also for change of density and font size. */ private void restartNavBars() { mCarFacetButtonController.removeAll(); @@ -216,6 +215,7 @@ public class CarStatusBar extends StatusBar implements protected void makeStatusBarView() { super.makeStatusBarView(); + mCarFacetButtonController = Dependency.get(CarFacetButtonController.class); mNotificationPanelBackground = getDefaultWallpaper(); mScrimController.setScrimBehindDrawable(mNotificationPanelBackground); @@ -513,6 +513,7 @@ public class CarStatusBar extends StatusBar implements @Override public void onDensityOrFontScaleChanged() { super.onDensityOrFontScaleChanged(); + restartNavBars(); // Need to update the background on density changed in case the change was due to night // mode. mNotificationPanelBackground = getDefaultWallpaper(); diff --git a/proto/Android.bp b/proto/Android.bp index f3811bdd7d81..9b7a1c15cf97 100644 --- a/proto/Android.bp +++ b/proto/Android.bp @@ -17,3 +17,24 @@ java_library_static { }, }, } + +java_library_static { + name: "metrics-constants-protos", + host_supported: true, + proto: { + type: "nano", + }, + srcs: ["src/metrics_constants.proto"], + no_framework_libs: true, + sdk_version: "system_current", + // Pin java_version until jarjar is certified to support later versions. http://b/72703434 + java_version: "1.8", + target: { + android: { + jarjar_rules: "jarjar-rules.txt", + }, + host: { + static_libs: ["libprotobuf-java-nano"], + }, + }, +} diff --git a/sax/tests/saxtests/Android.bp b/sax/tests/saxtests/Android.bp new file mode 100644 index 000000000000..5889f769a645 --- /dev/null +++ b/sax/tests/saxtests/Android.bp @@ -0,0 +1,11 @@ +android_test { + name: "FrameworksSaxTests", + // Include all test java files. + srcs: ["src/**/*.java"], + libs: [ + "android.test.runner", + "android.test.base", + ], + static_libs: ["junit"], + platform_apis: true, +} diff --git a/sax/tests/saxtests/Android.mk b/sax/tests/saxtests/Android.mk deleted file mode 100644 index c4517a9a954a..000000000000 --- a/sax/tests/saxtests/Android.mk +++ /dev/null @@ -1,16 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -# We only want this apk build for tests. -LOCAL_MODULE_TAGS := tests - -# Include all test java files. -LOCAL_SRC_FILES := $(call all-java-files-under, src) - -LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base -LOCAL_STATIC_JAVA_LIBRARIES := junit -LOCAL_PACKAGE_NAME := FrameworksSaxTests -LOCAL_PRIVATE_PLATFORM_APIS := true - -include $(BUILD_PACKAGE) - diff --git a/services/accessibility/OWNERS b/services/accessibility/OWNERS new file mode 100644 index 000000000000..265674a74b7e --- /dev/null +++ b/services/accessibility/OWNERS @@ -0,0 +1,3 @@ +svetoslavganov@google.com +pweaver@google.com +rhedjao@google.com diff --git a/services/core/Android.bp b/services/core/Android.bp index 9b8f51e49b26..48b69b029643 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -7,6 +7,7 @@ java_library_static { "frameworks/native/cmds/dumpstate/binder", "system/core/storaged/binder", "system/vold/binder", + "system/gsid/aidl", ], }, srcs: [ @@ -15,6 +16,7 @@ java_library_static { ":installd_aidl", ":storaged_aidl", ":vold_aidl", + ":gsiservice_aidl", ":mediaupdateservice_aidl", "java/com/android/server/EventLogTags.logtags", "java/com/android/server/am/EventLogTags.logtags", @@ -45,7 +47,7 @@ java_library_static { "android.hardware.vibrator-V1.0-java", "android.hardware.configstore-V1.0-java", "android.hardware.contexthub-V1.0-java", - "android.hidl.manager-V1.0-java", + "android.hidl.manager-V1.2-java", "netd_aidl_interface-java", "netd_event_listener_interface-java", ], diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 9892bfa50189..b5fcde4bf203 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -38,7 +38,6 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkPolicyManager.RULE_NONE; import static android.net.NetworkPolicyManager.uidRulesToString; -import static android.net.NetworkStack.NETWORKSTACK_PACKAGE_NAME; import static android.net.shared.NetworkMonitorUtils.isValidationRequired; import static android.net.shared.NetworkParcelableUtil.toStableParcelable; import static android.os.Process.INVALID_UID; @@ -58,9 +57,10 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.database.ContentObserver; +import android.net.CaptivePortal; import android.net.ConnectionInfo; import android.net.ConnectivityManager; -import android.net.ConnectivityManager.PacketKeepalive; +import android.net.ICaptivePortal; import android.net.IConnectivityManager; import android.net.IIpConnectivityMetrics; import android.net.INetd; @@ -87,12 +87,14 @@ import android.net.NetworkQuotaInfo; import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.NetworkStack; +import android.net.NetworkStackClient; import android.net.NetworkState; import android.net.NetworkUtils; import android.net.NetworkWatchlistManager; import android.net.PrivateDnsConfigParcel; import android.net.ProxyInfo; import android.net.RouteInfo; +import android.net.SocketKeepalive; import android.net.UidRange; import android.net.Uri; import android.net.VpnService; @@ -917,7 +919,8 @@ public class ConnectivityService extends IConnectivityManager.Stub mPermissionMonitor = new PermissionMonitor(mContext, mNMS); - //set up the listener for user state for creating user VPNs + // Set up the listener for user state for creating user VPNs. + // Should run on mHandler to avoid any races. IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_USER_STARTED); intentFilter.addAction(Intent.ACTION_USER_STOPPED); @@ -925,7 +928,11 @@ public class ConnectivityService extends IConnectivityManager.Stub intentFilter.addAction(Intent.ACTION_USER_REMOVED); intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); mContext.registerReceiverAsUser( - mIntentReceiver, UserHandle.ALL, intentFilter, null, null); + mIntentReceiver, + UserHandle.ALL, + intentFilter, + null /* broadcastPermission */, + mHandler); mContext.registerReceiverAsUser(mUserPresentReceiver, UserHandle.SYSTEM, new IntentFilter(Intent.ACTION_USER_PRESENT), null, null); @@ -936,7 +943,11 @@ public class ConnectivityService extends IConnectivityManager.Stub intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); intentFilter.addDataScheme("package"); mContext.registerReceiverAsUser( - mIntentReceiver, UserHandle.ALL, intentFilter, null, null); + mIntentReceiver, + UserHandle.ALL, + intentFilter, + null /* broadcastPermission */, + mHandler); try { mNMS.registerObserver(mTethering); @@ -1832,14 +1843,20 @@ public class ConnectivityService extends IConnectivityManager.Stub "ConnectivityService"); } - private void enforceAnyPermissionOf(String... permissions) { + private boolean checkAnyPermissionOf(String... permissions) { for (String permission : permissions) { if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { - return; + return true; } } - throw new SecurityException( - "Requires one of the following permissions: " + String.join(", ", permissions) + "."); + return false; + } + + private void enforceAnyPermissionOf(String... permissions) { + if (!checkAnyPermissionOf(permissions)) { + throw new SecurityException("Requires one of the following permissions: " + + String.join(", ", permissions) + "."); + } } private void enforceInternetPermission() { @@ -1859,19 +1876,22 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void enforceSettingsPermission() { - mContext.enforceCallingOrSelfPermission( + enforceAnyPermissionOf( android.Manifest.permission.NETWORK_SETTINGS, - "ConnectivityService"); + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); } private boolean checkSettingsPermission() { - return PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( - android.Manifest.permission.NETWORK_SETTINGS); + return checkAnyPermissionOf( + android.Manifest.permission.NETWORK_SETTINGS, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); } private boolean checkSettingsPermission(int pid, int uid) { return PERMISSION_GRANTED == mContext.checkPermission( - android.Manifest.permission.NETWORK_SETTINGS, pid, uid); + android.Manifest.permission.NETWORK_SETTINGS, pid, uid) + || PERMISSION_GRANTED == mContext.checkPermission( + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, pid, uid); } private void enforceTetherAccessPermission() { @@ -1881,9 +1901,9 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void enforceConnectivityInternalPermission() { - mContext.enforceCallingOrSelfPermission( + enforceAnyPermissionOf( android.Manifest.permission.CONNECTIVITY_INTERNAL, - "ConnectivityService"); + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); } private void enforceControlAlwaysOnVpnPermission() { @@ -1894,20 +1914,16 @@ public class ConnectivityService extends IConnectivityManager.Stub private void enforceNetworkStackSettingsOrSetup() { enforceAnyPermissionOf( - android.Manifest.permission.NETWORK_SETTINGS, - android.Manifest.permission.NETWORK_SETUP_WIZARD, - android.Manifest.permission.NETWORK_STACK); - } - - private void enforceNetworkStackPermission() { - mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, - "ConnectivityService"); + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); } private boolean checkNetworkStackPermission() { - return PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( - android.Manifest.permission.NETWORK_STACK); + return checkAnyPermissionOf( + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); } private void enforceConnectivityRestrictedNetworksPermission() { @@ -2487,8 +2503,8 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.networkMisc.acceptUnvalidated = msg.arg1 == 1; break; } - case NetworkAgent.EVENT_PACKET_KEEPALIVE: { - mKeepaliveTracker.handleEventPacketKeepalive(nai, msg); + case NetworkAgent.EVENT_SOCKET_KEEPALIVE: { + mKeepaliveTracker.handleEventSocketKeepalive(nai, msg); break; } } @@ -2661,9 +2677,9 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override - public void showProvisioningNotification(String action) { + public void showProvisioningNotification(String action, String packageName) { final Intent intent = new Intent(action); - intent.setPackage(NETWORKSTACK_PACKAGE_NAME); + intent.setPackage(packageName); final PendingIntent pendingIntent; // Only the system server can register notifications with package "android" @@ -2685,11 +2701,6 @@ public class ConnectivityService extends IConnectivityManager.Stub EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_HIDE, mNai.network.netId)); } - - @Override - public void logCaptivePortalLoginEvent(int eventId, String packageName) { - new MetricsLogger().action(eventId, packageName); - } } private boolean networkRequiresValidation(NetworkAgentInfo nai) { @@ -2828,6 +2839,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (DBG) { log(nai.name() + " got DISCONNECTED, was satisfying " + nai.numNetworkRequests()); } + // Clear all notifications of this network. + mNotifier.clearNotification(nai.network.netId); // A network agent has disconnected. // TODO - if we move the logic to the network agent (have them disconnect // because they lost all their requests or because their score isn't good) @@ -2853,8 +2866,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // sending all CALLBACK_LOST messages (for requests, not listens) at the end // of rematchAllNetworksAndRequests notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST); - mKeepaliveTracker.handleStopAllKeepalives(nai, - ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK); + mKeepaliveTracker.handleStopAllKeepalives(nai, SocketKeepalive.ERROR_INVALID_NETWORK); for (String iface : nai.linkProperties.getAllInterfaceNames()) { // Disable wakeup packet monitoring for each interface. wakeupModifyInterface(iface, nai.networkCapabilities, false); @@ -3231,6 +3243,66 @@ public class ConnectivityService extends IConnectivityManager.Stub }); } + /** + * NetworkStack endpoint to start the captive portal app. The NetworkStack needs to use this + * endpoint as it does not have INTERACT_ACROSS_USERS_FULL itself. + * @param network Network on which the captive portal was detected. + * @param appExtras Bundle to use as intent extras for the captive portal application. + * Must be treated as opaque to avoid preventing the captive portal app to + * update its arguments. + */ + @Override + public void startCaptivePortalAppInternal(Network network, Bundle appExtras) { + mContext.checkCallingOrSelfPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); + + final Intent appIntent = new Intent(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN); + appIntent.putExtras(appExtras); + appIntent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL, + new CaptivePortal(new CaptivePortalImpl(network).asBinder())); + appIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); + + Binder.withCleanCallingIdentity(() -> + mContext.startActivityAsUser(appIntent, UserHandle.CURRENT)); + } + + private class CaptivePortalImpl extends ICaptivePortal.Stub { + private final Network mNetwork; + + private CaptivePortalImpl(Network network) { + mNetwork = network; + } + + @Override + public void appResponse(final int response) throws RemoteException { + if (response == CaptivePortal.APP_RETURN_WANTED_AS_IS) { + enforceSettingsPermission(); + } + + // getNetworkAgentInfoForNetwork is thread-safe + final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(mNetwork); + if (nai == null) return; + + // nai.networkMonitor() is thread-safe + final INetworkMonitor nm = nai.networkMonitor(); + if (nm == null) return; + + final long token = Binder.clearCallingIdentity(); + try { + nm.notifyCaptivePortalAppFinished(response); + } finally { + // Not using Binder.withCleanCallingIdentity() to keep the checked RemoteException + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void logEvent(int eventId, String packageName) { + enforceSettingsPermission(); + + new MetricsLogger().action(eventId, packageName); + } + } + public boolean avoidBadWifi() { return mMultinetworkPolicyTracker.getAvoidBadWifi(); } @@ -3446,12 +3518,12 @@ public class ConnectivityService extends IConnectivityManager.Stub break; } // Sent by KeepaliveTracker to process an app request on the state machine thread. - case NetworkAgent.CMD_START_PACKET_KEEPALIVE: { + case NetworkAgent.CMD_START_SOCKET_KEEPALIVE: { mKeepaliveTracker.handleStartKeepalive(msg); break; } // Sent by KeepaliveTracker to process an app request on the state machine thread. - case NetworkAgent.CMD_STOP_PACKET_KEEPALIVE: { + case NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE: { NetworkAgentInfo nai = getNetworkAgentInfoForNetwork((Network) msg.obj); int slot = msg.arg1; int reason = msg.arg2; @@ -3643,6 +3715,20 @@ public class ConnectivityService extends IConnectivityManager.Stub mTethering.stopTethering(type); } + /** + * Get the latest value of the tethering entitlement check. + * + * Note: Allow privileged apps who have TETHER_PRIVILEGED permission to access. If it turns + * out some such apps are observed to abuse this API, change to per-UID limits on this API + * if it's really needed. + */ + @Override + public void getLatestTetheringEntitlementValue(int type, ResultReceiver receiver, + boolean showEntitlementUi, String callerPkg) { + ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg); + mTethering.getLatestTetheringEntitlementValue(type, receiver, showEntitlementUi); + } + // Called when we lose the default network and have no replacement yet. // This will automatically be cleared after X seconds or a new default network // becomes CONNECTED, whichever happens first. The timer is started by the @@ -4051,17 +4137,27 @@ public class ConnectivityService extends IConnectivityManager.Stub * handler thread through their agent, this is asynchronous. When the capabilities objects * are computed they will be up-to-date as they are computed synchronously from here and * this is running on the ConnectivityService thread. - * TODO : Fix this and call updateCapabilities inline to remove out-of-order events. */ private void updateAllVpnsCapabilities() { + Network defaultNetwork = getNetwork(getDefaultNetwork()); synchronized (mVpns) { for (int i = 0; i < mVpns.size(); i++) { final Vpn vpn = mVpns.valueAt(i); - vpn.updateCapabilities(); + NetworkCapabilities nc = vpn.updateCapabilities(defaultNetwork); + updateVpnCapabilities(vpn, nc); } } } + private void updateVpnCapabilities(Vpn vpn, @Nullable NetworkCapabilities nc) { + ensureRunningOnConnectivityServiceThread(); + NetworkAgentInfo vpnNai = getNetworkAgentInfoForNetId(vpn.getNetId()); + if (vpnNai == null || nc == null) { + return; + } + updateCapabilities(vpnNai.getCurrentScore(), vpnNai, nc); + } + @Override public boolean updateLockdownVpn() { if (Binder.getCallingUid() != Process.SYSTEM_UID) { @@ -4402,22 +4498,28 @@ public class ConnectivityService extends IConnectivityManager.Stub private void onUserAdded(int userId) { mPermissionMonitor.onUserAdded(userId); + Network defaultNetwork = getNetwork(getDefaultNetwork()); synchronized (mVpns) { final int vpnsSize = mVpns.size(); for (int i = 0; i < vpnsSize; i++) { Vpn vpn = mVpns.valueAt(i); vpn.onUserAdded(userId); + NetworkCapabilities nc = vpn.updateCapabilities(defaultNetwork); + updateVpnCapabilities(vpn, nc); } } } private void onUserRemoved(int userId) { mPermissionMonitor.onUserRemoved(userId); + Network defaultNetwork = getNetwork(getDefaultNetwork()); synchronized (mVpns) { final int vpnsSize = mVpns.size(); for (int i = 0; i < vpnsSize; i++) { Vpn vpn = mVpns.valueAt(i); vpn.onUserRemoved(userId); + NetworkCapabilities nc = vpn.updateCapabilities(defaultNetwork); + updateVpnCapabilities(vpn, nc); } } } @@ -4486,6 +4588,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + ensureRunningOnConnectivityServiceThread(); final String action = intent.getAction(); final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); @@ -4990,6 +5093,19 @@ public class ConnectivityService extends IConnectivityManager.Stub return getNetworkForRequest(mDefaultRequest.requestId); } + @Nullable + private Network getNetwork(@Nullable NetworkAgentInfo nai) { + return nai != null ? nai.network : null; + } + + private void ensureRunningOnConnectivityServiceThread() { + if (mHandler.getLooper().getThread() != Thread.currentThread()) { + throw new IllegalStateException( + "Not running on ConnectivityService thread: " + + Thread.currentThread().getName()); + } + } + private boolean isDefaultNetwork(NetworkAgentInfo nai) { return nai == getDefaultNetwork(); } @@ -5019,7 +5135,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (DBG) log("registerNetworkAgent " + nai); final long token = Binder.clearCallingIdentity(); try { - mContext.getSystemService(NetworkStack.class).makeNetworkMonitor( + getNetworkStack().makeNetworkMonitor( toStableParcelable(nai.network), name, new NetworkMonitorCallbacks(nai)); } finally { Binder.restoreCallingIdentity(token); @@ -5031,6 +5147,11 @@ public class ConnectivityService extends IConnectivityManager.Stub return nai.network.netId; } + @VisibleForTesting + protected NetworkStackClient getNetworkStack() { + return NetworkStackClient.getInstance(); + } + private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) { nai.onNetworkMonitorCreated(networkMonitor); if (VDBG) log("Got NetworkAgent Messenger"); @@ -5583,6 +5704,8 @@ public class ConnectivityService extends IConnectivityManager.Stub updateTcpBufferSizes(newNetwork.linkProperties.getTcpBufferSizes()); mDnsManager.setDefaultDnsSystemProperties(newNetwork.linkProperties.getDnsServers()); notifyIfacesChangedForNetworkStats(); + // Fix up the NetworkCapabilities of any VPNs that don't specify underlying networks. + updateAllVpnsCapabilities(); } private void processListenRequests(NetworkAgentInfo nai, boolean capabilitiesChanged) { @@ -6022,6 +6145,10 @@ public class ConnectivityService extends IConnectivityManager.Stub // doing. updateSignalStrengthThresholds(networkAgent, "CONNECT", null); + if (networkAgent.isVPN()) { + updateAllVpnsCapabilities(); + } + // Consider network even though it is not yet validated. final long now = SystemClock.elapsedRealtime(); rematchNetworkAndRequests(networkAgent, ReapUnvalidatedNetworks.REAP, now); @@ -6283,7 +6410,11 @@ public class ConnectivityService extends IConnectivityManager.Stub success = mVpns.get(user).setUnderlyingNetworks(networks); } if (success) { - mHandler.post(() -> notifyIfacesChangedForNetworkStats()); + mHandler.post(() -> { + // Update VPN's capabilities based on updated underlying network set. + updateAllVpnsCapabilities(); + notifyIfacesChangedForNetworkStats(); + }); } return success; } @@ -6301,7 +6432,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mKeepaliveTracker.startNattKeepalive( getNetworkAgentInfoForNetwork(network), intervalSeconds, messenger, binder, - srcAddr, srcPort, dstAddr, ConnectivityManager.PacketKeepalive.NATT_PORT); + srcAddr, srcPort, dstAddr, NattSocketKeepalive.NATT_PORT); } @Override @@ -6316,9 +6447,17 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override + public void startTcpKeepalive(Network network, FileDescriptor fd, int intervalSeconds, + Messenger messenger, IBinder binder) { + enforceKeepalivePermission(); + mKeepaliveTracker.startTcpKeepalive( + getNetworkAgentInfoForNetwork(network), fd, intervalSeconds, messenger, binder); + } + + @Override public void stopKeepalive(Network network, int slot) { mHandler.sendMessage(mHandler.obtainMessage( - NetworkAgent.CMD_STOP_PACKET_KEEPALIVE, slot, PacketKeepalive.SUCCESS, network)); + NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE, slot, SocketKeepalive.SUCCESS, network)); } @Override diff --git a/services/core/java/com/android/server/DynamicAndroidService.java b/services/core/java/com/android/server/DynamicAndroidService.java new file mode 100644 index 000000000000..12a3f02325d2 --- /dev/null +++ b/services/core/java/com/android/server/DynamicAndroidService.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.gsi.GsiProgress; +import android.gsi.IGsiService; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.IDynamicAndroidService; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Slog; + +/** + * DynamicAndroidService implements IDynamicAndroidService. It provides permission check before + * passing requests to gsid + */ +public class DynamicAndroidService extends IDynamicAndroidService.Stub implements DeathRecipient { + private static final String TAG = "DynamicAndroidService"; + private static final String NO_SERVICE_ERROR = "no gsiservice"; + + private Context mContext; + private volatile IGsiService mGsiService; + + DynamicAndroidService(Context context) { + mContext = context; + } + + private static IGsiService connect(DeathRecipient recipient) throws RemoteException { + IBinder binder = ServiceManager.getService("gsiservice"); + if (binder == null) { + throw new RemoteException(NO_SERVICE_ERROR); + } + /** + * The init will restart gsiservice if it crashed and the proxy object will need to be + * re-initialized in this case. + */ + binder.linkToDeath(recipient, 0); + return IGsiService.Stub.asInterface(binder); + } + + /** implements DeathRecipient */ + @Override + public void binderDied() { + Slog.w(TAG, "gsiservice died; reconnecting"); + synchronized (this) { + mGsiService = null; + } + } + + private IGsiService getGsiService() throws RemoteException { + checkPermission(); + synchronized (this) { + if (mGsiService == null) { + mGsiService = connect(this); + } + return mGsiService; + } + } + + private void checkPermission() { + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MANAGE_DYNAMIC_ANDROID) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires MANAGE_DYNAMIC_ANDROID permission"); + } + } + + @Override + public boolean startInstallation(long systemSize, long userdataSize) throws RemoteException { + return getGsiService().startGsiInstall(systemSize, userdataSize, true) == 0; + } + + @Override + public GsiProgress getInstallationProgress() throws RemoteException { + return getGsiService().getInstallProgress(); + } + + @Override + public boolean abort() throws RemoteException { + return getGsiService().cancelGsiInstall(); + } + + @Override + public boolean isInUse() throws RemoteException { + return getGsiService().isGsiRunning(); + } + + @Override + public boolean isInstalled() throws RemoteException { + return getGsiService().isGsiInstalled(); + } + + @Override + public boolean remove() throws RemoteException { + return getGsiService().removeGsiInstall(); + } + + @Override + public boolean toggle() throws RemoteException { + IGsiService gsiService = getGsiService(); + if (gsiService.isGsiRunning()) { + return gsiService.disableGsiInstall(); + } else { + return gsiService.setGsiBootable() == 0; + } + } + + @Override + public boolean write(byte[] buf) throws RemoteException { + return getGsiService().commitGsiChunkFromMemory(buf); + } + + @Override + public boolean commit() throws RemoteException { + return getGsiService().setGsiBootable() == 0; + } +} diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index da4df22d7b02..a4fda8e9f57b 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -20,18 +20,18 @@ import static android.Manifest.permission.CONNECTIVITY_INTERNAL; import static android.Manifest.permission.NETWORK_SETTINGS; import static android.Manifest.permission.NETWORK_STACK; import static android.Manifest.permission.SHUTDOWN; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE; +import static android.net.INetd.FIREWALL_BLACKLIST; +import static android.net.INetd.FIREWALL_CHAIN_DOZABLE; +import static android.net.INetd.FIREWALL_CHAIN_NONE; +import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE; +import static android.net.INetd.FIREWALL_CHAIN_STANDBY; +import static android.net.INetd.FIREWALL_RULE_ALLOW; +import static android.net.INetd.FIREWALL_RULE_DENY; +import static android.net.INetd.FIREWALL_WHITELIST; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NONE; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY; -import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW; import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; -import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY; -import static android.net.NetworkPolicyManager.FIREWALL_TYPE_BLACKLIST; -import static android.net.NetworkPolicyManager.FIREWALL_TYPE_WHITELIST; import static android.net.NetworkStats.SET_DEFAULT; import static android.net.NetworkStats.STATS_PER_UID; import static android.net.NetworkStats.TAG_ALL; @@ -1941,7 +1941,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub int numUids = 0; if (DBG) Slog.d(TAG, "Closing sockets after enabling chain " + chainName); - if (getFirewallType(chain) == FIREWALL_TYPE_WHITELIST) { + if (getFirewallType(chain) == FIREWALL_WHITELIST) { // Close all sockets on all non-system UIDs... ranges = new UidRange[] { // TODO: is there a better way of finding all existing users? If so, we could @@ -1953,7 +1953,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub final SparseIntArray rules = getUidFirewallRulesLR(chain); exemptUids = new int[rules.size()]; for (int i = 0; i < exemptUids.length; i++) { - if (rules.valueAt(i) == NetworkPolicyManager.FIREWALL_RULE_ALLOW) { + if (rules.valueAt(i) == FIREWALL_RULE_ALLOW) { exemptUids[numUids] = rules.keyAt(i); numUids++; } @@ -1975,7 +1975,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub final SparseIntArray rules = getUidFirewallRulesLR(chain); ranges = new UidRange[rules.size()]; for (int i = 0; i < ranges.length; i++) { - if (rules.valueAt(i) == NetworkPolicyManager.FIREWALL_RULE_DENY) { + if (rules.valueAt(i) == FIREWALL_RULE_DENY) { int uid = rules.keyAt(i); ranges[numUids] = new UidRange(uid, uid); numUids++; @@ -2047,13 +2047,13 @@ public class NetworkManagementService extends INetworkManagementService.Stub private int getFirewallType(int chain) { switch (chain) { case FIREWALL_CHAIN_STANDBY: - return FIREWALL_TYPE_BLACKLIST; + return FIREWALL_BLACKLIST; case FIREWALL_CHAIN_DOZABLE: - return FIREWALL_TYPE_WHITELIST; + return FIREWALL_WHITELIST; case FIREWALL_CHAIN_POWERSAVE: - return FIREWALL_TYPE_WHITELIST; + return FIREWALL_WHITELIST; default: - return isFirewallEnabled() ? FIREWALL_TYPE_WHITELIST : FIREWALL_TYPE_BLACKLIST; + return isFirewallEnabled() ? FIREWALL_WHITELIST : FIREWALL_BLACKLIST; } } @@ -2155,14 +2155,14 @@ public class NetworkManagementService extends INetworkManagementService.Stub private @NonNull String getFirewallRuleName(int chain, int rule) { String ruleName; - if (getFirewallType(chain) == FIREWALL_TYPE_WHITELIST) { - if (rule == NetworkPolicyManager.FIREWALL_RULE_ALLOW) { + if (getFirewallType(chain) == FIREWALL_WHITELIST) { + if (rule == FIREWALL_RULE_ALLOW) { ruleName = "allow"; } else { ruleName = "deny"; } } else { // Blacklist mode - if (rule == NetworkPolicyManager.FIREWALL_RULE_DENY) { + if (rule == FIREWALL_RULE_DENY) { ruleName = "deny"; } else { ruleName = "allow"; @@ -2188,7 +2188,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub private int getFirewallRuleType(int chain, int rule) { if (rule == NetworkPolicyManager.FIREWALL_RULE_DEFAULT) { - return getFirewallType(chain) == FIREWALL_TYPE_WHITELIST + return getFirewallType(chain) == FIREWALL_WHITELIST ? INetd.FIREWALL_RULE_DENY : INetd.FIREWALL_RULE_ALLOW; } return rule; diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 43af36f86f3d..3b5c9f53d9a1 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -26,6 +26,7 @@ import android.content.pm.PackageManager; import android.net.LinkProperties; import android.net.NetworkCapabilities; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -246,7 +247,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private PreciseDataConnectionState mPreciseDataConnectionState = new PreciseDataConnectionState(); - static final int ENFORCE_COARSE_LOCATION_PERMISSION_MASK = + // Nothing here yet, but putting it here in case we want to add more in the future. + static final int ENFORCE_COARSE_LOCATION_PERMISSION_MASK = 0; + + static final int ENFORCE_FINE_LOCATION_PERMISSION_MASK = PhoneStateListener.LISTEN_CELL_LOCATION | PhoneStateListener.LISTEN_CELL_INFO; @@ -637,8 +641,14 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) { try { if (VDBG) log("listen: call onSSC state=" + mServiceState[phoneId]); - r.callback.onServiceStateChanged( - new ServiceState(mServiceState[phoneId])); + ServiceState rawSs = new ServiceState(mServiceState[phoneId]); + if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { + r.callback.onServiceStateChanged(rawSs); + } else if (checkCoarseLocationAccess(r, Build.VERSION_CODES.Q)) { + r.callback.onServiceStateChanged(rawSs.sanitizeLocationInfo(false)); + } else { + r.callback.onServiceStateChanged(rawSs.sanitizeLocationInfo(true)); + } } catch (RemoteException ex) { remove(r.binder); } @@ -673,7 +683,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { try { if (DBG_LOC) log("listen: mCellLocation = " + mCellLocation[phoneId]); - if (checkLocationAccess(r)) { + if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { r.callback.onCellLocationChanged( new Bundle(mCellLocation[phoneId])); } @@ -722,7 +732,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { try { if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = " + mCellInfo.get(phoneId)); - if (checkLocationAccess(r)) { + if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { r.callback.onCellInfoChanged(mCellInfo.get(phoneId)); } } catch (RemoteException ex) { @@ -1009,13 +1019,22 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_SERVICE_STATE) && idMatch(r.subId, subId, phoneId)) { + try { + ServiceState stateToSend; + if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { + stateToSend = new ServiceState(state); + } else if (checkCoarseLocationAccess(r, Build.VERSION_CODES.Q)) { + stateToSend = state.sanitizeLocationInfo(false); + } else { + stateToSend = state.sanitizeLocationInfo(true); + } if (DBG) { log("notifyServiceStateForSubscriber: callback.onSSC r=" + r + " subId=" + subId + " phoneId=" + phoneId + " state=" + state); } - r.callback.onServiceStateChanged(new ServiceState(state)); + r.callback.onServiceStateChanged(stateToSend); } catch (RemoteException ex) { mRemoveList.add(r.binder); } @@ -1198,7 +1217,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO) && idMatch(r.subId, subId, phoneId) && - checkLocationAccess(r)) { + checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { try { if (DBG_LOC) { log("notifyCellInfo: mCellInfo=" + cellInfo + " r=" + r); @@ -1500,7 +1519,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION) && idMatch(r.subId, subId, phoneId) && - checkLocationAccess(r)) { + checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { try { if (DBG_LOC) { log("notifyCellLocation: cellLocation=" + cellLocation @@ -2109,12 +2128,35 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private boolean checkListenerPermission( int events, int subId, String callingPackage, String message) { + LocationAccessPolicy.LocationPermissionQuery.Builder locationQueryBuilder = + new LocationAccessPolicy.LocationPermissionQuery.Builder() + .setCallingPackage(callingPackage) + .setMethod(message + " events: " + events) + .setCallingPid(Binder.getCallingPid()) + .setCallingUid(Binder.getCallingUid()); + + boolean shouldCheckLocationPermissions = false; if ((events & ENFORCE_COARSE_LOCATION_PERMISSION_MASK) != 0) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_COARSE_LOCATION, null); - if (mAppOps.noteOp(AppOpsManager.OP_COARSE_LOCATION, Binder.getCallingUid(), - callingPackage) != AppOpsManager.MODE_ALLOWED) { - return false; + locationQueryBuilder.setMinSdkVersionForCoarse(0); + shouldCheckLocationPermissions = true; + } + + if ((events & ENFORCE_FINE_LOCATION_PERMISSION_MASK) != 0) { + // Everything that requires fine location started in Q. So far... + locationQueryBuilder.setMinSdkVersionForFine(Build.VERSION_CODES.Q); + shouldCheckLocationPermissions = true; + } + + if (shouldCheckLocationPermissions) { + LocationAccessPolicy.LocationPermissionResult result = + LocationAccessPolicy.checkLocationPermission( + mContext, locationQueryBuilder.build()); + switch (result) { + case DENIED_HARD: + throw new SecurityException("Unable to listen for events " + events + " due to " + + "insufficient location permissions."); + case DENIED_SOFT: + return false; } } @@ -2229,15 +2271,38 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - private boolean checkLocationAccess(Record r) { - long token = Binder.clearCallingIdentity(); - try { - return LocationAccessPolicy.canAccessCellLocation(mContext, - r.callingPackage, r.callerUid, r.callerPid, - /*throwOnDeniedPermission*/ false); - } finally { - Binder.restoreCallingIdentity(token); - } + private boolean checkFineLocationAccess(Record r, int minSdk) { + LocationAccessPolicy.LocationPermissionQuery query = + new LocationAccessPolicy.LocationPermissionQuery.Builder() + .setCallingPackage(r.callingPackage) + .setCallingPid(r.callerPid) + .setCallingUid(r.callerUid) + .setMethod("TelephonyRegistry push") + .setMinSdkVersionForFine(minSdk) + .build(); + + return Binder.withCleanCallingIdentity(() -> { + LocationAccessPolicy.LocationPermissionResult locationResult = + LocationAccessPolicy.checkLocationPermission(mContext, query); + return locationResult == LocationAccessPolicy.LocationPermissionResult.ALLOWED; + }); + } + + private boolean checkCoarseLocationAccess(Record r, int minSdk) { + LocationAccessPolicy.LocationPermissionQuery query = + new LocationAccessPolicy.LocationPermissionQuery.Builder() + .setCallingPackage(r.callingPackage) + .setCallingPid(r.callerPid) + .setCallingUid(r.callerUid) + .setMethod("TelephonyRegistry push") + .setMinSdkVersionForCoarse(minSdk) + .build(); + + return Binder.withCleanCallingIdentity(() -> { + LocationAccessPolicy.LocationPermissionResult locationResult = + LocationAccessPolicy.checkLocationPermission(mContext, query); + return locationResult == LocationAccessPolicy.LocationPermissionResult.ALLOWED; + }); } private void checkPossibleMissNotify(Record r, int phoneId) { @@ -2287,7 +2352,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { log("checkPossibleMissNotify: onCellInfoChanged[" + phoneId + "] = " + mCellInfo.get(phoneId)); } - if (checkLocationAccess(r)) { + if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { r.callback.onCellInfoChanged(mCellInfo.get(phoneId)); } } catch (RemoteException ex) { @@ -2337,7 +2402,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { try { if (DBG_LOC) log("checkPossibleMissNotify: onCellLocationChanged mCellLocation = " + mCellLocation[phoneId]); - if (checkLocationAccess(r)) { + if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { r.callback.onCellLocationChanged(new Bundle(mCellLocation[phoneId])); } } catch (RemoteException ex) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 8e80c744b75b..06d1ca632363 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -365,6 +365,7 @@ import android.provider.Downloads; import android.provider.Settings; import android.service.voice.IVoiceInteractionSession; import android.service.voice.VoiceInteractionManagerInternal; +import android.sysprop.DisplayProperties; import android.sysprop.VoldProperties; import android.telecom.TelecomManager; import android.text.TextUtils; @@ -7435,6 +7436,7 @@ public class ActivityManagerService extends IActivityManager.Stub // next app record if we are emulating process with anonymous threads. ProcessRecord app; long startTime = SystemClock.uptimeMillis(); + long bindApplicationTimeMillis; if (pid != MY_PID && pid >= 0) { synchronized (mPidsSelfLocked) { app = mPidsSelfLocked.get(pid); @@ -7665,6 +7667,7 @@ public class ActivityManagerService extends IActivityManager.Stub } checkTime(startTime, "attachApplicationLocked: immediately before bindApplication"); + bindApplicationTimeMillis = SystemClock.elapsedRealtime(); mStackSupervisor.getActivityMetricsLogger().notifyBindApplication(app); if (app.isolatedEntryPoint != null) { // This is an isolated process which should just call an entry point instead of @@ -7783,6 +7786,18 @@ public class ActivityManagerService extends IActivityManager.Stub checkTime(startTime, "attachApplicationLocked: after updateOomAdjLocked"); } + StatsLog.write( + StatsLog.PROCESS_START_TIME, + app.info.uid, + app.pid, + app.info.packageName, + StatsLog.PROCESS_START_TIME__TYPE__COLD, + app.startTime, + (int) (bindApplicationTimeMillis - app.startTime), + (int) (SystemClock.elapsedRealtime() - app.startTime), + app.hostingType, + (app.hostingNameStr != null ? app.hostingNameStr : "")); + return true; } @@ -14937,8 +14952,8 @@ public class ActivityManagerService extends IActivityManager.Stub mContext.getPackageManager().hasSystemFeature(FEATURE_LEANBACK_ONLY); mHiddenApiBlacklist.registerObserver(); - // Transfer any global setting for forcing RTL layout, into a System Property - SystemProperties.set(DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0"); + // Transfer any global setting for forcing RTL layout, into a Display Property + DisplayProperties.debug_force_rtl(forceRtl); final Configuration configuration = new Configuration(); Settings.System.getConfiguration(resolver, configuration); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 455a3e302480..e698b841a2fd 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3260,6 +3260,21 @@ public class AudioService extends IAudioService.Stub if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) { return; } + + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_PHONE_STATE) + != PackageManager.PERMISSION_GRANTED) { + synchronized (mSetModeDeathHandlers) { + for (SetModeDeathHandler h : mSetModeDeathHandlers) { + if (h.getMode() == AudioSystem.MODE_IN_CALL) { + Log.w(TAG, "getMode is call, Permission Denial: setSpeakerphoneOn from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); + return; + } + } + } + } + // for logging only final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on) .append(") from u/pid:").append(Binder.getCallingUid()).append("/") diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java index 1559ba8ba883..6cff57d4bbb1 100644 --- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java +++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java @@ -16,26 +16,32 @@ package com.android.server.connectivity; -// TODO: Clean up imports and remove references of PacketKeepalive constants. - -import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_INTERVAL; -import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_IP_ADDRESS; -import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK; -import static android.net.ConnectivityManager.PacketKeepalive.MIN_INTERVAL; -import static android.net.ConnectivityManager.PacketKeepalive.NATT_PORT; -import static android.net.ConnectivityManager.PacketKeepalive.NO_KEEPALIVE; -import static android.net.ConnectivityManager.PacketKeepalive.SUCCESS; -import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE; -import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE; -import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE; +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.NetworkAgent.EVENT_SOCKET_KEEPALIVE; +import static android.net.SocketKeepalive.BINDER_DIED; +import static android.net.SocketKeepalive.ERROR_INVALID_INTERVAL; +import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS; +import static android.net.SocketKeepalive.ERROR_INVALID_NETWORK; import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET; +import static android.net.SocketKeepalive.MAX_INTERVAL_SEC; +import static android.net.SocketKeepalive.MIN_INTERVAL_SEC; +import static android.net.SocketKeepalive.NO_KEEPALIVE; +import static android.net.SocketKeepalive.SUCCESS; import android.annotation.NonNull; import android.annotation.Nullable; -import android.net.ConnectivityManager.PacketKeepalive; import android.net.KeepalivePacketData; +import android.net.NattKeepalivePacketData; import android.net.NetworkAgent; import android.net.NetworkUtils; +import android.net.SocketKeepalive.InvalidPacketException; +import android.net.SocketKeepalive.InvalidSocketException; +import android.net.TcpKeepalivePacketData; +import android.net.TcpKeepalivePacketData.TcpSocketInfo; import android.net.util.IpUtils; import android.os.Binder; import android.os.Handler; @@ -60,11 +66,11 @@ import java.util.ArrayList; import java.util.HashMap; /** - * Manages packet keepalive requests. + * Manages socket keepalive requests. * * Provides methods to stop and start keepalive requests, and keeps track of keepalives across all * networks. This class is tightly coupled to ConnectivityService. It is not thread-safe and its - * methods must be called only from the ConnectivityService handler thread. + * handle* methods must be called only from the ConnectivityService handler thread. */ public class KeepaliveTracker { @@ -77,38 +83,54 @@ public class KeepaliveTracker { private final HashMap <NetworkAgentInfo, HashMap<Integer, KeepaliveInfo>> mKeepalives = new HashMap<> (); private final Handler mConnectivityServiceHandler; + @NonNull + private final TcpKeepaliveController mTcpController; public KeepaliveTracker(Handler handler) { mConnectivityServiceHandler = handler; + mTcpController = new TcpKeepaliveController(handler); } /** - * Tracks information about a packet keepalive. + * Tracks information about a socket keepalive. * * All information about this keepalive is known at construction time except the slot number, * which is only returned when the hardware has successfully started the keepalive. */ class KeepaliveInfo implements IBinder.DeathRecipient { - // Bookkeping data. + // Bookkeeping data. private final Messenger mMessenger; private final IBinder mBinder; private final int mUid; private final int mPid; private final NetworkAgentInfo mNai; + private final int mType; + private final FileDescriptor mFd; - /** Keepalive slot. A small integer that identifies this keepalive among the ones handled - * by this network. */ - private int mSlot = PacketKeepalive.NO_KEEPALIVE; + public static final int TYPE_NATT = 1; + public static final int TYPE_TCP = 2; + + // Keepalive slot. A small integer that identifies this keepalive among the ones handled + // by this network. + private int mSlot = NO_KEEPALIVE; // Packet data. private final KeepalivePacketData mPacket; private final int mInterval; - // Whether the keepalive is started or not. - public boolean isStarted; - - public KeepaliveInfo(Messenger messenger, IBinder binder, NetworkAgentInfo nai, - KeepalivePacketData packet, int interval) { + // Whether the keepalive is started or not. The initial state is NOT_STARTED. + private static final int NOT_STARTED = 1; + private static final int STARTING = 2; + private static final int STARTED = 3; + private int mStartedState = NOT_STARTED; + + KeepaliveInfo(@NonNull Messenger messenger, + @NonNull IBinder binder, + @NonNull NetworkAgentInfo nai, + @NonNull KeepalivePacketData packet, + int interval, + int type, + @NonNull FileDescriptor fd) { mMessenger = messenger; mBinder = binder; mPid = Binder.getCallingPid(); @@ -117,6 +139,8 @@ public class KeepaliveTracker { mNai = nai; mPacket = packet; mInterval = interval; + mType = type; + mFd = fd; try { mBinder.linkToDeath(this, 0); @@ -129,32 +153,40 @@ public class KeepaliveTracker { return mNai; } + private String startedStateString(final int state) { + switch (state) { + case NOT_STARTED : return "NOT_STARTED"; + case STARTING : return "STARTING"; + case STARTED : return "STARTED"; + } + throw new IllegalArgumentException("Unknown state"); + } + public String toString() { - return new StringBuffer("KeepaliveInfo [") - .append(" network=").append(mNai.network) - .append(" isStarted=").append(isStarted) - .append(" ") - .append(IpUtils.addressAndPortToString(mPacket.srcAddress, mPacket.srcPort)) - .append("->") - .append(IpUtils.addressAndPortToString(mPacket.dstAddress, mPacket.dstPort)) - .append(" interval=" + mInterval) - .append(" packetData=" + HexDump.toHexString(mPacket.getPacket())) - .append(" uid=").append(mUid).append(" pid=").append(mPid) - .append(" ]") - .toString(); - } - - /** Sends a message back to the application via its PacketKeepalive.Callback. */ + return "KeepaliveInfo [" + + " network=" + mNai.network + + " startedState=" + startedStateString(mStartedState) + + " " + + IpUtils.addressAndPortToString(mPacket.srcAddress, mPacket.srcPort) + + "->" + + IpUtils.addressAndPortToString(mPacket.dstAddress, mPacket.dstPort) + + " interval=" + mInterval + + " uid=" + mUid + " pid=" + mPid + + " packetData=" + HexDump.toHexString(mPacket.getPacket()) + + " ]"; + } + + /** Sends a message back to the application via its SocketKeepalive.Callback. */ void notifyMessenger(int slot, int err) { + if (DBG) { + Log.d(TAG, "notify keepalive " + mSlot + " on " + mNai.network + " for " + err); + } KeepaliveTracker.this.notifyMessenger(mMessenger, slot, err); } /** Called when the application process is killed. */ public void binderDied() { - // Not called from ConnectivityService handler thread, so send it a message. - mConnectivityServiceHandler.obtainMessage( - NetworkAgent.CMD_STOP_PACKET_KEEPALIVE, - mSlot, PacketKeepalive.BINDER_DIED, mNai.network).sendToTarget(); + stop(BINDER_DIED); } void unlinkDeathRecipient() { @@ -181,7 +213,10 @@ public class KeepaliveTracker { } private int checkInterval() { - return mInterval >= MIN_INTERVAL ? SUCCESS : ERROR_INVALID_INTERVAL; + if (mInterval < MIN_INTERVAL_SEC || mInterval > MAX_INTERVAL_SEC) { + return ERROR_INVALID_INTERVAL; + } + return SUCCESS; } private int isValid() { @@ -198,7 +233,26 @@ public class KeepaliveTracker { int error = isValid(); if (error == SUCCESS) { Log.d(TAG, "Starting keepalive " + mSlot + " on " + mNai.name()); - mNai.asyncChannel.sendMessage(CMD_START_PACKET_KEEPALIVE, slot, mInterval, mPacket); + switch (mType) { + case TYPE_NATT: + mNai.asyncChannel + .sendMessage(CMD_START_SOCKET_KEEPALIVE, slot, mInterval, mPacket); + break; + case TYPE_TCP: + mTcpController.startSocketMonitor(mFd, this, mSlot); + mNai.asyncChannel + .sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER, slot, 0 /* Unused */, + mPacket); + // TODO: check result from apf and notify of failure as needed. + mNai.asyncChannel + .sendMessage(CMD_START_SOCKET_KEEPALIVE, slot, mInterval, mPacket); + break; + default: + Log.wtf(TAG, "Starting keepalive with unknown type: " + mType); + handleStopKeepalive(mNai, mSlot, error); + return; + } + mStartedState = STARTING; } else { handleStopKeepalive(mNai, mSlot, error); return; @@ -212,20 +266,32 @@ public class KeepaliveTracker { Log.e(TAG, "Cannot stop unowned keepalive " + mSlot + " on " + mNai.network); } } - if (isStarted) { + if (NOT_STARTED != mStartedState) { Log.d(TAG, "Stopping keepalive " + mSlot + " on " + mNai.name()); - mNai.asyncChannel.sendMessage(CMD_STOP_PACKET_KEEPALIVE, mSlot); + if (mType == TYPE_NATT) { + mNai.asyncChannel.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, mSlot); + } else if (mType == TYPE_TCP) { + mNai.asyncChannel.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, mSlot); + mNai.asyncChannel.sendMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, mSlot); + mTcpController.stopSocketMonitor(mSlot); + } else { + Log.wtf(TAG, "Stopping keepalive with unknown type: " + mType); + } } // TODO: at the moment we unconditionally return failure here. In cases where the // NetworkAgent is alive, should we ask it to reply, so it can return failure? notifyMessenger(mSlot, reason); unlinkDeathRecipient(); } + + void onFileDescriptorInitiatedStop(final int socketKeepaliveReason) { + handleStopKeepalive(mNai, mSlot, socketKeepaliveReason); + } } void notifyMessenger(Messenger messenger, int slot, int err) { Message message = Message.obtain(); - message.what = EVENT_PACKET_KEEPALIVE; + message.what = EVENT_SOCKET_KEEPALIVE; message.arg1 = slot; message.arg2 = err; message.obj = null; @@ -310,7 +376,7 @@ public class KeepaliveTracker { } /** Handle keepalive events from lower layer. */ - public void handleEventPacketKeepalive(@NonNull NetworkAgentInfo nai, + public void handleEventSocketKeepalive(@NonNull NetworkAgentInfo nai, @NonNull Message message) { int slot = message.arg1; int reason = message.arg2; @@ -324,20 +390,38 @@ public class KeepaliveTracker { return; } - if (reason == SUCCESS && !ki.isStarted) { + // This can be called in a number of situations : + // - startedState is STARTING. + // - reason is SUCCESS => go to STARTED. + // - reason isn't SUCCESS => it's an error starting. Go to NOT_STARTED and stop keepalive. + // - startedState is STARTED. + // - reason is SUCCESS => it's a success stopping. Go to NOT_STARTED and stop keepalive. + // - reason isn't SUCCESS => it's an error in exec. Go to NOT_STARTED and stop keepalive. + // The control is not supposed to ever come here if the state is NOT_STARTED. This is + // because in NOT_STARTED state, the code will switch to STARTING before sending messages + // to start, and the only way to NOT_STARTED is this function, through the edges outlined + // above : in all cases, keepalive gets stopped and can't restart without going into + // STARTING as messages are ordered. This also depends on the hardware processing the + // messages in order. + // TODO : clarify this code and get rid of mStartedState. Using a StateMachine is an + // option. + if (reason == SUCCESS && KeepaliveInfo.STARTING == ki.mStartedState) { // Keepalive successfully started. if (DBG) Log.d(TAG, "Started keepalive " + slot + " on " + nai.name()); - ki.isStarted = true; + ki.mStartedState = KeepaliveInfo.STARTED; ki.notifyMessenger(slot, reason); } else { // Keepalive successfully stopped, or error. - ki.isStarted = false; + ki.mStartedState = KeepaliveInfo.NOT_STARTED; if (reason == SUCCESS) { + // The message indicated success stopping : don't call handleStopKeepalive. if (DBG) Log.d(TAG, "Successfully stopped keepalive " + slot + " on " + nai.name()); } else { + // The message indicated some error trying to start or during the course of + // keepalive : do call handleStopKeepalive. + handleStopKeepalive(nai, slot, reason); if (DBG) Log.d(TAG, "Keepalive " + slot + " on " + nai.name() + " error " + reason); } - handleStopKeepalive(nai, slot, reason); } } @@ -369,16 +453,55 @@ public class KeepaliveTracker { KeepalivePacketData packet; try { - packet = KeepalivePacketData.nattKeepalivePacket( + packet = NattKeepalivePacketData.nattKeepalivePacket( srcAddress, srcPort, dstAddress, NATT_PORT); - } catch (KeepalivePacketData.InvalidPacketException e) { + } catch (InvalidPacketException e) { notifyMessenger(messenger, NO_KEEPALIVE, e.error); return; } - KeepaliveInfo ki = new KeepaliveInfo(messenger, binder, nai, packet, intervalSeconds); - Log.d(TAG, "Created keepalive: " + ki.toString()); + KeepaliveInfo ki = new KeepaliveInfo(messenger, binder, nai, packet, intervalSeconds, + KeepaliveInfo.TYPE_NATT, null); mConnectivityServiceHandler.obtainMessage( - NetworkAgent.CMD_START_PACKET_KEEPALIVE, ki).sendToTarget(); + NetworkAgent.CMD_START_SOCKET_KEEPALIVE, ki).sendToTarget(); + } + + /** + * Called by ConnectivityService to start TCP keepalive on a file descriptor. + * + * In order to offload keepalive for application correctly, sequence number, ack number and + * other fields are needed to form the keepalive packet. Thus, this function synchronously + * puts the socket into repair mode to get the necessary information. After the socket has been + * put into repair mode, the application cannot access the socket until reverted to normal. + * + * See {@link android.net.SocketKeepalive}. + **/ + public void startTcpKeepalive(@Nullable NetworkAgentInfo nai, + @NonNull FileDescriptor fd, + int intervalSeconds, + @NonNull Messenger messenger, + @NonNull IBinder binder) { + if (nai == null) { + notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_NETWORK); + return; + } + + TcpKeepalivePacketData packet = null; + try { + TcpSocketInfo tsi = TcpKeepaliveController.switchToRepairMode(fd); + packet = TcpKeepalivePacketData.tcpKeepalivePacket(tsi); + } catch (InvalidPacketException | InvalidSocketException e) { + try { + TcpKeepaliveController.switchOutOfRepairMode(fd); + } catch (ErrnoException e1) { + Log.e(TAG, "Couldn't move fd out of repair mode after failure to start keepalive"); + } + notifyMessenger(messenger, NO_KEEPALIVE, e.error); + return; + } + KeepaliveInfo ki = new KeepaliveInfo(messenger, binder, nai, packet, intervalSeconds, + KeepaliveInfo.TYPE_TCP, fd); + Log.d(TAG, "Created keepalive: " + ki.toString()); + mConnectivityServiceHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, ki).sendToTarget(); } /** @@ -432,7 +555,7 @@ public class KeepaliveTracker { } public void dump(IndentingPrintWriter pw) { - pw.println("Packet keepalives:"); + pw.println("Socket keepalives:"); pw.increaseIndent(); for (NetworkAgentInfo nai : mKeepalives.keySet()) { pw.println(nai.name()); diff --git a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java new file mode 100644 index 000000000000..8a9ac23cf06a --- /dev/null +++ b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.connectivity; + +import static android.net.SocketKeepalive.DATA_RECEIVED; +import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET; +import static android.net.SocketKeepalive.ERROR_SOCKET_NOT_IDLE; +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; +import static android.system.OsConstants.FIONREAD; +import static android.system.OsConstants.IPPROTO_TCP; +import static android.system.OsConstants.TIOCOUTQ; + +import android.annotation.NonNull; +import android.net.NetworkUtils; +import android.net.SocketKeepalive.InvalidSocketException; +import android.net.TcpKeepalivePacketData.TcpSocketInfo; +import android.net.TcpRepairWindow; +import android.os.Handler; +import android.os.MessageQueue; +import android.os.Messenger; +import android.system.ErrnoException; +import android.system.Int32Ref; +import android.system.Os; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.connectivity.KeepaliveTracker.KeepaliveInfo; + +import java.io.FileDescriptor; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.SocketException; + +/** + * Manage tcp socket which offloads tcp keepalive. + * + * The input socket will be changed to repair mode and the application + * will not have permission to read/write data. If the application wants + * to write data, it must stop tcp keepalive offload to leave repair mode + * first. If a remote packet arrives, repair mode will be turned off and + * offload will be stopped. The application will receive a callback to know + * it can start reading data. + * + * {start,stop}SocketMonitor are thread-safe, but care must be taken in the + * order in which they are called. Please note that while calling + * {@link #startSocketMonitor(FileDescriptor, Messenger, int)} multiple times + * with either the same slot or the same FileDescriptor without stopping it in + * between will result in an exception, calling {@link #stopSocketMonitor(int)} + * multiple times with the same int is explicitly a no-op. + * Please also note that switching the socket to repair mode is not synchronized + * with either of these operations and has to be done in an orderly fashion + * with stopSocketMonitor. Take care in calling these in the right order. + * @hide + */ +public class TcpKeepaliveController { + private static final String TAG = "TcpKeepaliveController"; + private static final boolean DBG = false; + + private final MessageQueue mFdHandlerQueue; + + private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR; + + // Reference include/uapi/linux/tcp.h + private static final int TCP_REPAIR = 19; + private static final int TCP_REPAIR_QUEUE = 20; + private static final int TCP_QUEUE_SEQ = 21; + private static final int TCP_NO_QUEUE = 0; + private static final int TCP_RECV_QUEUE = 1; + private static final int TCP_SEND_QUEUE = 2; + private static final int TCP_REPAIR_OFF = 0; + private static final int TCP_REPAIR_ON = 1; + // Reference include/uapi/linux/sockios.h + private static final int SIOCINQ = FIONREAD; + private static final int SIOCOUTQ = TIOCOUTQ; + + /** + * Keeps track of packet listeners. + * Key: slot number of keepalive offload. + * Value: {@link FileDescriptor} being listened to. + */ + @GuardedBy("mListeners") + private final SparseArray<FileDescriptor> mListeners = new SparseArray<>(); + + public TcpKeepaliveController(final Handler connectivityServiceHandler) { + mFdHandlerQueue = connectivityServiceHandler.getLooper().getQueue(); + } + + /** + * Switch the tcp socket to repair mode and query tcp socket information. + * + * @param fd the fd of socket on which to use keepalive offload + * @return a {@link TcpKeepalivePacketData#TcpSocketInfo} object for current + * tcp/ip information. + */ + // TODO : make this private. It's far too confusing that this gets called from outside + // at a time that nobody can understand. + public static TcpSocketInfo switchToRepairMode(FileDescriptor fd) + throws InvalidSocketException { + if (DBG) Log.i(TAG, "switchToRepairMode to start tcp keepalive : " + fd); + final SocketAddress srcSockAddr; + final SocketAddress dstSockAddr; + final InetAddress srcAddress; + final InetAddress dstAddress; + final int srcPort; + final int dstPort; + int seq; + final int ack; + final TcpRepairWindow trw; + + // Query source address and port. + try { + srcSockAddr = Os.getsockname(fd); + } catch (ErrnoException e) { + Log.e(TAG, "Get sockname fail: ", e); + throw new InvalidSocketException(ERROR_INVALID_SOCKET, e); + } + if (srcSockAddr instanceof InetSocketAddress) { + srcAddress = getAddress((InetSocketAddress) srcSockAddr); + srcPort = getPort((InetSocketAddress) srcSockAddr); + } else { + Log.e(TAG, "Invalid or mismatched SocketAddress"); + throw new InvalidSocketException(ERROR_INVALID_SOCKET); + } + // Query destination address and port. + try { + dstSockAddr = Os.getpeername(fd); + } catch (ErrnoException e) { + Log.e(TAG, "Get peername fail: ", e); + throw new InvalidSocketException(ERROR_INVALID_SOCKET, e); + } + if (dstSockAddr instanceof InetSocketAddress) { + dstAddress = getAddress((InetSocketAddress) dstSockAddr); + dstPort = getPort((InetSocketAddress) dstSockAddr); + } else { + Log.e(TAG, "Invalid or mismatched peer SocketAddress"); + throw new InvalidSocketException(ERROR_INVALID_SOCKET); + } + + // Query sequence and ack number + dropAllIncomingPackets(fd, true); + try { + // Enter tcp repair mode. + Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR, TCP_REPAIR_ON); + // Check if socket is idle. + if (!isSocketIdle(fd)) { + throw new InvalidSocketException(ERROR_SOCKET_NOT_IDLE); + } + // Query write sequence number from SEND_QUEUE. + Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_SEND_QUEUE); + seq = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ); + // Query read sequence number from RECV_QUEUE. + Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_RECV_QUEUE); + ack = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ); + // Switch to NO_QUEUE to prevent illegal socket read/write in repair mode. + Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_NO_QUEUE); + // Finally, check if socket is still idle. TODO : this check needs to move to + // after starting polling to prevent a race. + if (!isSocketIdle(fd)) { + throw new InvalidSocketException(ERROR_INVALID_SOCKET); + } + + // Query tcp window size. + trw = NetworkUtils.getTcpRepairWindow(fd); + } catch (ErrnoException e) { + Log.e(TAG, "Exception reading TCP state from socket", e); + try { + Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR, TCP_REPAIR_OFF); + } catch (ErrnoException ex) { + Log.e(TAG, "Exception while turning off repair mode due to exception", ex); + } + throw new InvalidSocketException(ERROR_INVALID_SOCKET, e); + } finally { + dropAllIncomingPackets(fd, false); + } + + // Keepalive sequence number is last sequence number - 1. If it couldn't be retrieved, + // then it must be set to -1, so decrement in all cases. + seq = seq - 1; + + return new TcpSocketInfo(srcAddress, srcPort, dstAddress, dstPort, seq, ack, trw.rcvWnd, + trw.rcvWndScale); + } + + /** + * Switch the tcp socket out of repair mode. + * + * @param fd the fd of socket to switch back to normal. + */ + // TODO : make this private. + public static void switchOutOfRepairMode(@NonNull final FileDescriptor fd) + throws ErrnoException { + Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR, TCP_REPAIR_OFF); + } + + /** + * Start monitoring incoming packets. + * + * @param fd socket fd to monitor. + * @param messenger a callback to notify socket status. + * @param slot keepalive slot. + */ + public void startSocketMonitor(@NonNull final FileDescriptor fd, + @NonNull final KeepaliveInfo ki, final int slot) { + synchronized (mListeners) { + if (null != mListeners.get(slot)) { + throw new IllegalArgumentException("This slot is already taken"); + } + for (int i = 0; i < mListeners.size(); ++i) { + if (fd.equals(mListeners.valueAt(i))) { + throw new IllegalArgumentException("This fd is already registered"); + } + } + mFdHandlerQueue.addOnFileDescriptorEventListener(fd, FD_EVENTS, (readyFd, events) -> { + // This can't be called twice because the queue guarantees that once the listener + // is unregistered it can't be called again, even for a message that arrived + // before it was unregistered. + final int reason; + if (0 != (events & EVENT_ERROR)) { + reason = ERROR_INVALID_SOCKET; + } else { + reason = DATA_RECEIVED; + } + ki.onFileDescriptorInitiatedStop(reason); + // The listener returns the new set of events to listen to. Because 0 means no + // event, the listener gets unregistered. + return 0; + }); + mListeners.put(slot, fd); + } + } + + /** Stop socket monitor */ + // This slot may have been stopped automatically already because the socket received data, + // was closed on the other end or otherwise suffered some error. In this case, this function + // is a no-op. + public void stopSocketMonitor(final int slot) { + final FileDescriptor fd; + synchronized (mListeners) { + fd = mListeners.get(slot); + if (null == fd) return; + mListeners.remove(slot); + } + mFdHandlerQueue.removeOnFileDescriptorEventListener(fd); + try { + if (DBG) Log.d(TAG, "Moving socket out of repair mode for stop : " + fd); + switchOutOfRepairMode(fd); + } catch (ErrnoException e) { + Log.e(TAG, "Cannot switch socket out of repair mode", e); + // Well, there is not much to do here to recover + } + } + + private static InetAddress getAddress(InetSocketAddress inetAddr) { + return inetAddr.getAddress(); + } + + private static int getPort(InetSocketAddress inetAddr) { + return inetAddr.getPort(); + } + + private static boolean isSocketIdle(FileDescriptor fd) throws ErrnoException { + return isReceiveQueueEmpty(fd) && isSendQueueEmpty(fd); + } + + private static boolean isReceiveQueueEmpty(FileDescriptor fd) + throws ErrnoException { + Int32Ref result = new Int32Ref(-1); + Os.ioctlInt(fd, SIOCINQ, result); + if (result.value != 0) { + Log.e(TAG, "Read queue has data"); + return false; + } + return true; + } + + private static boolean isSendQueueEmpty(FileDescriptor fd) + throws ErrnoException { + Int32Ref result = new Int32Ref(-1); + Os.ioctlInt(fd, SIOCOUTQ, result); + if (result.value != 0) { + Log.e(TAG, "Write queue has data"); + return false; + } + return true; + } + + private static void dropAllIncomingPackets(FileDescriptor fd, boolean enable) + throws InvalidSocketException { + try { + if (enable) { + NetworkUtils.attachDropAllBPFFilter(fd); + } else { + NetworkUtils.detachBPFFilter(fd); + } + } catch (SocketException e) { + Log.e(TAG, "Socket Exception: ", e); + throw new InvalidSocketException(ERROR_INVALID_SOCKET, e); + } + } +} diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index eb5be77e4a33..c91e1a12078e 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -96,6 +96,7 @@ import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; +import com.android.internal.telephony.TelephonyIntents; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.MessageUtils; @@ -121,7 +122,6 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; - /** * @hide * @@ -181,6 +181,7 @@ public class Tethering extends BaseNetworkObserver { // into a single coherent structure. private final HashSet<IpServer> mForwardedDownstreams; private final VersionedBroadcastListener mCarrierConfigChange; + private final VersionedBroadcastListener mDefaultSubscriptionChange; private final TetheringDependencies mDeps; private final EntitlementManager mEntitlementMgr; @@ -223,7 +224,8 @@ public class Tethering extends BaseNetworkObserver { IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_CARRIER_CONFIG_CHANGED); - mEntitlementMgr = mDeps.getEntitlementManager(mContext, mLog, systemProperties); + mEntitlementMgr = mDeps.getEntitlementManager(mContext, mTetherMasterSM, + mLog, systemProperties); mCarrierConfigChange = new VersionedBroadcastListener( "CarrierConfigChangeListener", mContext, smHandler, filter, (Intent ignored) -> { @@ -232,6 +234,15 @@ public class Tethering extends BaseNetworkObserver { mEntitlementMgr.reevaluateSimCardProvisioning(); }); + filter = new IntentFilter(); + filter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); + mDefaultSubscriptionChange = new VersionedBroadcastListener( + "DefaultSubscriptionChangeListener", mContext, smHandler, filter, + (Intent ignored) -> { + mLog.log("OBSERVED default data subscription change"); + updateConfiguration(); + mEntitlementMgr.reevaluateSimCardProvisioning(); + }); mStateReceiver = new StateReceiver(); // Load tethering configuration. @@ -242,6 +253,7 @@ public class Tethering extends BaseNetworkObserver { private void startStateMachineUpdaters() { mCarrierConfigChange.startListening(); + mDefaultSubscriptionChange.startListening(); final Handler handler = mTetherMasterSM.getHandler(); IntentFilter filter = new IntentFilter(); @@ -270,7 +282,8 @@ public class Tethering extends BaseNetworkObserver { // NOTE: This is always invoked on the mLooper thread. private void updateConfiguration() { - mConfig = new TetheringConfiguration(mContext, mLog); + final int subId = mDeps.getDefaultDataSubscriptionId(); + mConfig = new TetheringConfiguration(mContext, mLog, subId); mUpstreamNetworkMonitor.updateMobileRequiresDun(mConfig.isDunRequired); mEntitlementMgr.updateConfiguration(mConfig); } @@ -470,6 +483,7 @@ public class Tethering extends BaseNetworkObserver { } else { sendTetherResult(receiver, resultCode); } + mEntitlementMgr.updateEntitlementCacheValue(type, resultCode); } }; @@ -1662,6 +1676,14 @@ public class Tethering extends BaseNetworkObserver { mUpstreamNetworkMonitor.startTrackDefaultNetwork(mDeps.getDefaultNetworkRequest()); } + /** Get the latest value of the tethering entitlement check. */ + public void getLatestTetheringEntitlementValue(int type, ResultReceiver receiver, + boolean showEntitlementUi) { + if (receiver != null) { + mEntitlementMgr.getLatestTetheringEntitlementValue(type, receiver, showEntitlementUi); + } + } + @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { // Binder.java closes the resource for us. @@ -1837,7 +1859,7 @@ public class Tethering extends BaseNetworkObserver { final TetherState tetherState = new TetherState( new IpServer(iface, mLooper, interfaceType, mLog, mNMService, mStatsService, makeControlCallback(), mConfig.enableLegacyDhcpServer, - mDeps.getIpServerDependencies(mContext))); + mDeps.getIpServerDependencies())); mTetherStates.put(iface, tetherState); tetherState.ipServer.start(); } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 250884431440..a7d16d8cd6ec 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -165,6 +165,7 @@ public class Vpn { private final NetworkInfo mNetworkInfo; private String mPackage; private int mOwnerUID; + private boolean mIsPackageTargetingAtLeastQ; private String mInterface; private Connection mConnection; private LegacyVpnRunner mLegacyVpnRunner; @@ -226,6 +227,7 @@ public class Vpn { mPackage = VpnConfig.LEGACY_VPN; mOwnerUID = getAppUid(mPackage, mUserHandle); + mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(mPackage); try { netService.registerObserver(mObserver); @@ -238,7 +240,7 @@ public class Vpn { mNetworkCapabilities = new NetworkCapabilities(); mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_VPN); mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN); - updateCapabilities(); + updateCapabilities(null /* defaultNetwork */); loadAlwaysOnPackage(); } @@ -265,29 +267,57 @@ public class Vpn { updateAlwaysOnNotification(detailedState); } - public void updateCapabilities() { - final Network[] underlyingNetworks = (mConfig != null) ? mConfig.underlyingNetworks : null; - updateCapabilities(mContext.getSystemService(ConnectivityManager.class), underlyingNetworks, - mNetworkCapabilities); + /** + * Updates {@link #mNetworkCapabilities} based on current underlying networks and returns a + * defensive copy. + * + * <p>Does not propagate updated capabilities to apps. + * + * @param defaultNetwork underlying network for VPNs following platform's default + */ + public synchronized NetworkCapabilities updateCapabilities( + @Nullable Network defaultNetwork) { + if (mConfig == null) { + // VPN is not running. + return null; + } - if (mNetworkAgent != null) { - mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); + Network[] underlyingNetworks = mConfig.underlyingNetworks; + if (underlyingNetworks == null && defaultNetwork != null) { + // null underlying networks means to track the default. + underlyingNetworks = new Network[] { defaultNetwork }; } + // Only apps targeting Q and above can explicitly declare themselves as metered. + final boolean isAlwaysMetered = mIsPackageTargetingAtLeastQ && mConfig.isMetered; + + applyUnderlyingCapabilities( + mContext.getSystemService(ConnectivityManager.class), + underlyingNetworks, + mNetworkCapabilities, + isAlwaysMetered); + + return new NetworkCapabilities(mNetworkCapabilities); } @VisibleForTesting - public static void updateCapabilities(ConnectivityManager cm, Network[] underlyingNetworks, - NetworkCapabilities caps) { + public static void applyUnderlyingCapabilities( + ConnectivityManager cm, + Network[] underlyingNetworks, + NetworkCapabilities caps, + boolean isAlwaysMetered) { int[] transportTypes = new int[] { NetworkCapabilities.TRANSPORT_VPN }; int downKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; int upKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; - boolean metered = false; + // VPN's meteredness is OR'd with isAlwaysMetered and meteredness of its underlying + // networks. + boolean metered = isAlwaysMetered; boolean roaming = false; boolean congested = false; boolean hadUnderlyingNetworks = false; if (null != underlyingNetworks) { for (Network underlying : underlyingNetworks) { + // TODO(b/124469351): Get capabilities directly from ConnectivityService instead. final NetworkCapabilities underlyingCaps = cm.getNetworkCapabilities(underlying); if (underlyingCaps == null) continue; hadUnderlyingNetworks = true; @@ -724,6 +754,7 @@ public class Vpn { Log.i(TAG, "Switched from " + mPackage + " to " + newPackage); mPackage = newPackage; mOwnerUID = getAppUid(newPackage, mUserHandle); + mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(newPackage); try { mNetd.allowProtect(mOwnerUID); } catch (Exception e) { @@ -789,6 +820,21 @@ public class Vpn { return result; } + private boolean doesPackageTargetAtLeastQ(String packageName) { + if (VpnConfig.LEGACY_VPN.equals(packageName)) { + return true; + } + PackageManager pm = mContext.getPackageManager(); + try { + ApplicationInfo appInfo = + pm.getApplicationInfoAsUser(packageName, 0 /*flags*/, mUserHandle); + return appInfo.targetSdkVersion >= VERSION_CODES.Q; + } catch (NameNotFoundException unused) { + Log.w(TAG, "Can't find \"" + packageName + "\""); + return false; + } + } + public NetworkInfo getNetworkInfo() { return mNetworkInfo; } @@ -982,9 +1028,8 @@ public class Vpn { } /** - * Establish a VPN network and return the file descriptor of the VPN - * interface. This methods returns {@code null} if the application is - * revoked or not prepared. + * Establish a VPN network and return the file descriptor of the VPN interface. This methods + * returns {@code null} if the application is revoked or not prepared. * * @param config The parameters to configure the network. * @return The file descriptor of the VPN interface. @@ -1231,6 +1276,11 @@ public class Vpn { return ranges; } + /** + * Updates UID ranges for this VPN and also updates its internal capabilities. + * + * <p>Should be called on primary ConnectivityService thread. + */ public void onUserAdded(int userHandle) { // If the user is restricted tie them to the parent user's VPN UserInfo user = UserManager.get(mContext).getUserInfo(userHandle); @@ -1241,8 +1291,9 @@ public class Vpn { try { addUserToRanges(existingRanges, userHandle, mConfig.allowedApplications, mConfig.disallowedApplications); + // ConnectivityService will call {@link #updateCapabilities} and apply + // those for VPN network. mNetworkCapabilities.setUids(existingRanges); - updateCapabilities(); } catch (Exception e) { Log.wtf(TAG, "Failed to add restricted user to owner", e); } @@ -1252,6 +1303,11 @@ public class Vpn { } } + /** + * Updates UID ranges for this VPN and also updates its capabilities. + * + * <p>Should be called on primary ConnectivityService thread. + */ public void onUserRemoved(int userHandle) { // clean up if restricted UserInfo user = UserManager.get(mContext).getUserInfo(userHandle); @@ -1263,8 +1319,9 @@ public class Vpn { final List<UidRange> removedRanges = uidRangesForUser(userHandle, existingRanges); existingRanges.removeAll(removedRanges); + // ConnectivityService will call {@link #updateCapabilities} and + // apply those for VPN network. mNetworkCapabilities.setUids(existingRanges); - updateCapabilities(); } catch (Exception e) { Log.wtf(TAG, "Failed to remove restricted user to owner", e); } @@ -1477,6 +1534,12 @@ public class Vpn { return success; } + /** + * 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()) { return false; @@ -1493,7 +1556,6 @@ public class Vpn { } } } - updateCapabilities(); return true; } @@ -1776,6 +1838,7 @@ public class Vpn { config.user = profile.key; config.interfaze = iface; config.session = profile.name; + config.isMetered = false; config.addLegacyRoutes(profile.routes); if (!profile.dnsServers.isEmpty()) { diff --git a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java index a4e3e1d85bcb..75aac106e0e0 100644 --- a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java +++ b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java @@ -21,6 +21,9 @@ import static android.net.ConnectivityManager.EXTRA_PROVISION_CALLBACK; import static android.net.ConnectivityManager.EXTRA_REM_TETHER_TYPE; import static android.net.ConnectivityManager.EXTRA_RUN_PROVISION; import static android.net.ConnectivityManager.EXTRA_SET_ALARM; +import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; +import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; +import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED; import static com.android.internal.R.string.config_wifi_tether_enable; @@ -31,15 +34,21 @@ import android.content.Intent; import android.content.res.Resources; import android.net.util.SharedLog; import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.Parcel; import android.os.PersistableBundle; import android.os.ResultReceiver; import android.os.UserHandle; import android.provider.Settings; import android.telephony.CarrierConfigManager; import android.util.ArraySet; +import android.util.Log; +import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.StateMachine; import com.android.server.connectivity.MockableSystemProperties; /** @@ -50,6 +59,7 @@ import com.android.server.connectivity.MockableSystemProperties; */ public class EntitlementManager { private static final String TAG = EntitlementManager.class.getSimpleName(); + private static final boolean DBG = false; // {@link ComponentName} of the Service used to run tether provisioning. private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString( @@ -65,15 +75,19 @@ public class EntitlementManager { private final Context mContext; private final MockableSystemProperties mSystemProperties; private final SharedLog mLog; + private final Handler mMasterHandler; + private final SparseIntArray mEntitlementCacheValue; @Nullable private TetheringConfiguration mConfig; - public EntitlementManager(Context ctx, SharedLog log, + public EntitlementManager(Context ctx, StateMachine tetherMasterSM, SharedLog log, MockableSystemProperties systemProperties) { mContext = ctx; - mLog = log; + mLog = log.forSubComponent(TAG); mCurrentTethers = new ArraySet<Integer>(); mSystemProperties = systemProperties; + mEntitlementCacheValue = new SparseIntArray(); + mMasterHandler = tetherMasterSM.getHandler(); } /** @@ -128,6 +142,10 @@ public class EntitlementManager { * Reference ConnectivityManager.TETHERING_{@code *} for each tether type. */ public void reevaluateSimCardProvisioning() { + synchronized (mEntitlementCacheValue) { + mEntitlementCacheValue.clear(); + } + if (!mConfig.hasMobileHotspotProvisionApp()) return; if (carrierConfigAffirmsEntitlementCheckNotRequired()) return; @@ -175,6 +193,11 @@ public class EntitlementManager { } public void runUiTetherProvisioningAndEnable(int type, ResultReceiver receiver) { + runUiTetherProvisioning(type, receiver); + } + + @VisibleForTesting + protected void runUiTetherProvisioning(int type, ResultReceiver receiver) { Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING); intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); @@ -221,4 +244,70 @@ public class EntitlementManager { Binder.restoreCallingIdentity(ident); } } + + private ResultReceiver buildProxyReceiver(int type, final ResultReceiver receiver) { + ResultReceiver rr = new ResultReceiver(mMasterHandler) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + int updatedCacheValue = updateEntitlementCacheValue(type, resultCode); + receiver.send(updatedCacheValue, null); + } + }; + + return writeToParcel(rr); + } + + private ResultReceiver writeToParcel(final ResultReceiver receiver) { + // This is necessary to avoid unmarshalling issues when sending the receiver + // across processes. + Parcel parcel = Parcel.obtain(); + receiver.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + ResultReceiver receiverForSending = ResultReceiver.CREATOR.createFromParcel(parcel); + parcel.recycle(); + return receiverForSending; + } + + /** + * Update the last entitlement value to internal cache + * + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + * @param resultCode last entitlement value + * @return the last updated entitlement value + */ + public int updateEntitlementCacheValue(int type, int resultCode) { + if (DBG) { + Log.d(TAG, "updateEntitlementCacheValue: " + type + ", result: " + resultCode); + } + synchronized (mEntitlementCacheValue) { + if (resultCode == TETHER_ERROR_NO_ERROR) { + mEntitlementCacheValue.put(type, resultCode); + return resultCode; + } else { + mEntitlementCacheValue.put(type, TETHER_ERROR_PROVISION_FAILED); + return TETHER_ERROR_PROVISION_FAILED; + } + } + } + + /** Get the last value of the tethering entitlement check. */ + public void getLatestTetheringEntitlementValue(int downstream, ResultReceiver receiver, + boolean showEntitlementUi) { + if (!isTetherProvisioningRequired()) { + receiver.send(TETHER_ERROR_NO_ERROR, null); + return; + } + + final int cacheValue; + synchronized (mEntitlementCacheValue) { + cacheValue = mEntitlementCacheValue.get( + downstream, TETHER_ERROR_ENTITLEMENT_UNKONWN); + } + if (cacheValue == TETHER_ERROR_NO_ERROR || !showEntitlementUi) { + receiver.send(cacheValue, null); + } else { + ResultReceiver proxy = buildProxyReceiver(downstream, receiver); + runUiTetherProvisioning(downstream, proxy); + } + } } diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java index 1e6bb04858a1..8a46ff18979f 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java @@ -26,8 +26,8 @@ import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER; import static com.android.internal.R.array.config_mobile_hotspot_provision_app; import static com.android.internal.R.array.config_tether_bluetooth_regexs; import static com.android.internal.R.array.config_tether_dhcp_range; -import static com.android.internal.R.array.config_tether_usb_regexs; import static com.android.internal.R.array.config_tether_upstream_types; +import static com.android.internal.R.array.config_tether_usb_regexs; import static com.android.internal.R.array.config_tether_wifi_regexs; import static com.android.internal.R.bool.config_tether_upstream_automatic; import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui; @@ -38,6 +38,7 @@ import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.util.SharedLog; import android.provider.Settings; +import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; @@ -100,29 +101,34 @@ public class TetheringConfiguration { public final String[] provisioningApp; public final String provisioningAppNoUi; - public TetheringConfiguration(Context ctx, SharedLog log) { + public final int subId; + + public TetheringConfiguration(Context ctx, SharedLog log, int id) { final SharedLog configLog = log.forSubComponent("config"); - tetherableUsbRegexs = getResourceStringArray(ctx, config_tether_usb_regexs); + subId = id; + Resources res = getResources(ctx, subId); + + tetherableUsbRegexs = getResourceStringArray(res, config_tether_usb_regexs); // TODO: Evaluate deleting this altogether now that Wi-Fi always passes // us an interface name. Careful consideration needs to be given to // implications for Settings and for provisioning checks. - tetherableWifiRegexs = getResourceStringArray(ctx, config_tether_wifi_regexs); - tetherableBluetoothRegexs = getResourceStringArray(ctx, config_tether_bluetooth_regexs); + tetherableWifiRegexs = getResourceStringArray(res, config_tether_wifi_regexs); + tetherableBluetoothRegexs = getResourceStringArray(res, config_tether_bluetooth_regexs); dunCheck = checkDunRequired(ctx); configLog.log("DUN check returned: " + dunCheckString(dunCheck)); - chooseUpstreamAutomatically = getResourceBoolean(ctx, config_tether_upstream_automatic); - preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(ctx, dunCheck); + chooseUpstreamAutomatically = getResourceBoolean(res, config_tether_upstream_automatic); + preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, dunCheck); isDunRequired = preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN); - legacyDhcpRanges = getLegacyDhcpRanges(ctx); + legacyDhcpRanges = getLegacyDhcpRanges(res); defaultIPv4DNS = copy(DEFAULT_IPV4_DNS); enableLegacyDhcpServer = getEnableLegacyDhcpServer(ctx); - provisioningApp = getResourceStringArray(ctx, config_mobile_hotspot_provision_app); - provisioningAppNoUi = getProvisioningAppNoUi(ctx); + provisioningApp = getResourceStringArray(res, config_mobile_hotspot_provision_app); + provisioningAppNoUi = getProvisioningAppNoUi(res); configLog.log(toString()); } @@ -144,6 +150,9 @@ public class TetheringConfiguration { } public void dump(PrintWriter pw) { + pw.print("subId: "); + pw.println(subId); + dumpStringArray(pw, "tetherableUsbRegexs", tetherableUsbRegexs); dumpStringArray(pw, "tetherableWifiRegexs", tetherableWifiRegexs); dumpStringArray(pw, "tetherableBluetoothRegexs", tetherableBluetoothRegexs); @@ -169,6 +178,7 @@ public class TetheringConfiguration { public String toString() { final StringJoiner sj = new StringJoiner(" "); + sj.add(String.format("subId:%d", subId)); sj.add(String.format("tetherableUsbRegexs:%s", makeString(tetherableUsbRegexs))); sj.add(String.format("tetherableWifiRegexs:%s", makeString(tetherableWifiRegexs))); sj.add(String.format("tetherableBluetoothRegexs:%s", @@ -235,8 +245,8 @@ public class TetheringConfiguration { } } - private static Collection<Integer> getUpstreamIfaceTypes(Context ctx, int dunCheck) { - final int ifaceTypes[] = ctx.getResources().getIntArray(config_tether_upstream_types); + private static Collection<Integer> getUpstreamIfaceTypes(Resources res, int dunCheck) { + final int[] ifaceTypes = res.getIntArray(config_tether_upstream_types); final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length); for (int i : ifaceTypes) { switch (i) { @@ -286,33 +296,33 @@ public class TetheringConfiguration { return false; } - private static String[] getLegacyDhcpRanges(Context ctx) { - final String[] fromResource = getResourceStringArray(ctx, config_tether_dhcp_range); + private static String[] getLegacyDhcpRanges(Resources res) { + final String[] fromResource = getResourceStringArray(res, config_tether_dhcp_range); if ((fromResource.length > 0) && (fromResource.length % 2 == 0)) { return fromResource; } return copy(LEGACY_DHCP_DEFAULT_RANGE); } - private static String getProvisioningAppNoUi(Context ctx) { + private static String getProvisioningAppNoUi(Resources res) { try { - return ctx.getResources().getString(config_mobile_hotspot_provision_app_no_ui); + return res.getString(config_mobile_hotspot_provision_app_no_ui); } catch (Resources.NotFoundException e) { return ""; } } - private static boolean getResourceBoolean(Context ctx, int resId) { + private static boolean getResourceBoolean(Resources res, int resId) { try { - return ctx.getResources().getBoolean(resId); + return res.getBoolean(resId); } catch (Resources.NotFoundException e404) { return false; } } - private static String[] getResourceStringArray(Context ctx, int resId) { + private static String[] getResourceStringArray(Resources res, int resId) { try { - final String[] strArray = ctx.getResources().getStringArray(resId); + final String[] strArray = res.getStringArray(resId); return (strArray != null) ? strArray : EMPTY_STRING_ARRAY; } catch (Resources.NotFoundException e404) { return EMPTY_STRING_ARRAY; @@ -325,6 +335,19 @@ public class TetheringConfiguration { return intVal != 0; } + private Resources getResources(Context ctx, int subId) { + if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + return getResourcesForSubIdWrapper(ctx, subId); + } else { + return ctx.getResources(); + } + } + + @VisibleForTesting + protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) { + return SubscriptionManager.getResourcesForSubId(ctx, subId); + } + private static String[] copy(String[] strarray) { return Arrays.copyOf(strarray, strarray.length); } diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java index a42efe960ff9..173d7860e4ac 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java @@ -21,6 +21,7 @@ import android.net.NetworkRequest; import android.net.ip.IpServer; import android.net.util.SharedLog; import android.os.Handler; +import android.telephony.SubscriptionManager; import com.android.internal.util.StateMachine; import com.android.server.connectivity.MockableSystemProperties; @@ -60,8 +61,8 @@ public class TetheringDependencies { /** * Get dependencies to be used by IpServer. */ - public IpServer.Dependencies getIpServerDependencies(Context context) { - return new IpServer.Dependencies(context); + public IpServer.Dependencies getIpServerDependencies() { + return new IpServer.Dependencies(); } /** @@ -81,8 +82,15 @@ public class TetheringDependencies { /** * Get a reference to the EntitlementManager to be used by tethering. */ - public EntitlementManager getEntitlementManager(Context ctx, SharedLog log, - MockableSystemProperties systemProperties) { - return new EntitlementManager(ctx, log, systemProperties); + public EntitlementManager getEntitlementManager(Context ctx, StateMachine target, + SharedLog log, MockableSystemProperties systemProperties) { + return new EntitlementManager(ctx, target, log, systemProperties); + } + + /** + * Get default data subscription id to build TetheringConfiguration. + */ + public int getDefaultDataSubscriptionId() { + return SubscriptionManager.getDefaultDataSubscriptionId(); } } diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java index 9e5b92a6b944..3f15b381c18b 100644 --- a/services/core/java/com/android/server/net/LockdownVpnTracker.java +++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java @@ -17,9 +17,6 @@ package com.android.server.net; import static android.Manifest.permission.CONNECTIVITY_INTERNAL; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NONE; -import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW; -import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; import static android.provider.Settings.ACTION_VPN_SETTINGS; import android.app.Notification; @@ -30,17 +27,14 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; -import android.net.LinkProperties; import android.net.LinkAddress; +import android.net.LinkProperties; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkInfo.State; -import android.net.NetworkPolicyManager; import android.os.INetworkManagementService; -import android.os.RemoteException; import android.security.Credentials; import android.security.KeyStore; -import android.system.Os; import android.text.TextUtils; import android.util.Slog; diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java index 31fdc01b8d4d..7cc357c3661c 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java +++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java @@ -15,15 +15,15 @@ */ package com.android.server.net; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE; +import static android.net.INetd.FIREWALL_CHAIN_DOZABLE; +import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE; +import static android.net.INetd.FIREWALL_CHAIN_STANDBY; +import static android.net.INetd.FIREWALL_RULE_ALLOW; +import static android.net.INetd.FIREWALL_RULE_DENY; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY; -import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW; import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; -import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY; import android.app.ActivityManager; import android.net.NetworkPolicyManager; diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index e539ffd5a85f..863ef67d4f0f 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -38,6 +38,11 @@ import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLE import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED; import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED; 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_STANDBY; +import static android.net.INetd.FIREWALL_RULE_ALLOW; +import static android.net.INetd.FIREWALL_RULE_DENY; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; @@ -45,12 +50,7 @@ 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.EXTRA_NETWORK_TEMPLATE; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY; -import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW; import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; -import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY; import static android.net.NetworkPolicyManager.MASK_ALL_NETWORKS; import static android.net.NetworkPolicyManager.MASK_METERED_NETWORKS; import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND; diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index f1b03d1fc9d6..81d6b63d9d85 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -344,6 +344,11 @@ public final class OverlayManagerService extends SystemService { private final class PackageReceiver extends BroadcastReceiver { @Override public void onReceive(@NonNull final Context context, @NonNull final Intent intent) { + final String action = intent.getAction(); + if (action == null) { + Slog.e(TAG, "Cannot handle package broadcast with null action"); + return; + } final Uri data = intent.getData(); if (data == null) { Slog.e(TAG, "Cannot handle package broadcast with null data"); @@ -361,7 +366,7 @@ public final class OverlayManagerService extends SystemService { userIds = new int[] { UserHandle.getUserId(extraUid) }; } - switch (intent.getAction()) { + switch (action) { case ACTION_PACKAGE_ADDED: if (replacing) { onPackageUpgraded(packageName, userIds); diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java index f736056c5c7f..1dada92ab118 100644 --- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java +++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java @@ -17,8 +17,10 @@ package com.android.server.os; import android.annotation.RequiresPermission; +import android.app.ActivityManager; import android.app.AppOpsManager; import android.content.Context; +import android.content.pm.UserInfo; import android.os.Binder; import android.os.BugreportParams; import android.os.IDumpstate; @@ -28,26 +30,29 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserManager; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; + import java.io.FileDescriptor; // TODO(b/111441001): -// 1. Handle the case where another bugreport is in progress -// 2. Make everything threadsafe -// 3. Pass validation & other errors on listener +// Intercept onFinished() & implement death recipient here and shutdown +// bugreportd service. /** * Implementation of the service that provides a privileged API to capture and consume bugreports. * - * <p>Delegates the actualy generation to a native implementation of {@code Dumpstate}. + * <p>Delegates the actualy generation to a native implementation of {@code IDumpstate}. */ class BugreportManagerServiceImpl extends IDumpstate.Stub { private static final String TAG = "BugreportManagerService"; private static final String BUGREPORT_SERVICE = "bugreportd"; private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000; - private IDumpstate mDs = null; + private final Object mLock = new Object(); private final Context mContext; private final AppOpsManager mAppOps; @@ -59,43 +64,44 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { @Override @RequiresPermission(android.Manifest.permission.DUMP) public IDumpstateToken setListener(String name, IDumpstateListener listener, - boolean getSectionDetails) throws RemoteException { - // TODO(b/111441001): Figure out if lazy setting of listener should be allowed - // and if so how to handle it. + boolean getSectionDetails) { throw new UnsupportedOperationException("setListener is not allowed on this service"); } - // TODO(b/111441001): Intercept onFinished here in system server and shutdown - // the bugreportd service. @Override @RequiresPermission(android.Manifest.permission.DUMP) public void startBugreport(int callingUidUnused, String callingPackage, FileDescriptor bugreportFd, FileDescriptor screenshotFd, - int bugreportMode, IDumpstateListener listener) throws RemoteException { - int callingUid = Binder.getCallingUid(); - // TODO(b/111441001): validate all arguments & ensure primary user - validate(bugreportMode); + int bugreportMode, IDumpstateListener listener) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "startBugreport"); + Preconditions.checkNotNull(callingPackage); + Preconditions.checkNotNull(bugreportFd); + Preconditions.checkNotNull(listener); + validateBugreportMode(bugreportMode); + ensureIsPrimaryUser(); + int callingUid = Binder.getCallingUid(); mAppOps.checkPackage(callingUid, callingPackage); - mDs = getDumpstateService(); - if (mDs == null) { - Slog.w(TAG, "Unable to get bugreport service"); - // TODO(b/111441001): pass error on listener - return; + + synchronized (mLock) { + startBugreportLocked(callingUid, callingPackage, bugreportFd, screenshotFd, + bugreportMode, listener); } - mDs.startBugreport(callingUid, callingPackage, - bugreportFd, screenshotFd, bugreportMode, listener); } @Override @RequiresPermission(android.Manifest.permission.DUMP) - public void cancelBugreport() throws RemoteException { - // This tells init to cancel bugreportd service. - SystemProperties.set("ctl.stop", BUGREPORT_SERVICE); - mDs = null; + public void cancelBugreport() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "startBugreport"); + // This tells init to cancel bugreportd service. Note that this is achieved through setting + // a system property which is not thread-safe. So the lock here offers thread-safety only + // among callers of the API. + synchronized (mLock) { + SystemProperties.set("ctl.stop", BUGREPORT_SERVICE); + } } - private boolean validate(@BugreportParams.BugreportMode int mode) { + private void validateBugreportMode(@BugreportParams.BugreportMode int mode) { if (mode != BugreportParams.BUGREPORT_MODE_FULL && mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE && mode != BugreportParams.BUGREPORT_MODE_REMOTE @@ -103,9 +109,66 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { && mode != BugreportParams.BUGREPORT_MODE_TELEPHONY && mode != BugreportParams.BUGREPORT_MODE_WIFI) { Slog.w(TAG, "Unknown bugreport mode: " + mode); - return false; + throw new IllegalArgumentException("Unknown bugreport mode: " + mode); + } + } + + /** + * Validates that the current user is the primary user. + * + * @throws IllegalArgumentException if the current user is not the primary user + */ + private void ensureIsPrimaryUser() { + UserInfo currentUser = null; + try { + currentUser = ActivityManager.getService().getCurrentUser(); + } catch (RemoteException e) { + // Impossible to get RemoteException for an in-process call. + } + + UserInfo primaryUser = UserManager.get(mContext).getPrimaryUser(); + if (currentUser == null) { + logAndThrow("No current user. Only primary user is allowed to take bugreports."); + } + if (primaryUser == null) { + logAndThrow("No primary user. Only primary user is allowed to take bugreports."); + } + if (primaryUser.id != currentUser.id) { + logAndThrow("Current user not primary user. Only primary user" + + " is allowed to take bugreports."); + } + } + + @GuardedBy("mLock") + private void startBugreportLocked(int callingUid, String callingPackage, + FileDescriptor bugreportFd, FileDescriptor screenshotFd, + int bugreportMode, IDumpstateListener listener) { + if (isDumpstateBinderServiceRunningLocked()) { + Slog.w(TAG, "'dumpstate' is already running. Cannot start a new bugreport" + + " while another one is currently in progress."); + // TODO(b/111441001): Use a new error code; add this to the documentation of the API. + reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR); + return; + } + + IDumpstate ds = startAndGetDumpstateBinderServiceLocked(); + if (ds == null) { + Slog.w(TAG, "Unable to get bugreport service"); + reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR); + return; + } + try { + ds.startBugreport(callingUid, callingPackage, + bugreportFd, screenshotFd, bugreportMode, listener); + } catch (RemoteException e) { + reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR); } - return true; + } + + @GuardedBy("mLock") + private boolean isDumpstateBinderServiceRunningLocked() { + IDumpstate ds = IDumpstate.Stub.asInterface(ServiceManager.getService("dumpstate")); + return ds != null; } /* @@ -115,8 +178,12 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { * <p>Generating bugreports requires root privileges. To limit the footprint * of the root access, the actual generation in Dumpstate binary is accessed as a * oneshot service 'bugreport'. + * + * <p>Note that starting the service is achieved through setting a system property, which is + * not thread-safe. So the lock here offers thread-safety only among callers of the API. */ - private IDumpstate getDumpstateService() { + @GuardedBy("mLock") + private IDumpstate startAndGetDumpstateBinderServiceLocked() { // Start bugreport service. SystemProperties.set("ctl.start", BUGREPORT_SERVICE); @@ -145,4 +212,18 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { } return ds; } + + private void reportError(IDumpstateListener listener, int errorCode) { + try { + listener.onError(errorCode); + } catch (RemoteException e) { + // Something went wrong in binder or app process. There's nothing to do here. + Slog.w(TAG, "onError() transaction threw RemoteException: " + e.getMessage()); + } + } + + private void logAndThrow(String message) { + Slog.w(TAG, message); + throw new IllegalArgumentException(message); + } } diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS index 60d792530101..640b155e69d5 100644 --- a/services/core/java/com/android/server/pm/OWNERS +++ b/services/core/java/com/android/server/pm/OWNERS @@ -19,6 +19,9 @@ per-file BackgroundDexOptService.java = ngeoffray@google.com per-file CompilerStats.java = agampe@google.com per-file CompilerStats.java = calin@google.com per-file CompilerStats.java = ngeoffray@google.com +per-file DynamicCodeLoggingService.java = agampe@google.com +per-file DynamicCodeLoggingService.java = calin@google.com +per-file DynamicCodeLoggingService.java = ngeoffray@google.com per-file InstructionSets.java = agampe@google.com per-file InstructionSets.java = calin@google.com per-file InstructionSets.java = ngeoffray@google.com @@ -51,6 +54,8 @@ per-file UserManagerService.java = omakoto@google.com per-file UserManagerService.java = yamasani@google.com per-file UserRestrictionsUtils.java = omakoto@google.com per-file UserRestrictionsUtils.java = yamasani@google.com +per-file UserRestrictionsUtils.java = rubinxu@google.com +per-file UserRestrictionsUtils.java = sandness@google.com # security per-file KeySetHandle.java = cbrubaker@google.com diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java index a8be07d76b58..08dea7f577a1 100644 --- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java +++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java @@ -480,8 +480,10 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { final String apkPath = pkg.baseCodePath; final ApplicationInfo appInfo = pkg.applicationInfo; final String outDexFile = appInfo.dataDir + "/code_cache/compiled_view.dex"; - if (appInfo.isPrivilegedApp()) { + if (appInfo.isPrivilegedApp() || appInfo.isDefaultToDeviceProtectedStorage()) { // Privileged apps prefer to load trusted code so they don't use compiled views. + // Also disable the view compiler for protected storage apps since there are + // selinux permissions required for writing to user_de. return false; } Log.i("PackageManager", "Compiling layouts in " + packageName + " (" + apkPath + diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java deleted file mode 100644 index 5f71b0b3a59a..000000000000 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.timezonedetector; - -import com.android.internal.util.DumpUtils; -import com.android.server.SystemService; -import android.app.timezonedetector.ITimeZoneDetectorService; -import android.content.Context; -import android.util.Slog; -import java.io.FileDescriptor; -import java.io.PrintWriter; - -public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub { - private static final String TAG = "timezonedetector.TimeZoneDetectorService"; - - public static class Lifecycle extends SystemService { - - public Lifecycle(Context context) { - super(context); - } - - @Override - public void onStart() { - TimeZoneDetectorService service = TimeZoneDetectorService.create(getContext()); - // Publish the binder service so it can be accessed from other (appropriately - // permissioned) processes. - publishBinderService(Context.TIME_ZONE_DETECTOR_SERVICE, service); - } - } - - private final Context mContext; - - private static TimeZoneDetectorService create(Context context) { - return new TimeZoneDetectorService(context); - } - - public TimeZoneDetectorService(Context context) { - mContext = context; - } - - @Override - public void stubbedCall() { - // Empty call for initial tests. - Slog.d(TAG, "stubbedCall() called"); - // TODO: Remove when there are real methods. - } - - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; - // TODO: Implement when there is state. - } -} diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java index c1607e94dd1e..f08e58579975 100644..100755 --- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java +++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java @@ -24,7 +24,6 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.PackageManager; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiHotplugEvent; @@ -46,7 +45,6 @@ import android.media.tv.ITvInputHardwareCallback; import android.media.tv.TvInputHardwareInfo; import android.media.tv.TvInputInfo; import android.media.tv.TvStreamConfig; -import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -56,7 +54,6 @@ import android.util.ArrayMap; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; -import android.view.KeyEvent; import android.view.Surface; import com.android.internal.util.DumpUtils; @@ -943,7 +940,7 @@ class TvInputHardwareManager implements TvInputHal.Callback { sinkChannelMask = sinkConfig.channelMask(); } if (sinkFormat == AudioFormat.ENCODING_DEFAULT) { - sinkChannelMask = sinkConfig.format(); + sinkFormat = sinkConfig.format(); } } diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index d5e59c8dfd6a..c30babd464f4 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -128,6 +128,8 @@ public final class TvInputManagerService extends SystemService { private final WatchLogHandler mWatchLogHandler; + private IBinder.DeathRecipient mDeathRecipient; + public TvInputManagerService(Context context) { super(context); @@ -672,6 +674,7 @@ public final class TvInputManagerService extends SystemService { if (sessionToken == userState.mainSessionToken) { setMainLocked(sessionToken, false, callingUid, userId); } + sessionState.session.asBinder().unlinkToDeath(sessionState, 0); sessionState.session.release(); } } catch (RemoteException | SessionNotFoundException e) { @@ -707,6 +710,7 @@ public final class TvInputManagerService extends SystemService { clientState.sessionTokens.remove(sessionToken); if (clientState.isEmpty()) { userState.clientStateMap.remove(sessionState.client.asBinder()); + sessionState.client.asBinder().unlinkToDeath(clientState, 0); } } @@ -1000,17 +1004,19 @@ public final class TvInputManagerService extends SystemService { synchronized (mLock) { final UserState userState = getOrCreateUserStateLocked(resolvedUserId); userState.callbackSet.add(callback); - try { - callback.asBinder().linkToDeath(new IBinder.DeathRecipient() { - @Override - public void binderDied() { - synchronized (mLock) { - if (userState.callbackSet != null) { - userState.callbackSet.remove(callback); - } + mDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + synchronized (mLock) { + if (userState.callbackSet != null) { + userState.callbackSet.remove(callback); } } - }, 0); + } + }; + + try { + callback.asBinder().linkToDeath(mDeathRecipient, 0); } catch (RemoteException e) { Slog.e(TAG, "client process has already died", e); } @@ -1029,6 +1035,7 @@ public final class TvInputManagerService extends SystemService { synchronized (mLock) { UserState userState = getOrCreateUserStateLocked(resolvedUserId); userState.callbackSet.remove(callback); + callback.asBinder().unlinkToDeath(mDeathRecipient, 0); } } finally { Binder.restoreCallingIdentity(identity); diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 5f2a0e8ba9d8..6735ab4d9ce2 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -120,6 +120,7 @@ cc_defaults { "android.frameworks.schedulerservice@1.0", "android.frameworks.sensorservice@1.0", "android.system.suspend@1.0", + "suspend_control_aidl_interface-cpp", ], static_libs: [ diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp index 0ff60e44b0ce..8f6cafb17042 100644 --- a/services/core/jni/com_android_server_am_BatteryStatsService.cpp +++ b/services/core/jni/com_android_server_am_BatteryStatsService.cpp @@ -30,8 +30,8 @@ #include <android/hardware/power/1.0/IPower.h> #include <android/hardware/power/1.1/IPower.h> -#include <android/system/suspend/1.0/ISystemSuspend.h> -#include <android/system/suspend/1.0/ISystemSuspendCallback.h> +#include <android/system/suspend/BnSuspendCallback.h> +#include <android/system/suspend/ISuspendControlService.h> #include <android_runtime/AndroidRuntime.h> #include <jni.h> @@ -44,14 +44,14 @@ using android::hardware::Return; using android::hardware::Void; +using android::system::suspend::BnSuspendCallback; using android::hardware::power::V1_0::PowerStatePlatformSleepState; using android::hardware::power::V1_0::PowerStateVoter; using android::hardware::power::V1_0::Status; using android::hardware::power::V1_1::PowerStateSubsystem; using android::hardware::power::V1_1::PowerStateSubsystemSleepState; using android::hardware::hidl_vec; -using android::system::suspend::V1_0::ISystemSuspend; -using android::system::suspend::V1_0::ISystemSuspendCallback; +using android::system::suspend::ISuspendControlService; using IPowerV1_1 = android::hardware::power::V1_1::IPower; using IPowerV1_0 = android::hardware::power::V1_0::IPower; @@ -66,7 +66,7 @@ static sem_t wakeup_sem; extern sp<IPowerV1_0> getPowerHalV1_0(); extern sp<IPowerV1_1> getPowerHalV1_1(); extern bool processPowerHalReturn(const Return<void> &ret, const char* functionName); -extern sp<ISystemSuspend> getSuspendHal(); +extern sp<ISuspendControlService> getSuspendControl(); // Java methods used in getLowPowerStats static jmethodID jgetAndUpdatePlatformState = NULL; @@ -74,17 +74,17 @@ static jmethodID jgetSubsystem = NULL; static jmethodID jputVoter = NULL; static jmethodID jputState = NULL; -class WakeupCallback : public ISystemSuspendCallback { -public: - Return<void> notifyWakeup(bool success) override { - ALOGV("In wakeup_callback: %s", success ? "resumed from suspend" : "suspend aborted"); +class WakeupCallback : public BnSuspendCallback { + public: + binder::Status notifyWakeup(bool success) override { + ALOGI("In wakeup_callback: %s", success ? "resumed from suspend" : "suspend aborted"); int ret = sem_post(&wakeup_sem); if (ret < 0) { char buf[80]; strerror_r(errno, buf, sizeof(buf)); ALOGE("Error posting wakeup sem: %s\n", buf); } - return Void(); + return binder::Status::ok(); } }; @@ -107,9 +107,12 @@ static jint nativeWaitWakeup(JNIEnv *env, jobject clazz, jobject outBuf) jniThrowException(env, "java/lang/IllegalStateException", buf); return -1; } - ALOGV("Registering callback..."); - sp<ISystemSuspend> suspendHal = getSuspendHal(); - suspendHal->registerCallback(new WakeupCallback()); + sp<ISuspendControlService> suspendControl = getSuspendControl(); + bool isRegistered = false; + suspendControl->registerCallback(new WakeupCallback(), &isRegistered); + if (!isRegistered) { + ALOGE("Failed to register wakeup callback"); + } } // Wait for wakeup. diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp index 0c9b5f4999a0..9be728bac532 100644 --- a/services/core/jni/com_android_server_power_PowerManagerService.cpp +++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp @@ -20,6 +20,7 @@ #include <android/hardware/power/1.1/IPower.h> #include <android/system/suspend/1.0/ISystemSuspend.h> +#include <android/system/suspend/ISuspendControlService.h> #include <nativehelper/JNIHelp.h> #include "jni.h" @@ -30,13 +31,14 @@ #include <android-base/chrono_utils.h> #include <android_runtime/AndroidRuntime.h> #include <android_runtime/Log.h> +#include <binder/IServiceManager.h> +#include <hardware/power.h> +#include <hardware_legacy/power.h> +#include <hidl/ServiceManagement.h> #include <utils/Timers.h> #include <utils/misc.h> #include <utils/String8.h> #include <utils/Log.h> -#include <hardware/power.h> -#include <hardware_legacy/power.h> -#include <hidl/ServiceManagement.h> #include "com_android_server_power_PowerManagerService.h" @@ -48,6 +50,7 @@ using android::String8; using android::system::suspend::V1_0::ISystemSuspend; using android::system::suspend::V1_0::IWakeLock; using android::system::suspend::V1_0::WakeLockType; +using android::system::suspend::ISuspendControlService; using IPowerV1_1 = android::hardware::power::V1_1::IPower; using IPowerV1_0 = android::hardware::power::V1_0::IPower; @@ -176,6 +179,7 @@ void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t } static sp<ISystemSuspend> gSuspendHal = nullptr; +static sp<ISuspendControlService> gSuspendControl = nullptr; static sp<IWakeLock> gSuspendBlocker = nullptr; static std::mutex gSuspendMutex; @@ -191,18 +195,33 @@ sp<ISystemSuspend> getSuspendHal() { return gSuspendHal; } +sp<ISuspendControlService> getSuspendControl() { + static std::once_flag suspendControlFlag; + std::call_once(suspendControlFlag, [](){ + while(gSuspendControl == nullptr) { + sp<IBinder> control = + defaultServiceManager()->getService(String16("suspend_control")); + if (control != nullptr) { + gSuspendControl = interface_cast<ISuspendControlService>(control); + } + } + }); + return gSuspendControl; +} + void enableAutoSuspend() { static bool enabled = false; - - std::lock_guard<std::mutex> lock(gSuspendMutex); if (!enabled) { - sp<ISystemSuspend> suspendHal = getSuspendHal(); - suspendHal->enableAutosuspend(); - enabled = true; + sp<ISuspendControlService> suspendControl = getSuspendControl(); + suspendControl->enableAutosuspend(&enabled); } - if (gSuspendBlocker) { - gSuspendBlocker->release(); - gSuspendBlocker.clear(); + + { + std::lock_guard<std::mutex> lock(gSuspendMutex); + if (gSuspendBlocker) { + gSuspendBlocker->release(); + gSuspendBlocker.clear(); + } } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 3ecbd47cf12e..406822c774ca 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -37,6 +37,7 @@ import android.content.res.Resources.Theme; import android.database.sqlite.SQLiteCompatibilityWalFlags; import android.database.sqlite.SQLiteGlobal; import android.hardware.display.DisplayManagerInternal; +import android.net.NetworkStackClient; import android.os.BaseBundle; import android.os.Binder; import android.os.Build; @@ -238,8 +239,6 @@ public final class SystemServer { "com.android.internal.car.CarServiceHelperService"; private static final String TIME_DETECTOR_SERVICE_CLASS = "com.android.server.timedetector.TimeDetectorService$Lifecycle"; - private static final String TIME_ZONE_DETECTOR_SERVICE_CLASS = - "com.android.server.timezonedetector.TimeZoneDetectorService$Lifecycle"; private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst"; @@ -758,6 +757,7 @@ public final class SystemServer { private void startOtherServices() { final Context context = mSystemContext; VibratorService vibrator = null; + DynamicAndroidService dynamicAndroid = null; IStorageManager storageManager = null; NetworkManagementService networkManagement = null; IpSecService ipSecService = null; @@ -867,6 +867,11 @@ public final class SystemServer { ServiceManager.addService("vibrator", vibrator); traceEnd(); + traceBeginAndSlog("StartDynamicAndroidService"); + dynamicAndroid = new DynamicAndroidService(context); + ServiceManager.addService("dynamic_android", dynamicAndroid); + traceEnd(); + if (!isWatch) { traceBeginAndSlog("StartConsumerIrService"); consumerIr = new ConsumerIrService(context); @@ -1236,9 +1241,7 @@ public final class SystemServer { traceBeginAndSlog("StartNetworkStack"); try { - final android.net.NetworkStack networkStack = - context.getSystemService(android.net.NetworkStack.class); - networkStack.start(context); + NetworkStackClient.getInstance().start(context); } catch (Throwable e) { reportWtf("starting Network Stack", e); } @@ -1310,14 +1313,6 @@ public final class SystemServer { reportWtf("starting StartTimeDetectorService service", e); } traceEnd(); - - traceBeginAndSlog("StartTimeZoneDetectorService"); - try { - mSystemServiceManager.startService(TIME_ZONE_DETECTOR_SERVICE_CLASS); - } catch (Throwable e) { - reportWtf("starting StartTimeZoneDetectorService service", e); - } - traceEnd(); } if (!isWatch) { diff --git a/services/net/Android.bp b/services/net/Android.bp index 638ec95ec544..8ad4d7679107 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -1,6 +1,10 @@ java_library_static { name: "services.net", srcs: ["java/**/*.java"], + static_libs: [ + "netd_aidl_interface-java", + "networkstack-aidl-interfaces-java", + ] } filegroup { diff --git a/services/net/java/android/net/NetworkStackClient.java b/services/net/java/android/net/NetworkStackClient.java new file mode 100644 index 000000000000..1eb7b98d801a --- /dev/null +++ b/services/net/java/android/net/NetworkStackClient.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net; + +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; +import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH; +import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.net.dhcp.DhcpServingParamsParcel; +import android.net.dhcp.IDhcpServerCallbacks; +import android.net.ip.IIpClientCallbacks; +import android.os.Binder; +import android.os.IBinder; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; + +/** + * Service used to communicate with the network stack, which is running in a separate module. + * @hide + */ +public class NetworkStackClient { + private static final String TAG = NetworkStackClient.class.getSimpleName(); + + private static final int NETWORKSTACK_TIMEOUT_MS = 10_000; + + private static NetworkStackClient sInstance; + + @NonNull + @GuardedBy("mPendingNetStackRequests") + private final ArrayList<NetworkStackCallback> mPendingNetStackRequests = new ArrayList<>(); + @Nullable + @GuardedBy("mPendingNetStackRequests") + private INetworkStackConnector mConnector; + + private volatile boolean mNetworkStackStartRequested = false; + + private interface NetworkStackCallback { + void onNetworkStackConnected(INetworkStackConnector connector); + } + + private NetworkStackClient() { } + + /** + * Get the NetworkStackClient singleton instance. + */ + public static synchronized NetworkStackClient getInstance() { + if (sInstance == null) { + sInstance = new NetworkStackClient(); + } + return sInstance; + } + + /** + * Create a DHCP server according to the specified parameters. + * + * <p>The server will be returned asynchronously through the provided callbacks. + */ + public void makeDhcpServer(final String ifName, final DhcpServingParamsParcel params, + final IDhcpServerCallbacks cb) { + requestConnector(connector -> { + try { + connector.makeDhcpServer(ifName, params, cb); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + }); + } + + /** + * Create an IpClient on the specified interface. + * + * <p>The IpClient will be returned asynchronously through the provided callbacks. + */ + public void makeIpClient(String ifName, IIpClientCallbacks cb) { + requestConnector(connector -> { + try { + connector.makeIpClient(ifName, cb); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + }); + } + + /** + * Create a NetworkMonitor. + * + * <p>The INetworkMonitor will be returned asynchronously through the provided callbacks. + */ + public void makeNetworkMonitor( + NetworkParcelable network, String name, INetworkMonitorCallbacks cb) { + requestConnector(connector -> { + try { + connector.makeNetworkMonitor(network, name, cb); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + }); + } + + private class NetworkStackConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + registerNetworkStackService(service); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + // TODO: crash/reboot the system ? + Slog.wtf(TAG, "Lost network stack connector"); + } + }; + + private void registerNetworkStackService(@NonNull IBinder service) { + final INetworkStackConnector connector = INetworkStackConnector.Stub.asInterface(service); + + ServiceManager.addService(Context.NETWORK_STACK_SERVICE, service, false /* allowIsolated */, + DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL); + + final ArrayList<NetworkStackCallback> requests; + synchronized (mPendingNetStackRequests) { + requests = new ArrayList<>(mPendingNetStackRequests); + mPendingNetStackRequests.clear(); + mConnector = connector; + } + + for (NetworkStackCallback r : requests) { + r.onNetworkStackConnected(connector); + } + } + + /** + * Start the network stack. Should be called only once on device startup. + * + * <p>This method will start the network stack either in the network stack process, or inside + * the system server on devices that do not support the network stack module. The network stack + * connector will then be delivered asynchronously to clients that requested it before it was + * started. + */ + public void start(Context context) { + mNetworkStackStartRequested = true; + // Try to bind in-process if the library is available + IBinder connector = null; + try { + final Class service = Class.forName( + "com.android.server.NetworkStackService", + true /* initialize */, + context.getClassLoader()); + connector = (IBinder) service.getMethod("makeConnector", Context.class) + .invoke(null, context); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + Slog.wtf(TAG, "Could not create network stack connector from NetworkStackService"); + // TODO: crash/reboot system here ? + return; + } catch (ClassNotFoundException e) { + // Normal behavior if stack is provided by the app: fall through + } + + // In-process network stack. Add the service to the service manager here. + if (connector != null) { + registerNetworkStackService(connector); + return; + } + // Start the network stack process. The service will be added to the service manager in + // NetworkStackConnection.onServiceConnected(). + final Intent intent = new Intent(INetworkStackConnector.class.getName()); + final ComponentName comp = intent.resolveSystemService(context.getPackageManager(), 0); + intent.setComponent(comp); + + if (comp == null) { + Slog.wtf(TAG, "Could not resolve the network stack with " + intent); + // TODO: crash/reboot system server ? + return; + } + final PackageManager pm = context.getPackageManager(); + int uid = -1; + try { + uid = pm.getPackageUid(comp.getPackageName(), UserHandle.USER_SYSTEM); + } catch (PackageManager.NameNotFoundException e) { + Slog.wtf("Network stack package not found", e); + // Fall through + } + if (uid != Process.NETWORK_STACK_UID) { + throw new SecurityException("Invalid network stack UID: " + uid); + } + + final int hasPermission = + pm.checkPermission(PERMISSION_MAINLINE_NETWORK_STACK, comp.getPackageName()); + if (hasPermission != PERMISSION_GRANTED) { + throw new SecurityException( + "Network stack does not have permission " + PERMISSION_MAINLINE_NETWORK_STACK); + } + + if (!context.bindServiceAsUser(intent, new NetworkStackConnection(), + Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM)) { + Slog.wtf(TAG, + "Could not bind to network stack in-process, or in app with " + intent); + // TODO: crash/reboot system server if no network stack after a timeout ? + } + } + + /** + * For non-system server clients, get the connector registered by the system server. + */ + private INetworkStackConnector getRemoteConnector() { + // Block until the NetworkStack connector is registered in ServiceManager. + // <p>This is only useful for non-system processes that do not have a way to be notified of + // registration completion. Adding a callback system would be too heavy weight considering + // that the connector is registered on boot, so it is unlikely that a client would request + // it before it is registered. + // TODO: consider blocking boot on registration and simplify much of the logic in this class + IBinder connector; + try { + final long before = System.currentTimeMillis(); + while ((connector = ServiceManager.getService(Context.NETWORK_STACK_SERVICE)) == null) { + Thread.sleep(20); + if (System.currentTimeMillis() - before > NETWORKSTACK_TIMEOUT_MS) { + Slog.e(TAG, "Timeout waiting for NetworkStack connector"); + return null; + } + } + } catch (InterruptedException e) { + Slog.e(TAG, "Error waiting for NetworkStack connector", e); + return null; + } + + return INetworkStackConnector.Stub.asInterface(connector); + } + + private void requestConnector(@NonNull NetworkStackCallback request) { + // TODO: PID check. + final int caller = Binder.getCallingUid(); + if (caller != Process.SYSTEM_UID && !UserHandle.isSameApp(caller, Process.BLUETOOTH_UID)) { + // Don't even attempt to obtain the connector and give a nice error message + throw new SecurityException( + "Only the system server should try to bind to the network stack."); + } + + if (!mNetworkStackStartRequested) { + // The network stack is not being started in this process, e.g. this process is not + // the system server. Get a remote connector registered by the system server. + final INetworkStackConnector connector = getRemoteConnector(); + synchronized (mPendingNetStackRequests) { + mConnector = connector; + } + request.onNetworkStackConnected(connector); + return; + } + + final INetworkStackConnector connector; + synchronized (mPendingNetStackRequests) { + connector = mConnector; + if (connector == null) { + mPendingNetStackRequests.add(request); + return; + } + } + + request.onNetworkStackConnected(connector); + } +} diff --git a/core/java/android/net/dhcp/DhcpServerCallbacks.java b/services/net/java/android/net/dhcp/DhcpServerCallbacks.java index bb56876c77f5..bb56876c77f5 100644 --- a/core/java/android/net/dhcp/DhcpServerCallbacks.java +++ b/services/net/java/android/net/dhcp/DhcpServerCallbacks.java diff --git a/core/java/android/net/ip/IpClientCallbacks.java b/services/net/java/android/net/ip/IpClientCallbacks.java index db01ae4d4d9c..db01ae4d4d9c 100644 --- a/core/java/android/net/ip/IpClientCallbacks.java +++ b/services/net/java/android/net/ip/IpClientCallbacks.java diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java index 2a2a67a92a86..bf917bf88b2d 100644 --- a/services/net/java/android/net/ip/IpClientUtil.java +++ b/services/net/java/android/net/ip/IpClientUtil.java @@ -23,8 +23,7 @@ import android.content.Context; import android.net.DhcpResultsParcelable; import android.net.LinkProperties; import android.net.LinkPropertiesParcelable; -import android.net.NetworkStack; -import android.net.ip.IIpClientCallbacks; +import android.net.NetworkStackClient; import android.os.ConditionVariable; import java.io.FileDescriptor; @@ -76,30 +75,17 @@ public class IpClientUtil { * * <p>This is a convenience method to allow clients to use {@link IpClientCallbacks} instead of * {@link IIpClientCallbacks}. - * @see {@link NetworkStack#makeIpClient(String, IIpClientCallbacks)} + * @see {@link NetworkStackClient#makeIpClient(String, IIpClientCallbacks)} */ public static void makeIpClient(Context context, String ifName, IpClientCallbacks callback) { - context.getSystemService(NetworkStack.class) - .makeIpClient(ifName, new IpClientCallbacksProxy(callback)); - } - - /** - * Create a new IpClient. - * - * <p>This is a convenience method to allow clients to use {@link IpClientCallbacksProxy} - * instead of {@link IIpClientCallbacks}. - * @see {@link NetworkStack#makeIpClient(String, IIpClientCallbacks)} - */ - public static void makeIpClient( - Context context, String ifName, IpClientCallbacksProxy callback) { - context.getSystemService(NetworkStack.class) - .makeIpClient(ifName, callback); + // TODO: migrate clients and remove context argument + NetworkStackClient.getInstance().makeIpClient(ifName, new IpClientCallbacksProxy(callback)); } /** * Wrapper to relay calls from {@link IIpClientCallbacks} to {@link IpClientCallbacks}. */ - public static class IpClientCallbacksProxy extends IIpClientCallbacks.Stub { + private static class IpClientCallbacksProxy extends IIpClientCallbacks.Stub { protected final IpClientCallbacks mCb; /** diff --git a/services/net/java/android/net/ip/IpServer.java b/services/net/java/android/net/ip/IpServer.java index 7910c9a69310..34fc7354d63e 100644 --- a/services/net/java/android/net/ip/IpServer.java +++ b/services/net/java/android/net/ip/IpServer.java @@ -22,7 +22,6 @@ import static android.net.util.NetworkConstants.FF; import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH; import static android.net.util.NetworkConstants.asByte; -import android.content.Context; import android.net.ConnectivityManager; import android.net.INetd; import android.net.INetworkStackStatusCallback; @@ -31,7 +30,7 @@ import android.net.InterfaceConfiguration; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; -import android.net.NetworkStack; +import android.net.NetworkStackClient; import android.net.RouteInfo; import android.net.dhcp.DhcpServerCallbacks; import android.net.dhcp.DhcpServingParamsParcel; @@ -132,10 +131,6 @@ public class IpServer extends StateMachine { } public static class Dependencies { - private final Context mContext; - public Dependencies(Context context) { - mContext = context; - } public RouterAdvertisementDaemon getRouterAdvertisementDaemon(InterfaceParams ifParams) { return new RouterAdvertisementDaemon(ifParams); } @@ -153,7 +148,7 @@ public class IpServer extends StateMachine { */ public void makeDhcpServer(String ifName, DhcpServingParamsParcel params, DhcpServerCallbacks cb) { - mContext.getSystemService(NetworkStack.class).makeDhcpServer(ifName, params, cb); + NetworkStackClient.getInstance().makeDhcpServer(ifName, params, cb); } } diff --git a/services/tests/servicestests/src/com/android/server/DynamicAndroidServiceTest.java b/services/tests/servicestests/src/com/android/server/DynamicAndroidServiceTest.java new file mode 100644 index 000000000000..149428443fa1 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/DynamicAndroidServiceTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.os.IDynamicAndroidService; +import android.os.ServiceManager; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; + +public class DynamicAndroidServiceTest extends AndroidTestCase { + private static final String TAG = "DynamicAndroidServiceTests"; + private IDynamicAndroidService mService; + + @Override + protected void setUp() throws Exception { + mService = + IDynamicAndroidService.Stub.asInterface( + ServiceManager.getService("dynamic_android")); + } + + @LargeTest + public void test1() { + assertTrue("dynamic_android service available", mService != null); + try { + mService.startInstallation(1 << 20, 8 << 30); + fail("DynamicAndroidService did not throw SecurityException as expected"); + } catch (SecurityException e) { + // expected + } catch (Exception e) { + fail(e.toString()); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/NetworkManagementInternalTest.java b/services/tests/servicestests/src/com/android/server/NetworkManagementInternalTest.java index c9180a99c98d..a5ac20e951ec 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkManagementInternalTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkManagementInternalTest.java @@ -16,12 +16,12 @@ package com.android.server; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY; -import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW; +import static android.net.INetd.FIREWALL_CHAIN_DOZABLE; +import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE; +import static android.net.INetd.FIREWALL_CHAIN_STANDBY; +import static android.net.INetd.FIREWALL_RULE_ALLOW; +import static android.net.INetd.FIREWALL_RULE_DENY; import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; -import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY; import static android.util.DebugUtils.valueToString; import static org.junit.Assert.assertEquals; diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java deleted file mode 100644 index 19d31cfafc35..000000000000 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.timezonedetector; - -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Unit tests for the {@link TimeZoneDetectorService}. - */ -@RunWith(AndroidJUnit4.class) -public class TimeZoneDetectorServiceTest { - - private TimeZoneDetectorService mTimeZoneDetectorService; - - @Before - public void setUp() { - final Context context = InstrumentationRegistry.getContext(); - mTimeZoneDetectorService = new TimeZoneDetectorService(context); - } - - @Test - public void testStubbedCall() { - mTimeZoneDetectorService.stubbedCall(); - } -} diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 84611667d113..39fc715c7770 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -124,6 +124,7 @@ public class AppStandbyControllerTests { static class MyInjector extends AppStandbyController.Injector { long mElapsedRealtime; + boolean mIsAppIdleEnabled = true; boolean mIsCharging; List<String> mPowerSaveWhitelistExceptIdle = new ArrayList<>(); boolean mDisplayOn; @@ -155,7 +156,7 @@ public class AppStandbyControllerTests { @Override boolean isAppIdleEnabled() { - return true; + return mIsAppIdleEnabled; } @Override @@ -266,6 +267,13 @@ public class AppStandbyControllerTests { } } + private void setAppIdleEnabled(AppStandbyController controller, boolean enabled) { + mInjector.mIsAppIdleEnabled = enabled; + if (controller != null) { + controller.setAppIdleEnabled(enabled); + } + } + private AppStandbyController setupController() throws Exception { mInjector.mElapsedRealtime = 0; setupPm(mInjector.getContext().getPackageManager()); @@ -335,7 +343,7 @@ public class AppStandbyControllerTests { public void onParoleStateChanged(boolean isParoleOn) { synchronized (this) { // Only record information if it is being looked for - if (mLatch.getCount() > 0) { + if (mLatch != null && mLatch.getCount() > 0) { mOnParole = isParoleOn; mLastParoleChangeTime = getCurrentTime(); mLatch.countDown(); @@ -396,6 +404,74 @@ public class AppStandbyControllerTests { marginOfError); } + @Test + public void testEnabledState() throws Exception { + TestParoleListener paroleListener = new TestParoleListener(); + mController.addListener(paroleListener); + long lastUpdateTime; + + // Test that listeners are notified if enabled changes when the device is not in parole. + setChargingState(mController, false); + + // Start off not enabled. Device is effectively on permanent parole. + setAppIdleEnabled(mController, false); + + // Enable controller + paroleListener.rearmLatch(); + setAppIdleEnabled(mController, true); + paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); + assertFalse(paroleListener.mOnParole); + lastUpdateTime = paroleListener.getLastParoleChangeTime(); + + paroleListener.rearmLatch(); + setAppIdleEnabled(mController, true); + paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); + assertFalse(paroleListener.mOnParole); + // Make sure AppStandbyController doesn't notify listeners when there's no change. + assertEquals(lastUpdateTime, paroleListener.getLastParoleChangeTime()); + + // Disable controller + paroleListener.rearmLatch(); + setAppIdleEnabled(mController, false); + paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); + assertTrue(paroleListener.mOnParole); + lastUpdateTime = paroleListener.getLastParoleChangeTime(); + + paroleListener.rearmLatch(); + setAppIdleEnabled(mController, false); + paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); + assertTrue(paroleListener.mOnParole); + // Make sure AppStandbyController doesn't notify listeners when there's no change. + assertEquals(lastUpdateTime, paroleListener.getLastParoleChangeTime()); + + + // Test that listeners aren't notified if enabled status changes when the device is already + // in parole. + + // A device is in parole whenever it's charging. + setChargingState(mController, true); + + // Start off not enabled. + paroleListener.rearmLatch(); + setAppIdleEnabled(mController, false); + paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); + assertTrue(paroleListener.mOnParole); + lastUpdateTime = paroleListener.getLastParoleChangeTime(); + + // Test that toggling doesn't notify the listener. + paroleListener.rearmLatch(); + setAppIdleEnabled(mController, true); + paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); + assertTrue(paroleListener.mOnParole); + assertEquals(lastUpdateTime, paroleListener.getLastParoleChangeTime()); + + paroleListener.rearmLatch(); + setAppIdleEnabled(mController, false); + paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); + assertTrue(paroleListener.mOnParole); + assertEquals(lastUpdateTime, paroleListener.getLastParoleChangeTime()); + } + private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket) { mInjector.mElapsedRealtime = elapsedTime; controller.checkIdleStates(USER_ID); diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java index 9c62700a9118..e380d7a6808b 100644 --- a/services/usage/java/com/android/server/usage/AppStandbyController.java +++ b/services/usage/java/com/android/server/usage/AppStandbyController.java @@ -30,18 +30,19 @@ import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_EXEMPTED_SYNC import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_BACKGROUND; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_FOREGROUND; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_NOTIFICATION_SEEN; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SLICE_PINNED; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SLICE_PINNED_PRIV; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYNC_ADAPTER; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYSTEM_INTERACTION; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYSTEM_UPDATE; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION; -import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SLICE_PINNED; -import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SLICE_PINNED_PRIV; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_EXEMPTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; + import static com.android.server.SystemService.PHASE_BOOT_COMPLETED; import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; @@ -340,14 +341,21 @@ public class AppStandbyController { } void setAppIdleEnabled(boolean enabled) { - mAppIdleEnabled = enabled; + synchronized (mAppIdleLock) { + if (mAppIdleEnabled != enabled) { + final boolean oldParoleState = isParoledOrCharging(); + mAppIdleEnabled = enabled; + if (isParoledOrCharging() != oldParoleState) { + postParoleStateChanged(); + } + } + } } public void onBootPhase(int phase) { mInjector.onBootPhase(phase); if (phase == PHASE_SYSTEM_SERVICES_READY) { Slog.d(TAG, "Setting app idle enabled state"); - setAppIdleEnabled(mInjector.isAppIdleEnabled()); // Observe changes to the threshold SettingsObserver settingsObserver = new SettingsObserver(mHandler); settingsObserver.registerObserver(); @@ -1807,8 +1815,6 @@ public class AppStandbyController { mContext.getContentResolver(), Global.APP_IDLE_CONSTANTS)); } - // Check if app_idle_enabled has changed - setAppIdleEnabled(mInjector.isAppIdleEnabled()); // Look at global settings for this. // TODO: Maybe apply different thresholds for different users. @@ -1880,6 +1886,10 @@ public class AppStandbyController { (KEY_STABLE_CHARGING_THRESHOLD, COMPRESS_TIME ? ONE_MINUTE : DEFAULT_STABLE_CHARGING_THRESHOLD); } + + // Check if app_idle_enabled has changed. Do this after getting the rest of the settings + // in case we need to change something based on the new values. + setAppIdleEnabled(mInjector.isAppIdleEnabled()); } long[] parseLongArray(String values, long[] defaults) { diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 7ea61e30dafe..da682c6df621 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -69,6 +69,7 @@ import android.os.storage.StorageVolume; import android.provider.Settings; import android.service.usb.UsbDeviceManagerProto; import android.service.usb.UsbHandlerProto; +import android.sysprop.AdbProperties; import android.sysprop.VoldProperties; import android.util.Pair; import android.util.Slog; @@ -285,7 +286,7 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver } mControlFds.put(UsbManager.FUNCTION_PTP, ptpFd); - boolean secureAdbEnabled = SystemProperties.getBoolean("ro.adb.secure", false); + boolean secureAdbEnabled = AdbProperties.secure().orElse(false); boolean dataEncrypted = "1".equals(VoldProperties.decrypt().orElse("")); if (secureAdbEnabled && !dataEncrypted) { mDebuggingManager = new UsbDebuggingManager(context); diff --git a/startop/view_compiler/OWNERS b/startop/view_compiler/OWNERS new file mode 100644 index 000000000000..e5aead9ddac8 --- /dev/null +++ b/startop/view_compiler/OWNERS @@ -0,0 +1,2 @@ +eholk@google.com +mathieuc@google.com diff --git a/startop/view_compiler/TEST_MAPPING b/startop/view_compiler/TEST_MAPPING index 700607536890..5f7d3f99ae81 100644 --- a/startop/view_compiler/TEST_MAPPING +++ b/startop/view_compiler/TEST_MAPPING @@ -4,6 +4,14 @@ "name": "dex-builder-test" }, { + "name": "CtsViewTestCases", + "options": [ + { + "include-filter": "android.view.cts.PrecompiledLayoutTest" + } + ] + }, + { "name": "view-compiler-tests", "host": true } diff --git a/startop/view_compiler/dex_builder.cc b/startop/view_compiler/dex_builder.cc index a78f7d53d135..6047e8c74e38 100644 --- a/startop/view_compiler/dex_builder.cc +++ b/startop/view_compiler/dex_builder.cc @@ -21,8 +21,6 @@ #include <fstream> #include <memory> -#define DCHECK_NOT_NULL(p) DCHECK((p) != nullptr) - namespace startop { namespace dex { @@ -32,6 +30,8 @@ using std::string; using ::dex::kAccPublic; using Op = Instruction::Op; +using Opcode = ::art::Instruction::Code; + const TypeDescriptor TypeDescriptor::Int() { return TypeDescriptor{"I"}; }; const TypeDescriptor TypeDescriptor::Void() { return TypeDescriptor{"V"}; }; @@ -42,6 +42,23 @@ constexpr uint8_t kDexFileMagic[]{0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x38, 0x00 // Strings lengths can be 32 bits long, but encoded as LEB128 this can take up to five bytes. constexpr size_t kMaxEncodedStringLength{5}; +// Converts invoke-* to invoke-*/range +constexpr Opcode InvokeToInvokeRange(Opcode opcode) { + switch (opcode) { + case ::art::Instruction::INVOKE_VIRTUAL: + return ::art::Instruction::INVOKE_VIRTUAL_RANGE; + case ::art::Instruction::INVOKE_DIRECT: + return ::art::Instruction::INVOKE_DIRECT_RANGE; + case ::art::Instruction::INVOKE_STATIC: + return ::art::Instruction::INVOKE_STATIC_RANGE; + case ::art::Instruction::INVOKE_INTERFACE: + return ::art::Instruction::INVOKE_INTERFACE_RANGE; + default: + LOG(FATAL) << opcode << " is not a recognized invoke opcode."; + UNREACHABLE(); + } +} + } // namespace std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode) { @@ -55,6 +72,9 @@ std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode) { case Instruction::Op::kMove: out << "kMove"; return out; + case Instruction::Op::kMoveObject: + out << "kMoveObject"; + return out; case Instruction::Op::kInvokeVirtual: out << "kInvokeVirtual"; return out; @@ -233,6 +253,11 @@ std::string Prototype::Shorty() const { return shorty; } +const TypeDescriptor& Prototype::ArgType(size_t index) const { + CHECK_LT(index, param_types_.size()); + return param_types_[index]; +} + ClassBuilder::ClassBuilder(DexBuilder* parent, const std::string& name, ir::Class* class_def) : parent_(parent), type_descriptor_{TypeDescriptor::FromClassname(name)}, class_(class_def) {} @@ -257,10 +282,10 @@ ir::EncodedMethod* MethodBuilder::Encode() { method->access_flags = kAccPublic | ::dex::kAccStatic; auto* code = dex_->Alloc<ir::Code>(); - DCHECK_NOT_NULL(decl_->prototype); + CHECK(decl_->prototype != nullptr); size_t const num_args = decl_->prototype->param_types != nullptr ? decl_->prototype->param_types->types.size() : 0; - code->registers = num_registers_ + num_args; + code->registers = num_registers_ + num_args + kMaxScratchRegisters; code->ins_count = num_args; EncodeInstructions(); code->instructions = slicer::ArrayView<const ::dex::u2>(buffer_.data(), buffer_.size()); @@ -292,7 +317,7 @@ void MethodBuilder::BuildReturn(Value src, bool is_object) { } void MethodBuilder::BuildConst4(Value target, int value) { - DCHECK_LT(value, 16); + CHECK_LT(value, 16); AddInstruction(Instruction::OpWithArgs(Op::kMove, target, Value::Immediate(value))); } @@ -315,6 +340,7 @@ void MethodBuilder::EncodeInstruction(const Instruction& instruction) { case Instruction::Op::kReturnObject: return EncodeReturn(instruction, ::art::Instruction::RETURN_OBJECT); case Instruction::Op::kMove: + case Instruction::Op::kMoveObject: return EncodeMove(instruction); case Instruction::Op::kInvokeVirtual: return EncodeInvoke(instruction, art::Instruction::INVOKE_VIRTUAL); @@ -338,33 +364,43 @@ void MethodBuilder::EncodeInstruction(const Instruction& instruction) { } void MethodBuilder::EncodeReturn(const Instruction& instruction, ::art::Instruction::Code opcode) { - DCHECK(!instruction.dest().has_value()); + CHECK(!instruction.dest().has_value()); if (instruction.args().size() == 0) { Encode10x(art::Instruction::RETURN_VOID); } else { - DCHECK_EQ(1, instruction.args().size()); + CHECK_EQ(1, instruction.args().size()); size_t source = RegisterValue(instruction.args()[0]); Encode11x(opcode, source); } } void MethodBuilder::EncodeMove(const Instruction& instruction) { - DCHECK_EQ(Instruction::Op::kMove, instruction.opcode()); - DCHECK(instruction.dest().has_value()); - DCHECK(instruction.dest()->is_register() || instruction.dest()->is_parameter()); - DCHECK_EQ(1, instruction.args().size()); + CHECK(Instruction::Op::kMove == instruction.opcode() || + Instruction::Op::kMoveObject == instruction.opcode()); + CHECK(instruction.dest().has_value()); + CHECK(instruction.dest()->is_variable()); + CHECK_EQ(1, instruction.args().size()); const Value& source = instruction.args()[0]; if (source.is_immediate()) { // TODO: support more registers - DCHECK_LT(RegisterValue(*instruction.dest()), 16); + CHECK_LT(RegisterValue(*instruction.dest()), 16); Encode11n(art::Instruction::CONST_4, RegisterValue(*instruction.dest()), source.value()); } else if (source.is_string()) { constexpr size_t kMaxRegisters = 256; - DCHECK_LT(RegisterValue(*instruction.dest()), kMaxRegisters); - DCHECK_LT(source.value(), 65536); // make sure we don't need a jumbo string + CHECK_LT(RegisterValue(*instruction.dest()), kMaxRegisters); + CHECK_LT(source.value(), 65536); // make sure we don't need a jumbo string Encode21c(::art::Instruction::CONST_STRING, RegisterValue(*instruction.dest()), source.value()); + } else if (source.is_variable()) { + // For the moment, we only use this when we need to reshuffle registers for + // an invoke instruction, meaning we are too big for the 4-bit version. + // We'll err on the side of caution and always generate the 16-bit form of + // the instruction. + Opcode opcode = instruction.opcode() == Instruction::Op::kMove + ? ::art::Instruction::MOVE_16 + : ::art::Instruction::MOVE_OBJECT_16; + Encode32x(opcode, RegisterValue(*instruction.dest()), RegisterValue(source)); } else { UNIMPLEMENTED(FATAL); } @@ -373,22 +409,61 @@ void MethodBuilder::EncodeMove(const Instruction& instruction) { void MethodBuilder::EncodeInvoke(const Instruction& instruction, ::art::Instruction::Code opcode) { constexpr size_t kMaxArgs = 5; + // Currently, we only support up to 5 arguments. CHECK_LE(instruction.args().size(), kMaxArgs); uint8_t arguments[kMaxArgs]{}; + bool has_long_args = false; for (size_t i = 0; i < instruction.args().size(); ++i) { CHECK(instruction.args()[i].is_variable()); arguments[i] = RegisterValue(instruction.args()[i]); + if (!IsShortRegister(arguments[i])) { + has_long_args = true; + } } - Encode35c(opcode, - instruction.args().size(), - instruction.method_id(), - arguments[0], - arguments[1], - arguments[2], - arguments[3], - arguments[4]); + if (has_long_args) { + // Some of the registers don't fit in the four bit short form of the invoke + // instruction, so we need to do an invoke/range. To do this, we need to + // first move all the arguments into contiguous temporary registers. + std::array<Value, kMaxArgs> scratch = GetScratchRegisters<kMaxArgs>(); + + const auto& prototype = dex_->GetPrototypeByMethodId(instruction.method_id()); + CHECK(prototype.has_value()); + + for (size_t i = 0; i < instruction.args().size(); ++i) { + Instruction::Op move_op; + if (opcode == ::art::Instruction::INVOKE_VIRTUAL || + opcode == ::art::Instruction::INVOKE_DIRECT) { + // In this case, there is an implicit `this` argument, which is always an object. + if (i == 0) { + move_op = Instruction::Op::kMoveObject; + } else { + move_op = prototype->ArgType(i - 1).is_object() ? Instruction::Op::kMoveObject + : Instruction::Op::kMove; + } + } else { + move_op = prototype->ArgType(i).is_object() ? Instruction::Op::kMoveObject + : Instruction::Op::kMove; + } + + EncodeMove(Instruction::OpWithArgs(move_op, scratch[i], instruction.args()[i])); + } + + Encode3rc(InvokeToInvokeRange(opcode), + instruction.args().size(), + instruction.method_id(), + RegisterValue(scratch[0])); + } else { + Encode35c(opcode, + instruction.args().size(), + instruction.method_id(), + arguments[0], + arguments[1], + arguments[2], + arguments[3], + arguments[4]); + } // If there is a return value, add a move-result instruction if (instruction.dest().has_value()) { @@ -416,26 +491,26 @@ void MethodBuilder::EncodeBranch(art::Instruction::Code op, const Instruction& i } void MethodBuilder::EncodeNew(const Instruction& instruction) { - DCHECK_EQ(Instruction::Op::kNew, instruction.opcode()); - DCHECK(instruction.dest().has_value()); - DCHECK(instruction.dest()->is_variable()); - DCHECK_EQ(1, instruction.args().size()); + CHECK_EQ(Instruction::Op::kNew, instruction.opcode()); + CHECK(instruction.dest().has_value()); + CHECK(instruction.dest()->is_variable()); + CHECK_EQ(1, instruction.args().size()); const Value& type = instruction.args()[0]; - DCHECK_LT(RegisterValue(*instruction.dest()), 256); - DCHECK(type.is_type()); + CHECK_LT(RegisterValue(*instruction.dest()), 256); + CHECK(type.is_type()); Encode21c(::art::Instruction::NEW_INSTANCE, RegisterValue(*instruction.dest()), type.value()); } void MethodBuilder::EncodeCast(const Instruction& instruction) { - DCHECK_EQ(Instruction::Op::kCheckCast, instruction.opcode()); - DCHECK(instruction.dest().has_value()); - DCHECK(instruction.dest()->is_variable()); - DCHECK_EQ(1, instruction.args().size()); + CHECK_EQ(Instruction::Op::kCheckCast, instruction.opcode()); + CHECK(instruction.dest().has_value()); + CHECK(instruction.dest()->is_variable()); + CHECK_EQ(1, instruction.args().size()); const Value& type = instruction.args()[0]; - DCHECK_LT(RegisterValue(*instruction.dest()), 256); - DCHECK(type.is_type()); + CHECK_LT(RegisterValue(*instruction.dest()), 256); + CHECK(type.is_type()); Encode21c(::art::Instruction::CHECK_CAST, RegisterValue(*instruction.dest()), type.value()); } @@ -443,9 +518,9 @@ size_t MethodBuilder::RegisterValue(const Value& value) const { if (value.is_register()) { return value.value(); } else if (value.is_parameter()) { - return value.value() + num_registers_; + return value.value() + num_registers_ + kMaxScratchRegisters; } - DCHECK(false && "Must be either a parameter or a register"); + CHECK(false && "Must be either a parameter or a register"); return 0; } @@ -498,7 +573,7 @@ const MethodDeclData& DexBuilder::GetOrDeclareMethod(TypeDescriptor type, const // update the index -> ir node map (see tools/dexter/slicer/dex_ir_builder.cc) auto new_index = dex_file_->methods_indexes.AllocateIndex(); auto& ir_node = dex_file_->methods_map[new_index]; - SLICER_CHECK(ir_node == nullptr); + CHECK(ir_node == nullptr); ir_node = decl; decl->orig_index = decl->index = new_index; @@ -508,6 +583,15 @@ const MethodDeclData& DexBuilder::GetOrDeclareMethod(TypeDescriptor type, const return entry; } +std::optional<const Prototype> DexBuilder::GetPrototypeByMethodId(size_t method_id) const { + for (const auto& entry : method_id_map_) { + if (entry.second.id == method_id) { + return entry.first.prototype; + } + } + return {}; +} + ir::Proto* DexBuilder::GetOrEncodeProto(Prototype prototype) { ir::Proto*& ir_proto = proto_map_[prototype]; if (ir_proto == nullptr) { diff --git a/startop/view_compiler/dex_builder.h b/startop/view_compiler/dex_builder.h index 757d863461f0..541d80077bd3 100644 --- a/startop/view_compiler/dex_builder.h +++ b/startop/view_compiler/dex_builder.h @@ -16,6 +16,7 @@ #ifndef DEX_BUILDER_H_ #define DEX_BUILDER_H_ +#include <array> #include <forward_list> #include <map> #include <optional> @@ -70,6 +71,8 @@ class TypeDescriptor { // Return the shorty descriptor, such as I or L std::string short_descriptor() const { return descriptor().substr(0, 1); } + bool is_object() const { return short_descriptor() == "L"; } + bool operator<(const TypeDescriptor& rhs) const { return descriptor_ < rhs.descriptor_; } private: @@ -92,6 +95,8 @@ class Prototype { // Get the shorty descriptor, such as VII for (Int, Int) -> Void std::string Shorty() const; + const TypeDescriptor& ArgType(size_t index) const; + bool operator<(const Prototype& rhs) const { return std::make_tuple(return_type_, param_types_) < std::make_tuple(rhs.return_type_, rhs.param_types_); @@ -124,11 +129,13 @@ class Value { size_t value() const { return value_; } + constexpr Value() : value_{0}, kind_{Kind::kInvalid} {} + private: - enum class Kind { kLocalRegister, kParameter, kImmediate, kString, kLabel, kType }; + enum class Kind { kInvalid, kLocalRegister, kParameter, kImmediate, kString, kLabel, kType }; - const size_t value_; - const Kind kind_; + size_t value_; + Kind kind_; constexpr Value(size_t value, Kind kind) : value_{value}, kind_{kind} {} }; @@ -151,6 +158,7 @@ class Instruction { kInvokeStatic, kInvokeVirtual, kMove, + kMoveObject, kNew, kReturn, kReturnObject, @@ -172,7 +180,7 @@ class Instruction { // A cast instruction. Basically, `(type)val` static inline Instruction Cast(Value val, Value type) { - DCHECK(type.is_type()); + CHECK(type.is_type()); return OpWithArgs(Op::kCheckCast, val, type); } @@ -343,21 +351,48 @@ class MethodBuilder { buffer_.push_back(b); } + inline void Encode32x(art::Instruction::Code opcode, uint16_t a, uint16_t b) { + buffer_.push_back(opcode); + buffer_.push_back(a); + buffer_.push_back(b); + } + inline void Encode35c(art::Instruction::Code opcode, size_t a, uint16_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f, uint8_t g) { // a|g|op|bbbb|f|e|d|c CHECK_LE(a, 5); - CHECK_LT(c, 16); - CHECK_LT(d, 16); - CHECK_LT(e, 16); - CHECK_LT(f, 16); - CHECK_LT(g, 16); + CHECK(IsShortRegister(c)); + CHECK(IsShortRegister(d)); + CHECK(IsShortRegister(e)); + CHECK(IsShortRegister(f)); + CHECK(IsShortRegister(g)); buffer_.push_back((a << 12) | (g << 8) | opcode); buffer_.push_back(b); buffer_.push_back((f << 12) | (e << 8) | (d << 4) | c); } + inline void Encode3rc(art::Instruction::Code opcode, size_t a, uint16_t b, uint16_t c) { + CHECK_LE(a, 255); + buffer_.push_back((a << 8) | opcode); + buffer_.push_back(b); + buffer_.push_back(c); + } + + static constexpr bool IsShortRegister(size_t register_value) { return register_value < 16; } + + // Returns an array of num_regs scratch registers. These are guaranteed to be + // contiguous, so they are suitable for the invoke-*/range instructions. + template <int num_regs> + std::array<Value, num_regs> GetScratchRegisters() const { + static_assert(num_regs <= kMaxScratchRegisters); + std::array<Value, num_regs> regs; + for (size_t i = 0; i < num_regs; ++i) { + regs[i] = std::move(Value::Local(num_registers_ + i)); + } + return regs; + } + // Converts a register or parameter to its DEX register number. size_t RegisterValue(const Value& value) const; @@ -379,6 +414,10 @@ class MethodBuilder { // A buffer to hold instructions that have been encoded. std::vector<::dex::u2> buffer_; + // We create some scratch registers for when we have to shuffle registers + // around to make legal DEX code. + static constexpr size_t kMaxScratchRegisters = 5; + // How many registers we've allocated size_t num_registers_{0}; @@ -447,6 +486,8 @@ class DexBuilder { const MethodDeclData& GetOrDeclareMethod(TypeDescriptor type, const std::string& name, Prototype prototype); + std::optional<const Prototype> GetPrototypeByMethodId(size_t method_id) const; + private: // Looks up the ir::Proto* corresponding to this given prototype, or creates one if it does not // exist. diff --git a/startop/view_compiler/dex_builder_test.cc b/startop/view_compiler/dex_builder_test.cc index 61c86b4091b3..90c256f271cf 100644 --- a/startop/view_compiler/dex_builder_test.cc +++ b/startop/view_compiler/dex_builder_test.cc @@ -140,3 +140,41 @@ TEST(DexBuilderTest, VerifyDexCallStringLength) { EXPECT_TRUE(EncodeAndVerify(&dex_file)); } + +// Write out and verify a DEX file that corresponds to: +// +// package dextest; +// public class DexTest { +// public static int foo(String s) { return s.length(); } +// } +TEST(DexBuilderTest, VerifyDexCallManyRegisters) { + DexBuilder dex_file; + + auto cbuilder{dex_file.MakeClass("dextest.DexTest")}; + + MethodBuilder method{cbuilder.CreateMethod( + "foo", Prototype{TypeDescriptor::Int()})}; + + Value result = method.MakeRegister(); + + // Make a bunch of registers + for (size_t i = 0; i < 25; ++i) { + method.MakeRegister(); + } + + // Now load a string literal into a register + Value string_val = method.MakeRegister(); + method.BuildConstString(string_val, "foo"); + + MethodDeclData string_length = + dex_file.GetOrDeclareMethod(TypeDescriptor::FromClassname("java.lang.String"), + "length", + Prototype{TypeDescriptor::Int()}); + + method.AddInstruction(Instruction::InvokeVirtual(string_length.id, result, string_val)); + method.BuildReturn(result); + + method.Encode(); + + EXPECT_TRUE(EncodeAndVerify(&dex_file)); +} diff --git a/startop/view_compiler/dex_builder_test/Android.bp b/startop/view_compiler/dex_builder_test/Android.bp index d4f38ed148c9..ac60e966fe43 100644 --- a/startop/view_compiler/dex_builder_test/Android.bp +++ b/startop/view_compiler/dex_builder_test/Android.bp @@ -15,7 +15,7 @@ // genrule { - name: "generate_compiled_layout", + name: "generate_compiled_layout1", tools: [":viewcompiler"], cmd: "$(location :viewcompiler) $(in) --dex --out $(out) --package android.startop.test", srcs: ["res/layout/layout1.xml"], @@ -24,6 +24,16 @@ genrule { ], } +genrule { + name: "generate_compiled_layout2", + tools: [":viewcompiler"], + cmd: "$(location :viewcompiler) $(in) --dex --out $(out) --package android.startop.test", + srcs: ["res/layout/layout2.xml"], + out: [ + "layout2.dex", + ], +} + android_test { name: "dex-builder-test", srcs: [ @@ -31,7 +41,7 @@ android_test { "src/android/startop/test/LayoutCompilerTest.java", ], sdk_version: "current", - data: [":generate_dex_testcases", ":generate_compiled_layout"], + data: [":generate_dex_testcases", ":generate_compiled_layout1", ":generate_compiled_layout2"], static_libs: [ "android-support-test", "guava", diff --git a/startop/view_compiler/dex_builder_test/AndroidTest.xml b/startop/view_compiler/dex_builder_test/AndroidTest.xml index 68d8fdc444d8..92e2a718bcce 100644 --- a/startop/view_compiler/dex_builder_test/AndroidTest.xml +++ b/startop/view_compiler/dex_builder_test/AndroidTest.xml @@ -26,6 +26,7 @@ <option name="push" value="trivial.dex->/data/local/tmp/dex-builder-test/trivial.dex" /> <option name="push" value="simple.dex->/data/local/tmp/dex-builder-test/simple.dex" /> <option name="push" value="layout1.dex->/data/local/tmp/dex-builder-test/layout1.dex" /> + <option name="push" value="layout2.dex->/data/local/tmp/dex-builder-test/layout2.dex" /> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > diff --git a/startop/view_compiler/dex_builder_test/res/layout/layout2.xml b/startop/view_compiler/dex_builder_test/res/layout/layout2.xml new file mode 100644 index 000000000000..b092e1c20311 --- /dev/null +++ b/startop/view_compiler/dex_builder_test/res/layout/layout2.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TableRow + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> + + <TableRow + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> + <TableRow + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> + + <TableRow + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> + </TableRow> + + </TableRow> + </TableRow> + </TableRow> +</LinearLayout> diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java index ce3ce8328559..a3b1b6c11ac3 100644 --- a/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java +++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java @@ -36,11 +36,20 @@ public class LayoutCompilerTest { } @Test - public void loadAndInflaterLayout1() throws Exception { + public void loadAndInflateLayout1() throws Exception { ClassLoader dex_file = loadDexFile("layout1.dex"); Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView"); Method layout1 = compiled_view.getMethod("layout1", Context.class, int.class); Context context = InstrumentationRegistry.getTargetContext(); layout1.invoke(null, context, R.layout.layout1); } + + @Test + public void loadAndInflateLayout2() throws Exception { + ClassLoader dex_file = loadDexFile("layout2.dex"); + Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView"); + Method layout1 = compiled_view.getMethod("layout2", Context.class, int.class); + Context context = InstrumentationRegistry.getTargetContext(); + layout1.invoke(null, context, R.layout.layout1); + } } diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 2820836282a1..dcaa49996d0b 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -239,6 +239,30 @@ public final class Call { "android.telecom.event.HANDOVER_FAILED"; public static class Details { + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + prefix = { "DIRECTION_" }, + value = {DIRECTION_UNKNOWN, DIRECTION_INCOMING, DIRECTION_OUTGOING}) + public @interface CallDirection {} + + /** + * Indicates that the call is neither and incoming nor an outgoing call. This can be the + * case for calls reported directly by a {@link ConnectionService} in special cases such as + * call handovers. + */ + public static final int DIRECTION_UNKNOWN = -1; + + /** + * Indicates that the call is an incoming call. + */ + public static final int DIRECTION_INCOMING = 0; + + /** + * Indicates that the call is an outgoing call. + */ + public static final int DIRECTION_OUTGOING = 1; + /** Call can currently be put on hold or unheld. */ public static final int CAPABILITY_HOLD = 0x00000001; @@ -519,6 +543,7 @@ public final class Call { private final Bundle mIntentExtras; private final long mCreationTimeMillis; private final CallIdentification mCallIdentification; + private final @CallDirection int mCallDirection; /** * Whether the supplied capabilities supports the specified capability. @@ -838,6 +863,14 @@ public final class Call { return mCallIdentification; } + /** + * Indicates whether the call is an incoming or outgoing call. + * @return The call's direction. + */ + public @CallDirection int getCallDirection() { + return mCallDirection; + } + @Override public boolean equals(Object o) { if (o instanceof Details) { @@ -859,7 +892,8 @@ public final class Call { areBundlesEqual(mExtras, d.mExtras) && areBundlesEqual(mIntentExtras, d.mIntentExtras) && Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) && - Objects.equals(mCallIdentification, d.mCallIdentification); + Objects.equals(mCallIdentification, d.mCallIdentification) && + Objects.equals(mCallDirection, d.mCallDirection); } return false; } @@ -881,7 +915,8 @@ public final class Call { mExtras, mIntentExtras, mCreationTimeMillis, - mCallIdentification); + mCallIdentification, + mCallDirection); } /** {@hide} */ @@ -902,7 +937,8 @@ public final class Call { Bundle extras, Bundle intentExtras, long creationTimeMillis, - CallIdentification callIdentification) { + CallIdentification callIdentification, + int callDirection) { mTelecomCallId = telecomCallId; mHandle = handle; mHandlePresentation = handlePresentation; @@ -920,6 +956,7 @@ public final class Call { mIntentExtras = intentExtras; mCreationTimeMillis = creationTimeMillis; mCallIdentification = callIdentification; + mCallDirection = callDirection; } /** {@hide} */ @@ -941,7 +978,8 @@ public final class Call { parcelableCall.getExtras(), parcelableCall.getIntentExtras(), parcelableCall.getCreationTimeMillis(), - parcelableCall.getCallIdentification()); + parcelableCall.getCallIdentification(), + parcelableCall.getCallDirection()); } @Override diff --git a/telecomm/java/android/telecom/CallIdentification.java b/telecomm/java/android/telecom/CallIdentification.java index 97af06c1d64c..87834fd5109d 100644 --- a/telecomm/java/android/telecom/CallIdentification.java +++ b/telecomm/java/android/telecom/CallIdentification.java @@ -250,8 +250,8 @@ public final class CallIdentification implements Parcelable { mDetails = details; mPhoto = photo; mNuisanceConfidence = nuisanceConfidence; - mCallScreeningAppName = callScreeningPackageName; - mCallScreeningPackageName = callScreeningAppName; + mCallScreeningAppName = callScreeningAppName; + mCallScreeningPackageName = callScreeningPackageName; } private String mName; @@ -430,4 +430,22 @@ public final class CallIdentification implements Parcelable { return Objects.hash(mName, mDescription, mDetails, mPhoto, mNuisanceConfidence, mCallScreeningAppName, mCallScreeningPackageName); } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("[CallId mName="); + sb.append(Log.pii(mName)); + sb.append(", mDesc="); + sb.append(mDescription); + sb.append(", mDet="); + sb.append(mDetails); + sb.append(", conf="); + sb.append(mNuisanceConfidence); + sb.append(", appName="); + sb.append(mCallScreeningAppName); + sb.append(", pkgName="); + sb.append(mCallScreeningPackageName); + return sb.toString(); + } } diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java index be96b3cac6f6..818ebd998f50 100644 --- a/telecomm/java/android/telecom/CallScreeningService.java +++ b/telecomm/java/android/telecom/CallScreeningService.java @@ -16,11 +16,13 @@ package android.telecom; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SdkConstant; import android.app.Service; import android.content.ComponentName; import android.content.Intent; +import android.net.Uri; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -31,10 +33,14 @@ import com.android.internal.os.SomeArgs; import com.android.internal.telecom.ICallScreeningAdapter; import com.android.internal.telecom.ICallScreeningService; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * This service can be implemented by the default dialer (see - * {@link TelecomManager#getDefaultDialerPackage()}) to allow or disallow incoming calls before - * they are shown to a user. + * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow + * incoming calls before they are shown to a user. This service can also provide + * {@link CallIdentification} information for calls. * <p> * Below is an example manifest registration for a {@code CallScreeningService}. * <pre> @@ -56,8 +62,158 @@ import com.android.internal.telecom.ICallScreeningService; * information about a {@link Call.Details call} which will be shown to the user in the * Dialer app.</li> * </ol> + * <p> + * <h2>Becoming the {@link CallScreeningService}</h2> + * Telecom will bind to a single app chosen by the user which implements the + * {@link CallScreeningService} API when there are new incoming and outgoing calls. + * <p> + * The code snippet below illustrates how your app can request that it fills the call screening + * role. + * <pre> + * {@code + * private static final int REQUEST_ID = 1; + * + * public void requestRole() { + * RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE); + * Intent intent = roleManager.createRequestRoleIntent("android.app.role.CALL_SCREENING_APP"); + * startActivityForResult(intent, REQUEST_ID); + * } + * + * @Override + * public void onActivityResult(int requestCode, int resultCode, Intent data) { + * if (requestCode == REQUEST_ID) { + * if (resultCode == android.app.Activity.RESULT_OK) { + * // Your app is now the call screening app + * } else { + * // Your app is not the call screening app + * } + * } + * } + * </pre> */ public abstract class CallScreeningService extends Service { + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + prefix = { "CALL_DURATION_" }, + value = {CALL_DURATION_VERY_SHORT, CALL_DURATION_SHORT, CALL_DURATION_MEDIUM, + CALL_DURATION_LONG}) + public @interface CallDuration {} + + /** + * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the + * {@link CallScreeningService} the duration of a call for which the user reported the nuisance + * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). The + * {@link CallScreeningService} can use this as a signal for training nuisance detection + * algorithms. The call duration is reported in coarse grained buckets to minimize exposure of + * identifying call log information to the {@link CallScreeningService}. + * <p> + * Indicates the call was < 3 seconds in duration. + */ + public static final int CALL_DURATION_VERY_SHORT = 1; + + /** + * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the + * {@link CallScreeningService} the duration of a call for which the user reported the nuisance + * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). The + * {@link CallScreeningService} can use this as a signal for training nuisance detection + * algorithms. The call duration is reported in coarse grained buckets to minimize exposure of + * identifying call log information to the {@link CallScreeningService}. + * <p> + * Indicates the call was greater than 3 seconds, but less than 60 seconds in duration. + */ + public static final int CALL_DURATION_SHORT = 2; + + /** + * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the + * {@link CallScreeningService} the duration of a call for which the user reported the nuisance + * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). The + * {@link CallScreeningService} can use this as a signal for training nuisance detection + * algorithms. The call duration is reported in coarse grained buckets to minimize exposure of + * identifying call log information to the {@link CallScreeningService}. + * <p> + * Indicates the call was greater than 60 seconds, but less than 120 seconds in duration. + */ + public static final int CALL_DURATION_MEDIUM = 3; + + /** + * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the + * {@link CallScreeningService} the duration of a call for which the user reported the nuisance + * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). The + * {@link CallScreeningService} can use this as a signal for training nuisance detection + * algorithms. The call duration is reported in coarse grained buckets to minimize exposure of + * identifying call log information to the {@link CallScreeningService}. + * <p> + * Indicates the call was greater than 120 seconds. + */ + public static final int CALL_DURATION_LONG = 4; + + /** + * Telecom sends this intent to the {@link CallScreeningService} which the user has chosen to + * fill the call screening role when the user indicates through the default dialer whether a + * call is a nuisance call or not (see + * {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). + * <p> + * The following extra values are provided for the call: + * <ol> + * <li>{@link #EXTRA_CALL_HANDLE} - the handle of the call.</li> + * <li>{@link #EXTRA_IS_NUISANCE} - {@code true} if the user reported the call as a nuisance + * call, {@code false} otherwise.</li> + * <li>{@link #EXTRA_CALL_TYPE} - reports the type of call (incoming, rejected, missed, + * blocked).</li> + * <li>{@link #EXTRA_CALL_DURATION} - the duration of the call (see + * {@link #EXTRA_CALL_DURATION} for valid values).</li> + * </ol> + * <p> + * {@link CallScreeningService} implementations which want to track whether the user reports + * calls are nuisance calls should use declare a broadcast receiver in their manifest for this + * intent. + * <p> + * Note: Only {@link CallScreeningService} implementations which have provided + * {@link CallIdentification} information for calls at some point will receive this intent. + */ + public static final String ACTION_NUISANCE_CALL_STATUS_CHANGED = + "android.telecom.action.NUISANCE_CALL_STATUS_CHANGED"; + + /** + * Extra used to provide the handle of the call for + * {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED}. The call handle is reported as a + * {@link Uri}. + */ + public static final String EXTRA_CALL_HANDLE = "android.telecom.extra.CALL_HANDLE"; + + /** + * Boolean extra used to indicate whether the user reported a call as a nuisance call. + * When {@code true}, the user reported that a call was a nuisance call, {@code false} + * otherwise. Sent with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED}. + */ + public static final String EXTRA_IS_NUISANCE = "android.telecom.extra.IS_NUISANCE"; + + /** + * Integer extra used with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED} to report the type of + * call. Valid values are: + * <UL> + * <li>{@link android.provider.CallLog.Calls#MISSED_TYPE}</li> + * <li>{@link android.provider.CallLog.Calls#INCOMING_TYPE}</li> + * <li>{@link android.provider.CallLog.Calls#BLOCKED_TYPE}</li> + * <li>{@link android.provider.CallLog.Calls#REJECTED_TYPE}</li> + * </UL> + */ + public static final String EXTRA_CALL_TYPE = "android.telecom.extra.CALL_TYPE"; + + /** + * Integer extra used to with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED} to report how long + * the call lasted. Valid values are: + * <UL> + * <LI>{@link #CALL_DURATION_VERY_SHORT}</LI> + * <LI>{@link #CALL_DURATION_SHORT}</LI> + * <LI>{@link #CALL_DURATION_MEDIUM}</LI> + * <LI>{@link #CALL_DURATION_LONG}</LI> + * </UL> + */ + public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION"; + /** * The {@link Intent} that must be declared as handled by the service. */ @@ -222,30 +378,46 @@ public abstract class CallScreeningService extends Service { } /** - * Called when a new incoming call is added. - * {@link CallScreeningService#respondToCall(Call.Details, CallScreeningService.CallResponse)} - * should be called to allow or disallow the call. + * Called when a new incoming or outgoing call is added which is not in the user's contact list. + * <p> + * A {@link CallScreeningService} must indicate whether an incoming call is allowed or not by + * calling + * {@link CallScreeningService#respondToCall(Call.Details, CallScreeningService.CallResponse)}. + * Your app can tell if a call is an incoming call by checking to see if + * {@link Call.Details#getCallDirection()} is {@link Call.Details#DIRECTION_INCOMING}. + * <p> + * For incoming or outgoing calls, the {@link CallScreeningService} can call + * {@link #provideCallIdentification(Call.Details, CallIdentification)} in order to provide + * {@link CallIdentification} for the call. * <p> * Note: The {@link Call.Details} instance provided to a call screening service will only have * the following properties set. The rest of the {@link Call.Details} properties will be set to * their default value or {@code null}. * <ul> - * <li>{@link Call.Details#getState()}</li> + * <li>{@link Call.Details#getCallDirection()}</li> * <li>{@link Call.Details#getConnectTimeMillis()}</li> * <li>{@link Call.Details#getCreationTimeMillis()}</li> * <li>{@link Call.Details#getHandle()}</li> * <li>{@link Call.Details#getHandlePresentation()}</li> * </ul> + * <p> + * Only calls where the {@link Call.Details#getHandle() handle} {@link Uri#getScheme() scheme} + * is {@link PhoneAccount#SCHEME_TEL} are passed for call + * screening. Further, only calls which are not in the user's contacts are passed for + * screening. For outgoing calls, no post-dial digits are passed. * - * @param callDetails Information about a new incoming call, see {@link Call.Details}. + * @param callDetails Information about a new call, see {@link Call.Details}. */ public abstract void onScreenCall(@NonNull Call.Details callDetails); /** - * Responds to the given call, either allowing it or disallowing it. + * Responds to the given incoming call, either allowing it or disallowing it. * <p> * The {@link CallScreeningService} calls this method to inform the system whether the call * should be silently blocked or not. + * <p> + * Calls to this method are ignored unless the {@link Call.Details#getCallDirection()} is + * {@link Call.Details#DIRECTION_INCOMING}. * * @param callDetails The call to allow. * <p> diff --git a/telecomm/java/android/telecom/ConferenceParticipant.java b/telecomm/java/android/telecom/ConferenceParticipant.java index 20b04ebed6a4..6317770676cd 100644 --- a/telecomm/java/android/telecom/ConferenceParticipant.java +++ b/telecomm/java/android/telecom/ConferenceParticipant.java @@ -19,6 +19,10 @@ package android.telecom; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.PhoneConstants; /** * Parcelable representation of a participant's state in a conference call. @@ -27,6 +31,11 @@ import android.os.Parcelable; public class ConferenceParticipant implements Parcelable { /** + * RFC5767 states that a SIP URI with an unknown number should use an address of + * {@code anonymous@anonymous.invalid}. E.g. the host name is anonymous.invalid. + */ + private static final String ANONYMOUS_INVALID_HOST = "anonymous.invalid"; + /** * The conference participant's handle (e.g., phone number). */ private final Uri mHandle; @@ -50,6 +59,16 @@ public class ConferenceParticipant implements Parcelable { private final int mState; /** + * The connect time of the participant. + */ + private long mConnectTime; + + /** + * The connect elapsed time of the participant. + */ + private long mConnectElapsedTime; + + /** * Creates an instance of {@code ConferenceParticipant}. * * @param handle The conference participant's handle (e.g., phone number). @@ -92,6 +111,54 @@ public class ConferenceParticipant implements Parcelable { } /** + * Determines the number presentation for a conference participant. Per RFC5767, if the host + * name contains {@code anonymous.invalid} we can assume that there is no valid caller ID + * information for the caller, otherwise we'll assume that the URI can be shown. + * + * @return The number presentation. + */ + @VisibleForTesting + public int getParticipantPresentation() { + Uri address = getHandle(); + if (address == null) { + return PhoneConstants.PRESENTATION_RESTRICTED; + } + + String number = address.getSchemeSpecificPart(); + // If no number, bail early and set restricted presentation. + if (TextUtils.isEmpty(number)) { + return PhoneConstants.PRESENTATION_RESTRICTED; + } + // Per RFC3261, the host name portion can also potentially include extra information: + // E.g. sip:anonymous1@anonymous.invalid;legid=1 + // In this case, hostName will be anonymous.invalid and there is an extra parameter for + // legid=1. + // Parameters are optional, and the address (e.g. test@test.com) will always be the first + // part, with any parameters coming afterwards. + String [] hostParts = number.split("[;]"); + String addressPart = hostParts[0]; + + // Get the number portion from the address part. + // This will typically be formatted similar to: 6505551212@test.com + String [] numberParts = addressPart.split("[@]"); + + // If we can't parse the host name out of the URI, then there is probably other data + // present, and is likely a valid SIP URI. + if (numberParts.length != 2) { + return PhoneConstants.PRESENTATION_ALLOWED; + } + String hostName = numberParts[1]; + + // If the hostname portion of the SIP URI is the invalid host string, presentation is + // restricted. + if (hostName.equals(ANONYMOUS_INVALID_HOST)) { + return PhoneConstants.PRESENTATION_RESTRICTED; + } + + return PhoneConstants.PRESENTATION_ALLOWED; + } + + /** * Writes the {@code ConferenceParticipant} to a parcel. * * @param dest The Parcel in which the object should be written. @@ -121,6 +188,10 @@ public class ConferenceParticipant implements Parcelable { sb.append(Log.pii(mEndpoint)); sb.append(" State: "); sb.append(Connection.stateToString(mState)); + sb.append(" ConnectTime: "); + sb.append(getConnectTime()); + sb.append(" ConnectElapsedTime: "); + sb.append(getConnectElapsedTime()); sb.append("]"); return sb.toString(); } @@ -155,4 +226,26 @@ public class ConferenceParticipant implements Parcelable { public int getState() { return mState; } + + /** + * The connect time of the participant to the conference. + */ + public long getConnectTime() { + return mConnectTime; + } + + public void setConnectTime(long connectTime) { + this.mConnectTime = connectTime; + } + + /** + * The connect elpased time of the participant to the conference. + */ + public long getConnectElapsedTime() { + return mConnectElapsedTime; + } + + public void setConnectElapsedTime(long connectElapsedTime) { + mConnectElapsedTime = connectElapsedTime; + } } diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 05d5a13092f1..bd0d4ae27800 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -16,10 +16,6 @@ package android.telecom; -import com.android.internal.os.SomeArgs; -import com.android.internal.telecom.IVideoCallback; -import com.android.internal.telecom.IVideoProvider; - import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -43,6 +39,10 @@ import android.telephony.TelephonyManager; import android.util.ArraySet; import android.view.Surface; +import com.android.internal.os.SomeArgs; +import com.android.internal.telecom.IVideoCallback; +import com.android.internal.telecom.IVideoProvider; + import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java index 1de67a5883e3..5a97c948f31f 100644 --- a/telecomm/java/android/telecom/DisconnectCause.java +++ b/telecomm/java/android/telecom/DisconnectCause.java @@ -16,9 +16,9 @@ package android.telecom; +import android.media.ToneGenerator; import android.os.Parcel; import android.os.Parcelable; -import android.media.ToneGenerator; import android.text.TextUtils; import java.util.Objects; @@ -91,6 +91,12 @@ public final class DisconnectCause implements Parcelable { */ public static final String REASON_IMS_ACCESS_BLOCKED = "REASON_IMS_ACCESS_BLOCKED"; + /** + * Reason code, which indicates that the conference call is simulating single party conference. + * @hide + */ + public static final String REASON_EMULATING_SINGLE_CALL = "EMULATING_SINGLE_CALL"; + private int mDisconnectCode; private CharSequence mDisconnectLabel; private CharSequence mDisconnectDescription; diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java index 1aeeca73c0b9..cea2fbfa588c 100644 --- a/telecomm/java/android/telecom/InCallService.java +++ b/telecomm/java/android/telecom/InCallService.java @@ -40,11 +40,30 @@ import java.util.Collections; import java.util.List; /** - * This service is implemented by any app that wishes to provide the user-interface for managing - * phone calls. Telecom binds to this service while there exists a live (active or incoming) call, - * and uses it to notify the in-call app of any live and recently disconnected calls. An app must - * first be set as the default phone app (See {@link TelecomManager#getDefaultDialerPackage()}) - * before the telecom service will bind to its {@code InCallService} implementation. + * This service is implemented by an app that wishes to provide functionality for managing + * phone calls. + * <p> + * There are three types of apps which Telecom can bind to when there exists a live (active or + * incoming) call: + * <ol> + * <li>Default Dialer/Phone app - the default dialer/phone app is one which provides the + * in-call user interface while the device is in a call. A device is bundled with a system + * provided default dialer/phone app. The user may choose a single app to take over this role + * from the system app.</li> + * <li>Default Car-mode Dialer/Phone app - the default car-mode dialer/phone app is one which + * provides the in-call user interface while the device is in a call and the device is in car + * mode. The user may choose a single app to fill this role.</li> + * <li>Call Companion app - a call companion app is one which provides no user interface itself, + * but exposes call information to another display surface, such as a wearable device. The + * user may choose multiple apps to fill this role.</li> + * </ol> + * <p> + * Apps which wish to fulfill one of the above roles use the {@code android.app.role.RoleManager} + * to request that they fill the desired role. + * + * <h2>Becoming the Default Phone App</h2> + * An app filling the role of the default phone app provides a user interface while the device is in + * a call, and the device is not in car mode. * <p> * Below is an example manifest registration for an {@code InCallService}. The meta-data * {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} indicates that this particular @@ -82,12 +101,34 @@ import java.util.List; * } * </pre> * <p> - * When a user installs your application and runs it for the first time, you should prompt the user - * to see if they would like your application to be the new default phone app. See the - * {@link TelecomManager#ACTION_CHANGE_DEFAULT_DIALER} intent documentation for more information on - * how to do this. + * When a user installs your application and runs it for the first time, you should use the + * {@code android.app.role.RoleManager} to prompt the user to see if they would like your app to + * be the new default phone app. + * <p id="requestRole"> + * The code below shows how your app can request to become the default phone/dialer app: + * <pre> + * {@code + * private static final int REQUEST_ID = 1; + * + * public void requestRole() { + * RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE); + * Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER); + * startActivityForResult(intent, REQUEST_ID); + * } + * + * @Override + * public void onActivityResult(int requestCode, int resultCode, Intent data) { + * if (requestCode == REQUEST_ID) { + * if (resultCode == android.app.Activity.RESULT_OK) { + * // Your app is now the default dialer app + * } else { + * // Your app is not the default dialer app + * } + * } + * } + * </pre> * <p id="incomingCallNotification"> - * <h2>Showing the Incoming Call Notification</h2> + * <h3>Showing the Incoming Call Notification</h3> * When your app receives a new incoming call via {@link InCallService#onCallAdded(Call)}, it is * responsible for displaying an incoming call UI for the incoming call. It should do this using * {@link android.app.NotificationManager} APIs to post a new incoming call notification. @@ -121,7 +162,7 @@ import java.util.List; * heads-up notification if the user is actively using the phone. When the user is not using the * phone, your full-screen incoming call UI is used instead. * For example: - * <pre><code> + * <pre><code>{@code * // Create an intent which triggers your fullscreen incoming call user interface. * Intent intent = new Intent(Intent.ACTION_MAIN, null); * intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK); @@ -151,7 +192,49 @@ import java.util.List; * NotificationManager notificationManager = mContext.getSystemService( * NotificationManager.class); * notificationManager.notify(YOUR_CHANNEL_ID, YOUR_TAG, YOUR_ID, builder.build()); - * </code></pre> + * }</pre> + * <p> + * <h2>Becoming the Default Car-mode Phone App</h2> + * An app filling the role of the default car-mode dialer/phone app provides a user interface while + * the device is in a call, and in car mode. See + * {@link android.app.UiModeManager#ACTION_ENTER_CAR_MODE} for more information about car mode. + * When the device is in car mode, Telecom binds to the default car-mode dialer/phone app instead + * of the usual dialer/phone app. + * <p> + * Similar to the requirements for becoming the default dialer/phone app, your app must declare a + * manifest entry for its {@link InCallService} implementation. Your manifest entry should ensure + * the following conditions are met: + * <ul> + * <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} metadata.</li> + * <li>Set the {@link TelecomManager#METADATA_IN_CALL_SERVICE_CAR_MODE_UI} metadata to + * {@code true}<li> + * <li>Your app must request the permission + * {@link android.Manifest.permission.CALL_COMPANION_APP}.</li> + * </ul> + * <p> + * Your app should request to fill the role {@code android.app.role.CAR_MODE_DIALER_APP} in order to + * become the default (see <a href="#requestRole">above</a> for how to request your app fills this + * role). + * + * <h2>Becoming a Call Companion App</h2> + * An app which fills the companion app role does not directly provide a user interface while the + * device is in a call. Instead, it is typically used to relay information about calls to another + * display surface, such as a wearable device. + * <p> + * Similar to the requirements for becoming the default dialer/phone app, your app must declare a + * manifest entry for its {@link InCallService} implementation. Your manifest entry should + * ensure the following conditions are met: + * <ul> + * <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} metadata.</li> + * <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_CAR_MODE_UI} + * metadata.</li> + * <li>Your app must request the permission + * {@link android.Manifest.permission.CALL_COMPANION_APP}.</li> + * </ul> + * <p> + * Your app should request to fill the role {@code android.app.role.CALL_COMPANION_APP} in order to + * become a call companion app (see <a href="#requestRole">above</a> for how to request your app + * fills this role). */ public abstract class InCallService extends Service { diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java index 911786e455c2..f7dec83c3ace 100644 --- a/telecomm/java/android/telecom/ParcelableCall.java +++ b/telecomm/java/android/telecom/ParcelableCall.java @@ -24,6 +24,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; +import android.telecom.Call.Details.CallDirection; import java.util.ArrayList; import java.util.Collections; @@ -64,6 +65,7 @@ public final class ParcelableCall implements Parcelable { private final Bundle mExtras; private final long mCreationTimeMillis; private final CallIdentification mCallIdentification; + private final int mCallDirection; public ParcelableCall( String id, @@ -92,7 +94,8 @@ public final class ParcelableCall implements Parcelable { Bundle intentExtras, Bundle extras, long creationTimeMillis, - CallIdentification callIdentification) { + CallIdentification callIdentification, + int callDirection) { mId = id; mState = state; mDisconnectCause = disconnectCause; @@ -120,6 +123,7 @@ public final class ParcelableCall implements Parcelable { mExtras = extras; mCreationTimeMillis = creationTimeMillis; mCallIdentification = callIdentification; + mCallDirection = callDirection; } /** The unique ID of the call. */ @@ -318,6 +322,13 @@ public final class ParcelableCall implements Parcelable { return mCallIdentification; } + /** + * Indicates whether the call is an incoming or outgoing call. + */ + public @CallDirection int getCallDirection() { + return mCallDirection; + } + /** Responsible for creating ParcelableCall objects for deserialized Parcels. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public static final Parcelable.Creator<ParcelableCall> CREATOR = @@ -356,6 +367,7 @@ public final class ParcelableCall implements Parcelable { ParcelableRttCall rttCall = source.readParcelable(classLoader); long creationTimeMillis = source.readLong(); CallIdentification callIdentification = source.readParcelable(classLoader); + int callDirection = source.readInt(); return new ParcelableCall( id, state, @@ -383,7 +395,8 @@ public final class ParcelableCall implements Parcelable { intentExtras, extras, creationTimeMillis, - callIdentification); + callIdentification, + callDirection); } @Override @@ -429,6 +442,7 @@ public final class ParcelableCall implements Parcelable { destination.writeParcelable(mRttCall, 0); destination.writeLong(mCreationTimeMillis); destination.writeParcelable(mCallIdentification, 0); + destination.writeInt(mCallDirection); } @Override diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index c3e80b480099..c60eb56005eb 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -289,6 +289,19 @@ public class TelecomManager { "android.telecom.extra.OUTGOING_CALL_EXTRAS"; /** + * An optional boolean extra on {@link android.content.Intent#ACTION_CALL_EMERGENCY} to tell + * whether the user's dial intent is emergency; this is required to specify when the dialed + * number is ambiguous, identified as both emergency number and any other non-emergency number; + * e.g. in some situation, 611 could be both an emergency number in a country and a + * non-emergency number of a carrier's customer service hotline. + * + * @hide + */ + @SystemApi + public static final String EXTRA_IS_USER_INTENT_EMERGENCY_CALL = + "android.telecom.extra.IS_USER_INTENT_EMERGENCY_CALL"; + + /** * @hide */ public static final String EXTRA_UNKNOWN_CALL_HANDLE = @@ -1397,8 +1410,14 @@ public class TelecomManager { * * @return {@code true} if there is a call which will be rejected or terminated, {@code false} * otherwise. + * @deprecated Companion apps for wearable devices should use the {@link InCallService} API + * instead. Apps performing call screening should use the {@link CallScreeningService} API + * instead. */ + + @RequiresPermission(Manifest.permission.ANSWER_PHONE_CALLS) + @Deprecated public boolean endCall() { try { if (isServiceConnected()) { @@ -1419,11 +1438,15 @@ public class TelecomManager { * * Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or * {@link android.Manifest.permission#ANSWER_PHONE_CALLS} + * + * @deprecated Companion apps for wearable devices should use the {@link InCallService} API + * instead. */ //TODO: L-release - need to convert all invocation of ITelecmmService#answerRingingCall to use // this method (clockwork & gearhead). @RequiresPermission(anyOf = {Manifest.permission.ANSWER_PHONE_CALLS, Manifest.permission.MODIFY_PHONE_STATE}) + @Deprecated public void acceptRingingCall() { try { if (isServiceConnected()) { @@ -1442,9 +1465,12 @@ public class TelecomManager { * {@link android.Manifest.permission#ANSWER_PHONE_CALLS} * * @param videoState The desired video state to answer the call with. + * @deprecated Companion apps for wearable devices should use the {@link InCallService} API + * instead. */ @RequiresPermission(anyOf = {Manifest.permission.ANSWER_PHONE_CALLS, Manifest.permission.MODIFY_PHONE_STATE}) + @Deprecated public void acceptRingingCall(int videoState) { try { if (isServiceConnected()) { @@ -1963,6 +1989,33 @@ public class TelecomManager { } /** + * Called by the default dialer to report to Telecom when the user has marked a previous + * incoming call as a nuisance call or not. + * <p> + * Where the user has chosen a {@link CallScreeningService} to fill the call screening role, + * Telecom will notify that {@link CallScreeningService} of the user's report. + * <p> + * Requires that the caller is the default dialer app. + * + * @param handle The phone number of an incoming call which the user is reporting as either a + * nuisance of non-nuisance call. + * @param isNuisanceCall {@code true} if the user is reporting the call as a nuisance call, + * {@code false} if the user is reporting the call as a non-nuisance call. + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public void reportNuisanceCallStatus(@NonNull Uri handle, boolean isNuisanceCall) { + ITelecomService service = getTelecomService(); + if (service != null) { + try { + service.reportNuisanceCallStatus(handle, isNuisanceCall, + mContext.getOpPackageName()); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#showCallScreen", e); + } + } + } + + /** * Handles {@link Intent#ACTION_CALL} intents trampolined from UserCallActivity. * @param intent The {@link Intent#ACTION_CALL} intent to handle. * @hide diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index e1d5c17d5e3a..5030f90afd3e 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -285,6 +285,8 @@ interface ITelecomService { */ boolean isInEmergencyCall(); + oneway void reportNuisanceCallStatus(in Uri address, boolean isNuisance, String callingPackage); + /** * @see TelecomServiceImpl#handleCallIntent */ @@ -299,4 +301,5 @@ interface ITelecomService { void addOrRemoveTestCallCompanionApp(String packageName, boolean isAdded); void setTestAutoModeApp(String packageName); + } diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java index fb8f3e78ff84..50b8f79892fd 100644 --- a/telephony/java/android/provider/Telephony.java +++ b/telephony/java/android/provider/Telephony.java @@ -2054,6 +2054,648 @@ public final class Telephony { } /** + * Columns for the "rcs_*" tables used by {@link android.telephony.ims.RcsMessageStore} classes. + * + * @hide - not meant for public use + */ + public interface RcsColumns { + // TODO(sahinc): Turn this to true once the schema finalizes, so that people can update + // their messaging databases. NOTE: move the switch/case update in MmsSmsDatabaseHelper to + // the latest version of the database before turning this flag to true. + boolean IS_RCS_TABLE_SCHEMA_CODE_COMPLETE = false; + + /** + * The authority for the content provider + */ + String AUTHORITY = "rcs"; + + /** + * The URI to start building upon to use {@link com.android.providers.telephony.RcsProvider} + */ + Uri CONTENT_AND_AUTHORITY = Uri.parse("content://" + AUTHORITY); + + /** + * The value to be used whenever a transaction that expects an integer to be returned + * failed. + */ + int TRANSACTION_FAILED = Integer.MIN_VALUE; + + /** + * The value that denotes a timestamp was not set before (e.g. a message that is not + * delivered yet will not have a DELIVERED_TIMESTAMP) + */ + long TIMESTAMP_NOT_SET = 0; + + /** + * The table that {@link android.telephony.ims.RcsThread} gets persisted to + */ + interface RcsThreadColumns { + /** + * The path that should be used for referring to + * {@link android.telephony.ims.RcsThread}s in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String RCS_THREAD_URI_PART = "thread"; + + /** + * The URI to query or modify {@link android.telephony.ims.RcsThread} via the content + * provider. + */ + Uri RCS_THREAD_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, RCS_THREAD_URI_PART); + + /** + * The unique identifier of an {@link android.telephony.ims.RcsThread} + */ + String RCS_THREAD_ID_COLUMN = "rcs_thread_id"; + } + + /** + * The table that {@link android.telephony.ims.Rcs1To1Thread} gets persisted to + */ + interface Rcs1To1ThreadColumns extends RcsThreadColumns { + /** + * The path that should be used for referring to + * {@link android.telephony.ims.Rcs1To1Thread}s in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String RCS_1_TO_1_THREAD_URI_PART = "p2p_thread"; + + /** + * The URI to query or modify {@link android.telephony.ims.Rcs1To1Thread}s via the + * content provider + */ + Uri RCS_1_TO_1_THREAD_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, + RCS_1_TO_1_THREAD_URI_PART); + + /** + * The SMS/MMS thread to fallback to in case of an RCS outage + */ + String FALLBACK_THREAD_ID_COLUMN = "rcs_fallback_thread_id"; + } + + /** + * The table that {@link android.telephony.ims.RcsGroupThread} gets persisted to + */ + interface RcsGroupThreadColumns extends RcsThreadColumns { + /** + * The path that should be used for referring to + * {@link android.telephony.ims.RcsGroupThread}s in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String RCS_GROUP_THREAD_URI_PART = "group_thread"; + + /** + * The URI to query or modify {@link android.telephony.ims.RcsGroupThread}s via the + * content provider + */ + Uri RCS_GROUP_THREAD_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, + RCS_GROUP_THREAD_URI_PART); + + /** + * The owner/admin of the {@link android.telephony.ims.RcsGroupThread} + */ + String OWNER_PARTICIPANT_COLUMN = "owner_participant"; + + /** + * The user visible name of the group + */ + String GROUP_NAME_COLUMN = "group_name"; + + /** + * The user visible icon of the group + */ + String GROUP_ICON_COLUMN = "group_icon"; + + /** + * The RCS conference URI for this group + */ + String CONFERENCE_URI_COLUMN = "conference_uri"; + } + + /** + * The view that enables polling from all types of RCS threads at once + */ + interface RcsUnifiedThreadColumns extends RcsThreadColumns, Rcs1To1ThreadColumns, + RcsGroupThreadColumns { + /** + * The type of this {@link android.telephony.ims.RcsThread} + */ + String THREAD_TYPE_COLUMN = "thread_type"; + + /** + * Integer returned as a result from a database query that denotes the thread is 1 to 1 + */ + int THREAD_TYPE_1_TO_1 = 0; + + /** + * Integer returned as a result from a database query that denotes the thread is 1 to 1 + */ + int THREAD_TYPE_GROUP = 1; + } + + /** + * The table that {@link android.telephony.ims.RcsParticipant} gets persisted to + */ + interface RcsParticipantColumns { + /** + * The path that should be used for referring to + * {@link android.telephony.ims.RcsParticipant}s in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String RCS_PARTICIPANT_URI_PART = "participant"; + + /** + * The URI to query or modify {@link android.telephony.ims.RcsParticipant}s via the + * content provider + */ + Uri RCS_PARTICIPANT_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, + RCS_PARTICIPANT_URI_PART); + + /** + * The unique identifier of the entry in the database + */ + String RCS_PARTICIPANT_ID_COLUMN = "rcs_participant_id"; + + /** + * A foreign key on canonical_address table, also used by SMS/MMS + */ + String CANONICAL_ADDRESS_ID_COLUMN = "canonical_address_id"; + + /** + * The user visible RCS alias for this participant. + */ + String RCS_ALIAS_COLUMN = "rcs_alias"; + } + + /** + * Additional constants to enable access to {@link android.telephony.ims.RcsParticipant} + * related data + */ + interface RcsParticipantHelpers extends RcsParticipantColumns { + /** + * The view that unifies "rcs_participant" and "canonical_addresses" tables for easy + * access to participant address. + */ + String RCS_PARTICIPANT_WITH_ADDRESS_VIEW = "rcs_participant_with_address_view"; + + /** + * The view that unifies "rcs_participant", "canonical_addresses" and + * "rcs_thread_participant" junction table to get full information on participants that + * contribute to threads. + */ + String RCS_PARTICIPANT_WITH_THREAD_VIEW = "rcs_participant_with_thread_view"; + } + + /** + * The table that {@link android.telephony.ims.RcsMessage} gets persisted to + */ + interface RcsMessageColumns { + /** + * Denotes the type of this message (i.e. + * {@link android.telephony.ims.RcsIncomingMessage} or + * {@link android.telephony.ims.RcsOutgoingMessage} + */ + String MESSAGE_TYPE_COLUMN = "rcs_message_type"; + + /** + * The unique identifier for the message in the database - i.e. the primary key. + */ + String MESSAGE_ID_COLUMN = "rcs_message_row_id"; + + /** + * The globally unique RCS identifier for the message. Please see 4.4.5.2 - GSMA + * RCC.53 (RCS Device API 1.6 Specification) + */ + String GLOBAL_ID_COLUMN = "rcs_message_global_id"; + + /** + * The subscription where this message was sent from/to. + */ + String SUB_ID_COLUMN = "sub_id"; + + /** + * The sending status of the message. + * @see android.telephony.ims.RcsMessage.RcsMessageStatus + */ + String STATUS_COLUMN = "status"; + + /** + * The creation timestamp of the message. + */ + String ORIGINATION_TIMESTAMP_COLUMN = "origination_timestamp"; + + /** + * The text content of the message. + */ + String MESSAGE_TEXT_COLUMN = "rcs_text"; + + /** + * The latitude content of the message, if it contains a location. + */ + String LATITUDE_COLUMN = "latitude"; + + /** + * The longitude content of the message, if it contains a location. + */ + String LONGITUDE_COLUMN = "longitude"; + } + + /** + * The table that additional information of {@link android.telephony.ims.RcsIncomingMessage} + * gets persisted to. + */ + interface RcsIncomingMessageColumns extends RcsMessageColumns { + /** + The path that should be used for referring to + * {@link android.telephony.ims.RcsIncomingMessage}s in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String INCOMING_MESSAGE_URI_PART = "incoming_message"; + + /** + * The URI to query incoming messages through + * {@link com.android.providers.telephony.RcsProvider} + */ + Uri INCOMING_MESSAGE_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, + INCOMING_MESSAGE_URI_PART); + + /** + * The ID of the {@link android.telephony.ims.RcsParticipant} that sent this message + */ + String SENDER_PARTICIPANT_ID_COLUMN = "sender_participant"; + + /** + * The timestamp of arrival for this message. + */ + String ARRIVAL_TIMESTAMP_COLUMN = "arrival_timestamp"; + + /** + * The time when the recipient has read this message. + */ + String SEEN_TIMESTAMP_COLUMN = "seen_timestamp"; + } + + /** + * The table that additional information of {@link android.telephony.ims.RcsOutgoingMessage} + * gets persisted to. + */ + interface RcsOutgoingMessageColumns extends RcsMessageColumns { + /** + * The path that should be used for referring to + * {@link android.telephony.ims.RcsOutgoingMessage}s in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String OUTGOING_MESSAGE_URI_PART = "outgoing_message"; + + /** + * The URI to query or modify {@link android.telephony.ims.RcsOutgoingMessage}s via the + * content provider + */ + Uri OUTGOING_MESSAGE_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, + OUTGOING_MESSAGE_URI_PART); + } + + /** + * The delivery information of an {@link android.telephony.ims.RcsOutgoingMessage} + */ + interface RcsMessageDeliveryColumns extends RcsOutgoingMessageColumns { + /** + * The path that should be used for referring to + * {@link android.telephony.ims.RcsOutgoingMessageDelivery}s in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String DELIVERY_URI_PART = "delivery"; + + /** + * The timestamp of delivery of this message. + */ + String DELIVERED_TIMESTAMP_COLUMN = "delivered_timestamp"; + + /** + * The time when the recipient has read this message. + */ + String SEEN_TIMESTAMP_COLUMN = "seen_timestamp"; + } + + /** + * The views that allow querying {@link android.telephony.ims.RcsIncomingMessage} and + * {@link android.telephony.ims.RcsOutgoingMessage} at the same time. + */ + interface RcsUnifiedMessageColumns extends RcsIncomingMessageColumns, + RcsOutgoingMessageColumns { + /** + * The path that is used to query all {@link android.telephony.ims.RcsMessage} in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String UNIFIED_MESSAGE_URI_PART = "message"; + + /** + * The URI to query all types of {@link android.telephony.ims.RcsMessage}s + */ + Uri UNIFIED_MESSAGE_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, + UNIFIED_MESSAGE_URI_PART); + + /** + * The name of the view that unites rcs_message and rcs_incoming_message tables. + */ + String UNIFIED_INCOMING_MESSAGE_VIEW = "unified_incoming_message_view"; + + /** + * The name of the view that unites rcs_message and rcs_outgoing_message tables. + */ + String UNIFIED_OUTGOING_MESSAGE_VIEW = "unified_outgoing_message_view"; + + /** + * The column that shows from which table the message entry came from. + */ + String MESSAGE_TYPE_COLUMN = "message_type"; + + /** + * Integer returned as a result from a database query that denotes that the message is + * an incoming message + */ + int MESSAGE_TYPE_INCOMING = 1; + + /** + * Integer returned as a result from a database query that denotes that the message is + * an outgoing message + */ + int MESSAGE_TYPE_OUTGOING = 0; + } + + /** + * The table that {@link android.telephony.ims.RcsFileTransferPart} gets persisted to. + */ + interface RcsFileTransferColumns { + /** + * The path that should be used for referring to + * {@link android.telephony.ims.RcsFileTransferPart}s in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String FILE_TRANSFER_URI_PART = "file_transfer"; + + /** + * The URI to query or modify {@link android.telephony.ims.RcsFileTransferPart}s via the + * content provider + */ + Uri FILE_TRANSFER_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, + FILE_TRANSFER_URI_PART); + + /** + * The globally unique file transfer ID for this RCS file transfer. + */ + String FILE_TRANSFER_ID_COLUMN = "rcs_file_transfer_id"; + + /** + * The RCS session ID for this file transfer. The ID is implementation dependent but + * should be unique. + */ + String SESSION_ID_COLUMN = "session_id"; + + /** + * The URI that points to the content of this file transfer + */ + String CONTENT_URI_COLUMN = "content_uri"; + + /** + * The file type of this file transfer in bytes. The validity of types is not enforced + * in {@link android.telephony.ims.RcsMessageStore} APIs. + */ + String CONTENT_TYPE_COLUMN = "content_type"; + + /** + * The size of the file transfer in bytes. + */ + String FILE_SIZE_COLUMN = "file_size"; + + /** + * Number of bytes that was successfully transmitted for this file transfer + */ + String SUCCESSFULLY_TRANSFERRED_BYTES = "transfer_offset"; + + /** + * The status of this file transfer + * @see android.telephony.ims.RcsFileTransferPart.RcsFileTransferStatus + */ + String TRANSFER_STATUS_COLUMN = "transfer_status"; + + /** + * The on-screen width of the file transfer, if it contains multi-media + */ + String WIDTH_COLUMN = "width"; + + /** + * The on-screen height of the file transfer, if it contains multi-media + */ + String HEIGHT_COLUMN = "height"; + + /** + * The duration of the content in milliseconds if this file transfer contains + * multi-media + */ + String DURATION_MILLIS_COLUMN = "duration"; + + /** + * The URI to the preview of the content of this file transfer + */ + String PREVIEW_URI_COLUMN = "preview_uri"; + + /** + * The type of the preview of the content of this file transfer. The validity of types + * is not enforced in {@link android.telephony.ims.RcsMessageStore} APIs. + */ + String PREVIEW_TYPE_COLUMN = "preview_type"; + } + + /** + * The table that holds the information for + * {@link android.telephony.ims.RcsGroupThreadEvent} and its subclasses. + */ + interface RcsThreadEventColumns { + /** + * The string used in the {@link com.android.providers.telephony.RcsProvider} URI to + * refer to participant joined events (example URI: + * {@code content://rcs/group_thread/3/participant_joined_event}) + */ + String PARTICIPANT_JOINED_URI_PART = "participant_joined_event"; + + /** + * The string used in the {@link com.android.providers.telephony.RcsProvider} URI to + * refer to participant left events. (example URI: + * {@code content://rcs/group_thread/3/participant_left_event/4}) + */ + String PARTICIPANT_LEFT_URI_PART = "participant_left_event"; + + /** + * The string used in the {@link com.android.providers.telephony.RcsProvider} URI to + * refer to name changed events. (example URI: + * {@code content://rcs/group_thread/3/name_changed_event}) + */ + String NAME_CHANGED_URI_PART = "name_changed_event"; + + /** + * The string used in the {@link com.android.providers.telephony.RcsProvider} URI to + * refer to icon changed events. (example URI: + * {@code content://rcs/group_thread/3/icon_changed_event}) + */ + String ICON_CHANGED_URI_PART = "icon_changed_event"; + + /** + * The unique ID of this event in the database, i.e. the primary key + */ + String EVENT_ID_COLUMN = "event_id"; + + /** + * The type of this event + * + * @see RcsEventTypes + */ + String EVENT_TYPE_COLUMN = "event_type"; + + /** + * The timestamp in milliseconds of when this event happened + */ + String TIMESTAMP_COLUMN = "origination_timestamp"; + + /** + * The participant that generated this event + */ + String SOURCE_PARTICIPANT_ID_COLUMN = "source_participant"; + + /** + * The receiving participant of this event if this was an + * {@link android.telephony.ims.RcsGroupThreadParticipantJoinedEvent} or + * {@link android.telephony.ims.RcsGroupThreadParticipantLeftEvent} + */ + String DESTINATION_PARTICIPANT_ID_COLUMN = "destination_participant"; + + /** + * The URI for the new icon of the group thread if this was an + * {@link android.telephony.ims.RcsGroupThreadIconChangedEvent} + */ + String NEW_ICON_URI_COLUMN = "new_icon_uri"; + + /** + * The URI for the new name of the group thread if this was an + * {@link android.telephony.ims.RcsGroupThreadNameChangedEvent} + */ + String NEW_NAME_COLUMN = "new_name"; + } + + /** + * The table that {@link android.telephony.ims.RcsParticipantAliasChangedEvent} gets + * persisted to + */ + interface RcsParticipantEventColumns { + /** + * The path that should be used for referring to + * {@link android.telephony.ims.RcsParticipantAliasChangedEvent}s in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String ALIAS_CHANGE_EVENT_URI_PART = "alias_change_event"; + + /** + * The new alias of the participant + */ + String NEW_ALIAS_COLUMN = "new_alias"; + } + + /** + * These values are used in {@link com.android.providers.telephony.RcsProvider} to determine + * what kind of event is present in the storage. + */ + interface RcsEventTypes { + /** + * Integer constant that is stored in the + * {@link com.android.providers.telephony.RcsProvider} database that denotes the event + * is of type {@link android.telephony.ims.RcsParticipantAliasChangedEvent} + */ + int PARTICIPANT_ALIAS_CHANGED_EVENT_TYPE = 1; + + /** + * Integer constant that is stored in the + * {@link com.android.providers.telephony.RcsProvider} database that denotes the event + * is of type {@link android.telephony.ims.RcsGroupThreadParticipantJoinedEvent} + */ + int PARTICIPANT_JOINED_EVENT_TYPE = 2; + + /** + * Integer constant that is stored in the + * {@link com.android.providers.telephony.RcsProvider} database that denotes the event + * is of type {@link android.telephony.ims.RcsGroupThreadParticipantLeftEvent} + */ + int PARTICIPANT_LEFT_EVENT_TYPE = 4; + + /** + * Integer constant that is stored in the + * {@link com.android.providers.telephony.RcsProvider} database that denotes the event + * is of type {@link android.telephony.ims.RcsGroupThreadIconChangedEvent} + */ + int ICON_CHANGED_EVENT_TYPE = 8; + + /** + * Integer constant that is stored in the + * {@link com.android.providers.telephony.RcsProvider} database that denotes the event + * is of type {@link android.telephony.ims.RcsGroupThreadNameChangedEvent} + */ + int NAME_CHANGED_EVENT_TYPE = 16; + } + + /** + * The view that allows unified querying across all events + */ + interface RcsUnifiedEventHelper extends RcsParticipantEventColumns, RcsThreadEventColumns { + /** + * The path that should be used for referring to + * {@link android.telephony.ims.RcsEvent}s in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String RCS_EVENT_QUERY_URI_PATH = "event"; + + /** + * The URI to query {@link android.telephony.ims.RcsEvent}s via the content provider. + */ + Uri RCS_EVENT_QUERY_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, + RCS_EVENT_QUERY_URI_PATH); + } + + /** + * Allows RCS specific canonical address handling. + */ + interface RcsCanonicalAddressHelper { + /** + * Returns the canonical address ID for a canonical address, if now row exists, this + * will add a row and return its ID. This helper works against the same table used by + * the SMS and MMS threads, but is accessible only by the phone process for use by RCS + * message storage. + * + * @throws IllegalArgumentException if unable to retrieve or create the canonical + * address entry. + */ + static long getOrCreateCanonicalAddressId( + ContentResolver contentResolver, String canonicalAddress) { + + Uri.Builder uriBuilder = CONTENT_AND_AUTHORITY.buildUpon(); + uriBuilder.appendPath("canonical-address"); + uriBuilder.appendQueryParameter("address", canonicalAddress); + Uri uri = uriBuilder.build(); + + try (Cursor cursor = contentResolver.query(uri, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + return cursor.getLong(cursor.getColumnIndex(CanonicalAddressesColumns._ID)); + } else { + Rlog.e(TAG, "getOrCreateCanonicalAddressId returned no rows"); + } + } + + Rlog.e(TAG, "getOrCreateCanonicalAddressId failed"); + throw new IllegalArgumentException( + "Unable to find or allocate a canonical address ID"); + } + } + } + + /** * Contains all MMS messages. */ public static final class Mms implements BaseMmsColumns { diff --git a/telephony/java/android/telephony/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java index 9c64cf6ddd8f..75165afe097e 100644 --- a/telephony/java/android/telephony/AccessNetworkConstants.java +++ b/telephony/java/android/telephony/AccessNetworkConstants.java @@ -54,8 +54,15 @@ public final class AccessNetworkConstants { */ @SystemApi public static final class TransportType { + /** + * Invalid transport type. + * @hide + */ + public static final int INVALID = -1; + /** Wireless Wide Area Networks (i.e. Cellular) */ public static final int WWAN = 1; + /** Wireless Local Area Networks (i.e. Wifi) */ public static final int WLAN = 2; @@ -65,6 +72,7 @@ public final class AccessNetworkConstants { /** @hide */ public static String toString(int type) { switch (type) { + case INVALID: return "INVALID"; case WWAN: return "WWAN"; case WLAN: return "WLAN"; default: return Integer.toString(type); diff --git a/telephony/java/android/telephony/AvailableNetworkInfo.java b/telephony/java/android/telephony/AvailableNetworkInfo.java index 4da79b34a55e..b407b2a03bc4 100644 --- a/telephony/java/android/telephony/AvailableNetworkInfo.java +++ b/telephony/java/android/telephony/AvailableNetworkInfo.java @@ -114,7 +114,7 @@ public final class AvailableNetworkInfo implements Parcelable { in.readStringList(mMccMncs); } - public AvailableNetworkInfo(int subId, int priority, ArrayList<String> mccMncs) { + public AvailableNetworkInfo(int subId, int priority, List<String> mccMncs) { mSubId = subId; mPriority = priority; mMccMncs = new ArrayList<String>(mccMncs); diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java index 2d29875aadb4..0d4f09f98b43 100644 --- a/telephony/java/android/telephony/CallAttributes.java +++ b/telephony/java/android/telephony/CallAttributes.java @@ -50,10 +50,9 @@ public class CallAttributes implements Parcelable { } private CallAttributes(Parcel in) { - mPreciseCallState = (PreciseCallState) - in.readValue(PreciseCallState.class.getClassLoader()); - mNetworkType = in.readInt(); - mCallQuality = (CallQuality) in.readValue(CallQuality.class.getClassLoader()); + this.mPreciseCallState = in.readParcelable(PreciseCallState.class.getClassLoader()); + this.mNetworkType = in.readInt(); + this.mCallQuality = in.readParcelable(CallQuality.class.getClassLoader()); } // getters @@ -118,9 +117,9 @@ public class CallAttributes implements Parcelable { CallAttributes s = (CallAttributes) o; - return (mPreciseCallState == s.mPreciseCallState + return (Objects.equals(mPreciseCallState, s.mPreciseCallState) && mNetworkType == s.mNetworkType - && mCallQuality == s.mCallQuality); + && Objects.equals(mCallQuality, s.mCallQuality)); } /** @@ -134,9 +133,9 @@ public class CallAttributes implements Parcelable { * {@link Parcelable#writeToParcel} */ public void writeToParcel(Parcel dest, @Parcelable.WriteFlags int flags) { - mPreciseCallState.writeToParcel(dest, flags); + dest.writeParcelable(mPreciseCallState, flags); dest.writeInt(mNetworkType); - mCallQuality.writeToParcel(dest, flags); + dest.writeParcelable(mCallQuality, flags); } public static final Parcelable.Creator<CallAttributes> CREATOR = new Parcelable.Creator() { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 190e82b6cc5b..5c5d8569f3e8 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2371,6 +2371,14 @@ public class CarrierConfigManager { "support_emergency_dialer_shortcut_bool"; /** + * Support ASCII 7-BIT encoding for long SMS. This carrier config is used to enable + * this feature. + * @hide + */ + public static final String KEY_ASCII_7_BIT_SUPPORT_FOR_LONG_MESSAGE_BOOL = + "ascii_7_bit_support_for_long_message_bool"; + + /** * Controls RSRP threshold at which OpportunisticNetworkService will decide whether * the opportunistic network is good enough for internet data. */ @@ -2780,6 +2788,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_CALL_WAITING_OVER_UT_WARNING_BOOL, false); sDefaults.putBoolean(KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL, true); sDefaults.putBoolean(KEY_SUPPORT_EMERGENCY_DIALER_SHORTCUT_BOOL, true); + sDefaults.putBoolean(KEY_ASCII_7_BIT_SUPPORT_FOR_LONG_MESSAGE_BOOL, false); /* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_GOOD */ sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSRP_INT, -108); /* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_MODERATE */ diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java index 37847aef9167..d47b55ca4372 100644 --- a/telephony/java/android/telephony/CarrierRestrictionRules.java +++ b/telephony/java/android/telephony/CarrierRestrictionRules.java @@ -27,6 +27,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * Contains the list of carrier restrictions. @@ -93,6 +94,9 @@ public final class CarrierRestrictionRules implements Parcelable { value = {CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED, CARRIER_RESTRICTION_DEFAULT_ALLOWED}) public @interface CarrierRestrictionDefault {} + /* Wild character for comparison */ + private static final char WILD_CHARACTER = '?'; + private List<CarrierIdentifier> mAllowedCarriers; private List<CarrierIdentifier> mExcludedCarriers; @CarrierRestrictionDefault @@ -166,6 +170,124 @@ public final class CarrierRestrictionRules implements Parcelable { } /** + * Tests an array of carriers with the carrier restriction configuration. The list of carrier + * ids passed as argument does not need to be the same as currently present in the device. + * + * @param carrierIds list of {@link CarrierIdentifier}, one for each SIM slot on the device + * @return a list of boolean with the same size as input, indicating if each + * {@link CarrierIdentifier} is allowed or not. + */ + public List<Boolean> isCarrierIdentifiersAllowed(@NonNull List<CarrierIdentifier> carrierIds) { + ArrayList<Boolean> result = new ArrayList<>(carrierIds.size()); + + // First calculate the result for each slot independently + for (int i = 0; i < carrierIds.size(); i++) { + boolean inAllowedList = isCarrierIdInList(carrierIds.get(i), mAllowedCarriers); + boolean inExcludedList = isCarrierIdInList(carrierIds.get(i), mExcludedCarriers); + if (mCarrierRestrictionDefault == CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED) { + result.add((inAllowedList && !inExcludedList) ? true : false); + } else { + result.add((inExcludedList && !inAllowedList) ? false : true); + } + } + // Apply the multi-slot policy, if needed. + if (mMultiSimPolicy == MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT) { + for (boolean b : result) { + if (b) { + result.replaceAll(x -> true); + break; + } + } + } + return result; + } + + /** + * Indicates if a certain carrier {@code id} is present inside a {@code list} + * + * @return true if the carrier {@code id} is present, false otherwise + */ + private static boolean isCarrierIdInList(CarrierIdentifier id, List<CarrierIdentifier> list) { + for (CarrierIdentifier listItem : list) { + // Compare MCC and MNC + if (!patternMatch(id.getMcc(), listItem.getMcc()) + || !patternMatch(id.getMnc(), listItem.getMnc())) { + continue; + } + + // Compare SPN. Comparison is on the complete strings, case insensitive and with wild + // characters. + String listItemValue = convertNullToEmpty(listItem.getSpn()); + String idValue = convertNullToEmpty(id.getSpn()); + if (!listItemValue.isEmpty()) { + if (!patternMatch(idValue, listItemValue)) { + continue; + } + } + + // The IMSI of the configuration can be shorter than actual IMSI in the SIM card. + listItemValue = convertNullToEmpty(listItem.getImsi()); + idValue = convertNullToEmpty(id.getImsi()); + if (!patternMatch( + idValue.substring(0, Math.min(idValue.length(), listItemValue.length())), + listItemValue)) { + continue; + } + + // The GID1 of the configuration can be shorter than actual GID1 in the SIM card. + listItemValue = convertNullToEmpty(listItem.getGid1()); + idValue = convertNullToEmpty(id.getGid1()); + if (!patternMatch( + idValue.substring(0, Math.min(idValue.length(), listItemValue.length())), + listItemValue)) { + continue; + } + + // The GID2 of the configuration can be shorter than actual GID2 in the SIM card. + listItemValue = convertNullToEmpty(listItem.getGid2()); + idValue = convertNullToEmpty(id.getGid2()); + if (!patternMatch( + idValue.substring(0, Math.min(idValue.length(), listItemValue.length())), + listItemValue)) { + continue; + } + + // Valid match was found in the list + return true; + } + return false; + } + + private static String convertNullToEmpty(String value) { + return Objects.toString(value, ""); + } + + /** + * Performs a case insensitive string comparison against a given pattern. The character '?' + * is used in the pattern as wild character in the comparison. The string must have the same + * length as the pattern. + * + * @param str string to match + * @param pattern string containing the pattern + * @return true in case of match, false otherwise + */ + private static boolean patternMatch(String str, String pattern) { + if (str.length() != pattern.length()) { + return false; + } + String lowerCaseStr = str.toLowerCase(); + String lowerCasePattern = pattern.toLowerCase(); + + for (int i = 0; i < lowerCasePattern.length(); i++) { + if (lowerCasePattern.charAt(i) != lowerCaseStr.charAt(i) + && lowerCasePattern.charAt(i) != WILD_CHARACTER) { + return false; + } + } + return true; + } + + /** * {@link Parcelable#writeToParcel} */ @Override diff --git a/telephony/java/android/telephony/CellIdentityNr.java b/telephony/java/android/telephony/CellIdentityNr.java index 6b1b84cd3458..856f08107fd7 100644 --- a/telephony/java/android/telephony/CellIdentityNr.java +++ b/telephony/java/android/telephony/CellIdentityNr.java @@ -30,6 +30,7 @@ public final class CellIdentityNr extends CellIdentity { private final int mNrArfcn; private final int mPci; private final int mTac; + private final long mNci; /** * @@ -44,11 +45,12 @@ public final class CellIdentityNr extends CellIdentity { * @hide */ public CellIdentityNr(int pci, int tac, int nrArfcn, String mccStr, String mncStr, - String alphal, String alphas) { + long nci, String alphal, String alphas) { super(TAG, CellInfo.TYPE_NR, mccStr, mncStr, alphal, alphas); mPci = pci; mTac = tac; mNrArfcn = nrArfcn; + mNci = nci; } /** @@ -62,7 +64,7 @@ public final class CellIdentityNr extends CellIdentity { @Override public int hashCode() { - return Objects.hash(super.hashCode(), mPci, mTac, mNrArfcn); + return Objects.hash(super.hashCode(), mPci, mTac, mNrArfcn, mNci); } @Override @@ -72,7 +74,17 @@ public final class CellIdentityNr extends CellIdentity { } CellIdentityNr o = (CellIdentityNr) other; - return super.equals(o) && mPci == o.mPci && mTac == o.mTac && mNrArfcn == o.mNrArfcn; + return super.equals(o) && mPci == o.mPci && mTac == o.mTac && mNrArfcn == o.mNrArfcn + && mNci == o.mNci; + } + + /** + * Get the NR Cell Identity. + * + * @return The NR Cell Identity in range [0, 68719476735] or Long.MAX_VALUE if unknown. + */ + public long getNci() { + return mNci; } /** @@ -122,6 +134,7 @@ public final class CellIdentityNr extends CellIdentity { .append(" mNrArfcn = ").append(mNrArfcn) .append(" mMcc = ").append(mMccStr) .append(" mMnc = ").append(mMncStr) + .append(" mNci = ").append(mNci) .append(" mAlphaLong = ").append(mAlphaLong) .append(" mAlphaShort = ").append(mAlphaShort) .append(" }") @@ -134,6 +147,7 @@ public final class CellIdentityNr extends CellIdentity { dest.writeInt(mPci); dest.writeInt(mTac); dest.writeInt(mNrArfcn); + dest.writeLong(mNci); } /** Construct from Parcel, type has already been processed */ @@ -142,6 +156,7 @@ public final class CellIdentityNr extends CellIdentity { mPci = in.readInt(); mTac = in.readInt(); mNrArfcn = in.readInt(); + mNci = in.readLong(); } /** Implement the Parcelable interface */ diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java index 38143335dbf1..dba437a3a007 100644 --- a/telephony/java/android/telephony/CellIdentityTdscdma.java +++ b/telephony/java/android/telephony/CellIdentityTdscdma.java @@ -141,6 +141,14 @@ public final class CellIdentityTdscdma extends CellIdentity { return mCpid; } + /** + * @return 16-bit UMTS Absolute RF Channel Number, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. + */ + public int getUarfcn() { + return mUarfcn; + } + /** @hide */ @Override public int getChannelNumber() { diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java index 30e641d61143..a4207c99ce4d 100644 --- a/telephony/java/android/telephony/CellSignalStrengthGsm.java +++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java @@ -63,6 +63,10 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P public CellSignalStrengthGsm(android.hardware.radio.V1_0.GsmSignalStrength gsm) { // Convert from HAL values as part of construction. this(getRssiDbmFromAsu(gsm.signalStrength), gsm.bitErrorRate, gsm.timingAdvance); + + if (mRssi == CellInfo.UNAVAILABLE) { + setDefaultValues(); + } } /** @hide */ diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java index 061cd4b33950..6f84ec58be60 100644 --- a/telephony/java/android/telephony/CellSignalStrengthNr.java +++ b/telephony/java/android/telephony/CellSignalStrengthNr.java @@ -77,6 +77,14 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa } /** + * @hide + * @param ss signal strength from modem. + */ + public CellSignalStrengthNr(android.hardware.radio.V1_4.NrSignalStrength ss) { + this(ss.csiRsrp, ss.csiRsrq, ss.csiSinr, ss.ssRsrp, ss.ssRsrq, ss.ssSinr); + } + + /** * Reference: 3GPP TS 38.215. * Range: -140 dBm to -44 dBm. * @return SS reference signal received power, {@link CellInfo#UNAVAILABLE} means unreported diff --git a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java index 6f52b853d23b..5ae89b0f8b3d 100644 --- a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java @@ -72,6 +72,10 @@ public final class CellSignalStrengthTdscdma extends CellSignalStrength implemen // Convert from HAL values as part of construction. this(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, tdscdma.rscp != CellInfo.UNAVAILABLE ? -tdscdma.rscp : tdscdma.rscp); + + if (mRssi == CellInfo.UNAVAILABLE && mRscp == CellInfo.UNAVAILABLE) { + setDefaultValues(); + } } /** @hide */ @@ -79,6 +83,10 @@ public final class CellSignalStrengthTdscdma extends CellSignalStrength implemen // Convert from HAL values as part of construction. this(getRssiDbmFromAsu(tdscdma.signalStrength), tdscdma.bitErrorRate, getRscpDbmFromAsu(tdscdma.rscp)); + + if (mRssi == CellInfo.UNAVAILABLE && mRscp == CellInfo.UNAVAILABLE) { + setDefaultValues(); + } } /** @hide */ diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java index 0760407171ae..efa3647f0e9b 100644 --- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java @@ -92,8 +92,12 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements /** @hide */ public CellSignalStrengthWcdma(android.hardware.radio.V1_0.WcdmaSignalStrength wcdma) { // Convert from HAL values as part of construction. - this(getRssiDbmFromAsu(wcdma.signalStrength), - wcdma.bitErrorRate, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE); + this(getRssiDbmFromAsu(wcdma.signalStrength), wcdma.bitErrorRate, + CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE); + + if (mRssi == CellInfo.UNAVAILABLE && mRscp == CellInfo.UNAVAILABLE) { + setDefaultValues(); + } } /** @hide */ @@ -103,6 +107,10 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements wcdma.base.bitErrorRate, getRscpDbmFromAsu(wcdma.rscp), getEcNoDbFromAsu(wcdma.ecno)); + + if (mRssi == CellInfo.UNAVAILABLE && mRscp == CellInfo.UNAVAILABLE) { + setDefaultValues(); + } } /** @hide */ diff --git a/telephony/java/android/telephony/DebugEventReporter.java b/telephony/java/android/telephony/DebugEventReporter.java new file mode 100644 index 000000000000..14b7dd6d1b72 --- /dev/null +++ b/telephony/java/android/telephony/DebugEventReporter.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.ParcelUuid; + +import com.android.internal.util.IndentingPrintWriter; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.List; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A Simple Surface for Telephony to notify a loosely-coupled debugger of particular issues. + * + * DebugEventReporter allows an optional external logging component to receive events detected by + * the framework and take action. This log surface is designed to provide maximium flexibility + * to the receiver of these events. Envisioned use cases of this include notifying a vendor + * component of: an event that necessitates (timely) log collection on non-AOSP components; + * notifying a vendor component of a rare event that should prompt further action such as a + * bug report or user intervention for debug purposes. + * + * <p>This surface is not intended to enable a diagnostic monitor, nor is it intended to support + * streaming logs. + * + * @hide + */ +public final class DebugEventReporter { + private static final String TAG = "DebugEventReporter"; + + private static Context sContext = null; + + private static Map<UUID, Integer> sEvents = new ConcurrentHashMap<>(); + + /* + * Because this is only supporting system packages, once we find a package, it will be the + * same package until the next system upgrade. Thus, to save time in processing debug events + * we can cache this info and skip the resolution process after it's done the first time. + */ + private static String sDebugPackageName = null; + + private DebugEventReporter() {}; + + /** + * If enabled, build and send an intent to a Debug Service for logging. + * + * This method sends the {@link TelephonyManager#DEBUG_EVENT DEBUG_EVENT} broadcast, which is + * system protected. Invoking this method unless you are the system will result in an error. + * + * @param eventId a fixed event ID that will be sent for each instance of the same event. This + * ID should be generated randomly. + * @param description an optional description, that if included will be used as the subject for + * identification and discussion of this event. This description should ideally be + * static and must not contain any sensitive information (especially PII). + */ + public static void sendEvent(@NonNull UUID eventId, String description) { + if (sContext == null) { + Rlog.w(TAG, "DebugEventReporter not yet initialized, dropping event=" + eventId); + return; + } + + // If this event has already occurred, skip sending intents for it; regardless log its + // invocation here. + Integer count = sEvents.containsKey(eventId) ? sEvents.get(eventId) + 1 : 1; + sEvents.put(eventId, count); + if (count > 1) return; + + // Even if we are initialized, that doesn't mean that a package name has been found. + // This is normal in many cases, such as when no debug package is installed on the system, + // so drop these events silently. + if (sDebugPackageName == null) return; + + Intent dbgIntent = new Intent(TelephonyManager.ACTION_DEBUG_EVENT); + dbgIntent.putExtra(TelephonyManager.EXTRA_DEBUG_EVENT_ID, new ParcelUuid(eventId)); + if (description != null) { + dbgIntent.putExtra(TelephonyManager.EXTRA_DEBUG_EVENT_DESCRIPTION, description); + } + dbgIntent.setPackage(sDebugPackageName); + sContext.sendBroadcast(dbgIntent, android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE); + } + + /** + * Initialize the DebugEventReporter with the current context. + * + * This method must be invoked before any calls to sendEvent() will succeed. This method should + * only be invoked at most once. + * + * @param context a Context object used to initialize this singleton DebugEventReporter in + * the current process. + */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public static void initialize(@NonNull Context context) { + if (context == null) { + throw new IllegalArgumentException("DebugEventReporter needs a non-null context."); + } + + // Ensure that this context has sufficient permissions to send debug events. + context.enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, + "This app does not have privileges to send debug events"); + + sContext = context; + + // Check to see if there is a valid debug package; if there are multiple, that's a config + // error, so just take the first one. + PackageManager pm = sContext.getPackageManager(); + if (pm == null) return; + List<ResolveInfo> packages = pm.queryBroadcastReceivers( + new Intent(TelephonyManager.ACTION_DEBUG_EVENT), + PackageManager.MATCH_SYSTEM_ONLY + | PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); + if (packages == null || packages.isEmpty()) return; + if (packages.size() > 1) { + Rlog.e(TAG, "Multiple DebugEvent Receivers installed."); + } + + for (ResolveInfo r : packages) { + if (r.activityInfo == null + || pm.checkPermission( + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + r.activityInfo.packageName) + != PackageManager.PERMISSION_GRANTED) { + Rlog.w(TAG, + "Found package without proper permissions or no activity" + + r.activityInfo.packageName); + continue; + } + Rlog.d(TAG, "Found a valid package " + r.activityInfo.packageName); + sDebugPackageName = r.activityInfo.packageName; + break; + } + // Initialization may only be performed once. + } + + /** Dump the contents of the DebugEventReporter */ + public static void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { + if (sContext == null) return; + IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); + sContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "Requires DUMP"); + pw.println("Initialized=" + (sContext != null ? "Yes" : "No")); + pw.println("Debug Package=" + sDebugPackageName); + pw.println("Event Counts:"); + pw.increaseIndent(); + for (UUID event : sEvents.keySet()) { + pw.println(event + ": " + sEvents.get(event)); + } + pw.decreaseIndent(); + pw.flush(); + } +} diff --git a/telephony/java/android/telephony/LocationAccessPolicy.java b/telephony/java/android/telephony/LocationAccessPolicy.java index 53d69f447a56..24db438580c9 100644 --- a/telephony/java/android/telephony/LocationAccessPolicy.java +++ b/telephony/java/android/telephony/LocationAccessPolicy.java @@ -26,11 +26,12 @@ import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.location.LocationManager; import android.os.Binder; +import android.os.Build; import android.os.Process; -import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; +import android.widget.Toast; import java.util.List; @@ -41,61 +42,236 @@ import java.util.List; public final class LocationAccessPolicy { private static final String TAG = "LocationAccessPolicy"; private static final boolean DBG = false; + public static final int MAX_SDK_FOR_ANY_ENFORCEMENT = Build.VERSION_CODES.P; - /** - * API to determine if the caller has permissions to get cell location. - * - * @param pkgName Package name of the application requesting access - * @param uid The uid of the package - * @param pid The pid of the package - * @param throwOnDeniedPermission Whether to throw if the location permission is denied. - * @return boolean true or false if permissions is granted - */ - public static boolean canAccessCellLocation(@NonNull Context context, @NonNull String pkgName, - int uid, int pid, boolean throwOnDeniedPermission) throws SecurityException { - Trace.beginSection("TelephonyLocationCheck"); - try { - // Always allow the phone process and system server to access location. This avoid - // breaking legacy code that rely on public-facing APIs to access cell location, and - // it doesn't create an info leak risk because the cell location is stored in the phone - // process anyway, and the system server already has location access. - if (uid == Process.PHONE_UID || uid == Process.SYSTEM_UID || uid == Process.ROOT_UID) { - return true; + public enum LocationPermissionResult { + ALLOWED, + /** + * Indicates that the denial is due to a transient device state + * (e.g. app-ops, location master switch) + */ + DENIED_SOFT, + /** + * Indicates that the denial is due to a misconfigured app (e.g. missing entry in manifest) + */ + DENIED_HARD, + } + + public static class LocationPermissionQuery { + public final String callingPackage; + public final int callingUid; + public final int callingPid; + public final int minSdkVersionForCoarse; + public final int minSdkVersionForFine; + public final String method; + + private LocationPermissionQuery(String callingPackage, int callingUid, int callingPid, + int minSdkVersionForCoarse, int minSdkVersionForFine, String method) { + this.callingPackage = callingPackage; + this.callingUid = callingUid; + this.callingPid = callingPid; + this.minSdkVersionForCoarse = minSdkVersionForCoarse; + this.minSdkVersionForFine = minSdkVersionForFine; + this.method = method; + } + + public static class Builder { + private String mCallingPackage; + private int mCallingUid; + private int mCallingPid; + private int mMinSdkVersionForCoarse = Integer.MAX_VALUE; + private int mMinSdkVersionForFine = Integer.MAX_VALUE; + private String mMethod; + + /** + * Mandatory parameter, used for performing permission checks. + */ + public Builder setCallingPackage(String callingPackage) { + mCallingPackage = callingPackage; + return this; } - // We always require the location permission and also require the - // location mode to be on for non-legacy apps. Legacy apps are - // required to be in the foreground to at least mitigate the case - // where a legacy app the user is not using tracks their location. - // Granting ACCESS_FINE_LOCATION to an app automatically grants it - // ACCESS_COARSE_LOCATION. - if (throwOnDeniedPermission) { - context.enforcePermission(Manifest.permission.ACCESS_COARSE_LOCATION, - pid, uid, "canAccessCellLocation"); - } else if (context.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION, - pid, uid) == PackageManager.PERMISSION_DENIED) { - if (DBG) Log.w(TAG, "Permission checked failed (" + pid + "," + uid + ")"); - return false; + /** + * Mandatory parameter, used for performing permission checks. + */ + public Builder setCallingUid(int callingUid) { + mCallingUid = callingUid; + return this; } - final int opCode = AppOpsManager.permissionToOpCode( - Manifest.permission.ACCESS_COARSE_LOCATION); - if (opCode != AppOpsManager.OP_NONE && context.getSystemService(AppOpsManager.class) - .noteOpNoThrow(opCode, uid, pkgName) != AppOpsManager.MODE_ALLOWED) { - if (DBG) Log.w(TAG, "AppOp check failed (" + uid + "," + pkgName + ")"); - return false; + + /** + * Mandatory parameter, used for performing permission checks. + */ + public Builder setCallingPid(int callingPid) { + mCallingPid = callingPid; + return this; } - if (!isLocationModeEnabled(context, UserHandle.getUserId(uid))) { - if (DBG) Log.w(TAG, "Location disabled, failed, (" + uid + ")"); - return false; + + /** + * Apps that target at least this sdk version will be checked for coarse location + * permission. Defaults to INT_MAX (which means don't check) + */ + public Builder setMinSdkVersionForCoarse( + int minSdkVersionForCoarse) { + mMinSdkVersionForCoarse = minSdkVersionForCoarse; + return this; } - // If the user or profile is current, permission is granted. - // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. - return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context); - } finally { - Trace.endSection(); + + /** + * Apps that target at least this sdk version will be checked for fine location + * permission. Defaults to INT_MAX (which means don't check) + */ + public Builder setMinSdkVersionForFine( + int minSdkVersionForFine) { + mMinSdkVersionForFine = minSdkVersionForFine; + return this; + } + + /** + * Optional, for logging purposes only. + */ + public Builder setMethod(String method) { + mMethod = method; + return this; + } + + public LocationPermissionQuery build() { + return new LocationPermissionQuery(mCallingPackage, mCallingUid, + mCallingPid, mMinSdkVersionForCoarse, mMinSdkVersionForFine, mMethod); + } + } + } + + private static void logError(Context context, String errorMsg) { + Log.e(TAG, errorMsg); + try { + if (Build.IS_DEBUGGABLE) { + Toast.makeText(context, errorMsg, Toast.LENGTH_SHORT).show(); + } + } catch (Throwable t) { + // whatever, not important + } + } + + private static LocationPermissionResult appOpsModeToPermissionResult(int appOpsMode) { + switch (appOpsMode) { + case AppOpsManager.MODE_ALLOWED: + return LocationPermissionResult.ALLOWED; + case AppOpsManager.MODE_ERRORED: + return LocationPermissionResult.DENIED_HARD; + default: + return LocationPermissionResult.DENIED_SOFT; } } + private static LocationPermissionResult checkAppLocationPermissionHelper(Context context, + LocationPermissionQuery query, String permissionToCheck) { + String locationTypeForLog = + Manifest.permission.ACCESS_FINE_LOCATION.equals(permissionToCheck) + ? "fine" : "coarse"; + + // Do the app-ops and the manifest check without any of the allow-overrides first. + boolean hasManifestPermission = checkManifestPermission(context, query.callingPid, + query.callingUid, permissionToCheck); + + int appOpMode = context.getSystemService(AppOpsManager.class) + .noteOpNoThrow(AppOpsManager.permissionToOpCode(permissionToCheck), + query.callingUid, query.callingPackage); + + if (hasManifestPermission && appOpMode == AppOpsManager.MODE_ALLOWED) { + // If the app did everything right, return without logging. + return LocationPermissionResult.ALLOWED; + } + + // If the app has the manifest permission but not the app-op permission, it means that + // it's aware of the requirement and the user denied permission explicitly. If we see + // this, don't let any of the overrides happen. + if (hasManifestPermission) { + Log.i(TAG, query.callingPackage + " is aware of " + locationTypeForLog + " but the" + + " app-ops permission is specifically denied."); + return appOpsModeToPermissionResult(appOpMode); + } + + int minSdkVersion = Manifest.permission.ACCESS_FINE_LOCATION.equals(permissionToCheck) + ? query.minSdkVersionForFine : query.minSdkVersionForCoarse; + + // If the app fails for some reason, see if it should be allowed to proceed. + if (minSdkVersion > MAX_SDK_FOR_ANY_ENFORCEMENT) { + String errorMsg = "Allowing " + query.callingPackage + " " + locationTypeForLog + + " because we're not enforcing API " + query.minSdkVersionForFine + " yet." + + " Please fix this app because it will break in the future. Called from " + + query.method; + logError(context, errorMsg); + return null; + } else if (!isAppAtLeastSdkVersion(context, query.callingPackage, minSdkVersion)) { + String errorMsg = "Allowing " + query.callingPackage + " " + locationTypeForLog + + " because it doesn't target API " + query.minSdkVersionForFine + " yet." + + " Please fix this app. Called from " + query.method; + logError(context, errorMsg); + return null; + } else { + // If we're not allowing it due to the above two conditions, this means that the app + // did not declare the permission in their manifest. + return LocationPermissionResult.DENIED_HARD; + } + } + + public static LocationPermissionResult checkLocationPermission( + Context context, LocationPermissionQuery query) { + // Always allow the phone process and system server to access location. This avoid + // breaking legacy code that rely on public-facing APIs to access cell location, and + // it doesn't create an info leak risk because the cell location is stored in the phone + // process anyway, and the system server already has location access. + if (query.callingUid == Process.PHONE_UID || query.callingUid == Process.SYSTEM_UID + || query.callingUid == Process.ROOT_UID) { + return LocationPermissionResult.ALLOWED; + } + + // Check the system-wide requirements. If the location master switch is off or + // the app's profile isn't in foreground, return a soft denial. + if (!checkSystemLocationAccess(context, query.callingUid, query.callingPid)) { + return LocationPermissionResult.DENIED_SOFT; + } + + // Do the check for fine, then for coarse. + if (query.minSdkVersionForFine < Integer.MAX_VALUE) { + LocationPermissionResult resultForFine = checkAppLocationPermissionHelper( + context, query, Manifest.permission.ACCESS_FINE_LOCATION); + if (resultForFine != null) { + return resultForFine; + } + } + + if (query.minSdkVersionForCoarse < Integer.MAX_VALUE) { + LocationPermissionResult resultForCoarse = checkAppLocationPermissionHelper( + context, query, Manifest.permission.ACCESS_COARSE_LOCATION); + if (resultForCoarse != null) { + return resultForCoarse; + } + } + + // At this point, we're out of location checks to do. If the app bypassed all the previous + // ones due to the SDK grandfathering schemes, allow it access. + return LocationPermissionResult.ALLOWED; + } + + + private static boolean checkManifestPermission(Context context, int pid, int uid, + String permissionToCheck) { + return context.checkPermission(permissionToCheck, pid, uid) + == PackageManager.PERMISSION_GRANTED; + } + + private static boolean checkSystemLocationAccess(@NonNull Context context, int uid, int pid) { + if (!isLocationModeEnabled(context, UserHandle.getUserId(uid))) { + if (DBG) Log.w(TAG, "Location disabled, failed, (" + uid + ")"); + return false; + } + // If the user or profile is current, permission is granted. + // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. + return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context, uid, pid); + } + private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) { LocationManager locationManager = context.getSystemService(LocationManager.class); if (locationManager == null) { @@ -105,10 +281,10 @@ public final class LocationAccessPolicy { return locationManager.isLocationEnabledForUser(UserHandle.of(userId)); } - private static boolean checkInteractAcrossUsersFull(@NonNull Context context) { - return context.checkCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) - == PackageManager.PERMISSION_GRANTED; + private static boolean checkInteractAcrossUsersFull( + @NonNull Context context, int pid, int uid) { + return checkManifestPermission(context, pid, uid, + Manifest.permission.INTERACT_ACROSS_USERS_FULL); } private static boolean isCurrentProfile(@NonNull Context context, int uid) { @@ -132,4 +308,18 @@ public final class LocationAccessPolicy { Binder.restoreCallingIdentity(token); } } -} + + private static boolean isAppAtLeastSdkVersion(Context context, String pkgName, int sdkVersion) { + try { + if (context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion + >= sdkVersion) { + return true; + } + } catch (PackageManager.NameNotFoundException e) { + // In case of exception, assume known app (more strict checking) + // Note: This case will never happen since checkPackage is + // called to verify validity before checking app's version. + } + return false; + } +}
\ No newline at end of file diff --git a/telephony/java/android/telephony/NetworkRegistrationState.java b/telephony/java/android/telephony/NetworkRegistrationState.java index ceb76b57ae0c..6e6d59e62148 100644 --- a/telephony/java/android/telephony/NetworkRegistrationState.java +++ b/telephony/java/android/telephony/NetworkRegistrationState.java @@ -27,6 +27,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Objects; +import java.util.stream.Collectors; /** * Description of a mobile network registration state @@ -151,7 +152,7 @@ public class NetworkRegistrationState implements Parcelable { private final int[] mAvailableServices; @Nullable - private final CellIdentity mCellIdentity; + private CellIdentity mCellIdentity; @Nullable private VoiceSpecificRegistrationStates mVoiceSpecificStates; @@ -360,7 +361,34 @@ public class NetworkRegistrationState implements Parcelable { return 0; } - private static String regStateToString(int regState) { + /** + * Convert service type to string + * + * @hide + * + * @param serviceType The service type + * @return The service type in string format + */ + public static String serviceTypeToString(@ServiceType int serviceType) { + switch (serviceType) { + case SERVICE_TYPE_VOICE: return "VOICE"; + case SERVICE_TYPE_DATA: return "DATA"; + case SERVICE_TYPE_SMS: return "SMS"; + case SERVICE_TYPE_VIDEO: return "VIDEO"; + case SERVICE_TYPE_EMERGENCY: return "EMERGENCY"; + } + return "Unknown service type " + serviceType; + } + + /** + * Convert registration state to string + * + * @hide + * + * @param regState The registration state + * @return The reg state in string + */ + public static String regStateToString(@RegState int regState) { switch (regState) { case REG_STATE_NOT_REG_NOT_SEARCHING: return "NOT_REG_NOT_SEARCHING"; case REG_STATE_HOME: return "HOME"; @@ -389,14 +417,17 @@ public class NetworkRegistrationState implements Parcelable { public String toString() { return new StringBuilder("NetworkRegistrationState{") .append(" domain=").append((mDomain == DOMAIN_CS) ? "CS" : "PS") - .append("transportType=").append(mTransportType) + .append(" transportType=").append(TransportType.toString(mTransportType)) .append(" regState=").append(regStateToString(mRegState)) - .append(" roamingType=").append(mRoamingType) + .append(" roamingType=").append(ServiceState.roamingTypeToString(mRoamingType)) .append(" accessNetworkTechnology=") .append(TelephonyManager.getNetworkTypeName(mAccessNetworkTechnology)) .append(" rejectCause=").append(mRejectCause) .append(" emergencyEnabled=").append(mEmergencyOnly) - .append(" supportedServices=").append(mAvailableServices) + .append(" availableServices=").append("[" + (mAvailableServices != null + ? Arrays.stream(mAvailableServices) + .mapToObj(type -> serviceTypeToString(type)) + .collect(Collectors.joining(",")) : null) + "]") .append(" cellIdentity=").append(mCellIdentity) .append(" voiceSpecificStates=").append(mVoiceSpecificStates) .append(" dataSpecificStates=").append(mDataSpecificStates) @@ -490,4 +521,22 @@ public class NetworkRegistrationState implements Parcelable { return new NetworkRegistrationState[size]; } }; + + /** + * @hide + */ + public NetworkRegistrationState sanitizeLocationInfo() { + NetworkRegistrationState result = copy(); + result.mCellIdentity = null; + return result; + } + + private NetworkRegistrationState copy() { + Parcel p = Parcel.obtain(); + this.writeToParcel(p, 0); + p.setDataPosition(0); + NetworkRegistrationState result = new NetworkRegistrationState(p); + p.recycle(); + return result; + } } diff --git a/telephony/java/android/telephony/PhoneCapability.java b/telephony/java/android/telephony/PhoneCapability.java index 2ebfa53ead23..21dad77d29b7 100644 --- a/telephony/java/android/telephony/PhoneCapability.java +++ b/telephony/java/android/telephony/PhoneCapability.java @@ -33,15 +33,17 @@ public class PhoneCapability implements Parcelable { public final int maxActiveVoiceCalls; public final int maxActiveData; public final int max5G; + public final boolean validationBeforeSwitchSupported; public final List<ModemInfo> logicalModemList; public PhoneCapability(int maxActiveVoiceCalls, int maxActiveData, int max5G, - List<ModemInfo> logicalModemList) { + List<ModemInfo> logicalModemList, boolean validationBeforeSwitchSupported) { this.maxActiveVoiceCalls = maxActiveVoiceCalls; this.maxActiveData = maxActiveData; this.max5G = max5G; // Make sure it's not null. this.logicalModemList = logicalModemList == null ? new ArrayList<>() : logicalModemList; + this.validationBeforeSwitchSupported = validationBeforeSwitchSupported; } @Override @@ -55,13 +57,15 @@ public class PhoneCapability implements Parcelable { maxActiveVoiceCalls = in.readInt(); maxActiveData = in.readInt(); max5G = in.readInt(); + validationBeforeSwitchSupported = in.readBoolean(); logicalModemList = new ArrayList<>(); in.readList(logicalModemList, ModemInfo.class.getClassLoader()); } @Override public int hashCode() { - return Objects.hash(maxActiveVoiceCalls, maxActiveData, max5G, logicalModemList); + return Objects.hash(maxActiveVoiceCalls, maxActiveData, max5G, logicalModemList, + validationBeforeSwitchSupported); } @Override @@ -79,6 +83,7 @@ public class PhoneCapability implements Parcelable { return (maxActiveVoiceCalls == s.maxActiveVoiceCalls && maxActiveData == s.maxActiveData && max5G == s.max5G + && validationBeforeSwitchSupported == s.validationBeforeSwitchSupported && logicalModemList.equals(s.logicalModemList)); } @@ -96,6 +101,7 @@ public class PhoneCapability implements Parcelable { dest.writeInt(maxActiveVoiceCalls); dest.writeInt(maxActiveData); dest.writeInt(max5G); + dest.writeBoolean(validationBeforeSwitchSupported); dest.writeList(logicalModemList); } diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java index fea1b7b08a20..3ce646cb400b 100644 --- a/telephony/java/android/telephony/PhoneStateListener.java +++ b/telephony/java/android/telephony/PhoneStateListener.java @@ -45,7 +45,8 @@ import java.util.concurrent.Executor; * <p> * Override the methods for the state that you wish to receive updates for, and * pass your PhoneStateListener object, along with bitwise-or of the LISTEN_ - * flags to {@link TelephonyManager#listen TelephonyManager.listen()}. + * flags to {@link TelephonyManager#listen TelephonyManager.listen()}. Methods are + * called when the state changes, as well as once on initial registration. * <p> * Note that access to some telephony information is * permission-protected. Your application won't receive updates for protected diff --git a/telephony/java/android/telephony/PreciseCallState.java b/telephony/java/android/telephony/PreciseCallState.java index 59f3e1f0e7f7..19e1931a30df 100644 --- a/telephony/java/android/telephony/PreciseCallState.java +++ b/telephony/java/android/telephony/PreciseCallState.java @@ -287,11 +287,11 @@ public final class PreciseCallState implements Parcelable { return false; } PreciseCallState other = (PreciseCallState) obj; - return (mRingingCallState != other.mRingingCallState && - mForegroundCallState != other.mForegroundCallState && - mBackgroundCallState != other.mBackgroundCallState && - mDisconnectCause != other.mDisconnectCause && - mPreciseDisconnectCause != other.mPreciseDisconnectCause); + return (mRingingCallState == other.mRingingCallState + && mForegroundCallState == other.mForegroundCallState + && mBackgroundCallState == other.mBackgroundCallState + && mDisconnectCause == other.mDisconnectCause + && mPreciseDisconnectCause == other.mPreciseDisconnectCause); } @Override diff --git a/telephony/java/android/telephony/RadioAccessFamily.java b/telephony/java/android/telephony/RadioAccessFamily.java index f63b753e075e..c1786befb096 100644 --- a/telephony/java/android/telephony/RadioAccessFamily.java +++ b/telephony/java/android/telephony/RadioAccessFamily.java @@ -17,12 +17,15 @@ package android.telephony; import android.annotation.UnsupportedAppUsage; +import android.hardware.radio.V1_0.RadioTechnology; +import android.hardware.radio.V1_4.CellInfo.Info; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import com.android.internal.telephony.RILConstants; + /** * Object to indicate the phone radio type and access technology. * @@ -33,29 +36,34 @@ public class RadioAccessFamily implements Parcelable { /** * TODO: get rid of RAF definition in RadioAccessFamily and * use {@link TelephonyManager.NetworkTypeBitMask} + * TODO: public definition {@link TelephonyManager.NetworkTypeBitMask} is long. + * TODO: Convert from int to long everywhere including HAL definitions. */ // 2G - public static final int RAF_UNKNOWN = TelephonyManager.NETWORK_TYPE_BITMASK_UNKNOWN; - public static final int RAF_GSM = TelephonyManager.NETWORK_TYPE_BITMASK_GSM; - public static final int RAF_GPRS = TelephonyManager.NETWORK_TYPE_BITMASK_GPRS; - public static final int RAF_EDGE = TelephonyManager.NETWORK_TYPE_BITMASK_EDGE; - public static final int RAF_IS95A = TelephonyManager.NETWORK_TYPE_BITMASK_CDMA; - public static final int RAF_IS95B = TelephonyManager.NETWORK_TYPE_BITMASK_CDMA; - public static final int RAF_1xRTT = TelephonyManager.NETWORK_TYPE_BITMASK_1xRTT; + public static final int RAF_UNKNOWN = (int) TelephonyManager.NETWORK_TYPE_BITMASK_UNKNOWN; + public static final int RAF_GSM = (int) TelephonyManager.NETWORK_TYPE_BITMASK_GSM; + public static final int RAF_GPRS = (int) TelephonyManager.NETWORK_TYPE_BITMASK_GPRS; + public static final int RAF_EDGE = (int) TelephonyManager.NETWORK_TYPE_BITMASK_EDGE; + public static final int RAF_IS95A = (int) TelephonyManager.NETWORK_TYPE_BITMASK_CDMA; + public static final int RAF_IS95B = (int) TelephonyManager.NETWORK_TYPE_BITMASK_CDMA; + public static final int RAF_1xRTT = (int) TelephonyManager.NETWORK_TYPE_BITMASK_1xRTT; // 3G - public static final int RAF_EVDO_0 = TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_0; - public static final int RAF_EVDO_A = TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_A; - public static final int RAF_EVDO_B = TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_B; - public static final int RAF_EHRPD = TelephonyManager.NETWORK_TYPE_BITMASK_EHRPD; - public static final int RAF_HSUPA = TelephonyManager.NETWORK_TYPE_BITMASK_HSUPA; - public static final int RAF_HSDPA = TelephonyManager.NETWORK_TYPE_BITMASK_HSDPA; - public static final int RAF_HSPA = TelephonyManager.NETWORK_TYPE_BITMASK_HSPA; - public static final int RAF_HSPAP = TelephonyManager.NETWORK_TYPE_BITMASK_HSPAP; - public static final int RAF_UMTS = TelephonyManager.NETWORK_TYPE_BITMASK_UMTS; - public static final int RAF_TD_SCDMA = TelephonyManager.NETWORK_TYPE_BITMASK_TD_SCDMA; + public static final int RAF_EVDO_0 = (int) TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_0; + public static final int RAF_EVDO_A = (int) TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_A; + public static final int RAF_EVDO_B = (int) TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_B; + public static final int RAF_EHRPD = (int) TelephonyManager.NETWORK_TYPE_BITMASK_EHRPD; + public static final int RAF_HSUPA = (int) TelephonyManager.NETWORK_TYPE_BITMASK_HSUPA; + public static final int RAF_HSDPA = (int) TelephonyManager.NETWORK_TYPE_BITMASK_HSDPA; + public static final int RAF_HSPA = (int) TelephonyManager.NETWORK_TYPE_BITMASK_HSPA; + public static final int RAF_HSPAP = (int) TelephonyManager.NETWORK_TYPE_BITMASK_HSPAP; + public static final int RAF_UMTS = (int) TelephonyManager.NETWORK_TYPE_BITMASK_UMTS; + public static final int RAF_TD_SCDMA = (int) TelephonyManager.NETWORK_TYPE_BITMASK_TD_SCDMA; // 4G - public static final int RAF_LTE = TelephonyManager.NETWORK_TYPE_BITMASK_LTE; - public static final int RAF_LTE_CA = TelephonyManager.NETWORK_TYPE_BITMASK_LTE_CA; + public static final int RAF_LTE = (int) TelephonyManager.NETWORK_TYPE_BITMASK_LTE; + public static final int RAF_LTE_CA = (int) TelephonyManager.NETWORK_TYPE_BITMASK_LTE_CA; + + // 5G + public static final int RAF_NR = (int) TelephonyManager.NETWORK_TYPE_BITMASK_NR; // Grouping of RAFs // 2G @@ -68,6 +76,9 @@ public class RadioAccessFamily implements Parcelable { // 4G private static final int LTE = RAF_LTE | RAF_LTE_CA; + // 5G + private static final int NR = RAF_NR; + /* Phone ID of phone */ private int mPhoneId; @@ -141,103 +152,97 @@ public class RadioAccessFamily implements Parcelable { /** * Implement the Parcelable interface. */ - public static final Creator<RadioAccessFamily> CREATOR = - new Creator<RadioAccessFamily>() { + public static final Creator<android.telephony.RadioAccessFamily> CREATOR = + new Creator<android.telephony.RadioAccessFamily>() { @Override - public RadioAccessFamily createFromParcel(Parcel in) { + public android.telephony.RadioAccessFamily createFromParcel(Parcel in) { int phoneId = in.readInt(); int radioAccessFamily = in.readInt(); - return new RadioAccessFamily(phoneId, radioAccessFamily); + return new android.telephony.RadioAccessFamily(phoneId, radioAccessFamily); } @Override - public RadioAccessFamily[] newArray(int size) { - return new RadioAccessFamily[size]; + public android.telephony.RadioAccessFamily[] newArray(int size) { + return new android.telephony.RadioAccessFamily[size]; } }; @UnsupportedAppUsage public static int getRafFromNetworkType(int type) { - int raf; - switch (type) { case RILConstants.NETWORK_MODE_WCDMA_PREF: - raf = GSM | WCDMA; - break; + return GSM | WCDMA; case RILConstants.NETWORK_MODE_GSM_ONLY: - raf = GSM; - break; + return GSM; case RILConstants.NETWORK_MODE_WCDMA_ONLY: - raf = WCDMA; - break; + return WCDMA; case RILConstants.NETWORK_MODE_GSM_UMTS: - raf = GSM | WCDMA; - break; + return GSM | WCDMA; case RILConstants.NETWORK_MODE_CDMA: - raf = CDMA | EVDO; - break; + return CDMA | EVDO; case RILConstants.NETWORK_MODE_LTE_CDMA_EVDO: - raf = LTE | CDMA | EVDO; - break; + return LTE | CDMA | EVDO; case RILConstants.NETWORK_MODE_LTE_GSM_WCDMA: - raf = LTE | GSM | WCDMA; - break; + return LTE | GSM | WCDMA; case RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA: - raf = LTE | CDMA | EVDO | GSM | WCDMA; - break; + return LTE | CDMA | EVDO | GSM | WCDMA; case RILConstants.NETWORK_MODE_LTE_ONLY: - raf = LTE; - break; + return LTE; case RILConstants.NETWORK_MODE_LTE_WCDMA: - raf = LTE | WCDMA; - break; + return LTE | WCDMA; case RILConstants.NETWORK_MODE_CDMA_NO_EVDO: - raf = CDMA; - break; + return CDMA; case RILConstants.NETWORK_MODE_EVDO_NO_CDMA: - raf = EVDO; - break; + return EVDO; case RILConstants.NETWORK_MODE_GLOBAL: - raf = GSM | WCDMA | CDMA | EVDO; - break; + return GSM | WCDMA | CDMA | EVDO; case RILConstants.NETWORK_MODE_TDSCDMA_ONLY: - raf = RAF_TD_SCDMA; - break; + return RAF_TD_SCDMA; case RILConstants.NETWORK_MODE_TDSCDMA_WCDMA: - raf = RAF_TD_SCDMA | WCDMA; - break; + return RAF_TD_SCDMA | WCDMA; case RILConstants.NETWORK_MODE_LTE_TDSCDMA: - raf = LTE | RAF_TD_SCDMA; - break; + return LTE | RAF_TD_SCDMA; case RILConstants.NETWORK_MODE_TDSCDMA_GSM: - raf = RAF_TD_SCDMA | GSM; - break; + return RAF_TD_SCDMA | GSM; case RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM: - raf = LTE | RAF_TD_SCDMA | GSM; - break; + return LTE | RAF_TD_SCDMA | GSM; case RILConstants.NETWORK_MODE_TDSCDMA_GSM_WCDMA: - raf = RAF_TD_SCDMA | GSM | WCDMA; - break; + return RAF_TD_SCDMA | GSM | WCDMA; case RILConstants.NETWORK_MODE_LTE_TDSCDMA_WCDMA: - raf = LTE | RAF_TD_SCDMA | WCDMA; - break; + return LTE | RAF_TD_SCDMA | WCDMA; case RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA: - raf = LTE | RAF_TD_SCDMA | GSM | WCDMA; - break; + return LTE | RAF_TD_SCDMA | GSM | WCDMA; case RILConstants.NETWORK_MODE_TDSCDMA_CDMA_EVDO_GSM_WCDMA: - raf = RAF_TD_SCDMA | CDMA | EVDO | GSM | WCDMA; - break; + return RAF_TD_SCDMA | CDMA | EVDO | GSM | WCDMA; case RILConstants.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA: - raf = LTE | RAF_TD_SCDMA | CDMA | EVDO | GSM | WCDMA; - break; + return LTE | RAF_TD_SCDMA | CDMA | EVDO | GSM | WCDMA; + case (RILConstants.NETWORK_MODE_NR_ONLY): + return NR; + case (RILConstants.NETWORK_MODE_NR_LTE): + return NR | LTE; + case (RILConstants.NETWORK_MODE_NR_LTE_CDMA_EVDO): + return NR | LTE | CDMA | EVDO; + case (RILConstants.NETWORK_MODE_NR_LTE_GSM_WCDMA): + return NR | LTE | GSM | WCDMA; + case (RILConstants.NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA): + return NR | LTE | CDMA | EVDO | GSM | WCDMA; + case (RILConstants.NETWORK_MODE_NR_LTE_WCDMA): + return NR | LTE | WCDMA; + case (RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA): + return NR | LTE | RAF_TD_SCDMA; + case (RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_GSM): + return NR | LTE | RAF_TD_SCDMA | GSM; + case (RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_WCDMA): + return NR | LTE | RAF_TD_SCDMA | WCDMA; + case (RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA): + return NR | LTE | RAF_TD_SCDMA | GSM | WCDMA; + case (RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA): + return NR | LTE | RAF_TD_SCDMA | CDMA | EVDO | GSM | WCDMA; default: - raf = RAF_UNKNOWN; - break; + return RAF_UNKNOWN; } - - return raf; } /** @@ -250,6 +255,7 @@ public class RadioAccessFamily implements Parcelable { raf = ((CDMA & raf) > 0) ? (CDMA | raf) : raf; raf = ((EVDO & raf) > 0) ? (EVDO | raf) : raf; raf = ((LTE & raf) > 0) ? (LTE | raf) : raf; + raf = ((NR & raf) > 0) ? (NR | raf) : raf; return raf; } @@ -274,83 +280,78 @@ public class RadioAccessFamily implements Parcelable { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public static int getNetworkTypeFromRaf(int raf) { - int type; - raf = getAdjustedRaf(raf); switch (raf) { case (GSM | WCDMA): - type = RILConstants.NETWORK_MODE_WCDMA_PREF; - break; + return RILConstants.NETWORK_MODE_WCDMA_PREF; case GSM: - type = RILConstants.NETWORK_MODE_GSM_ONLY; - break; + return RILConstants.NETWORK_MODE_GSM_ONLY; case WCDMA: - type = RILConstants.NETWORK_MODE_WCDMA_ONLY; - break; + return RILConstants.NETWORK_MODE_WCDMA_ONLY; case (CDMA | EVDO): - type = RILConstants.NETWORK_MODE_CDMA; - break; + return RILConstants.NETWORK_MODE_CDMA; case (LTE | CDMA | EVDO): - type = RILConstants.NETWORK_MODE_LTE_CDMA_EVDO; - break; + return RILConstants.NETWORK_MODE_LTE_CDMA_EVDO; case (LTE | GSM | WCDMA): - type = RILConstants.NETWORK_MODE_LTE_GSM_WCDMA; - break; + return RILConstants.NETWORK_MODE_LTE_GSM_WCDMA; case (LTE | CDMA | EVDO | GSM | WCDMA): - type = RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA; - break; + return RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA; case LTE: - type = RILConstants.NETWORK_MODE_LTE_ONLY; - break; + return RILConstants.NETWORK_MODE_LTE_ONLY; case (LTE | WCDMA): - type = RILConstants.NETWORK_MODE_LTE_WCDMA; - break; + return RILConstants.NETWORK_MODE_LTE_WCDMA; case CDMA: - type = RILConstants.NETWORK_MODE_CDMA_NO_EVDO; - break; + return RILConstants.NETWORK_MODE_CDMA_NO_EVDO; case EVDO: - type = RILConstants.NETWORK_MODE_EVDO_NO_CDMA; - break; + return RILConstants.NETWORK_MODE_EVDO_NO_CDMA; case (GSM | WCDMA | CDMA | EVDO): - type = RILConstants.NETWORK_MODE_GLOBAL; - break; + return RILConstants.NETWORK_MODE_GLOBAL; case RAF_TD_SCDMA: - type = RILConstants.NETWORK_MODE_TDSCDMA_ONLY; - break; + return RILConstants.NETWORK_MODE_TDSCDMA_ONLY; case (RAF_TD_SCDMA | WCDMA): - type = RILConstants.NETWORK_MODE_TDSCDMA_WCDMA; - break; + return RILConstants.NETWORK_MODE_TDSCDMA_WCDMA; case (LTE | RAF_TD_SCDMA): - type = RILConstants.NETWORK_MODE_LTE_TDSCDMA; - break; + return RILConstants.NETWORK_MODE_LTE_TDSCDMA; case (RAF_TD_SCDMA | GSM): - type = RILConstants.NETWORK_MODE_TDSCDMA_GSM; - break; + return RILConstants.NETWORK_MODE_TDSCDMA_GSM; case (LTE | RAF_TD_SCDMA | GSM): - type = RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM; - break; + return RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM; case (RAF_TD_SCDMA | GSM | WCDMA): - type = RILConstants.NETWORK_MODE_TDSCDMA_GSM_WCDMA; - break; + return RILConstants.NETWORK_MODE_TDSCDMA_GSM_WCDMA; case (LTE | RAF_TD_SCDMA | WCDMA): - type = RILConstants.NETWORK_MODE_LTE_TDSCDMA_WCDMA; - break; + return RILConstants.NETWORK_MODE_LTE_TDSCDMA_WCDMA; case (LTE | RAF_TD_SCDMA | GSM | WCDMA): - type = RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA; - break; + return RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA; case (RAF_TD_SCDMA | CDMA | EVDO | GSM | WCDMA): - type = RILConstants.NETWORK_MODE_TDSCDMA_CDMA_EVDO_GSM_WCDMA; - break; + return RILConstants.NETWORK_MODE_TDSCDMA_CDMA_EVDO_GSM_WCDMA; case (LTE | RAF_TD_SCDMA | CDMA | EVDO | GSM | WCDMA): - type = RILConstants.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA; - break; + return RILConstants.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA; + case (NR): + return RILConstants.NETWORK_MODE_NR_ONLY; + case (NR | LTE): + return RILConstants.NETWORK_MODE_NR_LTE; + case (NR | LTE | CDMA | EVDO): + return RILConstants.NETWORK_MODE_NR_LTE_CDMA_EVDO; + case (NR | LTE | GSM | WCDMA): + return RILConstants.NETWORK_MODE_NR_LTE_GSM_WCDMA; + case (NR | LTE | CDMA | EVDO | GSM | WCDMA): + return RILConstants.NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA; + case (NR | LTE | WCDMA): + return RILConstants.NETWORK_MODE_NR_LTE_WCDMA; + case (NR | LTE | RAF_TD_SCDMA): + return RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA; + case (NR | LTE | RAF_TD_SCDMA | GSM): + return RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_GSM; + case (NR | LTE | RAF_TD_SCDMA | WCDMA): + return RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_WCDMA; + case (NR | LTE | RAF_TD_SCDMA | GSM | WCDMA): + return RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA; + case (NR | LTE | RAF_TD_SCDMA | CDMA | EVDO | GSM | WCDMA): + return RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA; default: - type = RILConstants.PREFERRED_NETWORK_MODE ; - break; + return RILConstants.PREFERRED_NETWORK_MODE; } - - return type; } public static int singleRafTypeFromString(String rafString) { @@ -377,6 +378,7 @@ public class RadioAccessFamily implements Parcelable { case "EVDO": return EVDO; case "WCDMA": return WCDMA; case "LTE_CA": return RAF_LTE_CA; + case "NR": return RAF_NR; default: return RAF_UNKNOWN; } } @@ -394,76 +396,78 @@ public class RadioAccessFamily implements Parcelable { } /** - * convert RAF from {@link ServiceState.RilRadioTechnology} bitmask to + * convert RAF from {@link android.hardware.radio.V1_0.RadioAccessFamily} to * {@link TelephonyManager.NetworkTypeBitMask}, the bitmask represented by - * {@link TelephonyManager.NetworkType}. Reasons are {@link TelephonyManager.NetworkType} are - * public while {@link ServiceState.RilRadioTechnology} are hidden. We - * don't want to expose two sets of definition to public. + * {@link TelephonyManager.NetworkType}. * - * @param raf bitmask represented by {@link ServiceState.RilRadioTechnology} + * @param raf {@link android.hardware.radio.V1_0.RadioAccessFamily} * @return {@link TelephonyManager.NetworkTypeBitMask} */ public static int convertToNetworkTypeBitMask(int raf) { int networkTypeRaf = 0; - if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_GSM)) != 0) { + if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.GSM) != 0) { networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_GSM; } - if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_GPRS)) != 0) { + if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.GPRS) != 0) { networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_GPRS; } - if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_EDGE)) != 0) { + if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.EDGE) != 0) { networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_EDGE; } // convert both IS95A/IS95B to CDMA as network mode doesn't support CDMA - if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_IS95A)) != 0) { + if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.IS95A) != 0) { networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_CDMA; } - if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_IS95B)) != 0) { + if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.IS95B) != 0) { networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_CDMA; } - if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT)) != 0) { + if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.ONE_X_RTT) != 0) { networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_1xRTT; } - if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0)) != 0) { + if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.EVDO_0) != 0) { networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_0; } - if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A)) != 0) { + if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.EVDO_A) != 0) { networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_A; } - if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B)) != 0) { + if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.EVDO_B) != 0) { networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_B; } - if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD)) != 0) { + if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.EHRPD) != 0) { networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_EHRPD; } - if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA)) != 0) { + if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.HSUPA) != 0) { networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_HSUPA; } - if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA)) != 0) { + if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.HSDPA) != 0) { networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_HSDPA; } - if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_HSPA)) != 0) { + if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.HSPA) != 0) { networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_HSPA; } - if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP)) != 0) { + if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.HSPAP) != 0) { networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_HSPAP; } - if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_UMTS)) != 0) { + if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.UMTS) != 0) { networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_UMTS; } - if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_TD_SCDMA)) != 0) { + if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.TD_SCDMA) != 0) { networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_TD_SCDMA; } - if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_LTE)) != 0) { + if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.LTE) != 0) { networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_LTE; } - if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA)) != 0) { + if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.LTE_CA) != 0) { networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_LTE_CA; } - if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_NR)) != 0) { + if ((raf & android.hardware.radio.V1_4.RadioAccessFamily.NR) != 0) { networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_NR; } + // TODO: need hal definition + if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN)) != 0) { + networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_IWLAN; + } return (networkTypeRaf == 0) ? TelephonyManager.NETWORK_TYPE_UNKNOWN : networkTypeRaf; } diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 33178766f3a3..a1aee6d8217f 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -36,6 +36,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; /** * Contains phone state and service related information. @@ -887,6 +888,24 @@ public class ServiceState implements Parcelable { } /** + * Convert roaming type to string + * + * @param roamingType roaming type + * @return The roaming type in string format + * + * @hide + */ + public static String roamingTypeToString(@RoamingType int roamingType) { + switch (roamingType) { + case ROAMING_TYPE_NOT_ROAMING: return "NOT_ROAMING"; + case ROAMING_TYPE_UNKNOWN: return "UNKNOWN"; + case ROAMING_TYPE_DOMESTIC: return "DOMESTIC"; + case ROAMING_TYPE_INTERNATIONAL: return "INTERNATIONAL"; + } + return "Unknown roaming type " + roamingType; + } + + /** * Convert radio technology to String * * @param rt radioTechnology @@ -1867,4 +1886,29 @@ public class ServiceState implements Parcelable { ? range1 : range2; } + + /** + * Returns a copy of self with location-identifying information removed. + * Always clears the NetworkRegistrationState's CellIdentity fields, but if removeCoarseLocation + * is true, clears other info as well. + * @hide + */ + public ServiceState sanitizeLocationInfo(boolean removeCoarseLocation) { + ServiceState state = new ServiceState(this); + if (state.mNetworkRegistrationStates != null) { + state.mNetworkRegistrationStates = state.mNetworkRegistrationStates.stream() + .map(NetworkRegistrationState::sanitizeLocationInfo) + .collect(Collectors.toList()); + } + if (!removeCoarseLocation) return state; + + state.mDataOperatorAlphaLong = null; + state.mDataOperatorAlphaShort = null; + state.mDataOperatorNumeric = null; + state.mVoiceOperatorAlphaLong = null; + state.mVoiceOperatorAlphaShort = null; + state.mVoiceOperatorNumeric = null; + + return state; + } } diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java index 91375bc2f11e..d2ae106b5545 100644 --- a/telephony/java/android/telephony/SignalStrength.java +++ b/telephony/java/android/telephony/SignalStrength.java @@ -85,6 +85,7 @@ public class SignalStrength implements Parcelable { CellSignalStrengthWcdma mWcdma; CellSignalStrengthTdscdma mTdscdma; CellSignalStrengthLte mLte; + CellSignalStrengthNr mNr; /** * Create a new SignalStrength from a intent notifier Bundle @@ -116,7 +117,7 @@ public class SignalStrength implements Parcelable { public SignalStrength() { this(new CellSignalStrengthCdma(), new CellSignalStrengthGsm(), new CellSignalStrengthWcdma(), new CellSignalStrengthTdscdma(), - new CellSignalStrengthLte()); + new CellSignalStrengthLte(), new CellSignalStrengthNr()); } /** @@ -129,12 +130,14 @@ public class SignalStrength implements Parcelable { @NonNull CellSignalStrengthGsm gsm, @NonNull CellSignalStrengthWcdma wcdma, @NonNull CellSignalStrengthTdscdma tdscdma, - @NonNull CellSignalStrengthLte lte) { + @NonNull CellSignalStrengthLte lte, + @NonNull CellSignalStrengthNr nr) { mCdma = cdma; mGsm = gsm; mWcdma = wcdma; mTdscdma = tdscdma; mLte = lte; + mNr = nr; } /** @@ -147,7 +150,8 @@ public class SignalStrength implements Parcelable { new CellSignalStrengthGsm(signalStrength.gw), new CellSignalStrengthWcdma(), new CellSignalStrengthTdscdma(signalStrength.tdScdma), - new CellSignalStrengthLte(signalStrength.lte)); + new CellSignalStrengthLte(signalStrength.lte), + new CellSignalStrengthNr()); } /** @@ -160,7 +164,23 @@ public class SignalStrength implements Parcelable { new CellSignalStrengthGsm(signalStrength.gsm), new CellSignalStrengthWcdma(signalStrength.wcdma), new CellSignalStrengthTdscdma(signalStrength.tdScdma), - new CellSignalStrengthLte(signalStrength.lte)); + new CellSignalStrengthLte(signalStrength.lte), + new CellSignalStrengthNr()); + } + + /** + * Constructor for Radio HAL V1.4. + * + * @param signalStrength signal strength reported from modem. + * @hide + */ + public SignalStrength(android.hardware.radio.V1_4.SignalStrength signalStrength) { + this(new CellSignalStrengthCdma(signalStrength.cdma, signalStrength.evdo), + new CellSignalStrengthGsm(signalStrength.gsm), + new CellSignalStrengthWcdma(signalStrength.wcdma), + new CellSignalStrengthTdscdma(signalStrength.tdscdma), + new CellSignalStrengthLte(signalStrength.lte), + new CellSignalStrengthNr(signalStrength.nr)); } private CellSignalStrength getPrimary() { @@ -171,6 +191,7 @@ public class SignalStrength implements Parcelable { if (mTdscdma.isValid()) return mTdscdma; if (mWcdma.isValid()) return mWcdma; if (mGsm.isValid()) return mGsm; + if (mNr.isValid()) return mNr; return mLte; } @@ -200,6 +221,7 @@ public class SignalStrength implements Parcelable { if (mTdscdma.isValid()) cssList.add(mTdscdma); if (mWcdma.isValid()) cssList.add(mWcdma); if (mGsm.isValid()) cssList.add(mGsm); + if (mNr.isValid()) cssList.add(mNr); return cssList; } @@ -210,6 +232,7 @@ public class SignalStrength implements Parcelable { mWcdma.updateLevel(cc, ss); mTdscdma.updateLevel(cc, ss); mLte.updateLevel(cc, ss); + mNr.updateLevel(cc, ss); } /** @@ -234,6 +257,7 @@ public class SignalStrength implements Parcelable { mWcdma = new CellSignalStrengthWcdma(s.mWcdma); mTdscdma = new CellSignalStrengthTdscdma(s.mTdscdma); mLte = new CellSignalStrengthLte(s.mLte); + mNr = new CellSignalStrengthNr(s.mNr); } /** @@ -250,6 +274,7 @@ public class SignalStrength implements Parcelable { mWcdma = in.readParcelable(CellSignalStrengthWcdma.class.getClassLoader()); mTdscdma = in.readParcelable(CellSignalStrengthTdscdma.class.getClassLoader()); mLte = in.readParcelable(CellSignalStrengthLte.class.getClassLoader()); + mNr = in.readParcelable(CellSignalStrengthLte.class.getClassLoader()); } /** @@ -261,6 +286,7 @@ public class SignalStrength implements Parcelable { out.writeParcelable(mWcdma, flags); out.writeParcelable(mTdscdma, flags); out.writeParcelable(mLte, flags); + out.writeParcelable(mNr, flags); } /** @@ -814,7 +840,7 @@ public class SignalStrength implements Parcelable { */ @Override public int hashCode() { - return Objects.hash(mCdma, mGsm, mWcdma, mTdscdma, mLte); + return Objects.hash(mCdma, mGsm, mWcdma, mTdscdma, mLte, mNr); } /** @@ -830,7 +856,8 @@ public class SignalStrength implements Parcelable { && mGsm.equals(s.mGsm) && mWcdma.equals(s.mWcdma) && mTdscdma.equals(s.mTdscdma) - && mLte.equals(s.mLte); + && mLte.equals(s.mLte) + && mNr.equals(s.mNr); } /** @@ -844,6 +871,7 @@ public class SignalStrength implements Parcelable { .append(",mWcdma=").append(mWcdma) .append(",mTdscdma=").append(mTdscdma) .append(",mLte=").append(mLte) + .append(",mNr=").append(mNr) .append(",primary=").append(getPrimary().getClass().getSimpleName()) .append("}") .toString(); @@ -866,6 +894,7 @@ public class SignalStrength implements Parcelable { mWcdma = m.getParcelable("Wcdma"); mTdscdma = m.getParcelable("Tdscdma"); mLte = m.getParcelable("Lte"); + mNr = m.getParcelable("Nr"); } /** @@ -885,6 +914,7 @@ public class SignalStrength implements Parcelable { m.putParcelable("Wcdma", mWcdma); m.putParcelable("Tdscdma", mTdscdma); m.putParcelable("Lte", mLte); + m.putParcelable("Nr", mNr); } /** diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index 91e24a955013..2a73a5cdef54 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -187,15 +187,7 @@ public class SmsMessage { int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(); String format = (PHONE_TYPE_CDMA == activePhone) ? SmsConstants.FORMAT_3GPP2 : SmsConstants.FORMAT_3GPP; - message = createFromPdu(pdu, format); - - if (null == message || null == message.mWrappedSmsMessage) { - // decoding pdu failed based on activePhone type, must be other format - format = (PHONE_TYPE_CDMA == activePhone) ? - SmsConstants.FORMAT_3GPP : SmsConstants.FORMAT_3GPP2; - message = createFromPdu(pdu, format); - } - return message; + return createFromPdu(pdu, format); } /** @@ -211,11 +203,18 @@ public class SmsMessage { * {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} intent */ public static SmsMessage createFromPdu(byte[] pdu, String format) { - SmsMessageBase wrappedMessage; + return createFromPdu(pdu, format, true); + } + + private static SmsMessage createFromPdu(byte[] pdu, String format, + boolean fallbackToOtherFormat) { if (pdu == null) { Rlog.i(LOG_TAG, "createFromPdu(): pdu is null"); return null; } + SmsMessageBase wrappedMessage; + String otherFormat = SmsConstants.FORMAT_3GPP2.equals(format) ? SmsConstants.FORMAT_3GPP : + SmsConstants.FORMAT_3GPP2; if (SmsConstants.FORMAT_3GPP2.equals(format)) { wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromPdu(pdu); } else if (SmsConstants.FORMAT_3GPP.equals(format)) { @@ -228,8 +227,12 @@ public class SmsMessage { if (wrappedMessage != null) { return new SmsMessage(wrappedMessage); } else { - Rlog.e(LOG_TAG, "createFromPdu(): wrappedMessage is null"); - return null; + if (fallbackToOtherFormat) { + return createFromPdu(pdu, otherFormat, false); + } else { + Rlog.e(LOG_TAG, "createFromPdu(): wrappedMessage is null"); + return null; + } } } @@ -978,4 +981,13 @@ public class SmsMessage { return false; } + + /** + * {@hide} + * Returns the recipient address(receiver) of this SMS message in String form or null if + * unavailable. + */ + public String getRecipientAddress() { + return mWrappedSmsMessage.getRecipientAddress(); + } } diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 845d23ee4cc2..5b9e23228dcb 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -865,7 +865,8 @@ public class SubscriptionManager { } /** - * Callback invoked when there is any change to any SubscriptionInfo. Typically + * Callback invoked when there is any change to any SubscriptionInfo, as well as once on + * registering for changes with {@link #addOnSubscriptionsChangedListener}. Typically * this method would invoke {@link #getActiveSubscriptionInfoList} */ public void onSubscriptionsChanged() { @@ -917,7 +918,9 @@ public class SubscriptionManager { /** * Register for changes to the list of active {@link SubscriptionInfo} records or to the * individual records themselves. When a change occurs the onSubscriptionsChanged method of - * the listener will be invoked immediately if there has been a notification. + * the listener will be invoked immediately if there has been a notification. The + * onSubscriptionChanged method will also be triggered once initially when calling this + * function. * * @param listener an instance of {@link OnSubscriptionsChangedListener} with * onSubscriptionsChanged overridden. @@ -1860,7 +1863,7 @@ public class SubscriptionManager { iSub.setDefaultSmsSubId(subscriptionId); } } catch (RemoteException ex) { - // ignore it + ex.rethrowFromSystemServer(); } } @@ -2048,6 +2051,8 @@ public class SubscriptionManager { putPhoneIdAndSubIdExtra(intent, phoneId, subIds[0]); } else { logd("putPhoneIdAndSubIdExtra: no valid subs"); + intent.putExtra(PhoneConstants.PHONE_KEY, phoneId); + intent.putExtra(PhoneConstants.SLOT_KEY, phoneId); } } @@ -2860,6 +2865,95 @@ public class SubscriptionManager { } } + /** + * Enabled or disable a subscription. This is currently used in the settings page. + * + * <p> + * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required + * + * @param enable whether user is turning it on or off. + * @param subscriptionId Subscription to be enabled or disabled. + * It could be a eSIM or pSIM subscription. + * + * @return whether the operation is successful. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public boolean setSubscriptionEnabled(int subscriptionId, boolean enable) { + if (VDBG) { + logd("setSubscriptionActivated subId= " + subscriptionId + " enable " + enable); + } + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + return iSub.setSubscriptionEnabled(enable, subscriptionId); + } + } catch (RemoteException ex) { + // ignore it + } + + return false; + } + + /** + * Returns whether the subscription is enabled or not. This is different from activated + * or deactivated for two aspects. 1) For when user disables a physical subscription, we + * actually disable the modem because we can't switch off the subscription. 2) For eSIM, + * user may enable one subscription but the system may activate another temporarily. In this + * case, user enabled one is different from current active one. + + * @param subscriptionId The subscription it asks about. + * @return whether it's enabled or not. {@code true} if user set this subscription enabled + * earlier, or user never set subscription enable / disable on this slot explicitly, and + * this subscription is currently active. Otherwise, it returns {@code false}. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public boolean isSubscriptionEnabled(int subscriptionId) { + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + return iSub.isSubscriptionEnabled(subscriptionId); + } + } catch (RemoteException ex) { + // ignore it + } + + return false; + } + + /** + * Get which subscription is enabled on this slot. See {@link #isSubscriptionEnabled(int)} + * for more details. + * + * @param slotIndex which slot it asks about. + * @return which subscription is enabled on this slot. If there's no enabled subscription + * in this slot, it will return {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public int getEnabledSubscriptionId(int slotIndex) { + int subId = INVALID_SUBSCRIPTION_ID; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + subId = iSub.getEnabledSubscriptionId(slotIndex); + } + } catch (RemoteException ex) { + // ignore it + } + + if (VDBG) logd("getEnabledSubscriptionId, subId = " + subId); + return subId; + } + private interface CallISubMethodHelper { int callMethod(ISub iSub) throws RemoteException; } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index d8add3c2752e..1433b2ac9280 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -23,6 +23,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.IntDef; +import android.annotation.LongDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -71,6 +72,7 @@ import android.telephony.ims.feature.MmTelFeature; import android.telephony.ims.stub.ImsRegistrationImplBase; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.annotations.VisibleForTesting; @@ -227,10 +229,19 @@ public class TelephonyManager { public static final int SRVCC_STATE_HANDOVER_CANCELED = 3; /** - * An invalid UICC card identifier. See {@link #getCardIdForDefaultEuicc()} and - * {@link UiccCardInfo#getCardId()}. + * A UICC card identifier used if the device does not support the operation. + * For example, {@link #getCardIdForDefaultEuicc()} returns this value if the device has no + * eUICC, or the eUICC cannot be read. */ - public static final int INVALID_CARD_ID = -1; + public static final int UNSUPPORTED_CARD_ID = -1; + + /** + * A UICC card identifier used before the UICC card is loaded. See + * {@link #getCardIdForDefaultEuicc()} and {@link UiccCardInfo#getCardId()}. + * <p> + * Note that once the UICC card is loaded, the card ID may become {@link #UNSUPPORTED_CARD_ID}. + */ + public static final int UNINITIALIZED_CARD_ID = -2; /** @hide */ @Retention(RetentionPolicy.SOURCE) @@ -349,41 +360,30 @@ public class TelephonyManager { * Returns 0 if none of voice, sms, data is not supported * Returns 1 for Single standby mode (Single SIM functionality) * Returns 2 for Dual standby mode.(Dual SIM functionality) + * Returns 3 for Tri standby mode.(Tri SIM functionality) */ public int getPhoneCount() { - int phoneCount = 1; - switch (getMultiSimConfiguration()) { - case UNKNOWN: - // if voice or sms or data is supported, return 1 otherwise 0 - if (isVoiceCapable() || isSmsCapable()) { - phoneCount = 1; - } else { - // todo: try to clean this up further by getting rid of the nested conditions - if (mContext == null) { - phoneCount = 1; - } else { - // check for data support - ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService( - Context.CONNECTIVITY_SERVICE); - if (cm == null) { - phoneCount = 1; - } else { - if (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) { - phoneCount = 1; - } else { - phoneCount = 0; - } - } - } + int phoneCount = 0; + + // check for voice and data support, 0 if not supported + if (!isVoiceCapable() && !isSmsCapable()) { + ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService( + Context.CONNECTIVITY_SERVICE); + if (cm != null) { + if (!cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) { + return phoneCount; } - break; - case DSDS: - case DSDA: - phoneCount = PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM; - break; - case TSTS: - phoneCount = PhoneConstants.MAX_PHONE_COUNT_TRI_SIM; - break; + } + } + + phoneCount = 1; + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + phoneCount = telephony.getNumOfActiveSims(); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "getNumOfActiveSims RemoteException", ex); } return phoneCount; } @@ -1352,6 +1352,38 @@ public class TelephonyManager { @SystemApi public static final long MAX_NUMBER_VERIFICATION_TIMEOUT_MILLIS = 60000; + /** + * Intent sent when an error occurs that debug tools should log and possibly take further + * action such as capturing vendor-specific logs. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public static final String ACTION_DEBUG_EVENT = "android.telephony.action.DEBUG_EVENT"; + + /** + * An arbitrary ParcelUuid which should be consistent for each occurrence of the same event. + * + * This field must be included in all events. + * + * @hide + */ + @SystemApi + public static final String EXTRA_DEBUG_EVENT_ID = "android.telephony.extra.DEBUG_EVENT_ID"; + + /** + * A freeform string description of the event. + * + * This field is optional for all events and as a guideline should not exceed 80 characters + * and should be as short as possible to convey the essence of the event. + * + * @hide + */ + @SystemApi + public static final String EXTRA_DEBUG_EVENT_DESCRIPTION = + "android.telephony.extra.DEBUG_EVENT_DESCRIPTION"; + // // // Device Info @@ -1632,10 +1664,7 @@ public class TelephonyManager { * @deprecated use {@link #getAllCellInfo} instead, which returns a superset of this API. */ @Deprecated - @RequiresPermission(anyOf = { - android.Manifest.permission.ACCESS_COARSE_LOCATION, - android.Manifest.permission.ACCESS_FINE_LOCATION - }) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public CellLocation getCellLocation() { try { ITelephony telephony = getITelephony(); @@ -3110,24 +3139,25 @@ public class TelephonyManager { } /** - * Get the card ID of the default eUICC card. If there is no eUICC, returns - * {@link #INVALID_CARD_ID}. + * Get the card ID of the default eUICC card. If the eUICCs have not yet been loaded, returns + * {@link #UNINITIALIZED_CARD_ID}. If there is no eUICC or the device does not support card IDs + * for eUICCs, returns {@link #UNSUPPORTED_CARD_ID}. * * <p>The card ID is a unique identifier associated with a UICC or eUICC card. Card IDs are * unique to a device, and always refer to the same UICC or eUICC card unless the device goes * through a factory reset. * - * @return card ID of the default eUICC card. + * @return card ID of the default eUICC card, if loaded. */ public int getCardIdForDefaultEuicc() { try { ITelephony telephony = getITelephony(); if (telephony == null) { - return INVALID_CARD_ID; + return UNINITIALIZED_CARD_ID; } return telephony.getCardIdForDefaultEuicc(mSubId, mContext.getOpPackageName()); } catch (RemoteException e) { - return INVALID_CARD_ID; + return UNINITIALIZED_CARD_ID; } } @@ -3230,6 +3260,35 @@ public class TelephonyManager { } } + /** + * Get the mapping from logical slots to physical slots. The mapping represent by a pair list. + * The key of the piar is the logical slot id and the value of the pair is the physical + * slots id mapped to this logical slot id. + * + * @return an pair list indicates the mapping from logical slots to physical slots. The size of + * the list should be {@link #getPhoneCount()} if success, otherwise return an empty list. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @NonNull + public List<Pair<Integer, Integer>> getLogicalToPhysicalSlotMapping() { + List<Pair<Integer, Integer>> slotMapping = new ArrayList<>(); + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + int[] slotMappingArray = telephony.getSlotsMapping(); + for (int i = 0; i < slotMappingArray.length; i++) { + slotMapping.add(new Pair(i, slotMappingArray[i])); + } + } + } catch (RemoteException e) { + Log.e(TAG, "getSlotsMapping RemoteException", e); + } + return slotMapping; + } + // // // Subscriber Info @@ -4845,12 +4904,13 @@ public class TelephonyManager { * * <p>Apps targeting {@link android.os.Build.VERSION_CODES#Q Android Q} or higher will no * longer trigger a refresh of the cached CellInfo by invoking this API. Instead, those apps - * will receive the latest cached results. Apps targeting + * will receive the latest cached results, which may not be current. Apps targeting * {@link android.os.Build.VERSION_CODES#Q Android Q} or higher that wish to request updated * CellInfo should call - * {android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()} and - * listen for responses via {@link android.telephony.PhoneStateListener#onCellInfoChanged - * onCellInfoChanged()}. + * {@link android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()}; + * however, in all cases, updates will be rate-limited and are not guaranteed. To determine the + * recency of CellInfo data, callers should check + * {@link android.telephony.CellInfo#getTimeStamp CellInfo#getTimeStamp()}. * * <p>This method returns valid data for devices with * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY FEATURE_TELEPHONY}. In cases @@ -4865,7 +4925,7 @@ public class TelephonyManager { * @return List of {@link android.telephony.CellInfo}; null if cell * information is unavailable. */ - @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public List<CellInfo> getAllCellInfo() { try { ITelephony telephony = getITelephony(); @@ -4942,7 +5002,7 @@ public class TelephonyManager { * @param executor the executor on which callback will be invoked. * @param callback a callback to receive CellInfo. */ - @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate( @NonNull @CallbackExecutor Executor executor, @NonNull CellInfoCallback callback) { try { @@ -4981,7 +5041,7 @@ public class TelephonyManager { * @hide */ @SystemApi - @RequiresPermission(allOf = {android.Manifest.permission.ACCESS_COARSE_LOCATION, + @RequiresPermission(allOf = {android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull WorkSource workSource, @NonNull @CallbackExecutor Executor executor, @NonNull CellInfoCallback callback) { @@ -6247,197 +6307,258 @@ public class TelephonyManager { NETWORK_MODE_LTE_TDSCDMA_WCDMA, NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA, NETWORK_MODE_TDSCDMA_CDMA_EVDO_GSM_WCDMA, - NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA + NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA, + NETWORK_MODE_NR_ONLY, + NETWORK_MODE_NR_LTE, + NETWORK_MODE_NR_LTE_CDMA_EVDO, + NETWORK_MODE_NR_LTE_GSM_WCDMA, + NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA, + NETWORK_MODE_NR_LTE_WCDMA, + NETWORK_MODE_NR_LTE_TDSCDMA, + NETWORK_MODE_NR_LTE_TDSCDMA_GSM, + NETWORK_MODE_NR_LTE_TDSCDMA_WCDMA, + NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA, + NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA }) @Retention(RetentionPolicy.SOURCE) public @interface PrefNetworkMode{} /** - * network mode is GSM/WCDMA (WCDMA preferred). + * Preferred network mode is GSM/WCDMA (WCDMA preferred). * @hide */ - @SystemApi public static final int NETWORK_MODE_WCDMA_PREF = RILConstants.NETWORK_MODE_WCDMA_PREF; /** - * network mode is GSM only. + * Preferred network mode is GSM only. * @hide */ - @SystemApi public static final int NETWORK_MODE_GSM_ONLY = RILConstants.NETWORK_MODE_GSM_ONLY; /** - * network mode is WCDMA only. + * Preferred network mode is WCDMA only. * @hide */ - @SystemApi public static final int NETWORK_MODE_WCDMA_ONLY = RILConstants.NETWORK_MODE_WCDMA_ONLY; /** - * network mode is GSM/WCDMA (auto mode, according to PRL). + * Preferred network mode is GSM/WCDMA (auto mode, according to PRL). * @hide */ - @SystemApi public static final int NETWORK_MODE_GSM_UMTS = RILConstants.NETWORK_MODE_GSM_UMTS; /** - * network mode is CDMA and EvDo (auto mode, according to PRL). + * Preferred network mode is CDMA and EvDo (auto mode, according to PRL). * @hide */ - @SystemApi public static final int NETWORK_MODE_CDMA_EVDO = RILConstants.NETWORK_MODE_CDMA; /** - * network mode is CDMA only. + * Preferred network mode is CDMA only. * @hide */ - @SystemApi public static final int NETWORK_MODE_CDMA_NO_EVDO = RILConstants.NETWORK_MODE_CDMA_NO_EVDO; /** - * network mode is EvDo only. + * Preferred network mode is EvDo only. * @hide */ - @SystemApi public static final int NETWORK_MODE_EVDO_NO_CDMA = RILConstants.NETWORK_MODE_EVDO_NO_CDMA; /** - * network mode is GSM/WCDMA, CDMA, and EvDo (auto mode, according to PRL). + * Preferred network mode is GSM/WCDMA, CDMA, and EvDo (auto mode, according to PRL). * @hide */ - @SystemApi public static final int NETWORK_MODE_GLOBAL = RILConstants.NETWORK_MODE_GLOBAL; /** - * network mode is LTE, CDMA and EvDo. + * Preferred network mode is LTE, CDMA and EvDo. * @hide */ - @SystemApi public static final int NETWORK_MODE_LTE_CDMA_EVDO = RILConstants.NETWORK_MODE_LTE_CDMA_EVDO; /** - * preferred network mode is LTE, GSM/WCDMA. + * Preferred network mode is LTE, GSM/WCDMA. * @hide */ - @SystemApi public static final int NETWORK_MODE_LTE_GSM_WCDMA = RILConstants.NETWORK_MODE_LTE_GSM_WCDMA; /** - * network mode is LTE, CDMA, EvDo, GSM/WCDMA. + * Preferred network mode is LTE, CDMA, EvDo, GSM/WCDMA. * @hide */ - @SystemApi public static final int NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA = RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA; /** - * network mode is LTE Only. + * Preferred network mode is LTE Only. * @hide */ - @SystemApi public static final int NETWORK_MODE_LTE_ONLY = RILConstants.NETWORK_MODE_LTE_ONLY; /** - * network mode is LTE/WCDMA. + * Preferred network mode is LTE/WCDMA. * @hide */ - @SystemApi public static final int NETWORK_MODE_LTE_WCDMA = RILConstants.NETWORK_MODE_LTE_WCDMA; /** - * network mode is TD-SCDMA only. + * Preferred network mode is TD-SCDMA only. * @hide */ - @SystemApi public static final int NETWORK_MODE_TDSCDMA_ONLY = RILConstants.NETWORK_MODE_TDSCDMA_ONLY; /** - * network mode is TD-SCDMA and WCDMA. + * Preferred network mode is TD-SCDMA and WCDMA. * @hide */ - @SystemApi public static final int NETWORK_MODE_TDSCDMA_WCDMA = RILConstants.NETWORK_MODE_TDSCDMA_WCDMA; /** - * network mode is TD-SCDMA and LTE. + * Preferred network mode is TD-SCDMA and LTE. * @hide */ - @SystemApi public static final int NETWORK_MODE_LTE_TDSCDMA = RILConstants.NETWORK_MODE_LTE_TDSCDMA; /** - * network mode is TD-SCDMA and GSM. + * Preferred network mode is TD-SCDMA and GSM. * @hide */ - @SystemApi public static final int NETWORK_MODE_TDSCDMA_GSM = RILConstants.NETWORK_MODE_TDSCDMA_GSM; /** - * network mode is TD-SCDMA,GSM and LTE. + * Preferred network mode is TD-SCDMA,GSM and LTE. * @hide */ - @SystemApi public static final int NETWORK_MODE_LTE_TDSCDMA_GSM = RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM; /** - * network mode is TD-SCDMA, GSM/WCDMA. + * Preferred network mode is TD-SCDMA, GSM/WCDMA. * @hide */ - @SystemApi public static final int NETWORK_MODE_TDSCDMA_GSM_WCDMA = RILConstants.NETWORK_MODE_TDSCDMA_GSM_WCDMA; /** - * network mode is TD-SCDMA, WCDMA and LTE. + * Preferred network mode is TD-SCDMA, WCDMA and LTE. * @hide */ - @SystemApi public static final int NETWORK_MODE_LTE_TDSCDMA_WCDMA = RILConstants.NETWORK_MODE_LTE_TDSCDMA_WCDMA; /** - * network mode is TD-SCDMA, GSM/WCDMA and LTE. + * Preferred network mode is TD-SCDMA, GSM/WCDMA and LTE. * @hide */ - @SystemApi public static final int NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA = RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA; /** - * network mode is TD-SCDMA,EvDo,CDMA,GSM/WCDMA. + * Preferred network mode is TD-SCDMA,EvDo,CDMA,GSM/WCDMA. * @hide */ - @SystemApi public static final int NETWORK_MODE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = RILConstants.NETWORK_MODE_TDSCDMA_CDMA_EVDO_GSM_WCDMA; - /** - * network mode is TD-SCDMA/LTE/GSM/WCDMA, CDMA, and EvDo. + * Preferred network mode is TD-SCDMA/LTE/GSM/WCDMA, CDMA, and EvDo. * @hide */ - @SystemApi public static final int NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = RILConstants.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA; /** + * Preferred network mode is NR 5G only. + * @hide + */ + public static final int NETWORK_MODE_NR_ONLY = RILConstants.NETWORK_MODE_NR_ONLY; + + /** + * Preferred network mode is NR 5G, LTE. + * @hide + */ + public static final int NETWORK_MODE_NR_LTE = RILConstants.NETWORK_MODE_NR_LTE; + + /** + * Preferred network mode is NR 5G, LTE, CDMA and EvDo. + * @hide + */ + public static final int NETWORK_MODE_NR_LTE_CDMA_EVDO = + RILConstants.NETWORK_MODE_NR_LTE_CDMA_EVDO; + + /** + * Preferred network mode is NR 5G, LTE, GSM and WCDMA. + * @hide + */ + public static final int NETWORK_MODE_NR_LTE_GSM_WCDMA = + RILConstants.NETWORK_MODE_NR_LTE_GSM_WCDMA; + + /** + * Preferred network mode is NR 5G, LTE, CDMA, EvDo, GSM and WCDMA. + * @hide + */ + public static final int NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA = + RILConstants.NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA; + + /** + * Preferred network mode is NR 5G, LTE and WCDMA. + * @hide + */ + public static final int NETWORK_MODE_NR_LTE_WCDMA = RILConstants.NETWORK_MODE_NR_LTE_WCDMA; + + /** + * Preferred network mode is NR 5G, LTE and TDSCDMA. + * @hide + */ + public static final int NETWORK_MODE_NR_LTE_TDSCDMA = RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA; + + /** + * Preferred network mode is NR 5G, LTE, TD-SCDMA and GSM. + * @hide + */ + public static final int NETWORK_MODE_NR_LTE_TDSCDMA_GSM = + RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_GSM; + + /** + * Preferred network mode is NR 5G, LTE, TD-SCDMA, WCDMA. + * @hide + */ + public static final int NETWORK_MODE_NR_LTE_TDSCDMA_WCDMA = + RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_WCDMA; + + /** + * Preferred network mode is NR 5G, LTE, TD-SCDMA, GSM and WCDMA. + * @hide + */ + public static final int NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA = + RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA; + + /** + * Preferred network mode is NR 5G, LTE, TD-SCDMA, CDMA, EVDO, GSM and WCDMA. + * @hide + */ + public static final int NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = + RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA; + + /** * Get the preferred network type. * Used for device configuration by some CDMA operators. * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @return the preferred network type. * @hide */ - @RequiresPermission((android.Manifest.permission.MODIFY_PHONE_STATE)) - @SystemApi + @RequiresPermission((android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)) + @UnsupportedAppUsage public @PrefNetworkMode int getPreferredNetworkType(int subId) { try { ITelephony telephony = getITelephony(); - if (telephony != null) + if (telephony != null) { return telephony.getPreferredNetworkType(subId); + } } catch (RemoteException ex) { Rlog.e(TAG, "getPreferredNetworkType RemoteException", ex); } catch (NullPointerException ex) { @@ -6447,6 +6568,37 @@ public class TelephonyManager { } /** + * Get the preferred network type bitmap. + * + * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the + * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()} + * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * + * @return The bitmap of preferred network types. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @SystemApi + public @NetworkTypeBitMask long getPreferredNetworkTypeBitmap() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return (long) RadioAccessFamily.getRafFromNetworkType( + telephony.getPreferredNetworkType(getSubId())); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "getPreferredNetworkTypeBitmap RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "getPreferredNetworkTypeBitmap NPE", ex); + } + return 0; + } + + /** * Sets the network selection mode to automatic. * * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the @@ -6479,9 +6631,10 @@ public class TelephonyManager { * * <p> Note that this scan can take a long time (sometimes minutes) to happen. * - * <p>Requires Permission: + * <p>Requires Permissions: * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or that the calling app has carrier * privileges (see {@link #hasCarrierPrivileges}) + * and {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}. * * @return {@link CellNetworkScanResult} with the status * {@link CellNetworkScanResult#STATUS_SUCCESS} and a list of @@ -6490,12 +6643,15 @@ public class TelephonyManager { * * @hide */ - @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + @RequiresPermission(allOf = { + android.Manifest.permission.MODIFY_PHONE_STATE, + Manifest.permission.ACCESS_COARSE_LOCATION + }) public CellNetworkScanResult getAvailableNetworks() { try { ITelephony telephony = getITelephony(); if (telephony != null) { - return telephony.getCellNetworkScanResults(getSubId()); + return telephony.getCellNetworkScanResults(getSubId(), getOpPackageName()); } } catch (RemoteException ex) { Rlog.e(TAG, "getAvailableNetworks RemoteException", ex); @@ -6514,7 +6670,8 @@ public class TelephonyManager { * * <p>Requires Permission: * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling - * app has carrier privileges (see {@link #hasCarrierPrivileges}). + * app has carrier privileges (see {@link #hasCarrierPrivileges}) + * and {@link android.Manifest.permission#ACCESS_FINE_LOCATION}. * * @param request Contains all the RAT with bands/channels that need to be scanned. * @param executor The executor through which the callback should be invoked. Since the scan @@ -6525,7 +6682,10 @@ public class TelephonyManager { * @return A NetworkScan obj which contains a callback which can be used to stop the scan. */ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges - @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + @RequiresPermission(allOf = { + android.Manifest.permission.MODIFY_PHONE_STATE, + Manifest.permission.ACCESS_FINE_LOCATION + }) public NetworkScan requestNetworkScan( NetworkScanRequest request, Executor executor, TelephonyScanManager.NetworkScanCallback callback) { @@ -6534,7 +6694,8 @@ public class TelephonyManager { mTelephonyScanManager = new TelephonyScanManager(); } } - return mTelephonyScanManager.requestNetworkScan(getSubId(), request, executor, callback); + return mTelephonyScanManager.requestNetworkScan(getSubId(), request, executor, callback, + getOpPackageName()); } /** @@ -6544,7 +6705,10 @@ public class TelephonyManager { * @removed */ @Deprecated - @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + @RequiresPermission(allOf = { + android.Manifest.permission.MODIFY_PHONE_STATE, + Manifest.permission.ACCESS_FINE_LOCATION + }) public NetworkScan requestNetworkScan( NetworkScanRequest request, TelephonyScanManager.NetworkScanCallback callback) { return requestNetworkScan(request, AsyncTask.SERIAL_EXECUTOR, callback); @@ -6661,6 +6825,38 @@ public class TelephonyManager { } /** + * Set the preferred network type bitmap. + * + * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the + * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()} + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). + * + * @param networkTypeBitmap The bitmap of preferred network types. + * @return true on success; false on any failure. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + @SystemApi + public boolean setPreferredNetworkTypeBitmap(@NetworkTypeBitMask long networkTypeBitmap) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.setPreferredNetworkType( + getSubId(), RadioAccessFamily.getNetworkTypeFromRaf( + (int) networkTypeBitmap)); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "setPreferredNetworkType RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "setPreferredNetworkType NPE", ex); + } + return false; + } + + /** * Set the preferred network type to global mode which includes LTE, CDMA, EvDo and GSM/WCDMA. * * <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). @@ -7808,9 +8004,7 @@ public class TelephonyManager { * support for the feature and device firmware support. * * @return {@code true} if the device and carrier both support RTT, {@code false} otherwise. - * @hide */ - @TestApi public boolean isRttSupported() { try { ITelephony telephony = getITelephony(); @@ -8514,10 +8708,14 @@ public class TelephonyManager { * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()} * * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} - * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}) + * and {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}. */ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + @RequiresPermission(allOf = { + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_COARSE_LOCATION + }) public ServiceState getServiceState() { return getServiceStateForSubscriber(getSubId()); } @@ -9519,10 +9717,10 @@ public class TelephonyManager { * * <p> * Requires Permission: - * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} * @hide */ - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled() { String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>"; boolean isEnabled = false; @@ -9541,7 +9739,7 @@ public class TelephonyManager { /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, prefix = {"NETWORK_TYPE_BITMASK_"}, + @LongDef(flag = true, prefix = {"NETWORK_TYPE_BITMASK_"}, value = {NETWORK_TYPE_BITMASK_UNKNOWN, NETWORK_TYPE_BITMASK_GSM, NETWORK_TYPE_BITMASK_GPRS, @@ -9570,118 +9768,125 @@ public class TelephonyManager { * @hide */ @SystemApi - public static final int NETWORK_TYPE_BITMASK_UNKNOWN = (1 << NETWORK_TYPE_UNKNOWN); + public static final long NETWORK_TYPE_BITMASK_UNKNOWN = 0L; /** * network type bitmask indicating the support of radio tech GSM. * @hide */ @SystemApi - public static final int NETWORK_TYPE_BITMASK_GSM = (1 << NETWORK_TYPE_GSM); + public static final long NETWORK_TYPE_BITMASK_GSM = (1 << (NETWORK_TYPE_GSM -1)); /** * network type bitmask indicating the support of radio tech GPRS. * @hide */ @SystemApi - public static final int NETWORK_TYPE_BITMASK_GPRS = (1 << NETWORK_TYPE_GPRS); + public static final long NETWORK_TYPE_BITMASK_GPRS = (1 << (NETWORK_TYPE_GPRS -1)); /** * network type bitmask indicating the support of radio tech EDGE. * @hide */ @SystemApi - public static final int NETWORK_TYPE_BITMASK_EDGE = (1 << NETWORK_TYPE_EDGE); + public static final long NETWORK_TYPE_BITMASK_EDGE = (1 << (NETWORK_TYPE_EDGE -1)); /** * network type bitmask indicating the support of radio tech CDMA(IS95A/IS95B). * @hide */ @SystemApi - public static final int NETWORK_TYPE_BITMASK_CDMA = (1 << NETWORK_TYPE_CDMA); + public static final long NETWORK_TYPE_BITMASK_CDMA = (1 << (NETWORK_TYPE_CDMA -1)); /** * network type bitmask indicating the support of radio tech 1xRTT. * @hide */ @SystemApi - public static final int NETWORK_TYPE_BITMASK_1xRTT = (1 << NETWORK_TYPE_1xRTT); + public static final long NETWORK_TYPE_BITMASK_1xRTT = (1 << (NETWORK_TYPE_1xRTT - 1)); // 3G /** * network type bitmask indicating the support of radio tech EVDO 0. * @hide */ @SystemApi - public static final int NETWORK_TYPE_BITMASK_EVDO_0 = (1 << NETWORK_TYPE_EVDO_0); + public static final long NETWORK_TYPE_BITMASK_EVDO_0 = (1 << (NETWORK_TYPE_EVDO_0 -1)); /** * network type bitmask indicating the support of radio tech EVDO A. * @hide */ @SystemApi - public static final int NETWORK_TYPE_BITMASK_EVDO_A = (1 << NETWORK_TYPE_EVDO_A); + public static final long NETWORK_TYPE_BITMASK_EVDO_A = (1 << (NETWORK_TYPE_EVDO_A - 1)); /** * network type bitmask indicating the support of radio tech EVDO B. * @hide */ @SystemApi - public static final int NETWORK_TYPE_BITMASK_EVDO_B = (1 << NETWORK_TYPE_EVDO_B); + public static final long NETWORK_TYPE_BITMASK_EVDO_B = (1 << (NETWORK_TYPE_EVDO_B -1)); /** * network type bitmask indicating the support of radio tech EHRPD. * @hide */ @SystemApi - public static final int NETWORK_TYPE_BITMASK_EHRPD = (1 << NETWORK_TYPE_EHRPD); + public static final long NETWORK_TYPE_BITMASK_EHRPD = (1 << (NETWORK_TYPE_EHRPD -1)); /** * network type bitmask indicating the support of radio tech HSUPA. * @hide */ @SystemApi - public static final int NETWORK_TYPE_BITMASK_HSUPA = (1 << NETWORK_TYPE_HSUPA); + public static final long NETWORK_TYPE_BITMASK_HSUPA = (1 << (NETWORK_TYPE_HSUPA -1)); /** * network type bitmask indicating the support of radio tech HSDPA. * @hide */ @SystemApi - public static final int NETWORK_TYPE_BITMASK_HSDPA = (1 << NETWORK_TYPE_HSDPA); + public static final long NETWORK_TYPE_BITMASK_HSDPA = (1 << (NETWORK_TYPE_HSDPA -1)); /** * network type bitmask indicating the support of radio tech HSPA. * @hide */ @SystemApi - public static final int NETWORK_TYPE_BITMASK_HSPA = (1 << NETWORK_TYPE_HSPA); + public static final long NETWORK_TYPE_BITMASK_HSPA = (1 << (NETWORK_TYPE_HSPA -1)); /** * network type bitmask indicating the support of radio tech HSPAP. * @hide */ @SystemApi - public static final int NETWORK_TYPE_BITMASK_HSPAP = (1 << NETWORK_TYPE_HSPAP); + public static final long NETWORK_TYPE_BITMASK_HSPAP = (1 << (NETWORK_TYPE_HSPAP -1)); /** * network type bitmask indicating the support of radio tech UMTS. * @hide */ @SystemApi - public static final int NETWORK_TYPE_BITMASK_UMTS = (1 << NETWORK_TYPE_UMTS); + public static final long NETWORK_TYPE_BITMASK_UMTS = (1 << (NETWORK_TYPE_UMTS -1)); /** * network type bitmask indicating the support of radio tech TD_SCDMA. * @hide */ @SystemApi - public static final int NETWORK_TYPE_BITMASK_TD_SCDMA = (1 << NETWORK_TYPE_TD_SCDMA); + public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = (1 << (NETWORK_TYPE_TD_SCDMA -1)); // 4G /** * network type bitmask indicating the support of radio tech LTE. * @hide */ @SystemApi - public static final int NETWORK_TYPE_BITMASK_LTE = (1 << NETWORK_TYPE_LTE); + public static final long NETWORK_TYPE_BITMASK_LTE = (1 << (NETWORK_TYPE_LTE -1)); /** * network type bitmask indicating the support of radio tech LTE CA (carrier aggregation). * @hide */ @SystemApi - public static final int NETWORK_TYPE_BITMASK_LTE_CA = (1 << NETWORK_TYPE_LTE_CA); + public static final long NETWORK_TYPE_BITMASK_LTE_CA = (1 << (NETWORK_TYPE_LTE_CA -1)); /** * network type bitmask indicating the support of radio tech NR(New Radio) 5G. * @hide */ @SystemApi - public static final int NETWORK_TYPE_BITMASK_NR = (1 << NETWORK_TYPE_NR); + public static final long NETWORK_TYPE_BITMASK_NR = (1 << (NETWORK_TYPE_NR -1)); + + /** + * network type bitmask indicating the support of radio tech IWLAN. + * @hide + */ + @SystemApi + public static final long NETWORK_TYPE_BITMASK_IWLAN = (1 << (NETWORK_TYPE_IWLAN -1)); /** * @return Modem supported radio access family bitmask @@ -9692,11 +9897,11 @@ public class TelephonyManager { */ @SystemApi @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public @NetworkTypeBitMask int getSupportedRadioAccessFamily() { + public @NetworkTypeBitMask long getSupportedRadioAccessFamily() { try { ITelephony telephony = getITelephony(); if (telephony != null) { - return telephony.getRadioAccessFamily(getSlotIndex(), getOpPackageName()); + return (long) telephony.getRadioAccessFamily(getSlotIndex(), getOpPackageName()); } else { // This can happen when the ITelephony interface is not up yet. return NETWORK_TYPE_BITMASK_UNKNOWN; @@ -9903,12 +10108,17 @@ public class TelephonyManager { * Get preferred opportunistic data subscription Id * * <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}), - * or has permission {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}. + * or has either READ_PRIVILEGED_PHONE_STATE + * or {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} permission. * @return subId preferred opportunistic subscription id or * {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID} if there are no preferred * subscription id * */ + @RequiresPermission(anyOf = { + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + android.Manifest.permission.READ_PHONE_STATE + }) public int getPreferredOpportunisticDataSubscription() { String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>"; int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; @@ -10051,4 +10261,53 @@ public class TelephonyManager { */ public static final String EXTRA_NETWORK_COUNTRY = "android.telephony.extra.NETWORK_COUNTRY"; + /** + * Switch configs to enable multi-sim or switch back to single-sim + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the + * calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * @param numOfSims number of live SIMs we want to switch to + * @throws android.os.RemoteException + */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void switchMultiSimConfig(int numOfSims) { + //only proceed if multi-sim is not restricted + if (isMultisimCarrierRestricted()) { + Rlog.e(TAG, "switchMultiSimConfig not possible. It is restricted."); + return; + } + + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + telephony.switchMultiSimConfig(numOfSims); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "switchMultiSimConfig RemoteException", ex); + } + } + + /** + * Get whether reboot is required or not after making changes to modem configurations. + * The modem configuration change refers to switching from single SIM configuration to DSDS + * or the other way around. + * @Return {@code true} if reboot is required after making changes to modem configurations, + * otherwise return {@code false}. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public boolean isRebootRequiredForModemConfigChange() { + try { + ITelephony service = getITelephony(); + if (service != null) { + return service.isRebootRequiredForModemConfigChange(); + } + } catch (RemoteException e) { + Log.e(TAG, "isRebootRequiredForModemConfigChange RemoteException", e); + } + return false; + } } diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java index 96ff33255b53..91f74b867fa0 100644 --- a/telephony/java/android/telephony/TelephonyScanManager.java +++ b/telephony/java/android/telephony/TelephonyScanManager.java @@ -29,14 +29,14 @@ import android.os.Messenger; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; -import android.util.Log; import android.util.SparseArray; + +import com.android.internal.telephony.ITelephony; + import java.util.Arrays; import java.util.List; import java.util.concurrent.Executor; -import com.android.internal.telephony.ITelephony; - /** * Manages the radio access network scan requests and callbacks. */ @@ -183,6 +183,7 @@ public final class TelephonyScanManager { * * <p> * Requires Permission: + * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} and * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} * Or the calling app has carrier privileges. @see #hasCarrierPrivileges * @@ -192,11 +193,13 @@ public final class TelephonyScanManager { * @hide */ public NetworkScan requestNetworkScan(int subId, - NetworkScanRequest request, Executor executor, NetworkScanCallback callback) { + NetworkScanRequest request, Executor executor, NetworkScanCallback callback, + String callingPackage) { try { ITelephony telephony = getITelephony(); if (telephony != null) { - int scanId = telephony.requestNetworkScan(subId, request, mMessenger, new Binder()); + int scanId = telephony.requestNetworkScan( + subId, request, mMessenger, new Binder(), callingPackage); saveScanInfo(scanId, request, executor, callback); return new NetworkScan(scanId, subId); } diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index a5c0442948ac..828e3e93b7a4 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -253,7 +253,7 @@ public class EuiccManager { public static final int EUICC_OTA_STATUS_UNAVAILABLE = 5; private final Context mContext; - private final int mCardId; + private int mCardId; /** @hide */ public EuiccManager(Context context) { @@ -291,7 +291,7 @@ public class EuiccManager { public boolean isEnabled() { // In the future, this may reach out to IEuiccController (if non-null) to check any dynamic // restrictions. - return getIEuiccController() != null && mCardId != TelephonyManager.INVALID_CARD_ID; + return getIEuiccController() != null; } /** @@ -301,15 +301,15 @@ public class EuiccManager { * current eUICC. A calling app with carrier privileges for one eUICC may not necessarily have * access to the EID of another eUICC. * - * @return the EID. May be null if {@link #isEnabled()} is false or the eUICC is not ready. + * @return the EID. May be null if the eUICC is not ready. */ @Nullable public String getEid() { - if (!isEnabled()) { + if (!refreshCardIdIfUninitialized()) { return null; } try { - return getIEuiccController().getEid(mCardId); + return getIEuiccController().getEid(mCardId, mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -320,15 +320,15 @@ public class EuiccManager { * * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. * - * @return the status of eUICC OTA. If {@link #isEnabled()} is false or the eUICC is not ready, - * {@link OtaStatus#EUICC_OTA_STATUS_UNAVAILABLE} will be returned. + * @return the status of eUICC OTA. If the eUICC is not ready, + * {@link OtaStatus#EUICC_OTA_STATUS_UNAVAILABLE} will be returned. * * @hide */ @SystemApi @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus() { - if (!isEnabled()) { + if (!refreshCardIdIfUninitialized()) { return EUICC_OTA_STATUS_UNAVAILABLE; } try { @@ -347,6 +347,15 @@ public class EuiccManager { * Without the former, an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be * returned in the callback intent to prompt the user to accept the download. * + * <p>On a multi-active SIM device, requires the + * {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, or a calling app + * only if the targeted eUICC does not currently have an active subscription or the calling app + * is authorized to manage the active subscription on the target eUICC, and the calling app is + * authorized to manage any active subscription on any SIM. Without it, an + * {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be returned in the callback + * intent to prompt the user to accept the download. The caller should also be authorized to + * manage the subscription to be downloaded. + * * @param subscription the subscription to download. * @param switchAfterDownload if true, the profile will be activated upon successful download. * @param callbackIntent a PendingIntent to launch when the operation completes. @@ -354,7 +363,7 @@ public class EuiccManager { @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void downloadSubscription(DownloadableSubscription subscription, boolean switchAfterDownload, PendingIntent callbackIntent) { - if (!isEnabled()) { + if (!refreshCardIdIfUninitialized()) { sendUnavailableError(callbackIntent); return; } @@ -416,7 +425,7 @@ public class EuiccManager { @SystemApi @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void continueOperation(Intent resolutionIntent, Bundle resolutionExtras) { - if (!isEnabled()) { + if (!refreshCardIdIfUninitialized()) { PendingIntent callbackIntent = resolutionIntent.getParcelableExtra( EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT); @@ -453,7 +462,7 @@ public class EuiccManager { @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDownloadableSubscriptionMetadata( DownloadableSubscription subscription, PendingIntent callbackIntent) { - if (!isEnabled()) { + if (!refreshCardIdIfUninitialized()) { sendUnavailableError(callbackIntent); return; } @@ -483,7 +492,7 @@ public class EuiccManager { @SystemApi @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDefaultDownloadableSubscriptionList(PendingIntent callbackIntent) { - if (!isEnabled()) { + if (!refreshCardIdIfUninitialized()) { sendUnavailableError(callbackIntent); return; } @@ -498,12 +507,11 @@ public class EuiccManager { /** * Returns information about the eUICC chip/device. * - * @return the {@link EuiccInfo}. May be null if {@link #isEnabled()} is false or the eUICC is - * not ready. + * @return the {@link EuiccInfo}. May be null if the eUICC is not ready. */ @Nullable public EuiccInfo getEuiccInfo() { - if (!isEnabled()) { + if (!refreshCardIdIfUninitialized()) { return null; } try { @@ -528,7 +536,7 @@ public class EuiccManager { */ @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void deleteSubscription(int subscriptionId, PendingIntent callbackIntent) { - if (!isEnabled()) { + if (!refreshCardIdIfUninitialized()) { sendUnavailableError(callbackIntent); return; } @@ -549,14 +557,26 @@ public class EuiccManager { * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be returned in the callback * intent to prompt the user to accept the download. * + * <p>On a multi-active SIM device, requires the + * {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, or a calling app + * only if the targeted eUICC does not currently have an active subscription or the calling app + * is authorized to manage the active subscription on the target eUICC, and the calling app is + * authorized to manage any active subscription on any SIM. Without it, an + * {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be returned in the callback + * intent to prompt the user to accept the download. The caller should also be authorized to + * manage the subscription to be enabled. + * * @param subscriptionId the ID of the subscription to enable. May be * {@link android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID} to deactivate the - * current profile without activating another profile to replace it. + * current profile without activating another profile to replace it. If it's a disable + * operation, requires the {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} + * permission, or the calling app must be authorized to manage the active subscription on + * the target eUICC. * @param callbackIntent a PendingIntent to launch when the operation completes. */ @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void switchToSubscription(int subscriptionId, PendingIntent callbackIntent) { - if (!isEnabled()) { + if (!refreshCardIdIfUninitialized()) { sendUnavailableError(callbackIntent); return; } @@ -582,7 +602,7 @@ public class EuiccManager { @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void updateSubscriptionNickname( int subscriptionId, String nickname, PendingIntent callbackIntent) { - if (!isEnabled()) { + if (!refreshCardIdIfUninitialized()) { sendUnavailableError(callbackIntent); return; } @@ -606,7 +626,7 @@ public class EuiccManager { @SystemApi @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void eraseSubscriptions(PendingIntent callbackIntent) { - if (!isEnabled()) { + if (!refreshCardIdIfUninitialized()) { sendUnavailableError(callbackIntent); return; } @@ -636,7 +656,7 @@ public class EuiccManager { * @hide */ public void retainSubscriptionsForFactoryReset(PendingIntent callbackIntent) { - if (!isEnabled()) { + if (!refreshCardIdIfUninitialized()) { sendUnavailableError(callbackIntent); return; } @@ -647,6 +667,27 @@ public class EuiccManager { } } + /** + * Refreshes the cardId if its uninitialized, and returns whether we should continue the + * operation. + * <p> + * Note that after a successful refresh, the mCardId may be TelephonyManager.UNSUPPORTED_CARD_ID + * on older HALs. For backwards compatability, we continue to the LPA and let it decide which + * card to use. + */ + private boolean refreshCardIdIfUninitialized() { + // Refresh mCardId if its UNINITIALIZED_CARD_ID + if (mCardId == TelephonyManager.UNINITIALIZED_CARD_ID) { + TelephonyManager tm = (TelephonyManager) + mContext.getSystemService(Context.TELEPHONY_SERVICE); + mCardId = tm.getCardIdForDefaultEuicc(); + } + if (mCardId == TelephonyManager.UNINITIALIZED_CARD_ID) { + return false; + } + return true; + } + private static void sendUnavailableError(PendingIntent callbackIntent) { try { callbackIntent.send(EMBEDDED_SUBSCRIPTION_RESULT_ERROR); diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java index 59167b7d5bba..73f055649b3f 100644 --- a/telephony/java/android/telephony/ims/ImsCallProfile.java +++ b/telephony/java/android/telephony/ims/ImsCallProfile.java @@ -350,6 +350,9 @@ public final class ImsCallProfile implements Parcelable { /** Indicates if the call is for testing purpose */ private boolean mEmergencyCallTesting = false; + /** Indicates if we have known the intent of the user for the call is emergency */ + private boolean mHasKnownUserIntentEmergency = false; + /** * Extras associated with this {@link ImsCallProfile}. * <p> @@ -789,12 +792,13 @@ public final class ImsCallProfile implements Parcelable { * * @hide */ - public void setEmergencyCallInfo(EmergencyNumber num) { + public void setEmergencyCallInfo(EmergencyNumber num, boolean hasKnownUserIntentEmergency) { setEmergencyServiceCategories(num.getEmergencyServiceCategoryBitmaskInternalDial()); setEmergencyUrns(num.getEmergencyUrns()); setEmergencyCallRouting(num.getEmergencyCallRouting()); setEmergencyCallTesting(num.getEmergencyNumberSourceBitmask() == EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST); + setHasKnownUserIntentEmergency(hasKnownUserIntentEmergency); } /** @@ -860,6 +864,19 @@ public final class ImsCallProfile implements Parcelable { } /** + * Set if we have known the user intent of the call is emergency. + * + * This is only used to specify when the dialed number is ambiguous when it can be identified + * as both emergency number and any other non-emergency number; e.g. in some situation, 611 + * could be both an emergency number in a country and a non-emergency number of a carrier's + * customer service hotline. + */ + @VisibleForTesting + public void setHasKnownUserIntentEmergency(boolean hasKnownUserIntentEmergency) { + mHasKnownUserIntentEmergency = hasKnownUserIntentEmergency; + } + + /** * Get the emergency service categories, only valid if {@link #getServiceType} returns * {@link #SERVICE_TYPE_EMERGENCY} * @@ -916,4 +933,16 @@ public final class ImsCallProfile implements Parcelable { public boolean isEmergencyCallTesting() { return mEmergencyCallTesting; } + + /** + * Checks if we have known the user intent of the call is emergency. + * + * This is only used to specify when the dialed number is ambiguous when it can be identified + * as both emergency number and any other non-emergency number; e.g. in some situation, 611 + * could be both an emergency number in a country and a non-emergency number of a carrier's + * customer service hotline. + */ + public boolean hasKnownUserIntentEmergency() { + return mHasKnownUserIntentEmergency; + } } diff --git a/telephony/java/android/telephony/ims/ImsException.java b/telephony/java/android/telephony/ims/ImsException.java new file mode 100644 index 000000000000..ac4d17a0ce65 --- /dev/null +++ b/telephony/java/android/telephony/ims/ImsException.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019 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; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.text.TextUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This class defines an IMS-related exception that has been thrown while interacting with a + * device or carrier provided ImsService implementation. + * @hide + */ +@SystemApi +public class ImsException extends Exception { + + /** + * The operation has failed due to an unknown or unspecified error. + */ + public static final int CODE_ERROR_UNSPECIFIED = 0; + /** + * The operation has failed because there is no {@link ImsService} available to service it. This + * may be due to an {@link ImsService} crash or other illegal state. + * <p> + * This is a temporary error and the operation may be retried until the connection to the + * {@link ImsService} is restored. + */ + public static final int CODE_ERROR_SERVICE_UNAVAILABLE = 1; + + /** + * This device or carrier configuration does not support IMS for this subscription. + * <p> + * This is a permanent configuration error and there should be no retry. + */ + public static final int CODE_ERROR_UNSUPPORTED_OPERATION = 2; + + /**@hide*/ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "CODE_ERROR_", value = { + CODE_ERROR_UNSPECIFIED, + CODE_ERROR_SERVICE_UNAVAILABLE, + CODE_ERROR_UNSUPPORTED_OPERATION + }) + public @interface ImsErrorCode {} + + private int mCode = CODE_ERROR_UNSPECIFIED; + + /** + * A new {@link ImsException} with an unspecified {@link ImsErrorCode} code. + * @param message an optional message to detail the error condition more specifically. + */ + public ImsException(@Nullable String message) { + super(getMessage(message, CODE_ERROR_UNSPECIFIED)); + } + + /** + * A new {@link ImsException} that includes an {@link ImsErrorCode} error code. + * @param message an optional message to detail the error condition more specifically. + */ + public ImsException(@Nullable String message, @ImsErrorCode int code) { + super(getMessage(message, code)); + mCode = code; + } + + /** + * A new {@link ImsException} that includes an {@link ImsErrorCode} error code and a + * {@link Throwable} that contains the original error that was thrown to lead to this Exception. + * @param message an optional message to detail the error condition more specifically. + * @param cause the {@link Throwable} that caused this {@link ImsException} to be created. + */ + public ImsException(@Nullable String message, @ImsErrorCode int code, Throwable cause) { + super(getMessage(message, code), cause); + mCode = code; + } + + /** + * @return the IMS Error code that is associated with this {@link ImsException}. + */ + public @ImsErrorCode int getCode() { + return mCode; + } + + private static String getMessage(String message, int code) { + StringBuilder builder; + if (!TextUtils.isEmpty(message)) { + builder = new StringBuilder(message); + builder.append(" (code: "); + builder.append(code); + builder.append(")"); + return builder.toString(); + } else { + return "code: " + code; + } + } +} diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java index 5b2e635b179f..eb99d5dcaaeb 100644 --- a/telephony/java/android/telephony/ims/ImsMmTelManager.java +++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java @@ -54,7 +54,7 @@ import java.util.concurrent.Executor; * registration and MmTel capability status callbacks, as well as query/modify user settings for the * associated subscription. * - * @see #createForSubscriptionId(Context, int) + * @see #createForSubscriptionId(int) * @hide */ @SystemApi @@ -315,15 +315,12 @@ public class ImsMmTelManager { /** * Create an instance of ImsManager for the subscription id specified. * - * @param context The context to create this ImsMmTelManager instance within. * @param subId The ID of the subscription that this ImsMmTelManager will use. * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList() - * @throws IllegalArgumentException if the subscription is invalid or - * the subscription ID is not an active subscription. + * @throws IllegalArgumentException if the subscription is invalid. */ - public static ImsMmTelManager createForSubscriptionId(Context context, int subId) { - if (!SubscriptionManager.isValidSubscriptionId(subId) - || !getSubscriptionManager(context).isActiveSubscriptionId(subId)) { + public static ImsMmTelManager createForSubscriptionId(int subId) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) { throw new IllegalArgumentException("Invalid subscription ID"); } @@ -331,7 +328,7 @@ public class ImsMmTelManager { } /** - * Only visible for testing, use {@link #createForSubscriptionId(Context, int)} instead. + * Only visible for testing, use {@link #createForSubscriptionId(int)} instead. * @hide */ @VisibleForTesting @@ -341,7 +338,7 @@ public class ImsMmTelManager { /** * Registers a {@link RegistrationCallback} with the system, which will provide registration - * updates for the subscription specified in {@link #createForSubscriptionId(Context, int)}. Use + * updates for the subscription specified in {@link #createForSubscriptionId(int)}. Use * {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to Subscription changed * events and call {@link #unregisterImsRegistrationCallback(RegistrationCallback)} to clean up. * @@ -354,13 +351,14 @@ public class ImsMmTelManager { * @throws IllegalArgumentException if the subscription associated with this callback is not * active (SIM is not inserted, ESIM inactive) or invalid, or a null {@link Executor} or * {@link CapabilityCallback} callback. - * @throws IllegalStateException if the subscription associated with this callback is valid, but + * @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. + * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed + * reason. */ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(@CallbackExecutor Executor executor, - @NonNull RegistrationCallback c) { + @NonNull RegistrationCallback c) throws ImsException { if (c == null) { throw new IllegalArgumentException("Must include a non-null RegistrationCallback."); } @@ -372,6 +370,8 @@ public class ImsMmTelManager { getITelephony().registerImsRegistrationCallback(mSubId, c.getBinder()); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); + } catch (IllegalStateException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } @@ -403,7 +403,7 @@ public class ImsMmTelManager { /** * Registers a {@link CapabilityCallback} with the system, which will provide MmTel service * availability updates for the subscription specified in - * {@link #createForSubscriptionId(Context, int)}. The method {@link #isAvailable(int, int)} + * {@link #createForSubscriptionId(int)}. The method {@link #isAvailable(int, int)} * can also be used to query this information at any time. * * Use {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to @@ -419,13 +419,14 @@ public class ImsMmTelManager { * @throws IllegalArgumentException if the subscription associated with this callback is not * active (SIM is not inserted, ESIM inactive) or invalid, or a null {@link Executor} or * {@link CapabilityCallback} callback. - * @throws IllegalStateException if the subscription associated with this callback is valid, but + * @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. + * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed + * reason. */ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull @CallbackExecutor Executor executor, - @NonNull CapabilityCallback c) { + @NonNull CapabilityCallback c) throws ImsException { if (c == null) { throw new IllegalArgumentException("Must include a non-null RegistrationCallback."); } @@ -437,6 +438,8 @@ public class ImsMmTelManager { getITelephony().registerMmTelCapabilityCallback(mSubId, c.getBinder()); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); + } catch (IllegalStateException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } @@ -796,14 +799,6 @@ public class ImsMmTelManager { } } - private static SubscriptionManager getSubscriptionManager(Context context) { - SubscriptionManager manager = context.getSystemService(SubscriptionManager.class); - if (manager == null) { - throw new RuntimeException("Could not find SubscriptionManager."); - } - return manager; - } - private static ITelephony getITelephony() { ITelephony binder = ITelephony.Stub.asInterface( ServiceManager.getService(Context.TELEPHONY_SERVICE)); diff --git a/telephony/java/android/telephony/ims/ImsReasonInfo.java b/telephony/java/android/telephony/ims/ImsReasonInfo.java index 4d95e552c1da..d8d2d9e5951a 100644 --- a/telephony/java/android/telephony/ims/ImsReasonInfo.java +++ b/telephony/java/android/telephony/ims/ImsReasonInfo.java @@ -465,7 +465,7 @@ public final class ImsReasonInfo implements Parcelable { public static final int CODE_USER_REJECTED_SESSION_MODIFICATION = 511; /** - * Upgrade Downgrade request cacncelled by the user who initiated it + * Upgrade Downgrade request cancelled by the user who initiated it */ public static final int CODE_USER_CANCELLED_SESSION_MODIFICATION = 512; @@ -887,6 +887,185 @@ public final class ImsReasonInfo implements Parcelable { public static final int CODE_OEM_CAUSE_15 = 0xf00f; /** + * @hide + */ + @IntDef(value = { + CODE_UNSPECIFIED, + CODE_LOCAL_ILLEGAL_ARGUMENT, + CODE_LOCAL_ILLEGAL_STATE, + CODE_LOCAL_INTERNAL_ERROR, + CODE_LOCAL_IMS_SERVICE_DOWN, + CODE_LOCAL_NO_PENDING_CALL, + CODE_LOCAL_ENDED_BY_CONFERENCE_MERGE, + CODE_LOCAL_POWER_OFF, + CODE_LOCAL_LOW_BATTERY, + CODE_LOCAL_NETWORK_NO_SERVICE, + CODE_LOCAL_NETWORK_NO_LTE_COVERAGE, + CODE_LOCAL_NETWORK_ROAMING, + CODE_LOCAL_NETWORK_IP_CHANGED, + CODE_LOCAL_SERVICE_UNAVAILABLE, + CODE_LOCAL_NOT_REGISTERED, + CODE_LOCAL_CALL_EXCEEDED, + CODE_LOCAL_CALL_BUSY, + CODE_LOCAL_CALL_DECLINE, + CODE_LOCAL_CALL_VCC_ON_PROGRESSING, + CODE_LOCAL_CALL_RESOURCE_RESERVATION_FAILED, + CODE_LOCAL_CALL_CS_RETRY_REQUIRED, + CODE_LOCAL_CALL_VOLTE_RETRY_REQUIRED, + CODE_LOCAL_CALL_TERMINATED, + CODE_LOCAL_HO_NOT_FEASIBLE, + CODE_TIMEOUT_1XX_WAITING, + CODE_TIMEOUT_NO_ANSWER, + CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE, + CODE_CALL_BARRED, + CODE_FDN_BLOCKED, + CODE_IMEI_NOT_ACCEPTED, + CODE_DIAL_MODIFIED_TO_USSD, + CODE_DIAL_MODIFIED_TO_SS, + CODE_DIAL_MODIFIED_TO_DIAL, + CODE_DIAL_MODIFIED_TO_DIAL_VIDEO, + CODE_DIAL_VIDEO_MODIFIED_TO_DIAL, + CODE_DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO, + CODE_DIAL_VIDEO_MODIFIED_TO_SS, + CODE_DIAL_VIDEO_MODIFIED_TO_USSD, + CODE_SIP_REDIRECTED, + CODE_SIP_BAD_REQUEST, + CODE_SIP_FORBIDDEN, + CODE_SIP_NOT_FOUND, + CODE_SIP_NOT_SUPPORTED, + CODE_SIP_REQUEST_TIMEOUT, + CODE_SIP_TEMPRARILY_UNAVAILABLE, + CODE_SIP_BAD_ADDRESS, + CODE_SIP_BUSY, + CODE_SIP_REQUEST_CANCELLED, + CODE_SIP_NOT_ACCEPTABLE, + CODE_SIP_NOT_REACHABLE, + CODE_SIP_CLIENT_ERROR, + CODE_SIP_TRANSACTION_DOES_NOT_EXIST, + CODE_SIP_SERVER_INTERNAL_ERROR, + CODE_SIP_SERVICE_UNAVAILABLE, + CODE_SIP_SERVER_TIMEOUT, + CODE_SIP_SERVER_ERROR, + CODE_SIP_USER_REJECTED, + CODE_SIP_GLOBAL_ERROR, + CODE_EMERGENCY_TEMP_FAILURE, + CODE_EMERGENCY_PERM_FAILURE, + CODE_SIP_USER_MARKED_UNWANTED, + CODE_SIP_METHOD_NOT_ALLOWED, + CODE_SIP_PROXY_AUTHENTICATION_REQUIRED, + CODE_SIP_REQUEST_ENTITY_TOO_LARGE, + CODE_SIP_REQUEST_URI_TOO_LARGE, + CODE_SIP_EXTENSION_REQUIRED, + CODE_SIP_INTERVAL_TOO_BRIEF, + CODE_SIP_CALL_OR_TRANS_DOES_NOT_EXIST, + CODE_SIP_LOOP_DETECTED, + CODE_SIP_TOO_MANY_HOPS, + CODE_SIP_AMBIGUOUS, + CODE_SIP_REQUEST_PENDING, + CODE_SIP_UNDECIPHERABLE, + CODE_MEDIA_INIT_FAILED, + CODE_MEDIA_NO_DATA, + CODE_MEDIA_NOT_ACCEPTABLE, + CODE_MEDIA_UNSPECIFIED, + CODE_USER_TERMINATED, + CODE_USER_NOANSWER, + CODE_USER_IGNORE, + CODE_USER_DECLINE, + CODE_LOW_BATTERY, + CODE_BLACKLISTED_CALL_ID, + CODE_USER_TERMINATED_BY_REMOTE, + CODE_USER_REJECTED_SESSION_MODIFICATION, + CODE_USER_CANCELLED_SESSION_MODIFICATION, + CODE_SESSION_MODIFICATION_FAILED, + CODE_UT_NOT_SUPPORTED, + CODE_UT_SERVICE_UNAVAILABLE, + CODE_UT_OPERATION_NOT_ALLOWED, + CODE_UT_NETWORK_ERROR, + CODE_UT_CB_PASSWORD_MISMATCH, + CODE_UT_SS_MODIFIED_TO_DIAL, + CODE_UT_SS_MODIFIED_TO_USSD, + CODE_UT_SS_MODIFIED_TO_SS, + CODE_UT_SS_MODIFIED_TO_DIAL_VIDEO, + CODE_ECBM_NOT_SUPPORTED, + CODE_MULTIENDPOINT_NOT_SUPPORTED, + CODE_REGISTRATION_ERROR, + CODE_ANSWERED_ELSEWHERE, + CODE_CALL_PULL_OUT_OF_SYNC, + CODE_CALL_END_CAUSE_CALL_PULL, + CODE_CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE, + CODE_REJECTED_ELSEWHERE, + CODE_SUPP_SVC_FAILED, + CODE_SUPP_SVC_CANCELLED, + CODE_SUPP_SVC_REINVITE_COLLISION, + CODE_IWLAN_DPD_FAILURE, + CODE_EPDG_TUNNEL_ESTABLISH_FAILURE, + CODE_EPDG_TUNNEL_REKEY_FAILURE, + CODE_EPDG_TUNNEL_LOST_CONNECTION, + CODE_MAXIMUM_NUMBER_OF_CALLS_REACHED, + CODE_REMOTE_CALL_DECLINE, + CODE_DATA_LIMIT_REACHED, + CODE_DATA_DISABLED, + CODE_WIFI_LOST, + CODE_IKEV2_AUTH_FAILURE, + CODE_RADIO_OFF, + CODE_NO_VALID_SIM, + CODE_RADIO_INTERNAL_ERROR, + CODE_NETWORK_RESP_TIMEOUT, + CODE_NETWORK_REJECT, + CODE_RADIO_ACCESS_FAILURE, + CODE_RADIO_LINK_FAILURE, + CODE_RADIO_LINK_LOST, + CODE_RADIO_UPLINK_FAILURE, + CODE_RADIO_SETUP_FAILURE, + CODE_RADIO_RELEASE_NORMAL, + CODE_RADIO_RELEASE_ABNORMAL, + CODE_ACCESS_CLASS_BLOCKED, + CODE_NETWORK_DETACH, + CODE_SIP_ALTERNATE_EMERGENCY_CALL, + CODE_UNOBTAINABLE_NUMBER, + CODE_NO_CSFB_IN_CS_ROAM, + CODE_REJECT_UNKNOWN, + CODE_REJECT_ONGOING_CALL_WAITING_DISABLED, + CODE_REJECT_CALL_ON_OTHER_SUB, + CODE_REJECT_1X_COLLISION, + CODE_REJECT_SERVICE_NOT_REGISTERED, + CODE_REJECT_CALL_TYPE_NOT_ALLOWED, + CODE_REJECT_ONGOING_E911_CALL, + CODE_REJECT_ONGOING_CALL_SETUP, + CODE_REJECT_MAX_CALL_LIMIT_REACHED, + CODE_REJECT_UNSUPPORTED_SIP_HEADERS, + CODE_REJECT_UNSUPPORTED_SDP_HEADERS, + CODE_REJECT_ONGOING_CALL_TRANSFER, + CODE_REJECT_INTERNAL_ERROR, + CODE_REJECT_QOS_FAILURE, + CODE_REJECT_ONGOING_HANDOVER, + CODE_REJECT_VT_TTY_NOT_ALLOWED, + CODE_REJECT_ONGOING_CALL_UPGRADE, + CODE_REJECT_CONFERENCE_TTY_NOT_ALLOWED, + CODE_REJECT_ONGOING_CONFERENCE_CALL, + CODE_REJECT_VT_AVPF_NOT_ALLOWED, + CODE_REJECT_ONGOING_ENCRYPTED_CALL, + CODE_REJECT_ONGOING_CS_CALL, + CODE_OEM_CAUSE_1, + CODE_OEM_CAUSE_2, + CODE_OEM_CAUSE_3, + CODE_OEM_CAUSE_4, + CODE_OEM_CAUSE_5, + CODE_OEM_CAUSE_6, + CODE_OEM_CAUSE_7, + CODE_OEM_CAUSE_8, + CODE_OEM_CAUSE_9, + CODE_OEM_CAUSE_10, + CODE_OEM_CAUSE_11, + CODE_OEM_CAUSE_12, + CODE_OEM_CAUSE_13, + CODE_OEM_CAUSE_14, + CODE_OEM_CAUSE_15 + }, prefix = "CODE_") + @Retention(RetentionPolicy.SOURCE) + public @interface ImsCode {} + + /** * Network string error messages. * mExtraMessage may have these values. */ @@ -964,7 +1143,7 @@ public final class ImsReasonInfo implements Parcelable { /** * @return an integer representing more information about the completion of an operation. */ - public int getCode() { + public @ImsCode int getCode() { return mCode; } diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index 086a76546b2d..204891b7b86e 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -172,15 +172,13 @@ public class ProvisioningManager { /** * Create a new {@link ProvisioningManager} for the subscription specified. - * @param context The context that this manager will use. + * * @param subId The ID of the subscription that this ProvisioningManager will use. * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList() - * @throws IllegalArgumentException if the subscription is invalid or - * the subscription ID is not an active subscription. + * @throws IllegalArgumentException if the subscription is invalid. */ - public static ProvisioningManager createForSubscriptionId(Context context, int subId) { - if (!SubscriptionManager.isValidSubscriptionId(subId) - || !getSubscriptionManager(context).isActiveSubscriptionId(subId)) { + public static ProvisioningManager createForSubscriptionId(int subId) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) { throw new IllegalArgumentException("Invalid subscription ID"); } @@ -202,18 +200,21 @@ public class ProvisioningManager { * @see SubscriptionManager.OnSubscriptionsChangedListener * @throws IllegalArgumentException if the subscription associated with this callback is not * active (SIM is not inserted, ESIM inactive) or the subscription is invalid. - * @throws IllegalStateException if the subscription associated with this callback is valid, but + * @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. + * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed + * reason. */ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(@CallbackExecutor Executor executor, - @NonNull Callback callback) { + @NonNull Callback callback) throws ImsException { callback.setExecutor(executor); try { getITelephony().registerImsProvisioningChangedCallback(mSubId, callback.getBinder()); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); + } catch (IllegalStateException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } @@ -369,14 +370,6 @@ public class ProvisioningManager { } } - private static SubscriptionManager getSubscriptionManager(Context context) { - SubscriptionManager manager = context.getSystemService(SubscriptionManager.class); - if (manager == null) { - throw new RuntimeException("Could not find SubscriptionManager."); - } - return manager; - } - private static ITelephony getITelephony() { ITelephony binder = ITelephony.Stub.asInterface( ServiceManager.getService(Context.TELEPHONY_SERVICE)); diff --git a/telephony/java/android/telephony/ims/Rcs1To1Thread.java b/telephony/java/android/telephony/ims/Rcs1To1Thread.java index 709b3aa0f804..d4a78ffb77db 100644 --- a/telephony/java/android/telephony/ims/Rcs1To1Thread.java +++ b/telephony/java/android/telephony/ims/Rcs1To1Thread.java @@ -15,42 +15,70 @@ */ package android.telephony.ims; -import android.os.Parcel; +import android.annotation.NonNull; +import android.annotation.WorkerThread; /** * Rcs1To1Thread represents a single RCS conversation thread with a total of two - * {@link RcsParticipant}s. - * @hide - TODO(sahinc) make this public + * {@link RcsParticipant}s. Please see Section 5 (1-to-1 Messaging) - GSMA RCC.71 (RCS Universal + * Profile Service Definition Document) */ public class Rcs1To1Thread extends RcsThread { + private int mThreadId; + + /** + * Public constructor only for RcsMessageStoreController to initialize new threads. + * + * @hide + */ public Rcs1To1Thread(int threadId) { super(threadId); + mThreadId = threadId; } - public static final Creator<Rcs1To1Thread> CREATOR = new Creator<Rcs1To1Thread>() { - @Override - public Rcs1To1Thread createFromParcel(Parcel in) { - return new Rcs1To1Thread(in); - } - - @Override - public Rcs1To1Thread[] newArray(int size) { - return new Rcs1To1Thread[size]; - } - }; + /** + * @return Returns {@code false} as this is always a 1 to 1 thread. + */ + @Override + public boolean isGroup() { + return false; + } - protected Rcs1To1Thread(Parcel in) { - super(in); + /** + * {@link Rcs1To1Thread}s can fall back to SMS as a back-up protocol. This function returns the + * thread id to be used to query {@code content://mms-sms/conversation/#} to get the fallback + * thread. + * + * @return The thread id to be used to query the mms-sms authority + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public long getFallbackThreadId() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.get1To1ThreadFallbackThreadId(mThreadId)); } - @Override - public int describeContents() { - return 0; + /** + * If the RCS client allows falling back to SMS, it needs to create an MMS-SMS thread in the + * SMS/MMS Provider( see {@link android.provider.Telephony.MmsSms#CONTENT_CONVERSATIONS_URI}. + * Use this function to link the {@link Rcs1To1Thread} to the MMS-SMS thread. This function + * also updates the storage. + * + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setFallbackThreadId(long fallbackThreadId) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.set1To1ThreadFallbackThreadId(mThreadId, fallbackThreadId)); } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(RCS_1_TO_1_TYPE); - super.writeToParcel(dest, flags); + /** + * @return Returns the {@link RcsParticipant} that receives the messages sent in this thread. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @NonNull + @WorkerThread + public RcsParticipant getRecipient() throws RcsMessageStoreException { + return new RcsParticipant( + RcsControllerCall.call(iRcs -> iRcs.get1To1ThreadOtherParticipantId(mThreadId))); } } diff --git a/telephony/java/android/telephony/ims/RcsControllerCall.java b/telephony/java/android/telephony/ims/RcsControllerCall.java new file mode 100644 index 000000000000..5512c4c7b19d --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsControllerCall.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2019 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; + +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.telephony.ims.aidl.IRcs; + +/** + * A wrapper class around RPC calls that {@link RcsMessageStore} APIs to minimize boilerplate code. + * + * @hide - not meant for public use + */ +class RcsControllerCall { + static <R> R call(RcsServiceCall<R> serviceCall) throws RcsMessageStoreException { + IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_RCS_SERVICE)); + if (iRcs == null) { + throw new RcsMessageStoreException("Could not connect to RCS storage service"); + } + + try { + return serviceCall.methodOnIRcs(iRcs); + } catch (RemoteException exception) { + throw new RcsMessageStoreException(exception.getMessage()); + } + } + + static void callWithNoReturn(RcsServiceCallWithNoReturn serviceCall) + throws RcsMessageStoreException { + IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_RCS_SERVICE)); + if (iRcs == null) { + throw new RcsMessageStoreException("Could not connect to RCS storage service"); + } + + try { + serviceCall.methodOnIRcs(iRcs); + } catch (RemoteException exception) { + throw new RcsMessageStoreException(exception.getMessage()); + } + } + + interface RcsServiceCall<R> { + R methodOnIRcs(IRcs iRcs) throws RemoteException; + } + + interface RcsServiceCallWithNoReturn { + void methodOnIRcs(IRcs iRcs) throws RemoteException; + } +} diff --git a/telephony/java/android/telephony/ims/RcsPart.aidl b/telephony/java/android/telephony/ims/RcsEvent.aidl index 8b8077d57676..08974e0a771c 100644 --- a/telephony/java/android/telephony/ims/RcsPart.aidl +++ b/telephony/java/android/telephony/ims/RcsEvent.aidl @@ -17,4 +17,4 @@ package android.telephony.ims; -parcelable RcsPart; +parcelable RcsEvent; diff --git a/telephony/java/android/telephony/ims/RcsEvent.java b/telephony/java/android/telephony/ims/RcsEvent.java new file mode 100644 index 000000000000..df62277f9ac1 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsEvent.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.telephony.ims; + +import android.os.Parcel; + +/** + * The base class for events that can happen on {@link RcsParticipant}s and {@link RcsThread}s. + */ +public abstract class RcsEvent { + /** + * @hide + */ + protected final long mTimestamp; + + protected RcsEvent(long timestamp) { + mTimestamp = timestamp; + } + + /** + * @return Returns the time of when this event happened. The timestamp is defined as + * milliseconds passed after midnight, January 1, 1970 UTC + */ + public long getTimestamp() { + return mTimestamp; + } + + /** + * Persists the event to the data store + * + * @hide + */ + abstract void persist() throws RcsMessageStoreException; + + /** + * @hide + */ + RcsEvent(Parcel in) { + mTimestamp = in.readLong(); + } + + /** + * @hide + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mTimestamp); + } +} diff --git a/telephony/java/android/telephony/ims/RcsIncomingMessage.aidl b/telephony/java/android/telephony/ims/RcsEventQueryParams.aidl index 6552a82c9072..f18c4dfd2dcd 100644 --- a/telephony/java/android/telephony/ims/RcsIncomingMessage.aidl +++ b/telephony/java/android/telephony/ims/RcsEventQueryParams.aidl @@ -17,4 +17,4 @@ package android.telephony.ims; -parcelable RcsIncomingMessage; +parcelable RcsEventQueryParams; diff --git a/telephony/java/android/telephony/ims/RcsEventQueryParams.java b/telephony/java/android/telephony/ims/RcsEventQueryParams.java new file mode 100644 index 000000000000..9dbfe4393213 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsEventQueryParams.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2019 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; + +import static android.provider.Telephony.RcsColumns.RcsEventTypes.ICON_CHANGED_EVENT_TYPE; +import static android.provider.Telephony.RcsColumns.RcsEventTypes.NAME_CHANGED_EVENT_TYPE; +import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_ALIAS_CHANGED_EVENT_TYPE; +import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_JOINED_EVENT_TYPE; +import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_LEFT_EVENT_TYPE; + +import android.annotation.CheckResult; +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.security.InvalidParameterException; + +/** + * The parameters to pass into + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParams)} in order to select a + * subset of {@link RcsEvent}s present in the message store. + */ +public final class RcsEventQueryParams implements Parcelable { + /** + * Flag to be used with {@link Builder#setEventType(int)} to make + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParams)} return all types of + * {@link RcsEvent}s + */ + public static final int ALL_EVENTS = -1; + + /** + * Flag to be used with {@link Builder#setEventType(int)} to make + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParams)} return sub-types of + * {@link RcsGroupThreadEvent}s + */ + public static final int ALL_GROUP_THREAD_EVENTS = 0; + + /** + * Flag to be used with {@link Builder#setEventType(int)} to make + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParams)} return only + * {@link RcsParticipantAliasChangedEvent}s + */ + public static final int PARTICIPANT_ALIAS_CHANGED_EVENT = + PARTICIPANT_ALIAS_CHANGED_EVENT_TYPE; + + /** + * Flag to be used with {@link Builder#setEventType(int)} to make + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParams)} return only + * {@link RcsGroupThreadParticipantJoinedEvent}s + */ + public static final int GROUP_THREAD_PARTICIPANT_JOINED_EVENT = + PARTICIPANT_JOINED_EVENT_TYPE; + + /** + * Flag to be used with {@link Builder#setEventType(int)} to make + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParams)} return only + * {@link RcsGroupThreadParticipantLeftEvent}s + */ + public static final int GROUP_THREAD_PARTICIPANT_LEFT_EVENT = + PARTICIPANT_LEFT_EVENT_TYPE; + + /** + * Flag to be used with {@link Builder#setEventType(int)} to make + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParams)} return only + * {@link RcsGroupThreadNameChangedEvent}s + */ + public static final int GROUP_THREAD_NAME_CHANGED_EVENT = NAME_CHANGED_EVENT_TYPE; + + /** + * Flag to be used with {@link Builder#setEventType(int)} to make + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParams)} return only + * {@link RcsGroupThreadIconChangedEvent}s + */ + public static final int GROUP_THREAD_ICON_CHANGED_EVENT = ICON_CHANGED_EVENT_TYPE; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ALL_EVENTS, ALL_GROUP_THREAD_EVENTS, PARTICIPANT_ALIAS_CHANGED_EVENT, + GROUP_THREAD_PARTICIPANT_JOINED_EVENT, GROUP_THREAD_PARTICIPANT_LEFT_EVENT, + GROUP_THREAD_NAME_CHANGED_EVENT, GROUP_THREAD_ICON_CHANGED_EVENT}) + public @interface EventType { + } + + /** + * Flag to be used with {@link Builder#setSortProperty(int)} that makes the result set sorted + * in the order of creation for faster query results. + */ + public static final int SORT_BY_CREATION_ORDER = 0; + + /** + * Flag to be used with {@link Builder#setSortProperty(int)} that makes the result set sorted + * with respect to {@link RcsEvent#getTimestamp()} + */ + public static final int SORT_BY_TIMESTAMP = 1; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({SORT_BY_CREATION_ORDER, SORT_BY_TIMESTAMP}) + public @interface SortingProperty { + } + + /** + * The key to pass into a Bundle, for usage in RcsProvider.query(Bundle) + * @hide - not meant for public use + */ + public static final String EVENT_QUERY_PARAMETERS_KEY = "event_query_parameters"; + + // Which types of events the results should be limited to + private @EventType int mEventType; + // The property which the results should be sorted against + private int mSortingProperty; + // Whether the results should be sorted in ascending order + private boolean mIsAscending; + // The number of results that should be returned with this query + private int mLimit; + // The thread that the results are limited to + private int mThreadId; + + RcsEventQueryParams(@EventType int eventType, int threadId, + @SortingProperty int sortingProperty, boolean isAscending, int limit) { + mEventType = eventType; + mSortingProperty = sortingProperty; + mIsAscending = isAscending; + mLimit = limit; + mThreadId = threadId; + } + + /** + * @return Returns the type of {@link RcsEvent}s that this {@link RcsEventQueryParams} is + * set to query for. + */ + public @EventType int getEventType() { + return mEventType; + } + + /** + * @return Returns the number of {@link RcsEvent}s to be returned from the query. A value of + * 0 means there is no set limit. + */ + public int getLimit() { + return mLimit; + } + + /** + * @return Returns the property where the results should be sorted against. + * @see SortingProperty + */ + public int getSortingProperty() { + return mSortingProperty; + } + + /** + * @return Returns {@code true} if the result set will be sorted in ascending order, + * {@code false} if it will be sorted in descending order. + */ + public boolean getSortDirection() { + return mIsAscending; + } + + /** + * @return Returns the ID of the {@link RcsGroupThread} that the results are limited to. As this + * API exposes an ID, it should stay hidden. + * + * @hide + */ + public int getThreadId() { + return mThreadId; + } + + /** + * A helper class to build the {@link RcsEventQueryParams}. + */ + public static class Builder { + private @EventType int mEventType; + private @SortingProperty int mSortingProperty; + private boolean mIsAscending; + private int mLimit = 100; + private int mThreadId; + + /** + * Creates a new builder for {@link RcsEventQueryParams} to be used in + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParams)} + */ + public Builder() { + // empty implementation + } + + /** + * Desired number of events to be returned from the query. Passing in 0 will return all + * existing events at once. The limit defaults to 100. + * + * @param limit The number to limit the query result to. + * @return The same instance of the builder to chain parameters. + * @throws InvalidParameterException If the given limit is negative. + */ + @CheckResult + public Builder setResultLimit(@IntRange(from = 0) int limit) + throws InvalidParameterException { + if (limit < 0) { + throw new InvalidParameterException("The query limit must be non-negative"); + } + + mLimit = limit; + return this; + } + + /** + * Sets the type of events to be returned from the query. + * + * @param eventType The type of event to be returned. + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setEventType(@EventType int eventType) { + mEventType = eventType; + return this; + } + + /** + * Sets the property where the results should be sorted against. Defaults to + * {@link RcsEventQueryParams.SortingProperty#SORT_BY_CREATION_ORDER} + * + * @param sortingProperty against which property the results should be sorted + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setSortProperty(@SortingProperty int sortingProperty) { + mSortingProperty = sortingProperty; + return this; + } + + /** + * Sets whether the results should be sorted ascending or descending + * + * @param isAscending whether the results should be sorted ascending + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setSortDirection(boolean isAscending) { + mIsAscending = isAscending; + return this; + } + + /** + * Limits the results to the given {@link RcsGroupThread}. Setting this value prevents + * returning any instances of {@link RcsParticipantAliasChangedEvent}. + * + * @param groupThread The thread to limit the results to. + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setGroupThread(@NonNull RcsGroupThread groupThread) { + mThreadId = groupThread.getThreadId(); + return this; + } + + /** + * Builds the {@link RcsEventQueryParams} to use in + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParams)} + * + * @return An instance of {@link RcsEventQueryParams} to use with the event query. + */ + public RcsEventQueryParams build() { + return new RcsEventQueryParams(mEventType, mThreadId, mSortingProperty, + mIsAscending, mLimit); + } + } + + private RcsEventQueryParams(Parcel in) { + mEventType = in.readInt(); + mThreadId = in.readInt(); + mSortingProperty = in.readInt(); + mIsAscending = in.readBoolean(); + mLimit = in.readInt(); + } + + public static final Creator<RcsEventQueryParams> CREATOR = + new Creator<RcsEventQueryParams>() { + @Override + public RcsEventQueryParams createFromParcel(Parcel in) { + return new RcsEventQueryParams(in); + } + + @Override + public RcsEventQueryParams[] newArray(int size) { + return new RcsEventQueryParams[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mEventType); + dest.writeInt(mThreadId); + dest.writeInt(mSortingProperty); + dest.writeBoolean(mIsAscending); + dest.writeInt(mLimit); + } +} diff --git a/telephony/java/android/telephony/ims/Rcs1To1Thread.aidl b/telephony/java/android/telephony/ims/RcsEventQueryResult.aidl index 9fdc41d2bd5f..7d133350973c 100644 --- a/telephony/java/android/telephony/ims/Rcs1To1Thread.aidl +++ b/telephony/java/android/telephony/ims/RcsEventQueryResult.aidl @@ -17,4 +17,4 @@ package android.telephony.ims; -parcelable Rcs1To1Thread; +parcelable RcsEventQueryResult; diff --git a/telephony/java/android/telephony/ims/RcsEventQueryResult.java b/telephony/java/android/telephony/ims/RcsEventQueryResult.java new file mode 100644 index 000000000000..c30e4ccd7aa2 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsEventQueryResult.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2019 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; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; + +/** + * The result of a {@link RcsMessageStore#getRcsEvents(RcsEventQueryParams)} + * call. This class allows getting the token for querying the next batch of events in order to + * prevent handling large amounts of data at once. + */ +public final class RcsEventQueryResult implements Parcelable { + private RcsQueryContinuationToken mContinuationToken; + private List<RcsEvent> mEvents; + + /** + * Internal constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController} + * to create query results + * + * @hide + */ + public RcsEventQueryResult( + RcsQueryContinuationToken continuationToken, + List<RcsEvent> events) { + mContinuationToken = continuationToken; + mEvents = events; + } + + /** + * Returns a token to call + * {@link RcsMessageStore#getRcsEvents(RcsQueryContinuationToken)} + * to get the next batch of {@link RcsEvent}s. + */ + public RcsQueryContinuationToken getContinuationToken() { + return mContinuationToken; + } + + /** + * Returns all the {@link RcsEvent}s in the current query result. Call {@link + * RcsMessageStore#getRcsEvents(RcsQueryContinuationToken)} to get the next batch + * of {@link RcsEvent}s. + */ + public List<RcsEvent> getEvents() { + return mEvents; + } + + private RcsEventQueryResult(Parcel in) { + mContinuationToken = in.readParcelable(RcsQueryContinuationToken.class.getClassLoader()); + } + + public static final Creator<RcsEventQueryResult> CREATOR = new Creator<RcsEventQueryResult>() { + @Override + public RcsEventQueryResult createFromParcel(Parcel in) { + return new RcsEventQueryResult(in); + } + + @Override + public RcsEventQueryResult[] newArray(int size) { + return new RcsEventQueryResult[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mContinuationToken, flags); + } +} diff --git a/telephony/java/android/telephony/ims/RcsFileTransferCreationParams.aidl b/telephony/java/android/telephony/ims/RcsFileTransferCreationParams.aidl new file mode 100644 index 000000000000..155219038d7b --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsFileTransferCreationParams.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 2019, 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; + +parcelable RcsFileTransferCreationParams; diff --git a/telephony/java/android/telephony/ims/RcsFileTransferCreationParams.java b/telephony/java/android/telephony/ims/RcsFileTransferCreationParams.java new file mode 100644 index 000000000000..14af8ea63a67 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsFileTransferCreationParams.java @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2019 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; + +import android.annotation.CheckResult; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Pass an instance of this class to + * {@link RcsMessage#insertFileTransfer(RcsFileTransferCreationParams)} create an + * {@link RcsFileTransferPart} and save it into storage. + */ +public final class RcsFileTransferCreationParams implements Parcelable { + private String mRcsFileTransferSessionId; + private Uri mContentUri; + private String mContentMimeType; + private long mFileSize; + private long mTransferOffset; + private int mWidth; + private int mHeight; + private long mMediaDuration; + private Uri mPreviewUri; + private String mPreviewMimeType; + private @RcsFileTransferPart.RcsFileTransferStatus int mFileTransferStatus; + + /** + * @return Returns the globally unique RCS file transfer session ID for the + * {@link RcsFileTransferPart} to be created + */ + public String getRcsFileTransferSessionId() { + return mRcsFileTransferSessionId; + } + + /** + * @return Returns the URI for the content of the {@link RcsFileTransferPart} to be created + */ + public Uri getContentUri() { + return mContentUri; + } + + /** + * @return Returns the MIME type for the content of the {@link RcsFileTransferPart} to be + * created + */ + public String getContentMimeType() { + return mContentMimeType; + } + + /** + * @return Returns the file size in bytes for the {@link RcsFileTransferPart} to be created + */ + public long getFileSize() { + return mFileSize; + } + + /** + * @return Returns the transfer offset for the {@link RcsFileTransferPart} to be created. The + * file transfer offset is defined as how many bytes have been successfully transferred to the + * receiver of this file transfer. + */ + public long getTransferOffset() { + return mTransferOffset; + } + + /** + * @return Returns the width of the {@link RcsFileTransferPart} to be created. The value is in + * pixels. + */ + public int getWidth() { + return mWidth; + } + + /** + * @return Returns the height of the {@link RcsFileTransferPart} to be created. The value is in + * pixels. + */ + public int getHeight() { + return mHeight; + } + + /** + * @return Returns the duration of the {@link RcsFileTransferPart} to be created. + */ + public long getMediaDuration() { + return mMediaDuration; + } + + /** + * @return Returns the URI of the preview of the content of the {@link RcsFileTransferPart} to + * be created. This should only be used for multi-media files. + */ + public Uri getPreviewUri() { + return mPreviewUri; + } + + /** + * @return Returns the MIME type of the preview of the content of the + * {@link RcsFileTransferPart} to be created. This should only be used for multi-media files. + */ + public String getPreviewMimeType() { + return mPreviewMimeType; + } + + /** + * @return Returns the status of the {@link RcsFileTransferPart} to be created. + */ + public @RcsFileTransferPart.RcsFileTransferStatus int getFileTransferStatus() { + return mFileTransferStatus; + } + + /** + * @hide + */ + RcsFileTransferCreationParams(Builder builder) { + mRcsFileTransferSessionId = builder.mRcsFileTransferSessionId; + mContentUri = builder.mContentUri; + mContentMimeType = builder.mContentMimeType; + mFileSize = builder.mFileSize; + mTransferOffset = builder.mTransferOffset; + mWidth = builder.mWidth; + mHeight = builder.mHeight; + mMediaDuration = builder.mLength; + mPreviewUri = builder.mPreviewUri; + mPreviewMimeType = builder.mPreviewMimeType; + mFileTransferStatus = builder.mFileTransferStatus; + } + + /** + * A builder to create instances of {@link RcsFileTransferCreationParams} + */ + public class Builder { + private String mRcsFileTransferSessionId; + private Uri mContentUri; + private String mContentMimeType; + private long mFileSize; + private long mTransferOffset; + private int mWidth; + private int mHeight; + private long mLength; + private Uri mPreviewUri; + private String mPreviewMimeType; + private @RcsFileTransferPart.RcsFileTransferStatus int mFileTransferStatus; + + /** + * Sets the globally unique RCS file transfer session ID for the {@link RcsFileTransferPart} + * to be created + * + * @param sessionId The RCS file transfer session ID + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setFileTransferSessionId(String sessionId) { + mRcsFileTransferSessionId = sessionId; + return this; + } + + /** + * Sets the URI for the content of the {@link RcsFileTransferPart} to be created + * + * @param contentUri The URI for the file + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setContentUri(Uri contentUri) { + mContentUri = contentUri; + return this; + } + + /** + * Sets the MIME type for the content of the {@link RcsFileTransferPart} to be created + * + * @param contentType The MIME type of the file + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setContentMimeType(String contentType) { + mContentMimeType = contentType; + return this; + } + + /** + * Sets the file size for the {@link RcsFileTransferPart} to be created + * + * @param size The size of the file in bytes + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setFileSize(long size) { + mFileSize = size; + return this; + } + + /** + * Sets the transfer offset for the {@link RcsFileTransferPart} to be created. The file + * transfer offset is defined as how many bytes have been successfully transferred to the + * receiver of this file transfer. + * + * @param offset The transfer offset in bytes + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setTransferOffset(long offset) { + mTransferOffset = offset; + return this; + } + + /** + * Sets the width of the {@link RcsFileTransferPart} to be created. This should only be used + * for multi-media files. + * + * @param width The width of the multi-media file in pixels. + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setWidth(int width) { + mWidth = width; + return this; + } + + /** + * Sets the height of the {@link RcsFileTransferPart} to be created. This should only be + * used for multi-media files. + * + * @param height The height of the multi-media file in pixels. + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setHeight(int height) { + mHeight = height; + return this; + } + + /** + * Sets the length of the {@link RcsFileTransferPart} to be created. This should only be + * used for multi-media files such as audio or video. + * + * @param length The length of the multi-media file in milliseconds + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setMediaDuration(long length) { + mLength = length; + return this; + } + + /** + * Sets the URI of the preview of the content of the {@link RcsFileTransferPart} to be + * created. This should only be used for multi-media files. + * + * @param previewUri The URI of the preview of the file transfer + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setPreviewUri(Uri previewUri) { + mPreviewUri = previewUri; + return this; + } + + /** + * Sets the MIME type of the preview of the content of the {@link RcsFileTransferPart} to + * be created. This should only be used for multi-media files. + * + * @param previewType The MIME type of the preview of the file transfer + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setPreviewMimeType(String previewType) { + mPreviewMimeType = previewType; + return this; + } + + /** + * Sets the status of the {@link RcsFileTransferPart} to be created. + * + * @param status The status of the file transfer + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setFileTransferStatus( + @RcsFileTransferPart.RcsFileTransferStatus int status) { + mFileTransferStatus = status; + return this; + } + + /** + * Creates an instance of {@link RcsFileTransferCreationParams} with the given + * parameters. + * + * @return The same instance of {@link Builder} to chain methods + * @see RcsMessage#insertFileTransfer(RcsFileTransferCreationParams) + */ + public RcsFileTransferCreationParams build() { + return new RcsFileTransferCreationParams(this); + } + } + + private RcsFileTransferCreationParams(Parcel in) { + mRcsFileTransferSessionId = in.readString(); + mContentUri = in.readParcelable(Uri.class.getClassLoader()); + mContentMimeType = in.readString(); + mFileSize = in.readLong(); + mTransferOffset = in.readLong(); + mWidth = in.readInt(); + mHeight = in.readInt(); + mMediaDuration = in.readLong(); + mPreviewUri = in.readParcelable(Uri.class.getClassLoader()); + mPreviewMimeType = in.readString(); + mFileTransferStatus = in.readInt(); + } + + public static final Creator<RcsFileTransferCreationParams> CREATOR = + new Creator<RcsFileTransferCreationParams>() { + @Override + public RcsFileTransferCreationParams createFromParcel(Parcel in) { + return new RcsFileTransferCreationParams(in); + } + + @Override + public RcsFileTransferCreationParams[] newArray(int size) { + return new RcsFileTransferCreationParams[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mRcsFileTransferSessionId); + dest.writeParcelable(mContentUri, flags); + dest.writeString(mContentMimeType); + dest.writeLong(mFileSize); + dest.writeLong(mTransferOffset); + dest.writeInt(mWidth); + dest.writeInt(mHeight); + dest.writeLong(mMediaDuration); + dest.writeParcelable(mPreviewUri, flags); + dest.writeString(mPreviewMimeType); + dest.writeInt(mFileTransferStatus); + } +} diff --git a/telephony/java/android/telephony/ims/RcsFileTransferPart.java b/telephony/java/android/telephony/ims/RcsFileTransferPart.java index 39c58dd9c15b..9531c2e2f981 100644 --- a/telephony/java/android/telephony/ims/RcsFileTransferPart.java +++ b/telephony/java/android/telephony/ims/RcsFileTransferPart.java @@ -15,34 +15,344 @@ */ package android.telephony.ims; -import android.os.Parcel; +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.annotation.WorkerThread; +import android.net.Uri; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** - * A part of a composite {@link RcsMessage} that holds a file transfer. - * @hide - TODO(sahinc) make this public + * A part of a composite {@link RcsMessage} that holds a file transfer. Please see Section 7 + * (File Transfer) - GSMA RCC.71 (RCS Universal Profile Service Definition Document) */ -public class RcsFileTransferPart extends RcsPart { - public static final Creator<RcsFileTransferPart> CREATOR = new Creator<RcsFileTransferPart>() { - @Override - public RcsFileTransferPart createFromParcel(Parcel in) { - return new RcsFileTransferPart(in); - } +public class RcsFileTransferPart { + /** + * The status to indicate that this {@link RcsFileTransferPart} is not set yet. + */ + public static final int NOT_SET = 0; + + /** + * The status to indicate that this {@link RcsFileTransferPart} is a draft and is not in the + * process of sending yet. + */ + public static final int DRAFT = 1; + + /** + * The status to indicate that this {@link RcsFileTransferPart} is actively being sent right + * now. + */ + public static final int SENDING = 2; + + /** + * The status to indicate that this {@link RcsFileTransferPart} was being sent, but the user has + * paused the sending process. + */ + public static final int SENDING_PAUSED = 3; + + /** + * The status to indicate that this {@link RcsFileTransferPart} was attempted, but failed to + * send. + */ + public static final int SENDING_FAILED = 4; + + /** + * The status to indicate that this {@link RcsFileTransferPart} is permanently cancelled to + * send. + */ + public static final int SENDING_CANCELLED = 5; + + /** + * The status to indicate that this {@link RcsFileTransferPart} is actively being downloaded + * right now. + */ + public static final int DOWNLOADING = 6; + + /** + * The status to indicate that this {@link RcsFileTransferPart} was being downloaded, but the + * user paused the downloading process. + */ + public static final int DOWNLOADING_PAUSED = 7; - @Override - public RcsFileTransferPart[] newArray(int size) { - return new RcsFileTransferPart[size]; - } - }; + /** + * The status to indicate that this {@link RcsFileTransferPart} was attempted, but failed to + * download. + */ + public static final int DOWNLOADING_FAILED = 8; + + /** + * The status to indicate that this {@link RcsFileTransferPart} is permanently cancelled to + * download. + */ + public static final int DOWNLOADING_CANCELLED = 9; + + /** + * The status to indicate that this {@link RcsFileTransferPart} was successfully sent or + * received. + */ + public static final int SUCCEEDED = 10; + + @IntDef({ + DRAFT, SENDING, SENDING_PAUSED, SENDING_FAILED, SENDING_CANCELLED, DOWNLOADING, + DOWNLOADING_PAUSED, DOWNLOADING_FAILED, DOWNLOADING_CANCELLED, SUCCEEDED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RcsFileTransferStatus { + } + + private int mId; + + /** + * @hide + */ + RcsFileTransferPart(int id) { + mId = id; + } + + /** + * @hide + */ + public void setId(int id) { + mId = id; + } + + /** + * @hide + */ + public int getId() { + return mId; + } + + /** + * Sets the RCS file transfer session ID for this file transfer and persists into storage. + * + * @param sessionId The session ID to be used for this file transfer. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setFileTransferSessionId(String sessionId) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferSessionId(mId, sessionId)); + } + + /** + * @return Returns the file transfer session ID. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public String getFileTransferSessionId() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferSessionId(mId)); + } + + /** + * Sets the content URI for this file transfer and persists into storage. The file transfer + * should be reachable using this URI. + * + * @param contentUri The URI for this file transfer. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setContentUri(Uri contentUri) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferContentUri(mId, contentUri)); + } + + /** + * @return Returns the URI for this file transfer + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @Nullable + @WorkerThread + public Uri getContentUri() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferContentUri(mId)); + } + + /** + * Sets the MIME type of this file transfer and persists into storage. Whether this type + * actually matches any known or supported types is not checked. + * + * @param contentMimeType The type of this file transfer. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setContentMimeType(String contentMimeType) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setFileTransferContentType(mId, contentMimeType)); + } + + /** + * @return Returns the content type of this file transfer + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + @Nullable + public String getContentMimeType() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferContentType(mId)); + } + + /** + * Sets the content length (i.e. file size) for this file transfer and persists into storage. + * + * @param contentLength The content length of this file transfer + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setFileSize(long contentLength) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setFileTransferFileSize(mId, contentLength)); + } + + /** + * @return Returns the content length (i.e. file size) for this file transfer. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public long getFileSize() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferFileSize(mId)); + } + + /** + * Sets the transfer offset for this file transfer and persists into storage. The file transfer + * offset is defined as how many bytes have been successfully transferred to the receiver of + * this file transfer. + * + * @param transferOffset The transfer offset for this file transfer. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setTransferOffset(long transferOffset) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setFileTransferTransferOffset(mId, transferOffset)); + } + + /** + * @return Returns the number of bytes that have successfully transferred. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public long getTransferOffset() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferTransferOffset(mId)); + } + + /** + * Sets the status for this file transfer and persists into storage. + * + * @param status The status of this file transfer. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setFileTransferStatus(@RcsFileTransferStatus int status) + throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferStatus(mId, status)); + } + + /** + * @return Returns the status of this file transfer. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public @RcsFileTransferStatus int getFileTransferStatus() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferStatus(mId)); + } + + /** + * @return Returns the width of this multi-media message part in pixels. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public int getWidth() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferWidth(mId)); + } + + /** + * Sets the width of this RCS multi-media message part and persists into storage. + * + * @param width The width value in pixels + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setWidth(int width) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferWidth(mId, width)); + } + + /** + * @return Returns the height of this multi-media message part in pixels. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public int getHeight() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferHeight(mId)); + } + + /** + * Sets the height of this RCS multi-media message part and persists into storage. + * + * @param height The height value in pixels + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setHeight(int height) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferHeight(mId, height)); + } + + /** + * @return Returns the length of this multi-media file (e.g. video or audio) in milliseconds. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public long getLength() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferLength(mId)); + } + + /** + * Sets the length of this multi-media file (e.g. video or audio) and persists into storage. + * + * @param length The length of the file in milliseconds. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setLength(long length) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferLength(mId, length)); + } + + /** + * @return Returns the URI for the preview of this multi-media file (e.g. an image thumbnail for + * a video) + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public Uri getPreviewUri() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferPreviewUri(mId)); + } - protected RcsFileTransferPart(Parcel in) { + /** + * Sets the URI for the preview of this multi-media file and persists into storage. + * + * @param previewUri The URI to access to the preview file. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setPreviewUri(Uri previewUri) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferPreviewUri(mId, previewUri)); } - @Override - public int describeContents() { - return 0; + /** + * @return Returns the MIME type of this multi-media file's preview. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public String getPreviewMimeType() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferPreviewType(mId)); } - @Override - public void writeToParcel(Parcel dest, int flags) { + /** + * Sets the MIME type for this multi-media file's preview and persists into storage. + * + * @param previewMimeType The MIME type for the preview + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setPreviewMimeType(String previewMimeType) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setFileTransferPreviewType(mId, previewMimeType)); } } diff --git a/telephony/java/android/telephony/ims/RcsGroupThread.java b/telephony/java/android/telephony/ims/RcsGroupThread.java index d954b2d70ac3..6e17bc2a685f 100644 --- a/telephony/java/android/telephony/ims/RcsGroupThread.java +++ b/telephony/java/android/telephony/ims/RcsGroupThread.java @@ -15,38 +15,189 @@ */ package android.telephony.ims; -import android.os.Parcel; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.WorkerThread; +import android.net.Uri; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; /** * RcsGroupThread represents a single RCS conversation thread where {@link RcsParticipant}s can join - * or leave. - * @hide - TODO(sahinc) make this public + * or leave. Please see Section 6 (Group Chat) - GSMA RCC.71 (RCS Universal Profile Service + * Definition Document) */ public class RcsGroupThread extends RcsThread { - public static final Creator<RcsGroupThread> CREATOR = new Creator<RcsGroupThread>() { - @Override - public RcsGroupThread createFromParcel(Parcel in) { - return new RcsGroupThread(in); + /** + * Public constructor only for RcsMessageStoreController to initialize new threads. + * + * @hide + */ + public RcsGroupThread(int threadId) { + super(threadId); + } + + /** + * @return Returns {@code true} as this is always a group thread + */ + @Override + public boolean isGroup() { + return true; + } + + /** + * @return Returns the given name of this {@link RcsGroupThread}. Please see US6-2 - GSMA RCC.71 + * (RCS Universal Profile Service Definition Document) + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @Nullable + @WorkerThread + public String getGroupName() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getGroupThreadName(mThreadId)); + } + + /** + * Sets the name of this {@link RcsGroupThread} and saves it into storage. Please see US6-2 - + * GSMA RCC.71 (RCS Universal Profile Service Definition Document) + * + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setGroupName(String groupName) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setGroupThreadName(mThreadId, groupName)); + } + + /** + * @return Returns a URI that points to the group's icon {@link RcsGroupThread}. Please see + * US6-2 - GSMA RCC.71 (RCS Universal Profile Service Definition Document) + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @Nullable + public Uri getGroupIcon() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getGroupThreadIcon(mThreadId)); + } + + /** + * Sets the icon for this {@link RcsGroupThread} and saves it into storage. Please see US6-2 - + * GSMA RCC.71 (RCS Universal Profile Service Definition Document) + * + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setGroupIcon(@Nullable Uri groupIcon) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setGroupThreadIcon(mThreadId, groupIcon)); + } + + /** + * @return Returns the owner of this thread or {@code null} if there doesn't exist an owner + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @Nullable + @WorkerThread + public RcsParticipant getOwner() throws RcsMessageStoreException { + return new RcsParticipant(RcsControllerCall.call( + iRcs -> iRcs.getGroupThreadOwner(mThreadId))); + } + + /** + * Sets the owner of this {@link RcsGroupThread} and saves it into storage. This is intended to + * be used for selecting a new owner for a group thread if the owner leaves the thread. The + * owner needs to be in the list of existing participants. + * + * @param participant The new owner of the thread. {@code null} values are allowed. + * @throws RcsMessageStoreException if the operation could not be persisted into storage + */ + @WorkerThread + public void setOwner(@Nullable RcsParticipant participant) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setGroupThreadOwner(mThreadId, participant.getId())); + } + + /** + * Adds a new {@link RcsParticipant} to this group thread and persists into storage. If the user + * is actively participating in this {@link RcsGroupThread}, an {@link RcsParticipant} on behalf + * of them should be added. + * + * @param participant The new participant to be added to the thread. + * @throws RcsMessageStoreException if the operation could not be persisted into storage + */ + @WorkerThread + public void addParticipant(@NonNull RcsParticipant participant) + throws RcsMessageStoreException { + if (participant == null) { + return; } - @Override - public RcsGroupThread[] newArray(int size) { - return new RcsGroupThread[size]; + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.addParticipantToGroupThread(mThreadId, participant.getId())); + } + + /** + * Removes an {@link RcsParticipant} from this group thread and persists into storage. If the + * removed participant was the owner of this group, the owner will become null. + * + * @throws RcsMessageStoreException if the operation could not be persisted into storage + */ + @WorkerThread + public void removeParticipant(@NonNull RcsParticipant participant) + throws RcsMessageStoreException { + if (participant == null) { + return; } - }; - protected RcsGroupThread(Parcel in) { - super(in); + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.removeParticipantFromGroupThread(mThreadId, participant.getId())); } - @Override - public int describeContents() { - return 0; + /** + * Returns the set of {@link RcsParticipant}s that contribute to this group thread. The + * returned set does not support modifications, please use + * {@link RcsGroupThread#addParticipant(RcsParticipant)} + * and {@link RcsGroupThread#removeParticipant(RcsParticipant)} instead. + * + * @return the immutable set of {@link RcsParticipant} in this group thread. + * @throws RcsMessageStoreException if the values could not be read from the storage + */ + @WorkerThread + @NonNull + public Set<RcsParticipant> getParticipants() throws RcsMessageStoreException { + RcsParticipantQueryParams queryParameters = + new RcsParticipantQueryParams.Builder().setThread(this).build(); + + RcsParticipantQueryResult queryResult = RcsControllerCall.call( + iRcs -> iRcs.getParticipants(queryParameters)); + + List<RcsParticipant> participantList = queryResult.getParticipants(); + Set<RcsParticipant> participantSet = new LinkedHashSet<>(participantList); + return Collections.unmodifiableSet(participantSet); } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(RCS_GROUP_TYPE); - super.writeToParcel(dest, flags); + /** + * Returns the conference URI for this {@link RcsGroupThread}. Please see 4.4.5.2 - GSMA RCC.53 + * (RCS Device API 1.6 Specification + * + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @Nullable + @WorkerThread + public Uri getConferenceUri() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getGroupThreadConferenceUri(mThreadId)); + } + + /** + * Sets the conference URI for this {@link RcsGroupThread} and persists into storage. Please see + * 4.4.5.2 - GSMA RCC.53 (RCS Device API 1.6 Specification + * + * @param conferenceUri The URI as String to be used as the conference URI. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @Nullable + @WorkerThread + public void setConferenceUri(Uri conferenceUri) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setGroupThreadConferenceUri(mThreadId, conferenceUri)); } } diff --git a/telephony/java/android/telephony/ims/RcsLocationPart.aidl b/telephony/java/android/telephony/ims/RcsGroupThreadEvent.aidl index 4fe5ca97a30d..77a23722f080 100644 --- a/telephony/java/android/telephony/ims/RcsLocationPart.aidl +++ b/telephony/java/android/telephony/ims/RcsGroupThreadEvent.aidl @@ -1,5 +1,4 @@ /* - * * Copyright 2019, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,4 +16,4 @@ package android.telephony.ims; -parcelable RcsLocationPart; +parcelable RcsGroupThreadEvent; diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadEvent.java b/telephony/java/android/telephony/ims/RcsGroupThreadEvent.java new file mode 100644 index 000000000000..609b1740a536 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsGroupThreadEvent.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.telephony.ims; + +import android.annotation.NonNull; +import android.os.Parcel; + +/** + * An event that happened on an {@link RcsGroupThread}. + */ +public abstract class RcsGroupThreadEvent extends RcsEvent { + private final int mRcsGroupThreadId; + private final int mOriginatingParticipantId; + + RcsGroupThreadEvent(long timestamp, int rcsGroupThreadId, + int originatingParticipantId) { + super(timestamp); + mRcsGroupThreadId = rcsGroupThreadId; + mOriginatingParticipantId = originatingParticipantId; + } + + /** + * @return Returns the {@link RcsGroupThread} that this event happened on. + */ + @NonNull + public RcsGroupThread getRcsGroupThread() { + return new RcsGroupThread(mRcsGroupThreadId); + } + + /** + * @return Returns the {@link RcsParticipant} that performed the event. + */ + @NonNull + public RcsParticipant getOriginatingParticipant() { + return new RcsParticipant(mOriginatingParticipantId); + } + + /** + * @hide + */ + RcsGroupThreadEvent(Parcel in) { + super(in); + mRcsGroupThreadId = in.readInt(); + mOriginatingParticipantId = in.readInt(); + } + + /** + * @hide + */ + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(mRcsGroupThreadId); + dest.writeInt(mOriginatingParticipantId); + } +} diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.aidl new file mode 100644 index 000000000000..daea7922f3df --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2019, 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; + +parcelable RcsGroupThreadIconChangedEvent; diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.java b/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.java new file mode 100644 index 000000000000..e768439d6cfa --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.telephony.ims; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * An event that indicates an {@link RcsGroupThread}'s icon was changed. Please see R6-2-5 - GSMA + * RCC.71 (RCS Universal Profile Service Definition Document) + */ +public final class RcsGroupThreadIconChangedEvent extends RcsGroupThreadEvent implements + Parcelable { + private final Uri mNewIcon; + + /** + * Creates a new {@link RcsGroupThreadIconChangedEvent}. This event is not persisted into + * storage until {@link RcsMessageStore#persistRcsEvent(RcsEvent)} is called. + * + * @param timestamp The timestamp of when this event happened, in milliseconds passed after + * midnight, January 1st, 1970 UTC + * @param rcsGroupThread The {@link RcsGroupThread} that this event happened on + * @param originatingParticipant The {@link RcsParticipant} that changed the + * {@link RcsGroupThread}'s icon. + * @param newIcon {@link Uri} to the new icon of this {@link RcsGroupThread} + * @see RcsMessageStore#persistRcsEvent(RcsEvent) + */ + public RcsGroupThreadIconChangedEvent(long timestamp, @NonNull RcsGroupThread rcsGroupThread, + @NonNull RcsParticipant originatingParticipant, @Nullable Uri newIcon) { + super(timestamp, rcsGroupThread.getThreadId(), originatingParticipant.getId()); + mNewIcon = newIcon; + } + + /** + * @hide - internal constructor for queries + */ + public RcsGroupThreadIconChangedEvent(long timestamp, int rcsGroupThreadId, + int originatingParticipantId, @Nullable Uri newIcon) { + super(timestamp, rcsGroupThreadId, originatingParticipantId); + mNewIcon = newIcon; + } + + /** + * @return Returns the {@link Uri} to the icon of the {@link RcsGroupThread} after this + * {@link RcsGroupThreadIconChangedEvent} occured. + */ + @Nullable + public Uri getNewIcon() { + return mNewIcon; + } + + /** + * Persists the event to the data store. + * + * @hide - not meant for public use. + */ + @Override + public void persist() throws RcsMessageStoreException { + // TODO ensure failure throws + RcsControllerCall.call(iRcs -> iRcs.createGroupThreadIconChangedEvent( + getTimestamp(), getRcsGroupThread().getThreadId(), + getOriginatingParticipant().getId(), mNewIcon)); + } + + public static final Creator<RcsGroupThreadIconChangedEvent> CREATOR = + new Creator<RcsGroupThreadIconChangedEvent>() { + @Override + public RcsGroupThreadIconChangedEvent createFromParcel(Parcel in) { + return new RcsGroupThreadIconChangedEvent(in); + } + + @Override + public RcsGroupThreadIconChangedEvent[] newArray(int size) { + return new RcsGroupThreadIconChangedEvent[size]; + } + }; + + private RcsGroupThreadIconChangedEvent(Parcel in) { + super(in); + mNewIcon = in.readParcelable(Uri.class.getClassLoader()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeParcelable(mNewIcon, flags); + } +} diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.aidl new file mode 100644 index 000000000000..3ed9bd11dc70 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 2019, 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; + +parcelable RcsGroupThreadNameChangedEvent; diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.java b/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.java new file mode 100644 index 000000000000..02030bc6a2ec --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.telephony.ims; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * An event that indicates an {@link RcsGroupThread}'s name was changed. Please see R6-2-5 - GSMA + * RCC.71 (RCS Universal Profile Service Definition Document) + */ +public final class RcsGroupThreadNameChangedEvent extends RcsGroupThreadEvent implements + Parcelable { + private final String mNewName; + + /** + * Creates a new {@link RcsGroupThreadNameChangedEvent}. This event is not persisted into + * storage until {@link RcsMessageStore#persistRcsEvent(RcsEvent)} is called. + * + * @param timestamp The timestamp of when this event happened, in milliseconds passed after + * midnight, January 1st, 1970 UTC + * @param rcsGroupThread The {@link RcsGroupThread} that this event happened on + * @param originatingParticipant The {@link RcsParticipant} that changed the + * {@link RcsGroupThread}'s icon. + * @param newName The new name of the {@link RcsGroupThread} + * @see RcsMessageStore#persistRcsEvent(RcsEvent) + */ + public RcsGroupThreadNameChangedEvent(long timestamp, @NonNull RcsGroupThread rcsGroupThread, + @NonNull RcsParticipant originatingParticipant, @Nullable String newName) { + super(timestamp, rcsGroupThread.getThreadId(), originatingParticipant.getId()); + mNewName = newName; + } + + /** + * @hide - internal constructor for queries + */ + public RcsGroupThreadNameChangedEvent(long timestamp, int rcsGroupThreadId, + int originatingParticipantId, @Nullable String newName) { + super(timestamp, rcsGroupThreadId, originatingParticipantId); + mNewName = newName; + } + + /** + * @return Returns the name of this {@link RcsGroupThread} after this + * {@link RcsGroupThreadNameChangedEvent} happened. + */ + @Nullable + public String getNewName() { + return mNewName; + } + + /** + * Persists the event to the data store. + * + * @hide - not meant for public use. + */ + @Override + public void persist() throws RcsMessageStoreException { + RcsControllerCall.call(iRcs -> iRcs.createGroupThreadNameChangedEvent( + getTimestamp(), getRcsGroupThread().getThreadId(), + getOriginatingParticipant().getId(), mNewName)); + } + + public static final Creator<RcsGroupThreadNameChangedEvent> CREATOR = + new Creator<RcsGroupThreadNameChangedEvent>() { + @Override + public RcsGroupThreadNameChangedEvent createFromParcel(Parcel in) { + return new RcsGroupThreadNameChangedEvent(in); + } + + @Override + public RcsGroupThreadNameChangedEvent[] newArray(int size) { + return new RcsGroupThreadNameChangedEvent[size]; + } + }; + + private RcsGroupThreadNameChangedEvent(Parcel in) { + super(in); + mNewName = in.readString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeString(mNewName); + } +} diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.aidl b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.aidl new file mode 100644 index 000000000000..420abffa067a --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.ims; + +parcelable RcsGroupThreadParticipantJoinedEvent; diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.java b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.java new file mode 100644 index 000000000000..0d1a5730f0a0 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.telephony.ims; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * An event that indicates an RCS participant has joined an {@link RcsThread}. Please see US6-3 - + * GSMA RCC.71 (RCS Universal Profile Service Definition Document) + */ +public final class RcsGroupThreadParticipantJoinedEvent extends RcsGroupThreadEvent implements + Parcelable { + private final int mJoinedParticipantId; + + /** + * Creates a new {@link RcsGroupThreadParticipantJoinedEvent}. This event is not persisted into + * storage until {@link RcsMessageStore#persistRcsEvent(RcsEvent)} is called. + * + * @param timestamp The timestamp of when this event happened, in milliseconds passed after + * midnight, January 1st, 1970 UTC + * @param rcsGroupThread The {@link RcsGroupThread} that this event happened on + * @param originatingParticipant The {@link RcsParticipant} that added or invited the new + * {@link RcsParticipant} into the {@link RcsGroupThread} + * @param joinedParticipant The new {@link RcsParticipant} that joined the + * {@link RcsGroupThread} + * @see RcsMessageStore#persistRcsEvent(RcsEvent) + */ + public RcsGroupThreadParticipantJoinedEvent(long timestamp, + @NonNull RcsGroupThread rcsGroupThread, @NonNull RcsParticipant originatingParticipant, + @NonNull RcsParticipant joinedParticipant) { + super(timestamp, rcsGroupThread.getThreadId(), originatingParticipant.getId()); + mJoinedParticipantId = joinedParticipant.getId(); + } + + /** + * @hide - internal constructor for queries + */ + public RcsGroupThreadParticipantJoinedEvent(long timestamp, int rcsGroupThreadId, + int originatingParticipantId, int joinedParticipantId) { + super(timestamp, rcsGroupThreadId, originatingParticipantId); + mJoinedParticipantId = joinedParticipantId; + } + + /** + * @return Returns the {@link RcsParticipant} that joined the associated {@link RcsGroupThread} + */ + public RcsParticipant getJoinedParticipant() { + return new RcsParticipant(mJoinedParticipantId); + } + + /** + * Persists the event to the data store. + * + * @hide - not meant for public use. + */ + @Override + public void persist() throws RcsMessageStoreException { + RcsControllerCall.call( + iRcs -> iRcs.createGroupThreadParticipantJoinedEvent(getTimestamp(), + getRcsGroupThread().getThreadId(), getOriginatingParticipant().getId(), + getJoinedParticipant().getId())); + } + + public static final Creator<RcsGroupThreadParticipantJoinedEvent> CREATOR = + new Creator<RcsGroupThreadParticipantJoinedEvent>() { + @Override + public RcsGroupThreadParticipantJoinedEvent createFromParcel(Parcel in) { + return new RcsGroupThreadParticipantJoinedEvent(in); + } + + @Override + public RcsGroupThreadParticipantJoinedEvent[] newArray(int size) { + return new RcsGroupThreadParticipantJoinedEvent[size]; + } + }; + + private RcsGroupThreadParticipantJoinedEvent(Parcel in) { + super(in); + mJoinedParticipantId = in.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(mJoinedParticipantId); + } +} diff --git a/telephony/java/android/telephony/ims/RcsMessage.aidl b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEvent.aidl index b32cd1208c40..ff139ac0ab1e 100644 --- a/telephony/java/android/telephony/ims/RcsMessage.aidl +++ b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEvent.aidl @@ -17,4 +17,4 @@ package android.telephony.ims; -parcelable RcsMessage; +parcelable RcsGroupThreadParticipantLeftEvent; diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEvent.java b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEvent.java new file mode 100644 index 000000000000..cd525086749a --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEvent.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.telephony.ims; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * An event that indicates an RCS participant has left an {@link RcsThread}. Please see US6-23 - + * GSMA RCC.71 (RCS Universal Profile Service Definition Document) + */ +public final class RcsGroupThreadParticipantLeftEvent extends RcsGroupThreadEvent implements + Parcelable { + private final int mLeavingParticipantId; + + /** + * Creates a new {@link RcsGroupThreadParticipantLeftEvent}. his event is not persisted into + * storage until {@link RcsMessageStore#persistRcsEvent(RcsEvent)} is called. + * + * @param timestamp The timestamp of when this event happened, in milliseconds passed after + * midnight, January 1st, 1970 UTC + * @param rcsGroupThread The {@link RcsGroupThread} that this event happened on + * @param originatingParticipant The {@link RcsParticipant} that removed the + * {@link RcsParticipant} from the {@link RcsGroupThread}. It is + * possible that originatingParticipant and leavingParticipant are + * the same (i.e. {@link RcsParticipant} left the group + * themselves) + * @param leavingParticipant The {@link RcsParticipant} that left the {@link RcsGroupThread} + * @see RcsMessageStore#persistRcsEvent(RcsEvent) + */ + public RcsGroupThreadParticipantLeftEvent(long timestamp, + @NonNull RcsGroupThread rcsGroupThread, @NonNull RcsParticipant originatingParticipant, + @NonNull RcsParticipant leavingParticipant) { + super(timestamp, rcsGroupThread.getThreadId(), originatingParticipant.getId()); + mLeavingParticipantId = leavingParticipant.getId(); + } + + /** + * @hide - internal constructor for queries + */ + public RcsGroupThreadParticipantLeftEvent(long timestamp, int rcsGroupThreadId, + int originatingParticipantId, int leavingParticipantId) { + super(timestamp, rcsGroupThreadId, originatingParticipantId); + mLeavingParticipantId = leavingParticipantId; + } + + /** + * @return Returns the {@link RcsParticipant} that left the associated {@link RcsGroupThread} + * after this {@link RcsGroupThreadParticipantLeftEvent} happened. + */ + @NonNull + public RcsParticipant getLeavingParticipantId() { + return new RcsParticipant(mLeavingParticipantId); + } + + @Override + public void persist() throws RcsMessageStoreException { + RcsControllerCall.call( + iRcs -> iRcs.createGroupThreadParticipantJoinedEvent(getTimestamp(), + getRcsGroupThread().getThreadId(), getOriginatingParticipant().getId(), + getLeavingParticipantId().getId())); + } + + public static final Creator<RcsGroupThreadParticipantLeftEvent> CREATOR = + new Creator<RcsGroupThreadParticipantLeftEvent>() { + @Override + public RcsGroupThreadParticipantLeftEvent createFromParcel(Parcel in) { + return new RcsGroupThreadParticipantLeftEvent(in); + } + + @Override + public RcsGroupThreadParticipantLeftEvent[] newArray(int size) { + return new RcsGroupThreadParticipantLeftEvent[size]; + } + }; + + private RcsGroupThreadParticipantLeftEvent(Parcel in) { + super(in); + mLeavingParticipantId = in.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(mLeavingParticipantId); + } +} diff --git a/telephony/java/android/telephony/ims/RcsIncomingMessage.java b/telephony/java/android/telephony/ims/RcsIncomingMessage.java index f39e06db068a..61911abd00c5 100644 --- a/telephony/java/android/telephony/ims/RcsIncomingMessage.java +++ b/telephony/java/android/telephony/ims/RcsIncomingMessage.java @@ -15,34 +15,80 @@ */ package android.telephony.ims; -import android.os.Parcel; +import android.annotation.WorkerThread; /** * This is a single instance of a message received over RCS. - * @hide - TODO(sahinc) make this public */ public class RcsIncomingMessage extends RcsMessage { - public static final Creator<RcsIncomingMessage> CREATOR = new Creator<RcsIncomingMessage>() { - @Override - public RcsIncomingMessage createFromParcel(Parcel in) { - return new RcsIncomingMessage(in); - } - - @Override - public RcsIncomingMessage[] newArray(int size) { - return new RcsIncomingMessage[size]; - } - }; - - protected RcsIncomingMessage(Parcel in) { + /** + * @hide + */ + RcsIncomingMessage(int id) { + super(id); } - @Override - public int describeContents() { - return 0; + /** + * Sets the timestamp of arrival for this message and persists into storage. The timestamp is + * defined as milliseconds passed after midnight, January 1, 1970 UTC + * + * @param arrivalTimestamp The timestamp to set to. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setArrivalTimestamp(long arrivalTimestamp) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setMessageArrivalTimestamp(mId, true, arrivalTimestamp)); + } + + /** + * @return Returns the timestamp of arrival for this message. The timestamp is defined as + * milliseconds passed after midnight, January 1, 1970 UTC + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public long getArrivalTimestamp() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getMessageArrivalTimestamp(mId, true)); + } + + /** + * Sets the timestamp of when the user saw this message and persists into storage. The timestamp + * is defined as milliseconds passed after midnight, January 1, 1970 UTC + * + * @param notifiedTimestamp The timestamp to set to. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setSeenTimestamp(long notifiedTimestamp) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setMessageSeenTimestamp(mId, true, notifiedTimestamp)); + } + + /** + * @return Returns the timestamp of when the user saw this message. The timestamp is defined as + * milliseconds passed after midnight, January 1, 1970 UTC + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public long getSeenTimestamp() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getMessageSeenTimestamp(mId, true)); + } + + /** + * @return Returns the sender of this incoming message. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public RcsParticipant getSenderParticipant() throws RcsMessageStoreException { + return new RcsParticipant( + RcsControllerCall.call(iRcs -> iRcs.getSenderParticipant(mId))); } + /** + * @return Returns {@code true} as this is an incoming message + */ @Override - public void writeToParcel(Parcel dest, int flags) { + public boolean isIncoming() { + return true; } } diff --git a/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParams.aidl b/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParams.aidl new file mode 100644 index 000000000000..1f1d4f68213a --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParams.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 2019, 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; + +parcelable RcsIncomingMessageCreationParams; diff --git a/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParams.java b/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParams.java new file mode 100644 index 000000000000..61dedbc1578a --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParams.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2019 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; + +import android.annotation.CheckResult; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * {@link RcsIncomingMessageCreationParams} is a collection of parameters that should be passed + * into {@link RcsThread#addIncomingMessage(RcsIncomingMessageCreationParams)} to generate an + * {@link RcsIncomingMessage} on that {@link RcsThread} + */ +public final class RcsIncomingMessageCreationParams extends RcsMessageCreationParams implements + Parcelable { + // The arrival timestamp for the RcsIncomingMessage to be created + private final long mArrivalTimestamp; + // The seen timestamp for the RcsIncomingMessage to be created + private final long mSeenTimestamp; + // The participant that sent this incoming message + private final int mSenderParticipantId; + + /** + * Builder to help create an {@link RcsIncomingMessageCreationParams} + * + * @see RcsThread#addIncomingMessage(RcsIncomingMessageCreationParams) + */ + public static class Builder extends RcsMessageCreationParams.Builder { + private RcsParticipant mSenderParticipant; + private long mArrivalTimestamp; + private long mSeenTimestamp; + + /** + * Creates a {@link Builder} to create an instance of + * {@link RcsIncomingMessageCreationParams} + * + * @param originationTimestamp The timestamp of {@link RcsMessage} creation. The origination + * timestamp value in milliseconds passed after midnight, + * January 1, 1970 UTC + * @param arrivalTimestamp The timestamp of arrival, defined as milliseconds passed after + * midnight, January 1, 1970 UTC + * @param subscriptionId The subscription ID that was used to send or receive this + * {@link RcsMessage} + */ + public Builder(long originationTimestamp, long arrivalTimestamp, int subscriptionId) { + super(originationTimestamp, subscriptionId); + mArrivalTimestamp = arrivalTimestamp; + } + + /** + * Sets the {@link RcsParticipant} that send this {@link RcsIncomingMessage} + * + * @param senderParticipant The {@link RcsParticipant} that sent this + * {@link RcsIncomingMessage} + * @return The same instance of {@link Builder} to chain methods. + */ + @CheckResult + public Builder setSenderParticipant(RcsParticipant senderParticipant) { + mSenderParticipant = senderParticipant; + return this; + } + + /** + * Sets the time of the arrival of this {@link RcsIncomingMessage} + + * @return The same instance of {@link Builder} to chain methods. + * @see RcsIncomingMessage#setArrivalTimestamp(long) + */ + @CheckResult + public Builder setArrivalTimestamp(long arrivalTimestamp) { + mArrivalTimestamp = arrivalTimestamp; + return this; + } + + /** + * Sets the time of the when this user saw the {@link RcsIncomingMessage} + * @param seenTimestamp The seen timestamp , defined as milliseconds passed after midnight, + * January 1, 1970 UTC + * @return The same instance of {@link Builder} to chain methods. + * @see RcsIncomingMessage#setSeenTimestamp(long) + */ + @CheckResult + public Builder setSeenTimestamp(long seenTimestamp) { + mSeenTimestamp = seenTimestamp; + return this; + } + + /** + * Creates parameters for creating a new incoming message. + * @return A new instance of {@link RcsIncomingMessageCreationParams} to create a new + * {@link RcsIncomingMessage} + */ + public RcsIncomingMessageCreationParams build() { + return new RcsIncomingMessageCreationParams(this); + } + } + + private RcsIncomingMessageCreationParams(Builder builder) { + super(builder); + mArrivalTimestamp = builder.mArrivalTimestamp; + mSeenTimestamp = builder.mSeenTimestamp; + mSenderParticipantId = builder.mSenderParticipant.getId(); + } + + private RcsIncomingMessageCreationParams(Parcel in) { + super(in); + mArrivalTimestamp = in.readLong(); + mSeenTimestamp = in.readLong(); + mSenderParticipantId = in.readInt(); + } + + /** + * @return Returns the arrival timestamp for the {@link RcsIncomingMessage} to be created. + * Timestamp is defined as milliseconds passed after midnight, January 1, 1970 UTC + */ + public long getArrivalTimestamp() { + return mArrivalTimestamp; + } + + /** + * @return Returns the seen timestamp for the {@link RcsIncomingMessage} to be created. + * Timestamp is defined as milliseconds passed after midnight, January 1, 1970 UTC + */ + public long getSeenTimestamp() { + return mSeenTimestamp; + } + + /** + * Helper getter for {@link com.android.internal.telephony.ims.RcsMessageStoreController} to + * create {@link RcsIncomingMessage}s + * + * Since the API doesn't expose any ID's to API users, this should be hidden. + * @hide + */ + public int getSenderParticipantId() { + return mSenderParticipantId; + } + + public static final Creator<RcsIncomingMessageCreationParams> CREATOR = + new Creator<RcsIncomingMessageCreationParams>() { + @Override + public RcsIncomingMessageCreationParams createFromParcel(Parcel in) { + return new RcsIncomingMessageCreationParams(in); + } + + @Override + public RcsIncomingMessageCreationParams[] newArray(int size) { + return new RcsIncomingMessageCreationParams[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest); + dest.writeLong(mArrivalTimestamp); + dest.writeLong(mSeenTimestamp); + dest.writeInt(mSenderParticipantId); + } +} diff --git a/telephony/java/android/telephony/ims/RcsLocationPart.java b/telephony/java/android/telephony/ims/RcsLocationPart.java deleted file mode 100644 index 19be4ceaf688..000000000000 --- a/telephony/java/android/telephony/ims/RcsLocationPart.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2019 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; - -import android.os.Parcel; - -/** - * A part of a composite {@link RcsMessage} that holds a location - * @hide - TODO(sahinc) make this public - */ -public class RcsLocationPart extends RcsPart { - public static final Creator<RcsLocationPart> CREATOR = new Creator<RcsLocationPart>() { - @Override - public RcsLocationPart createFromParcel(Parcel in) { - return new RcsLocationPart(in); - } - - @Override - public RcsLocationPart[] newArray(int size) { - return new RcsLocationPart[size]; - } - }; - - protected RcsLocationPart(Parcel in) { - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - } -} diff --git a/telephony/java/android/telephony/ims/RcsManager.java b/telephony/java/android/telephony/ims/RcsManager.java index df108c88e3b0..22e4b2249c36 100644 --- a/telephony/java/android/telephony/ims/RcsManager.java +++ b/telephony/java/android/telephony/ims/RcsManager.java @@ -20,15 +20,21 @@ import android.content.Context; /** * The manager class for RCS related utilities. - * @hide */ @SystemService(Context.TELEPHONY_RCS_SERVICE) public class RcsManager { + /** + * @hide + */ + public RcsManager() { + // empty constructor + } + private static final RcsMessageStore sRcsMessageStoreInstance = new RcsMessageStore(); /** - * Returns an instance of RcsMessageStore. + * Returns an instance of {@link RcsMessageStore} */ public RcsMessageStore getRcsMessageStore() { return sRcsMessageStoreInstance; diff --git a/telephony/java/android/telephony/ims/RcsMessage.java b/telephony/java/android/telephony/ims/RcsMessage.java index d46685c4a572..32274131a5ad 100644 --- a/telephony/java/android/telephony/ims/RcsMessage.java +++ b/telephony/java/android/telephony/ims/RcsMessage.java @@ -15,11 +15,315 @@ */ package android.telephony.ims; -import android.os.Parcelable; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.WorkerThread; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; /** * This is a single instance of a message sent or received over RCS. - * @hide - TODO(sahinc) make this public */ -public abstract class RcsMessage implements Parcelable { +public abstract class RcsMessage { + /** + * The value to indicate that this {@link RcsMessage} does not have any location information. + */ + public static final double LOCATION_NOT_SET = Double.MIN_VALUE; + + /** + * The status to indicate that this {@link RcsMessage}s status is not set yet. + */ + public static final int NOT_SET = 0; + + /** + * The status to indicate that this {@link RcsMessage} is a draft and is not in the process of + * sending yet. + */ + public static final int DRAFT = 1; + + /** + * The status to indicate that this {@link RcsMessage} was successfully sent. + */ + public static final int QUEUED = 2; + + /** + * The status to indicate that this {@link RcsMessage} is actively being sent. + */ + public static final int SENDING = 3; + + /** + * The status to indicate that this {@link RcsMessage} was successfully sent. + */ + public static final int SENT = 4; + + /** + * The status to indicate that this {@link RcsMessage} failed to send in an attempt before, and + * now being retried. + */ + public static final int RETRYING = 5; + + /** + * The status to indicate that this {@link RcsMessage} has permanently failed to send. + */ + public static final int FAILED = 6; + + /** + * The status to indicate that this {@link RcsMessage} was successfully received. + */ + public static final int RECEIVED = 7; + + /** + * The status to indicate that this {@link RcsMessage} was seen. + */ + public static final int SEEN = 9; + + /** + * @hide + */ + protected final int mId; + + @IntDef({ + DRAFT, QUEUED, SENDING, SENT, RETRYING, FAILED, RECEIVED, SEEN + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RcsMessageStatus { + } + + RcsMessage(int id) { + mId = id; + } + + /** + * Returns the row Id from the common message. + * + * @hide + */ + public int getId() { + return mId; + } + + /** + * @return Returns the subscription ID that this {@link RcsMessage} was sent from, or delivered + * to. + * @throws RcsMessageStoreException if the value could not be read from the storage + * @see android.telephony.SubscriptionInfo#getSubscriptionId + */ + public int getSubscriptionId() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getMessageSubId(mId, isIncoming())); + } + + /** + * Sets the subscription ID that this {@link RcsMessage} was sent from, or delivered to and + * persists it into storage. + * + * @param subId The subscription ID to persists into storage. + * @throws RcsMessageStoreException if the value could not be persisted into storage + * @see android.telephony.SubscriptionInfo#getSubscriptionId + */ + @WorkerThread + public void setSubscriptionId(int subId) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setMessageSubId(mId, isIncoming(), subId)); + } + + /** + * Sets the status of this message and persists it into storage. Please see + * {@link RcsFileTransferPart#setFileTransferStatus(int)} to set statuses around file transfers. + * + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setStatus(@RcsMessageStatus int rcsMessageStatus) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setMessageStatus(mId, isIncoming(), rcsMessageStatus)); + } + + /** + * @return Returns the status of this message. Please see + * {@link RcsFileTransferPart#setFileTransferStatus(int)} to set statuses around file transfers. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public @RcsMessageStatus int getStatus() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getMessageStatus(mId, isIncoming())); + } + + /** + * Sets the origination timestamp of this message and persists it into storage. Origination is + * defined as when the sender tapped the send button. + * + * @param timestamp The origination timestamp value in milliseconds passed after midnight, + * January 1, 1970 UTC + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setOriginationTimestamp(long timestamp) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setMessageOriginationTimestamp(mId, isIncoming(), timestamp)); + } + + /** + * @return Returns the origination timestamp of this message in milliseconds passed after + * midnight, January 1, 1970 UTC. Origination is defined as when the sender tapped the send + * button. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public long getOriginationTimestamp() throws RcsMessageStoreException { + return RcsControllerCall.call( + iRcs -> iRcs.getMessageOriginationTimestamp(mId, isIncoming())); + } + + /** + * Sets the globally unique RCS message identifier for this message and persists it into + * storage. This function does not confirm that this message id is unique. Please see 4.4.5.2 + * - GSMA RCC.53 (RCS Device API 1.6 Specification + * + * @param rcsMessageGlobalId The globally RCS message identifier + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setRcsMessageId(String rcsMessageGlobalId) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setGlobalMessageIdForMessage(mId, isIncoming(), rcsMessageGlobalId)); + } + + /** + * @return Returns the globally unique RCS message identifier for this message. Please see + * 4.4.5.2 - GSMA RCC.53 (RCS Device API 1.6 Specification + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public String getRcsMessageId() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getGlobalMessageIdForMessage(mId, isIncoming())); + } + + /** + * @return Returns the user visible text included in this message. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public String getText() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getTextForMessage(mId, isIncoming())); + } + + /** + * Sets the user visible text for this message and persists in storage. + * + * @param text The text this message now has + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setText(String text) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setTextForMessage(mId, isIncoming(), text)); + } + + /** + * @return Returns the associated latitude for this message, or + * {@link RcsMessage#LOCATION_NOT_SET} if it does not contain a location. + * + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public double getLatitude() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getLatitudeForMessage(mId, isIncoming())); + } + + /** + * Sets the latitude for this message and persists in storage. + * + * @param latitude The latitude for this location message. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setLatitude(double latitude) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setLatitudeForMessage(mId, isIncoming(), latitude)); + } + + /** + * @return Returns the associated longitude for this message, or + * {@link RcsMessage#LOCATION_NOT_SET} if it does not contain a location. + * + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public double getLongitude() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getLongitudeForMessage(mId, isIncoming())); + } + + /** + * Sets the longitude for this message and persists in storage. + * + * @param longitude The longitude for this location message. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setLongitude(double longitude) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setLongitudeForMessage(mId, isIncoming(), longitude)); + } + + /** + * Attaches an {@link RcsFileTransferPart} to this message and persists into storage. + * + * @param fileTransferCreationParameters The parameters to be used to create the + * {@link RcsFileTransferPart} + * @return A new instance of {@link RcsFileTransferPart} + * @throws RcsMessageStoreException if the file transfer could not be persisted into storage. + */ + @NonNull + @WorkerThread + public RcsFileTransferPart insertFileTransfer( + RcsFileTransferCreationParams fileTransferCreationParameters) + throws RcsMessageStoreException { + return new RcsFileTransferPart(RcsControllerCall.call( + iRcs -> iRcs.storeFileTransfer(mId, isIncoming(), fileTransferCreationParameters))); + } + + /** + * @return Returns all the {@link RcsFileTransferPart}s associated with this message in an + * unmodifiable set. + * @throws RcsMessageStoreException if the file transfers could not be read from the storage + */ + @NonNull + @WorkerThread + public Set<RcsFileTransferPart> getFileTransferParts() throws RcsMessageStoreException { + Set<RcsFileTransferPart> fileTransferParts = new HashSet<>(); + + int[] fileTransferIds = RcsControllerCall.call( + iRcs -> iRcs.getFileTransfersAttachedToMessage(mId, isIncoming())); + + for (int fileTransfer : fileTransferIds) { + fileTransferParts.add(new RcsFileTransferPart(fileTransfer)); + } + + return Collections.unmodifiableSet(fileTransferParts); + } + + /** + * Removes a {@link RcsFileTransferPart} from this message, and deletes it in storage. + * + * @param fileTransferPart The part to delete. + * @throws RcsMessageStoreException if the file transfer could not be removed from storage + */ + @WorkerThread + public void removeFileTransferPart(@NonNull RcsFileTransferPart fileTransferPart) + throws RcsMessageStoreException { + if (fileTransferPart == null) { + return; + } + + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.deleteFileTransfer(fileTransferPart.getId())); + } + + /** + * @return Returns {@code true} if this message was received on this device, {@code false} if it + * was sent. + */ + public abstract boolean isIncoming(); } diff --git a/telephony/java/android/telephony/ims/RcsMessageCreationParams.java b/telephony/java/android/telephony/ims/RcsMessageCreationParams.java new file mode 100644 index 000000000000..c46c605d861d --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsMessageCreationParams.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2019 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; + +import static android.telephony.ims.RcsMessage.LOCATION_NOT_SET; + +import android.annotation.CheckResult; +import android.annotation.Nullable; +import android.os.Parcel; + +/** + * The collection of parameters to be passed into + * {@link RcsThread#addIncomingMessage(RcsIncomingMessageCreationParams)} and + * {@link RcsThread#addOutgoingMessage(RcsOutgoingMessageCreationParams)} to create and persist + * {@link RcsMessage}s on an {@link RcsThread} + */ +public class RcsMessageCreationParams { + // The globally unique id of the RcsMessage to be created. + private final String mRcsMessageGlobalId; + + // The subscription that this message was/will be received/sent from. + private final int mSubId; + // The sending/receiving status of the message + private final @RcsMessage.RcsMessageStatus int mMessageStatus; + // The timestamp of message creation + private final long mOriginationTimestamp; + // The user visible content of the message + private final String mText; + // The latitude of the message if this is a location message + private final double mLatitude; + // The longitude of the message if this is a location message + private final double mLongitude; + + /** + * @return Returns the globally unique RCS Message ID for the {@link RcsMessage} to be created. + * Please see 4.4.5.2 - GSMA RCC.53 (RCS Device API 1.6 Specification + */ + @Nullable + public String getRcsMessageGlobalId() { + return mRcsMessageGlobalId; + } + + /** + * @return Returns the subscription ID that was used to send or receive the {@link RcsMessage} + * to be created. + */ + public int getSubId() { + return mSubId; + } + + /** + * @return Returns the status for the {@link RcsMessage} to be created. + * @see RcsMessage.RcsMessageStatus + */ + public int getMessageStatus() { + return mMessageStatus; + } + + /** + * @return Returns the origination timestamp of the {@link RcsMessage} to be created in + * milliseconds passed after midnight, January 1, 1970 UTC. Origination is defined as when + * the sender tapped the send button. + */ + public long getOriginationTimestamp() { + return mOriginationTimestamp; + } + + /** + * @return Returns the user visible text contained in the {@link RcsMessage} to be created + */ + @Nullable + public String getText() { + return mText; + } + + /** + * @return Returns the latitude of the {@link RcsMessage} to be created, or + * {@link RcsMessage#LOCATION_NOT_SET} if the message does not contain a location. + */ + public double getLatitude() { + return mLatitude; + } + + /** + * @return Returns the longitude of the {@link RcsMessage} to be created, or + * {@link RcsMessage#LOCATION_NOT_SET} if the message does not contain a location. + */ + public double getLongitude() { + return mLongitude; + } + + /** + * The base builder for creating {@link RcsMessage}s on {@link RcsThread}s. + * + * @see RcsIncomingMessageCreationParams + */ + public static class Builder { + private String mRcsMessageGlobalId; + private int mSubId; + private @RcsMessage.RcsMessageStatus int mMessageStatus; + private long mOriginationTimestamp; + private String mText; + private double mLatitude = LOCATION_NOT_SET; + private double mLongitude = LOCATION_NOT_SET; + + /** + * @hide + */ + public Builder(long originationTimestamp, int subscriptionId) { + mOriginationTimestamp = originationTimestamp; + mSubId = subscriptionId; + } + + /** + * Sets the status of the {@link RcsMessage} to be built. + * + * @param rcsMessageStatus The status to be set + * @return The same instance of {@link Builder} to chain methods + * @see RcsMessage#setStatus(int) + */ + @CheckResult + public Builder setStatus(@RcsMessage.RcsMessageStatus int rcsMessageStatus) { + mMessageStatus = rcsMessageStatus; + return this; + } + + /** + * Sets the globally unique RCS message identifier for the {@link RcsMessage} to be built. + * This function does not confirm that this message id is unique. Please see 4.4.5.2 - GSMA + * RCC.53 (RCS Device API 1.6 Specification) + * + * @param rcsMessageId The ID to be set + * @return The same instance of {@link Builder} to chain methods + * @see RcsMessage#setRcsMessageId(String) + */ + @CheckResult + public Builder setRcsMessageId(String rcsMessageId) { + mRcsMessageGlobalId = rcsMessageId; + return this; + } + + /** + * Sets the text of the {@link RcsMessage} to be built. + * + * @param text The user visible text of the message + * @return The same instance of {@link Builder} to chain methods + * @see RcsMessage#setText(String) + */ + @CheckResult + public Builder setText(String text) { + mText = text; + return this; + } + + /** + * Sets the latitude of the {@link RcsMessage} to be built. Please see US5-24 - GSMA RCC.71 + * (RCS Universal Profile Service Definition Document) + * + * @param latitude The latitude of the location information associated with this message. + * @return The same instance of {@link Builder} to chain methods + * @see RcsMessage#setLatitude(double) + */ + @CheckResult + public Builder setLatitude(double latitude) { + mLatitude = latitude; + return this; + } + + /** + * Sets the longitude of the {@link RcsMessage} to be built. Please see US5-24 - GSMA RCC.71 + * (RCS Universal Profile Service Definition Document) + * + * @param longitude The longitude of the location information associated with this message. + * @return The same instance of {@link Builder} to chain methods + * @see RcsMessage#setLongitude(double) + */ + @CheckResult + public Builder setLongitude(double longitude) { + mLongitude = longitude; + return this; + } + + /** + * @return Builds and returns a newly created {@link RcsMessageCreationParams} + */ + public RcsMessageCreationParams build() { + return new RcsMessageCreationParams(this); + } + } + + protected RcsMessageCreationParams(Builder builder) { + mRcsMessageGlobalId = builder.mRcsMessageGlobalId; + mSubId = builder.mSubId; + mMessageStatus = builder.mMessageStatus; + mOriginationTimestamp = builder.mOriginationTimestamp; + mText = builder.mText; + mLatitude = builder.mLatitude; + mLongitude = builder.mLongitude; + } + + /** + * @hide + */ + RcsMessageCreationParams(Parcel in) { + mRcsMessageGlobalId = in.readString(); + mSubId = in.readInt(); + mMessageStatus = in.readInt(); + mOriginationTimestamp = in.readLong(); + mText = in.readString(); + mLatitude = in.readDouble(); + mLongitude = in.readDouble(); + } + + /** + * @hide + */ + public void writeToParcel(Parcel dest) { + dest.writeString(mRcsMessageGlobalId); + dest.writeInt(mSubId); + dest.writeInt(mMessageStatus); + dest.writeLong(mOriginationTimestamp); + dest.writeString(mText); + dest.writeDouble(mLatitude); + dest.writeDouble(mLongitude); + } +} diff --git a/telephony/java/android/telephony/ims/RcsGroupThread.aidl b/telephony/java/android/telephony/ims/RcsMessageQueryParams.aidl index c4ce5299e512..e9cbd9cc4ebe 100644 --- a/telephony/java/android/telephony/ims/RcsGroupThread.aidl +++ b/telephony/java/android/telephony/ims/RcsMessageQueryParams.aidl @@ -17,4 +17,4 @@ package android.telephony.ims; -parcelable RcsGroupThread; +parcelable RcsMessageQueryParams; diff --git a/telephony/java/android/telephony/ims/RcsMessageQueryParams.java b/telephony/java/android/telephony/ims/RcsMessageQueryParams.java new file mode 100644 index 000000000000..535a597f5e1e --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsMessageQueryParams.java @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2019 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; + +import android.annotation.CheckResult; +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.security.InvalidParameterException; + +/** + * The parameters to pass into + * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParams)} in order to select a + * subset of {@link RcsMessage}s present in the message store. + */ +public final class RcsMessageQueryParams implements Parcelable { + /** + * @hide - not meant for public use + */ + public static final int THREAD_ID_NOT_SET = -1; + + /** + * Flag to be used with {@link Builder#setSortProperty(int)} to denote that the results should + * be sorted in the same order of {@link RcsMessage}s that got persisted into storage for faster + * results. + */ + public static final int SORT_BY_CREATION_ORDER = 0; + + /** + * Flag to be used with {@link Builder#setSortProperty(int)} to denote that the results should + * be sorted according to the timestamp of {@link RcsMessage#getOriginationTimestamp()} + */ + public static final int SORT_BY_TIMESTAMP = 1; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({SORT_BY_CREATION_ORDER, SORT_BY_TIMESTAMP}) + public @interface SortingProperty { + } + + /** + * Bitmask flag to be used with {@link Builder#setMessageType(int)} to make + * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParams)} return + * {@link RcsIncomingMessage}s. + */ + public static final int MESSAGE_TYPE_INCOMING = 0x0001; + + /** + * Bitmask flag to be used with {@link Builder#setMessageType(int)} to make + * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParams)} return + * {@link RcsOutgoingMessage}s. + */ + public static final int MESSAGE_TYPE_OUTGOING = 0x0002; + + /** + * Bitmask flag to be used with {@link Builder#setFileTransferPresence(int)} to make + * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParams)} return {@link RcsMessage}s + * that have an {@link RcsFileTransferPart} attached. + */ + public static final int MESSAGES_WITH_FILE_TRANSFERS = 0x0004; + + /** + * Bitmask flag to be used with {@link Builder#setFileTransferPresence(int)} to make + * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParams)} return {@link RcsMessage}s + * that don't have an {@link RcsFileTransferPart} attached. + */ + public static final int MESSAGES_WITHOUT_FILE_TRANSFERS = 0x0008; + + /** + * @hide - not meant for public use + */ + public static final String MESSAGE_QUERY_PARAMETERS_KEY = "message_query_parameters"; + + // Whether the result should be filtered against incoming or outgoing messages + private int mMessageType; + // Whether the result should have file transfer messages attached or not + private int mFileTransferPresence; + // The SQL "Like" clause to filter messages + private String mMessageLike; + // The property the messages should be sorted against + private @SortingProperty int mSortingProperty; + // Whether the messages should be sorted in ascending order + private boolean mIsAscending; + // The number of results that should be returned with this query + private int mLimit; + // The thread that the results should be limited to + private int mThreadId; + + RcsMessageQueryParams(int messageType, int fileTransferPresence, String messageLike, + int threadId, @SortingProperty int sortingProperty, boolean isAscending, int limit) { + mMessageType = messageType; + mFileTransferPresence = fileTransferPresence; + mMessageLike = messageLike; + mSortingProperty = sortingProperty; + mIsAscending = isAscending; + mLimit = limit; + mThreadId = threadId; + } + + /** + * @return Returns the type of {@link RcsMessage}s that this {@link RcsMessageQueryParams} + * is set to query for. + */ + public int getMessageType() { + return mMessageType; + } + + /** + * @return Returns whether the result query should return {@link RcsMessage}s with + * {@link RcsFileTransferPart}s or not + */ + public int getFileTransferPresence() { + return mFileTransferPresence; + } + + /** + * @return Returns the SQL-inspired "LIKE" clause that will be used to match {@link RcsMessage}s + */ + public String getMessageLike() { + return mMessageLike; + } + + /** + * @return Returns the number of {@link RcsThread}s to be returned from the query. A value of + * 0 means there is no set limit. + */ + public int getLimit() { + return mLimit; + } + + /** + * @return Returns the property that will be used to sort the result against. + * @see SortingProperty + */ + public @SortingProperty int getSortingProperty() { + return mSortingProperty; + } + + /** + * @return Returns {@code true} if the result set will be sorted in ascending order, + * {@code false} if it will be sorted in descending order. + */ + public boolean getSortDirection() { + return mIsAscending; + } + + /** + * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get + * the thread that the result query should be limited to. + * + * As we do not expose any sort of integer ID's to public usage, this should be hidden. + * + * @hide - not meant for public use + */ + public int getThreadId() { + return mThreadId; + } + + /** + * A helper class to build the {@link RcsMessageQueryParams}. + */ + public static class Builder { + private @SortingProperty int mSortingProperty; + private int mMessageType; + private int mFileTransferPresence; + private String mMessageLike; + private boolean mIsAscending; + private int mLimit = 100; + private int mThreadId = THREAD_ID_NOT_SET; + + /** + * Creates a new builder for {@link RcsMessageQueryParams} to be used in + * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParams)} + * + */ + public Builder() { + // empty implementation + } + + /** + * Desired number of threads to be returned from the query. Passing in 0 will return all + * existing threads at once. The limit defaults to 100. + * + * @param limit The number to limit the query result to. + * @return The same instance of the builder to chain parameters. + * @throws InvalidParameterException If the given limit is negative. + */ + @CheckResult + public Builder setResultLimit(@IntRange(from = 0) int limit) + throws InvalidParameterException { + if (limit < 0) { + throw new InvalidParameterException("The query limit must be non-negative"); + } + + mLimit = limit; + return this; + } + + /** + * Sets the type of messages to be returned from the query. + * + * @param messageType The type of message to be returned. + * @return The same instance of the builder to chain parameters. + * @see RcsMessageQueryParams#MESSAGE_TYPE_INCOMING + * @see RcsMessageQueryParams#MESSAGE_TYPE_OUTGOING + */ + @CheckResult + public Builder setMessageType(int messageType) { + mMessageType = messageType; + return this; + } + + /** + * Sets whether file transfer messages should be included in the query result or not. + * + * @param fileTransferPresence Whether file transfers should be included in the result + * @return The same instance of the builder to chain parameters. + * @see RcsMessageQueryParams#MESSAGES_WITH_FILE_TRANSFERS + * @see RcsMessageQueryParams#MESSAGES_WITHOUT_FILE_TRANSFERS + */ + @CheckResult + public Builder setFileTransferPresence(int fileTransferPresence) { + mFileTransferPresence = fileTransferPresence; + return this; + } + + /** + * Sets an SQL-inspired "like" clause to match with messages. Using a percent sign ('%') + * wildcard matches any sequence of zero or more characters. Using an underscore ('_') + * wildcard matches any single character. Not using any wildcards would only perform a + * string match. The input string is case-insensitive. + * + * The input "Wh%" would match messages "who", "where" and "what", while the input "Wh_" + * would only match "who" + * + * @param messageLike The "like" clause for matching {@link RcsMessage}s. + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setMessageLike(String messageLike) { + mMessageLike = messageLike; + return this; + } + + /** + * Sets the property where the results should be sorted against. Defaults to + * {@link RcsMessageQueryParams.SortingProperty#SORT_BY_CREATION_ORDER} + * + * @param sortingProperty against which property the results should be sorted + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setSortProperty(@SortingProperty int sortingProperty) { + mSortingProperty = sortingProperty; + return this; + } + + /** + * Sets whether the results should be sorted ascending or descending + * + * @param isAscending whether the results should be sorted ascending + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setSortDirection(boolean isAscending) { + mIsAscending = isAscending; + return this; + } + + /** + * Limits the results to the given thread. + * + * @param thread the {@link RcsThread} that results should be limited to. If set to + * {@code null}, messages on all threads will be queried + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setThread(@Nullable RcsThread thread) { + if (thread == null) { + mThreadId = THREAD_ID_NOT_SET; + } else { + mThreadId = thread.getThreadId(); + } + return this; + } + + /** + * Builds the {@link RcsMessageQueryParams} to use in + * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParams)} + * + * @return An instance of {@link RcsMessageQueryParams} to use with the message + * query. + */ + public RcsMessageQueryParams build() { + return new RcsMessageQueryParams(mMessageType, mFileTransferPresence, mMessageLike, + mThreadId, mSortingProperty, mIsAscending, mLimit); + } + } + + /** + * Parcelable boilerplate below. + */ + private RcsMessageQueryParams(Parcel in) { + mMessageType = in.readInt(); + mFileTransferPresence = in.readInt(); + mMessageLike = in.readString(); + mSortingProperty = in.readInt(); + mIsAscending = in.readBoolean(); + mLimit = in.readInt(); + mThreadId = in.readInt(); + } + + public static final Creator<RcsMessageQueryParams> CREATOR = + new Creator<RcsMessageQueryParams>() { + @Override + public RcsMessageQueryParams createFromParcel(Parcel in) { + return new RcsMessageQueryParams(in); + } + + @Override + public RcsMessageQueryParams[] newArray(int size) { + return new RcsMessageQueryParams[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mMessageType); + dest.writeInt(mFileTransferPresence); + dest.writeString(mMessageLike); + dest.writeInt(mSortingProperty); + dest.writeBoolean(mIsAscending); + dest.writeInt(mLimit); + dest.writeInt(mThreadId); + } +} diff --git a/telephony/java/android/telephony/ims/RcsMessageQueryResult.aidl b/telephony/java/android/telephony/ims/RcsMessageQueryResult.aidl new file mode 100644 index 000000000000..a73ba50b6591 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsMessageQueryResult.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 2019, 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; + +parcelable RcsMessageQueryResult; diff --git a/telephony/java/android/telephony/ims/RcsMessageQueryResult.java b/telephony/java/android/telephony/ims/RcsMessageQueryResult.java new file mode 100644 index 000000000000..3514b48e80a1 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsMessageQueryResult.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2019 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; + +import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.MESSAGE_TYPE_INCOMING; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.ims.RcsTypeIdPair; + +import java.util.ArrayList; +import java.util.List; + +/** + * The result of a {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParams)} + * call. This class allows getting the token for querying the next batch of messages in order to + * prevent handling large amounts of data at once. + */ +public final class RcsMessageQueryResult implements Parcelable { + // The token to continue the query to get the next batch of results + private RcsQueryContinuationToken mContinuationToken; + // The message type and message ID pairs for all the messages in this query result + private List<RcsTypeIdPair> mMessageTypeIdPairs; + + /** + * Internal constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController} + * to create query results + * + * @hide + */ + public RcsMessageQueryResult( + RcsQueryContinuationToken continuationToken, + List<RcsTypeIdPair> messageTypeIdPairs) { + mContinuationToken = continuationToken; + mMessageTypeIdPairs = messageTypeIdPairs; + } + + /** + * Returns a token to call + * {@link RcsMessageStore#getRcsMessages(RcsQueryContinuationToken)} + * to get the next batch of {@link RcsMessage}s. + */ + @Nullable + public RcsQueryContinuationToken getContinuationToken() { + return mContinuationToken; + } + + /** + * Returns all the {@link RcsMessage}s in the current query result. Call {@link + * RcsMessageStore#getRcsMessages(RcsQueryContinuationToken)} to get the next batch + * of {@link RcsMessage}s. + */ + @NonNull + public List<RcsMessage> getMessages() { + List<RcsMessage> messages = new ArrayList<>(); + for (RcsTypeIdPair typeIdPair : mMessageTypeIdPairs) { + if (typeIdPair.getType() == MESSAGE_TYPE_INCOMING) { + messages.add(new RcsIncomingMessage(typeIdPair.getId())); + } else { + messages.add(new RcsOutgoingMessage(typeIdPair.getId())); + } + } + + return messages; + } + + private RcsMessageQueryResult(Parcel in) { + mContinuationToken = in.readParcelable( + RcsQueryContinuationToken.class.getClassLoader()); + in.readTypedList(mMessageTypeIdPairs, RcsTypeIdPair.CREATOR); + } + + public static final Creator<RcsMessageQueryResult> CREATOR = + new Creator<RcsMessageQueryResult>() { + @Override + public RcsMessageQueryResult createFromParcel(Parcel in) { + return new RcsMessageQueryResult(in); + } + + @Override + public RcsMessageQueryResult[] newArray(int size) { + return new RcsMessageQueryResult[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mContinuationToken, flags); + dest.writeTypedList(mMessageTypeIdPairs); + } +} diff --git a/telephony/java/android/telephony/ims/RcsManager.aidl b/telephony/java/android/telephony/ims/RcsMessageSnippet.aidl index 63bc71c5ee46..99b8eb704e00 100644 --- a/telephony/java/android/telephony/ims/RcsManager.aidl +++ b/telephony/java/android/telephony/ims/RcsMessageSnippet.aidl @@ -17,4 +17,4 @@ package android.telephony.ims; -parcelable RcsManager; +parcelable RcsMessageSnippet; diff --git a/telephony/java/android/telephony/ims/RcsMessageSnippet.java b/telephony/java/android/telephony/ims/RcsMessageSnippet.java new file mode 100644 index 000000000000..b0b930c56e91 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsMessageSnippet.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2019 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; + +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.ims.RcsMessage.RcsMessageStatus; + +/** + * An immutable summary of the latest {@link RcsMessage} on an {@link RcsThread} + */ +public final class RcsMessageSnippet implements Parcelable { + private final String mText; + private final @RcsMessageStatus int mStatus; + private final long mTimestamp; + + /** + * @hide + */ + public RcsMessageSnippet(String text, @RcsMessageStatus int status, long timestamp) { + mText = text; + mStatus = status; + mTimestamp = timestamp; + } + + /** + * @return Returns the text of the {@link RcsMessage} with highest origination timestamp value + * (i.e. latest) in this thread + */ + @Nullable + public String getSnippetText() { + return mText; + } + + /** + * @return Returns the status of the {@link RcsMessage} with highest origination timestamp value + * (i.e. latest) in this thread + */ + public @RcsMessageStatus int getSnippetStatus() { + return mStatus; + } + + /** + * @return Returns the timestamp of the {@link RcsMessage} with highest origination timestamp + * value (i.e. latest) in this thread + */ + public long getSnippetTimestamp() { + return mTimestamp; + } + + private RcsMessageSnippet(Parcel in) { + mText = in.readString(); + mStatus = in.readInt(); + mTimestamp = in.readLong(); + } + + public static final Creator<RcsMessageSnippet> CREATOR = + new Creator<RcsMessageSnippet>() { + @Override + public RcsMessageSnippet createFromParcel(Parcel in) { + return new RcsMessageSnippet(in); + } + + @Override + public RcsMessageSnippet[] newArray(int size) { + return new RcsMessageSnippet[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mText); + dest.writeInt(mStatus); + dest.writeLong(mTimestamp); + } +} diff --git a/telephony/java/android/telephony/ims/RcsMessageStore.java b/telephony/java/android/telephony/ims/RcsMessageStore.java index 1bf6ffd81ca0..d811c6e93a56 100644 --- a/telephony/java/android/telephony/ims/RcsMessageStore.java +++ b/telephony/java/android/telephony/ims/RcsMessageStore.java @@ -16,106 +16,221 @@ package android.telephony.ims; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.WorkerThread; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.telephony.Rlog; -import android.telephony.ims.aidl.IRcs; +import android.net.Uri; + +import java.util.List; /** * RcsMessageStore is the application interface to RcsProvider and provides access methods to * RCS related database tables. - * @hide - TODO make this public */ public class RcsMessageStore { - static final String TAG = "RcsMessageStore"; - /** * Returns the first chunk of existing {@link RcsThread}s in the common storage. + * * @param queryParameters Parameters to specify to return a subset of all RcsThreads. * Passing a value of null will return all threads. + * @throws RcsMessageStoreException if the query could not be completed on the storage */ @WorkerThread - public RcsThreadQueryResult getRcsThreads(@Nullable RcsThreadQueryParameters queryParameters) { - try { - IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs")); - if (iRcs != null) { - return iRcs.getRcsThreads(queryParameters); - } - } catch (RemoteException re) { - Rlog.e(TAG, "RcsMessageStore: Exception happened during getRcsThreads", re); - } - - return null; + @NonNull + public RcsThreadQueryResult getRcsThreads(@Nullable RcsThreadQueryParams queryParameters) + throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getRcsThreads(queryParameters)); } /** * Returns the next chunk of {@link RcsThread}s in the common storage. + * * @param continuationToken A token to continue the query to get the next chunk. This is - * obtained through {@link RcsThreadQueryResult#nextChunkToken}. + * obtained through {@link RcsThreadQueryResult#getContinuationToken}. + * @throws RcsMessageStoreException if the query could not be completed on the storage */ @WorkerThread - public RcsThreadQueryResult getRcsThreads(RcsThreadQueryContinuationToken continuationToken) { - try { - IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs")); - if (iRcs != null) { - return iRcs.getRcsThreadsWithToken(continuationToken); - } - } catch (RemoteException re) { - Rlog.e(TAG, "RcsMessageStore: Exception happened during getRcsThreads", re); - } + @NonNull + public RcsThreadQueryResult getRcsThreads(@NonNull RcsQueryContinuationToken continuationToken) + throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getRcsThreadsWithToken(continuationToken)); + } + + /** + * Returns the first chunk of existing {@link RcsParticipant}s in the common storage. + * + * @param queryParameters Parameters to specify to return a subset of all RcsParticipants. + * Passing a value of null will return all participants. + * @throws RcsMessageStoreException if the query could not be completed on the storage + */ + @WorkerThread + @NonNull + public RcsParticipantQueryResult getRcsParticipants( + @Nullable RcsParticipantQueryParams queryParameters) + throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getParticipants(queryParameters)); + } + + /** + * Returns the next chunk of {@link RcsParticipant}s in the common storage. + * + * @param continuationToken A token to continue the query to get the next chunk. This is + * obtained through + * {@link RcsParticipantQueryResult#getContinuationToken} + * @throws RcsMessageStoreException if the query could not be completed on the storage + */ + @WorkerThread + @NonNull + public RcsParticipantQueryResult getRcsParticipants( + @NonNull RcsQueryContinuationToken continuationToken) + throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getParticipantsWithToken(continuationToken)); + } + + /** + * Returns the first chunk of existing {@link RcsMessage}s in the common storage. + * + * @param queryParameters Parameters to specify to return a subset of all RcsMessages. + * Passing a value of null will return all messages. + * @throws RcsMessageStoreException if the query could not be completed on the storage + */ + @WorkerThread + @NonNull + public RcsMessageQueryResult getRcsMessages( + @Nullable RcsMessageQueryParams queryParameters) throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getMessages(queryParameters)); + } + + /** + * Returns the next chunk of {@link RcsMessage}s in the common storage. + * + * @param continuationToken A token to continue the query to get the next chunk. This is + * obtained through {@link RcsMessageQueryResult#getContinuationToken} + * @throws RcsMessageStoreException if the query could not be completed on the storage + */ + @WorkerThread + @NonNull + public RcsMessageQueryResult getRcsMessages( + @NonNull RcsQueryContinuationToken continuationToken) throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getMessagesWithToken(continuationToken)); + } + + /** + * Returns the first chunk of existing {@link RcsEvent}s in the common storage. + * + * @param queryParameters Parameters to specify to return a subset of all RcsEvents. + * Passing a value of null will return all events. + * @throws RcsMessageStoreException if the query could not be completed on the storage + */ + @WorkerThread + @NonNull + public RcsEventQueryResult getRcsEvents( + @Nullable RcsEventQueryParams queryParameters) throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getEvents(queryParameters)); + } - return null; + /** + * Returns the next chunk of {@link RcsEvent}s in the common storage. + * + * @param continuationToken A token to continue the query to get the next chunk. This is + * obtained through {@link RcsEventQueryResult#getContinuationToken}. + * @throws RcsMessageStoreException if the query could not be completed on the storage + */ + @WorkerThread + @NonNull + public RcsEventQueryResult getRcsEvents( + @NonNull RcsQueryContinuationToken continuationToken) throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getEventsWithToken(continuationToken)); + } + + /** + * Persists an {@link RcsEvent} to common storage. + * + * @param persistableEvent The {@link RcsEvent} to persist into storage. + * @throws RcsMessageStoreException if the query could not be completed on the storage + * + * @see RcsGroupThreadNameChangedEvent + * @see RcsGroupThreadIconChangedEvent + * @see RcsGroupThreadParticipantJoinedEvent + * @see RcsGroupThreadParticipantLeftEvent + * @see RcsParticipantAliasChangedEvent + */ + @WorkerThread + @NonNull + public void persistRcsEvent(RcsEvent persistableEvent) throws RcsMessageStoreException { + persistableEvent.persist(); } /** * Creates a new 1 to 1 thread with the given participant and persists it in the storage. + * + * @param recipient The {@link RcsParticipant} that will receive the messages in this thread. + * @return The newly created {@link Rcs1To1Thread} + * @throws RcsMessageStoreException if the thread could not be persisted in the storage */ @WorkerThread - public Rcs1To1Thread createRcs1To1Thread(RcsParticipant recipient) { - try { - IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs")); - if (iRcs != null) { - return iRcs.createRcs1To1Thread(recipient); + @NonNull + public Rcs1To1Thread createRcs1To1Thread(@NonNull RcsParticipant recipient) + throws RcsMessageStoreException { + return new Rcs1To1Thread( + RcsControllerCall.call(iRcs -> iRcs.createRcs1To1Thread(recipient.getId()))); + } + + /** + * Creates a new group thread with the given participants and persists it in the storage. + * + * @throws RcsMessageStoreException if the thread could not be persisted in the storage + */ + @WorkerThread + @NonNull + public RcsGroupThread createGroupThread(@Nullable List<RcsParticipant> recipients, + @Nullable String groupName, @Nullable Uri groupIcon) throws RcsMessageStoreException { + int[] recipientIds = null; + if (recipients != null) { + recipientIds = new int[recipients.size()]; + + for (int i = 0; i < recipients.size(); i++) { + recipientIds[i] = recipients.get(i).getId(); } - } catch (RemoteException re) { - Rlog.e(TAG, "RcsMessageStore: Exception happened during createRcs1To1Thread", re); } - return null; + int[] finalRecipientIds = recipientIds; + return new RcsGroupThread(RcsControllerCall.call( + iRcs -> iRcs.createGroupThread(finalRecipientIds, groupName, groupIcon))); } /** - * Delete the {@link RcsThread} identified by the given threadId. - * @param threadId threadId of the thread to be deleted. + * Delete the given {@link RcsThread} from the storage. + * + * @param thread The thread to be deleted. + * @throws RcsMessageStoreException if the thread could not be deleted from the storage */ @WorkerThread - public void deleteThread(int threadId) { - try { - IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs")); - if (iRcs != null) { - iRcs.deleteThread(threadId); - } - } catch (RemoteException re) { - Rlog.e(TAG, "RcsMessageStore: Exception happened during deleteThread", re); + public void deleteThread(@NonNull RcsThread thread) throws RcsMessageStoreException { + if (thread == null) { + return; + } + + boolean isDeleteSucceeded = RcsControllerCall.call( + iRcs -> iRcs.deleteThread(thread.getThreadId(), thread.getThreadType())); + + if (!isDeleteSucceeded) { + throw new RcsMessageStoreException("Could not delete RcsThread"); } } /** * Creates a new participant and persists it in the storage. + * * @param canonicalAddress The defining address (e.g. phone number) of the participant. + * @param alias The RCS alias for the participant. + * @throws RcsMessageStoreException if the participant could not be created on the storage */ - public RcsParticipant createRcsParticipant(String canonicalAddress) { - try { - IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs")); - if (iRcs != null) { - return iRcs.createRcsParticipant(canonicalAddress); - } - } catch (RemoteException re) { - Rlog.e(TAG, "RcsMessageStore: Exception happened during createRcsParticipant", re); - } - - return null; + @WorkerThread + @NonNull + public RcsParticipant createRcsParticipant(String canonicalAddress, @Nullable String alias) + throws RcsMessageStoreException { + return new RcsParticipant( + RcsControllerCall.call(iRcs -> iRcs.createRcsParticipant(canonicalAddress, alias))); } } diff --git a/telephony/java/android/telephony/ims/RcsThreadEvent.java b/telephony/java/android/telephony/ims/RcsMessageStoreException.java index e10baab9d8c5..f25bb173be37 100644 --- a/telephony/java/android/telephony/ims/RcsThreadEvent.java +++ b/telephony/java/android/telephony/ims/RcsMessageStoreException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (c) 2019 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. @@ -13,13 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.telephony.ims; -import android.os.Parcelable; +package android.telephony.ims; /** - * An event that happened on an {@link RcsThread}. - * @hide - TODO(sahinc) make this public + * An exception that happened on {@link RcsMessageStore} or one of the derived storage classes in + * {@link android.telephony.ims} */ -public abstract class RcsThreadEvent implements Parcelable { +public class RcsMessageStoreException extends Exception { + + /** + * Constructs an {@link RcsMessageStoreException} with the specified detail message. + * @param message The detail message + * @see Throwable#getMessage() + */ + public RcsMessageStoreException(String message) { + super(message); + } } diff --git a/telephony/java/android/telephony/ims/RcsMultiMediaPart.java b/telephony/java/android/telephony/ims/RcsMultiMediaPart.java deleted file mode 100644 index d295fba365f0..000000000000 --- a/telephony/java/android/telephony/ims/RcsMultiMediaPart.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2019 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; - -import android.os.Parcel; - -/** - * A part of a composite {@link RcsMessage} that holds a media that is rendered on the screen - * (i.e. image, video etc) - * @hide - TODO(sahinc) make this public - */ -public class RcsMultiMediaPart extends RcsFileTransferPart { - public static final Creator<RcsMultiMediaPart> CREATOR = new Creator<RcsMultiMediaPart>() { - @Override - public RcsMultiMediaPart createFromParcel(Parcel in) { - return new RcsMultiMediaPart(in); - } - - @Override - public RcsMultiMediaPart[] newArray(int size) { - return new RcsMultiMediaPart[size]; - } - }; - - protected RcsMultiMediaPart(Parcel in) { - super(in); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - } -} diff --git a/telephony/java/android/telephony/ims/RcsMultimediaPart.aidl b/telephony/java/android/telephony/ims/RcsMultimediaPart.aidl deleted file mode 100644 index 5992d95c3b9c..000000000000 --- a/telephony/java/android/telephony/ims/RcsMultimediaPart.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 2019, 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; - -parcelable RcsMultimediaPart; diff --git a/telephony/java/android/telephony/ims/RcsOutgoingMessage.aidl b/telephony/java/android/telephony/ims/RcsOutgoingMessage.aidl deleted file mode 100644 index 6e0c80f3af81..000000000000 --- a/telephony/java/android/telephony/ims/RcsOutgoingMessage.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 2019, 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; - -parcelable RcsOutgoingMessage; diff --git a/telephony/java/android/telephony/ims/RcsOutgoingMessage.java b/telephony/java/android/telephony/ims/RcsOutgoingMessage.java index bfb161133618..06fb83268afb 100644 --- a/telephony/java/android/telephony/ims/RcsOutgoingMessage.java +++ b/telephony/java/android/telephony/ims/RcsOutgoingMessage.java @@ -15,34 +15,51 @@ */ package android.telephony.ims; -import android.os.Parcel; +import android.annotation.NonNull; +import android.annotation.WorkerThread; + +import java.util.ArrayList; +import java.util.List; /** * This is a single instance of a message sent over RCS. - * @hide - TODO(sahinc) make this public */ public class RcsOutgoingMessage extends RcsMessage { - public static final Creator<RcsOutgoingMessage> CREATOR = new Creator<RcsOutgoingMessage>() { - @Override - public RcsOutgoingMessage createFromParcel(Parcel in) { - return new RcsOutgoingMessage(in); - } + RcsOutgoingMessage(int id) { + super(id); + } - @Override - public RcsOutgoingMessage[] newArray(int size) { - return new RcsOutgoingMessage[size]; - } - }; + /** + * @return Returns the {@link RcsOutgoingMessageDelivery}s associated with this message. Please + * note that the deliveries returned for the {@link RcsOutgoingMessage} may not always match the + * {@link RcsParticipant}s on the {@link RcsGroupThread} as the group recipients may have + * changed. + * @throws RcsMessageStoreException if the outgoing deliveries could not be read from storage. + */ + @NonNull + @WorkerThread + public List<RcsOutgoingMessageDelivery> getOutgoingDeliveries() + throws RcsMessageStoreException { + int[] deliveryParticipants; + List<RcsOutgoingMessageDelivery> messageDeliveries = new ArrayList<>(); - protected RcsOutgoingMessage(Parcel in) { - } + deliveryParticipants = RcsControllerCall.call( + iRcs -> iRcs.getMessageRecipients(mId)); - @Override - public int describeContents() { - return 0; + if (deliveryParticipants != null) { + for (Integer deliveryParticipant : deliveryParticipants) { + messageDeliveries.add(new RcsOutgoingMessageDelivery(deliveryParticipant, mId)); + } + } + + return messageDeliveries; } + /** + * @return Returns {@code false} as this is not an incoming message. + */ @Override - public void writeToParcel(Parcel dest, int flags) { + public boolean isIncoming() { + return false; } } diff --git a/telephony/java/android/telephony/ims/RcsOutgoingMessageCreationParams.aidl b/telephony/java/android/telephony/ims/RcsOutgoingMessageCreationParams.aidl new file mode 100644 index 000000000000..0c38d9f5766b --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsOutgoingMessageCreationParams.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 2019, 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; + +parcelable RcsOutgoingMessageCreationParams; diff --git a/telephony/java/android/telephony/ims/RcsOutgoingMessageCreationParams.java b/telephony/java/android/telephony/ims/RcsOutgoingMessageCreationParams.java new file mode 100644 index 000000000000..979634a069df --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsOutgoingMessageCreationParams.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2019 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; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * {@link RcsOutgoingMessageCreationParams} is a collection of parameters that should be passed + * into {@link RcsThread#addOutgoingMessage(RcsOutgoingMessageCreationParams)} to generate an + * {@link RcsOutgoingMessage} on that {@link RcsThread} + */ +public final class RcsOutgoingMessageCreationParams extends RcsMessageCreationParams + implements Parcelable { + /** + * A builder to instantiate and persist an {@link RcsOutgoingMessage} + */ + public static class Builder extends RcsMessageCreationParams.Builder { + + /** + * Creates a new {@link Builder} to create an instance of + * {@link RcsOutgoingMessageCreationParams}. + * + * @param originationTimestamp The timestamp of {@link RcsMessage} creation. The origination + * timestamp value in milliseconds passed after midnight, + * January 1, 1970 UTC + * @param subscriptionId The subscription ID that was used to send or receive this + * {@link RcsMessage} + * @see android.telephony.SubscriptionInfo#getSubscriptionId() + */ + public Builder(long originationTimestamp, int subscriptionId) { + super(originationTimestamp, subscriptionId); + } + + /** + * Creates configuration parameters for a new message. + */ + public RcsOutgoingMessageCreationParams build() { + return new RcsOutgoingMessageCreationParams(this); + } + } + + private RcsOutgoingMessageCreationParams(Builder builder) { + super(builder); + } + + private RcsOutgoingMessageCreationParams(Parcel in) { + super(in); + } + + public static final Creator<RcsOutgoingMessageCreationParams> CREATOR = + new Creator<RcsOutgoingMessageCreationParams>() { + @Override + public RcsOutgoingMessageCreationParams createFromParcel(Parcel in) { + return new RcsOutgoingMessageCreationParams(in); + } + + @Override + public RcsOutgoingMessageCreationParams[] newArray(int size) { + return new RcsOutgoingMessageCreationParams[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest); + } +} diff --git a/telephony/java/android/telephony/ims/RcsOutgoingMessageDelivery.java b/telephony/java/android/telephony/ims/RcsOutgoingMessageDelivery.java new file mode 100644 index 000000000000..1c87b13f0dfb --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsOutgoingMessageDelivery.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.telephony.ims; + +import android.annotation.NonNull; +import android.annotation.WorkerThread; + +/** + * This class holds the delivery information of an {@link RcsOutgoingMessage} for each + * {@link RcsParticipant} that the message was intended for. + */ +public class RcsOutgoingMessageDelivery { + // The participant that this delivery is intended for + private final int mRecipientId; + // The message this delivery is associated with + private final int mRcsOutgoingMessageId; + + /** + * Constructor to be used with RcsOutgoingMessage.getDelivery() + * + * @hide + */ + RcsOutgoingMessageDelivery(int recipientId, int messageId) { + mRecipientId = recipientId; + mRcsOutgoingMessageId = messageId; + } + + /** + * Sets the delivery time of this outgoing delivery and persists into storage. + * + * @param deliveredTimestamp The timestamp to set to delivery. It is defined as milliseconds + * passed after midnight, January 1, 1970 UTC + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setDeliveredTimestamp(long deliveredTimestamp) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setOutgoingDeliveryDeliveredTimestamp( + mRcsOutgoingMessageId, mRecipientId, deliveredTimestamp)); + } + + /** + * @return Returns the delivered timestamp of the associated message to the associated + * participant. Timestamp is defined as milliseconds passed after midnight, January 1, 1970 UTC. + * Returns 0 if the {@link RcsOutgoingMessage} is not delivered yet. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public long getDeliveredTimestamp() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getOutgoingDeliveryDeliveredTimestamp( + mRcsOutgoingMessageId, mRecipientId)); + } + + /** + * Sets the seen time of this outgoing delivery and persists into storage. + * + * @param seenTimestamp The timestamp to set to delivery. It is defined as milliseconds + * passed after midnight, January 1, 1970 UTC + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setSeenTimestamp(long seenTimestamp) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setOutgoingDeliverySeenTimestamp( + mRcsOutgoingMessageId, mRecipientId, seenTimestamp)); + } + + /** + * @return Returns the seen timestamp of the associated message by the associated + * participant. Timestamp is defined as milliseconds passed after midnight, January 1, 1970 UTC. + * Returns 0 if the {@link RcsOutgoingMessage} is not seen yet. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public long getSeenTimestamp() throws RcsMessageStoreException { + return RcsControllerCall.call( + iRcs -> iRcs.getOutgoingDeliverySeenTimestamp(mRcsOutgoingMessageId, mRecipientId)); + } + + /** + * Sets the status of this outgoing delivery and persists into storage. + * + * @param status The status of the associated {@link RcsMessage}s delivery to the associated + * {@link RcsParticipant} + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setStatus(@RcsMessage.RcsMessageStatus int status) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setOutgoingDeliveryStatus( + mRcsOutgoingMessageId, mRecipientId, status)); + } + + /** + * @return Returns the status of this outgoing delivery. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public @RcsMessage.RcsMessageStatus int getStatus() throws RcsMessageStoreException { + return RcsControllerCall.call( + iRcs -> iRcs.getOutgoingDeliveryStatus(mRcsOutgoingMessageId, mRecipientId)); + } + + /** + * @return Returns the recipient associated with this delivery. + */ + @NonNull + public RcsParticipant getRecipient() { + return new RcsParticipant(mRecipientId); + } + + /** + * @return Returns the {@link RcsOutgoingMessage} associated with this delivery. + */ + @NonNull + public RcsOutgoingMessage getMessage() { + return new RcsOutgoingMessage(mRcsOutgoingMessageId); + } +} diff --git a/telephony/java/android/telephony/ims/RcsParticipant.aidl b/telephony/java/android/telephony/ims/RcsParticipant.aidl deleted file mode 100644 index 1c4436367e54..000000000000 --- a/telephony/java/android/telephony/ims/RcsParticipant.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 2019, 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; - -parcelable RcsParticipant; diff --git a/telephony/java/android/telephony/ims/RcsParticipant.java b/telephony/java/android/telephony/ims/RcsParticipant.java index f678ec7e435b..7ba5d8e65f76 100644 --- a/telephony/java/android/telephony/ims/RcsParticipant.java +++ b/telephony/java/android/telephony/ims/RcsParticipant.java @@ -15,33 +15,15 @@ */ package android.telephony.ims; -import static android.telephony.ims.RcsMessageStore.TAG; - -import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.WorkerThread; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.telephony.Rlog; -import android.telephony.ims.aidl.IRcs; -import android.text.TextUtils; - -import com.android.internal.util.Preconditions; /** * RcsParticipant is an RCS capable contact that can participate in {@link RcsThread}s. - * @hide - TODO(sahinc) make this public */ -public class RcsParticipant implements Parcelable { +public class RcsParticipant { // The row ID of this participant in the database private int mId; - // The phone number of this participant - private String mCanonicalAddress; - // The RCS alias of this participant. This is different than the name of the contact in the - // Contacts app - i.e. RCS protocol allows users to define aliases for themselves that doesn't - // require other users to add them as contacts and give them a name. - private String mAlias; /** * Constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController} @@ -49,106 +31,95 @@ public class RcsParticipant implements Parcelable { * * @hide */ - public RcsParticipant(int id, @NonNull String canonicalAddress) { + public RcsParticipant(int id) { mId = id; - mCanonicalAddress = canonicalAddress; } /** - * @return Returns the canonical address (i.e. normalized phone number) for this participant + * @return Returns the canonical address (i.e. normalized phone number) for this + * {@link RcsParticipant} + * @throws RcsMessageStoreException if the value could not be read from the storage */ - public String getCanonicalAddress() { - return mCanonicalAddress; + @Nullable + @WorkerThread + public String getCanonicalAddress() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getRcsParticipantCanonicalAddress(mId)); } /** - * Sets the canonical address for this participant and updates it in storage. - * @param canonicalAddress the canonical address to update to. + * @return Returns the alias for this {@link RcsParticipant}. Alias is usually the real name of + * the person themselves. Please see US5-15 - GSMA RCC.71 (RCS Universal Profile Service + * Definition Document) + * @throws RcsMessageStoreException if the value could not be read from the storage */ + @Nullable @WorkerThread - public void setCanonicalAddress(@NonNull String canonicalAddress) { - Preconditions.checkNotNull(canonicalAddress); - if (canonicalAddress.equals(mCanonicalAddress)) { - return; - } - - mCanonicalAddress = canonicalAddress; - - try { - IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs")); - if (iRcs != null) { - iRcs.updateRcsParticipantCanonicalAddress(mId, mCanonicalAddress); - } - } catch (RemoteException re) { - Rlog.e(TAG, "RcsParticipant: Exception happened during setCanonicalAddress", re); - } + public String getAlias() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getRcsParticipantAlias(mId)); } /** - * @return Returns the alias for this participant. Alias is usually the real name of the person - * themselves. + * Sets the alias for this {@link RcsParticipant} and persists it in storage. Alias is usually + * the real name of the person themselves. Please see US5-15 - GSMA RCC.71 (RCS Universal + * Profile Service Definition Document) + * + * @param alias The alias to set to. + * @throws RcsMessageStoreException if the value could not be persisted into storage */ - public String getAlias() { - return mAlias; + @WorkerThread + public void setAlias(String alias) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setRcsParticipantAlias(mId, alias)); } /** - * Sets the alias for this participant and persists it in storage. Alias is usually the real - * name of the person themselves. + * @return Returns the contact ID for this {@link RcsParticipant}. Contact ID is a unique ID for + * an {@link RcsParticipant} that is RCS provisioned. Please see 4.4.5 - GSMA RCC.53 (RCS Device + * API 1.6 Specification) + * @throws RcsMessageStoreException if the value could not be read from the storage */ + @Nullable @WorkerThread - public void setAlias(String alias) { - if (TextUtils.equals(mAlias, alias)) { - return; - } - mAlias = alias; - - try { - IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs")); - if (iRcs != null) { - iRcs.updateRcsParticipantAlias(mId, mAlias); - } - } catch (RemoteException re) { - Rlog.e(TAG, "RcsParticipant: Exception happened during setCanonicalAddress", re); - } + public String getContactId() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getRcsParticipantContactId(mId)); } /** - * Returns the row id of this participant. This is not meant to be part of the SDK + * Sets the contact ID for this {@link RcsParticipant}. Contact ID is a unique ID for + * an {@link RcsParticipant} that is RCS provisioned. Please see 4.4.5 - GSMA RCC.53 (RCS Device + * API 1.6 Specification) * - * @hide + * @param contactId The contact ID to set to. + * @throws RcsMessageStoreException if the value could not be persisted into storage */ - public int getId() { - return mId; + @WorkerThread + public void setContactId(String contactId) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setRcsParticipantContactId(mId, contactId)); } - public static final Creator<RcsParticipant> CREATOR = new Creator<RcsParticipant>() { - @Override - public RcsParticipant createFromParcel(Parcel in) { - return new RcsParticipant(in); + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; } - - @Override - public RcsParticipant[] newArray(int size) { - return new RcsParticipant[size]; + if (!(obj instanceof RcsParticipant)) { + return false; } - }; + RcsParticipant other = (RcsParticipant) obj; - protected RcsParticipant(Parcel in) { - mId = in.readInt(); - mCanonicalAddress = in.readString(); - mAlias = in.readString(); + return mId == other.mId; } @Override - public int describeContents() { - return 0; + public int hashCode() { + return mId; } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mId); - dest.writeString(mCanonicalAddress); - dest.writeString(mAlias); + /** + * Returns the row id of this participant. This is not meant to be part of the SDK + * + * @hide + */ + public int getId() { + return mId; } } diff --git a/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEvent.java b/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEvent.java index b9ca5a86f84d..c9a2b0d07bc8 100644 --- a/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEvent.java +++ b/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEvent.java @@ -15,27 +15,92 @@ */ package android.telephony.ims; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Parcel; +import android.os.Parcelable; /** - * An event that indicates an {@link RcsParticipant}'s alias was changed. - * @hide - TODO(sahinc) make this public + * An event that indicates an {@link RcsParticipant}'s alias was changed. Please see US18-2 - GSMA + * RCC.71 (RCS Universal Profile Service Definition Document) */ -public class RcsParticipantAliasChangedEvent extends RcsParticipantEvent { +public final class RcsParticipantAliasChangedEvent extends RcsEvent implements Parcelable { + // The ID of the participant that changed their alias + private final int mParticipantId; + // The new alias of the above participant + private final String mNewAlias; + + /** + * Creates a new {@link RcsParticipantAliasChangedEvent}. This event is not persisted into + * storage until {@link RcsMessageStore#persistRcsEvent(RcsEvent)} is called. + * + * @param timestamp The timestamp of when this event happened, in milliseconds passed after + * midnight, January 1st, 1970 UTC + * @param participant The {@link RcsParticipant} that got their alias changed + * @param newAlias The new alias the {@link RcsParticipant} has. + * @see RcsMessageStore#persistRcsEvent(RcsEvent) + */ + public RcsParticipantAliasChangedEvent(long timestamp, @NonNull RcsParticipant participant, + @Nullable String newAlias) { + super(timestamp); + mParticipantId = participant.getId(); + mNewAlias = newAlias; + } + + /** + * @hide - internal constructor for queries + */ + public RcsParticipantAliasChangedEvent(long timestamp, int participantId, + @Nullable String newAlias) { + super(timestamp); + mParticipantId = participantId; + mNewAlias = newAlias; + } + + /** + * @return Returns the {@link RcsParticipant} whose alias was changed. + */ + @NonNull + public RcsParticipant getParticipantId() { + return new RcsParticipant(mParticipantId); + } + + /** + * @return Returns the alias of the associated {@link RcsParticipant} after this event happened + */ + @Nullable + public String getNewAlias() { + return mNewAlias; + } + + /** + * Persists the event to the data store. + * + * @hide - not meant for public use. + */ + @Override + public void persist() throws RcsMessageStoreException { + RcsControllerCall.call(iRcs -> iRcs.createParticipantAliasChangedEvent( + getTimestamp(), getParticipantId().getId(), getNewAlias())); + } + public static final Creator<RcsParticipantAliasChangedEvent> CREATOR = new Creator<RcsParticipantAliasChangedEvent>() { - @Override - public RcsParticipantAliasChangedEvent createFromParcel(Parcel in) { - return new RcsParticipantAliasChangedEvent(in); - } + @Override + public RcsParticipantAliasChangedEvent createFromParcel(Parcel in) { + return new RcsParticipantAliasChangedEvent(in); + } - @Override - public RcsParticipantAliasChangedEvent[] newArray(int size) { - return new RcsParticipantAliasChangedEvent[size]; - } - }; + @Override + public RcsParticipantAliasChangedEvent[] newArray(int size) { + return new RcsParticipantAliasChangedEvent[size]; + } + }; - protected RcsParticipantAliasChangedEvent(Parcel in) { + private RcsParticipantAliasChangedEvent(Parcel in) { + super(in); + mNewAlias = in.readString(); + mParticipantId = in.readInt(); } @Override @@ -45,5 +110,8 @@ public class RcsParticipantAliasChangedEvent extends RcsParticipantEvent { @Override public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeString(mNewAlias); + dest.writeInt(mParticipantId); } } diff --git a/telephony/java/android/telephony/ims/RcsParticipantEvent.aidl b/telephony/java/android/telephony/ims/RcsParticipantEvent.aidl deleted file mode 100644 index c0a77897abd5..000000000000 --- a/telephony/java/android/telephony/ims/RcsParticipantEvent.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 2019, 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; - -parcelable RcsParticipantEvent; diff --git a/telephony/java/android/telephony/ims/RcsParticipantQueryParams.aidl b/telephony/java/android/telephony/ims/RcsParticipantQueryParams.aidl new file mode 100644 index 000000000000..b7c0f93c8c5f --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsParticipantQueryParams.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 2019, 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; + +parcelable RcsParticipantQueryParams; diff --git a/telephony/java/android/telephony/ims/RcsParticipantQueryParams.java b/telephony/java/android/telephony/ims/RcsParticipantQueryParams.java new file mode 100644 index 000000000000..d24d079d7038 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsParticipantQueryParams.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2019 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; + +import android.annotation.CheckResult; +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.security.InvalidParameterException; + +/** + * The parameters to pass into + * {@link RcsMessageStore#getRcsParticipants(RcsParticipantQueryParams)} in order to select a + * subset of {@link RcsThread}s present in the message store. + */ +public final class RcsParticipantQueryParams implements Parcelable { + /** + * Flag to set with {@link Builder#setSortProperty(int)} to sort the results in the order of + * creation time for faster query results + */ + public static final int SORT_BY_CREATION_ORDER = 0; + + /** + * Flag to set with {@link Builder#setSortProperty(int)} to sort depending on the + * {@link RcsParticipant} aliases + */ + public static final int SORT_BY_ALIAS = 1; + + /** + * Flag to set with {@link Builder#setSortProperty(int)} to sort depending on the + * {@link RcsParticipant} canonical addresses + */ + public static final int SORT_BY_CANONICAL_ADDRESS = 2; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({SORT_BY_CREATION_ORDER, SORT_BY_ALIAS, SORT_BY_CANONICAL_ADDRESS}) + public @interface SortingProperty { + } + + // The SQL "like" statement to filter against participant aliases + private String mAliasLike; + // The SQL "like" statement to filter against canonical addresses + private String mCanonicalAddressLike; + // The property to sort the result against + private @SortingProperty int mSortingProperty; + // Whether to sort the result in ascending order + private boolean mIsAscending; + // The number of results to be returned from the query + private int mLimit; + // Used to limit the results to participants of a single thread + private int mThreadId; + + /** + * @hide + */ + public static final String PARTICIPANT_QUERY_PARAMETERS_KEY = "participant_query_parameters"; + + RcsParticipantQueryParams(int rcsThreadId, String aliasLike, String canonicalAddressLike, + @SortingProperty int sortingProperty, boolean isAscending, + int limit) { + mThreadId = rcsThreadId; + mAliasLike = aliasLike; + mCanonicalAddressLike = canonicalAddressLike; + mSortingProperty = sortingProperty; + mIsAscending = isAscending; + mLimit = limit; + } + + /** + * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get + * the thread that the result query should be limited to. + * + * As we do not expose any sort of integer ID's to public usage, this should be hidden. + * + * @hide - not meant for public use + */ + public int getThreadId() { + return mThreadId; + } + + /** + * @return Returns the SQL-inspired "LIKE" clause that will be used to match + * {@link RcsParticipant}s with respect to their aliases + * + * @see RcsParticipant#getAlias() + */ + public String getAliasLike() { + return mAliasLike; + } + + /** + * @return Returns the SQL-inspired "LIKE" clause that will be used to match + * {@link RcsParticipant}s with respect to their canonical addresses. + * + * @see RcsParticipant#getCanonicalAddress() + */ + public String getCanonicalAddressLike() { + return mCanonicalAddressLike; + } + + /** + * @return Returns the number of {@link RcsParticipant}s to be returned from the query. A value + * of 0 means there is no set limit. + */ + public int getLimit() { + return mLimit; + } + + /** + * @return Returns the property that will be used to sort the result against. + * @see SortingProperty + */ + public int getSortingProperty() { + return mSortingProperty; + } + + /** + * @return Returns {@code true} if the result set will be sorted in ascending order, + * {@code false} if it will be sorted in descending order. + */ + public boolean getSortDirection() { + return mIsAscending; + } + + /** + * A helper class to build the {@link RcsParticipantQueryParams}. + */ + public static class Builder { + private String mAliasLike; + private String mCanonicalAddressLike; + private @SortingProperty int mSortingProperty; + private boolean mIsAscending; + private int mLimit = 100; + private int mThreadId; + + /** + * Creates a new builder for {@link RcsParticipantQueryParams} to be used in + * {@link RcsMessageStore#getRcsParticipants(RcsParticipantQueryParams)} + */ + public Builder() { + // empty implementation + } + + /** + * Limits the resulting {@link RcsParticipant}s to only the given {@link RcsThread} + * + * @param rcsThread The thread that the participants should be searched in. + * @return The same {@link Builder} to chain methods. + */ + @CheckResult + public Builder setThread(RcsThread rcsThread) { + mThreadId = rcsThread.getThreadId(); + return this; + } + + /** + * Sets an SQL-inspired "like" clause to match with participant aliases. Using a percent + * sign ('%') wildcard matches any sequence of zero or more characters. Using an underscore + * ('_') wildcard matches any single character. Not using any wildcards would only perform a + * string match.The input string is case-insensitive. + * + * The input "An%e" would match {@link RcsParticipant}s with names Anne, Annie, Antonie, + * while the input "An_e" would only match Anne. + * + * @param likeClause The like clause to use for matching {@link RcsParticipant} aliases. + * @return The same {@link Builder} to chain methods + */ + @CheckResult + public Builder setAliasLike(String likeClause) { + mAliasLike = likeClause; + return this; + } + + /** + * Sets an SQL-inspired "like" clause to match with participant addresses. Using a percent + * sign ('%') wildcard matches any sequence of zero or more characters. Using an underscore + * ('_') wildcard matches any single character. Not using any wildcards would only perform a + * string match. The input string is case-insensitive. + * + * The input "+999%111" would match {@link RcsParticipant}s with addresses like "+9995111" + * or "+99955555111", while the input "+999_111" would only match "+9995111". + * + * @param likeClause The like clause to use for matching {@link RcsParticipant} canonical + * addresses. + * @return The same {@link Builder} to chain methods + */ + @CheckResult + public Builder setCanonicalAddressLike(String likeClause) { + mCanonicalAddressLike = likeClause; + return this; + } + + /** + * Desired number of threads to be returned from the query. Passing in 0 will return all + * existing threads at once. The limit defaults to 100. + * + * @param limit The number to limit the query result to. + * @return The same instance of the builder to chain parameters. + * @throws InvalidParameterException If the given limit is negative. + */ + @CheckResult + public Builder setResultLimit(@IntRange(from = 0) int limit) + throws InvalidParameterException { + if (limit < 0) { + throw new InvalidParameterException("The query limit must be non-negative"); + } + + mLimit = limit; + return this; + } + + /** + * Sets the property where the results should be sorted against. Defaults to + * {@link RcsParticipantQueryParams.SortingProperty#SORT_BY_CREATION_ORDER} + * + * @param sortingProperty against which property the results should be sorted + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setSortProperty(@SortingProperty int sortingProperty) { + mSortingProperty = sortingProperty; + return this; + } + + /** + * Sets whether the results should be sorted ascending or descending + * + * @param isAscending whether the results should be sorted ascending + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setSortDirection(boolean isAscending) { + mIsAscending = isAscending; + return this; + } + + /** + * Builds the {@link RcsParticipantQueryParams} to use in + * {@link RcsMessageStore#getRcsParticipants(RcsParticipantQueryParams)} + * + * @return An instance of {@link RcsParticipantQueryParams} to use with the participant + * query. + */ + public RcsParticipantQueryParams build() { + return new RcsParticipantQueryParams(mThreadId, mAliasLike, mCanonicalAddressLike, + mSortingProperty, mIsAscending, mLimit); + } + } + + /** + * Parcelable boilerplate below. + */ + private RcsParticipantQueryParams(Parcel in) { + mAliasLike = in.readString(); + mCanonicalAddressLike = in.readString(); + mSortingProperty = in.readInt(); + mIsAscending = in.readByte() == 1; + mLimit = in.readInt(); + mThreadId = in.readInt(); + } + + public static final Creator<RcsParticipantQueryParams> CREATOR = + new Creator<RcsParticipantQueryParams>() { + @Override + public RcsParticipantQueryParams createFromParcel(Parcel in) { + return new RcsParticipantQueryParams(in); + } + + @Override + public RcsParticipantQueryParams[] newArray(int size) { + return new RcsParticipantQueryParams[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mAliasLike); + dest.writeString(mCanonicalAddressLike); + dest.writeInt(mSortingProperty); + dest.writeByte((byte) (mIsAscending ? 1 : 0)); + dest.writeInt(mLimit); + dest.writeInt(mThreadId); + } + +} diff --git a/telephony/java/android/telephony/ims/RcsParticipantQueryResult.aidl b/telephony/java/android/telephony/ims/RcsParticipantQueryResult.aidl new file mode 100644 index 000000000000..db5c00c8ce00 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsParticipantQueryResult.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 2019, 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; + +parcelable RcsParticipantQueryResult; diff --git a/telephony/java/android/telephony/ims/RcsParticipantQueryResult.java b/telephony/java/android/telephony/ims/RcsParticipantQueryResult.java new file mode 100644 index 000000000000..505f1a55d1f0 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsParticipantQueryResult.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2019 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.List; + +/** + * The result of a {@link RcsMessageStore#getRcsParticipants(RcsParticipantQueryParams)} + * call. This class allows getting the token for querying the next batch of participants in order to + * prevent handling large amounts of data at once. + */ +public final class RcsParticipantQueryResult implements Parcelable { + // A token for the caller to continue their query for the next batch of results + private RcsQueryContinuationToken mContinuationToken; + // The list of participant IDs returned with this query + private List<Integer> mParticipants; + + /** + * Internal constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController} + * to create query results + * + * @hide + */ + public RcsParticipantQueryResult( + RcsQueryContinuationToken continuationToken, + List<Integer> participants) { + mContinuationToken = continuationToken; + mParticipants = participants; + } + + /** + * Returns a token to call + * {@link RcsMessageStore#getRcsParticipants(RcsQueryContinuationToken)} + * to get the next batch of {@link RcsParticipant}s. + */ + @Nullable + public RcsQueryContinuationToken getContinuationToken() { + return mContinuationToken; + } + + /** + * Returns all the {@link RcsParticipant}s in the current query result. Call {@link + * RcsMessageStore#getRcsParticipants(RcsQueryContinuationToken)} to get the next + * batch of {@link RcsParticipant}s. + */ + @NonNull + public List<RcsParticipant> getParticipants() { + List<RcsParticipant> participantList = new ArrayList<>(); + for (Integer participantId : mParticipants) { + participantList.add(new RcsParticipant(participantId)); + } + + return participantList; + } + + private RcsParticipantQueryResult(Parcel in) { + mContinuationToken = in.readParcelable( + RcsQueryContinuationToken.class.getClassLoader()); + } + + public static final Creator<RcsParticipantQueryResult> CREATOR = + new Creator<RcsParticipantQueryResult>() { + @Override + public RcsParticipantQueryResult createFromParcel(Parcel in) { + return new RcsParticipantQueryResult(in); + } + + @Override + public RcsParticipantQueryResult[] newArray(int size) { + return new RcsParticipantQueryResult[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mContinuationToken, flags); + } +} diff --git a/telephony/java/android/telephony/ims/RcsQueryContinuationToken.aidl b/telephony/java/android/telephony/ims/RcsQueryContinuationToken.aidl new file mode 100644 index 000000000000..319379a462de --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsQueryContinuationToken.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 2019, 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; + +parcelable RcsQueryContinuationToken; diff --git a/telephony/java/android/telephony/ims/RcsQueryContinuationToken.java b/telephony/java/android/telephony/ims/RcsQueryContinuationToken.java new file mode 100644 index 000000000000..08643de51d40 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsQueryContinuationToken.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2019 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; + +import android.annotation.IntDef; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A token for enabling continuation queries. Instances are acquired through + * {@code getContinuationToken} on result objects after initial query is done. + * + * @see RcsEventQueryResult#getContinuationToken() + * @see RcsMessageQueryResult#getContinuationToken() + * @see RcsParticipantQueryResult#getContinuationToken() + * @see RcsThreadQueryResult#getContinuationToken() + */ +public final class RcsQueryContinuationToken implements Parcelable { + /** + * Denotes that this {@link RcsQueryContinuationToken} token is meant to allow continuing + * {@link RcsEvent} queries + */ + public static final int EVENT_QUERY_CONTINUATION_TOKEN_TYPE = 0; + + /** + * Denotes that this {@link RcsQueryContinuationToken} token is meant to allow continuing + * {@link RcsMessage} queries + */ + public static final int MESSAGE_QUERY_CONTINUATION_TOKEN_TYPE = 1; + + /** + * Denotes that this {@link RcsQueryContinuationToken} token is meant to allow continuing + * {@link RcsParticipant} queries + */ + public static final int PARTICIPANT_QUERY_CONTINUATION_TOKEN_TYPE = 2; + + /** + * Denotes that this {@link RcsQueryContinuationToken} token is meant to allow continuing + * {@link RcsThread} queries + */ + public static final int THREAD_QUERY_CONTINUATION_TOKEN_TYPE = 3; + + /** + * @hide - not meant for public use + */ + public static final String QUERY_CONTINUATION_TOKEN = "query_continuation_token"; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({EVENT_QUERY_CONTINUATION_TOKEN_TYPE, MESSAGE_QUERY_CONTINUATION_TOKEN_TYPE, + PARTICIPANT_QUERY_CONTINUATION_TOKEN_TYPE, THREAD_QUERY_CONTINUATION_TOKEN_TYPE}) + public @interface ContinuationTokenType {} + + // The type of query this token should allow to continue + private @ContinuationTokenType int mQueryType; + // The raw query string for the initial query + private final String mRawQuery; + // The number of results that is returned with each query + private final int mLimit; + // The offset value that this query should start the query from + private int mOffset; + + /** + * @hide + */ + public RcsQueryContinuationToken(@ContinuationTokenType int queryType, String rawQuery, + int limit, int offset) { + mQueryType = queryType; + mRawQuery = rawQuery; + mLimit = limit; + mOffset = offset; + } + + /** + * Returns the original raw query used on {@link com.android.providers.telephony.RcsProvider} + * @hide + */ + public String getRawQuery() { + return mRawQuery; + } + + /** + * Returns which index this continuation query should start from + * @hide + */ + public int getOffset() { + return mOffset; + } + + /** + * Increments the offset by the amount of result rows returned with the continuation query for + * the next query. + * @hide + */ + public void incrementOffset() { + mOffset += mLimit; + } + + /** + * Returns the type of query that this {@link RcsQueryContinuationToken} is intended to be used + * to continue. + */ + public @ContinuationTokenType int getQueryType() { + return mQueryType; + } + + private RcsQueryContinuationToken(Parcel in) { + mQueryType = in.readInt(); + mRawQuery = in.readString(); + mLimit = in.readInt(); + mOffset = in.readInt(); + } + + public static final Creator<RcsQueryContinuationToken> CREATOR = + new Creator<RcsQueryContinuationToken>() { + @Override + public RcsQueryContinuationToken createFromParcel(Parcel in) { + return new RcsQueryContinuationToken(in); + } + + @Override + public RcsQueryContinuationToken[] newArray(int size) { + return new RcsQueryContinuationToken[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mQueryType); + dest.writeString(mRawQuery); + dest.writeInt(mLimit); + dest.writeInt(mOffset); + } +} diff --git a/telephony/java/android/telephony/ims/RcsTextPart.aidl b/telephony/java/android/telephony/ims/RcsTextPart.aidl deleted file mode 100644 index 4f9fe1fe26fe..000000000000 --- a/telephony/java/android/telephony/ims/RcsTextPart.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 2019, 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; - -parcelable RcsTextPart; diff --git a/telephony/java/android/telephony/ims/RcsTextPart.java b/telephony/java/android/telephony/ims/RcsTextPart.java deleted file mode 100644 index 2a72df17f32a..000000000000 --- a/telephony/java/android/telephony/ims/RcsTextPart.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2019 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; - -import android.os.Parcel; - -/** - * A part of a composite {@link RcsMessage} that holds a string - * @hide - TODO(sahinc) make this public - */ -public class RcsTextPart extends RcsPart { - public static final Creator<RcsTextPart> CREATOR = new Creator<RcsTextPart>() { - @Override - public RcsTextPart createFromParcel(Parcel in) { - return new RcsTextPart(in); - } - - @Override - public RcsTextPart[] newArray(int size) { - return new RcsTextPart[size]; - } - }; - - protected RcsTextPart(Parcel in) { - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - } -} diff --git a/telephony/java/android/telephony/ims/RcsThread.aidl b/telephony/java/android/telephony/ims/RcsThread.aidl deleted file mode 100644 index d9cf6dbc0ff0..000000000000 --- a/telephony/java/android/telephony/ims/RcsThread.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.telephony; - -parcelable RcsThread;
\ No newline at end of file diff --git a/telephony/java/android/telephony/ims/RcsThread.java b/telephony/java/android/telephony/ims/RcsThread.java index c0a0d946d204..e015dd3e9c0a 100644 --- a/telephony/java/android/telephony/ims/RcsThread.java +++ b/telephony/java/android/telephony/ims/RcsThread.java @@ -16,60 +16,118 @@ package android.telephony.ims; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.Log; +import static android.provider.Telephony.RcsColumns.RcsUnifiedThreadColumns.THREAD_TYPE_1_TO_1; +import static android.provider.Telephony.RcsColumns.RcsUnifiedThreadColumns.THREAD_TYPE_GROUP; + +import android.annotation.NonNull; +import android.annotation.WorkerThread; + +import com.android.internal.annotations.VisibleForTesting; /** * RcsThread represents a single RCS conversation thread. It holds messages that were sent and * received and events that occurred on that thread. - * @hide - TODO(sahinc) make this public */ -public abstract class RcsThread implements Parcelable { - // Since this is an abstract class that gets parcelled, the sub-classes need to write these - // magic values into the parcel so that we know which type to unparcel into. - protected static final int RCS_1_TO_1_TYPE = 998; - protected static final int RCS_GROUP_TYPE = 999; - +public abstract class RcsThread { + /** + * The rcs_participant_thread_id that represents this thread in the database + * @hide + */ protected int mThreadId; + /** + * @hide + */ protected RcsThread(int threadId) { mThreadId = threadId; } - protected RcsThread(Parcel in) { - mThreadId = in.readInt(); + /** + * @return Returns the summary of the latest message in this {@link RcsThread} packaged in an + * {@link RcsMessageSnippet} object + */ + @WorkerThread + @NonNull + public RcsMessageSnippet getSnippet() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getMessageSnippet(mThreadId)); + } + + /** + * Adds a new {@link RcsIncomingMessage} to this RcsThread and persists it in storage. + * + * @throws RcsMessageStoreException if the message could not be persisted into storage. + */ + @WorkerThread + @NonNull + public RcsIncomingMessage addIncomingMessage( + @NonNull RcsIncomingMessageCreationParams rcsIncomingMessageCreationParams) + throws RcsMessageStoreException { + return new RcsIncomingMessage(RcsControllerCall.call(iRcs -> iRcs.addIncomingMessage( + mThreadId, rcsIncomingMessageCreationParams))); } - public static final Creator<RcsThread> CREATOR = new Creator<RcsThread>() { - @Override - public RcsThread createFromParcel(Parcel in) { - int type = in.readInt(); + /** + * Adds a new {@link RcsOutgoingMessage} to this RcsThread and persists it in storage. + * + * @throws RcsMessageStoreException if the message could not be persisted into storage. + */ + @WorkerThread + @NonNull + public RcsOutgoingMessage addOutgoingMessage( + @NonNull RcsOutgoingMessageCreationParams rcsOutgoingMessageCreationParams) + throws RcsMessageStoreException { + int messageId = RcsControllerCall.call(iRcs -> iRcs.addOutgoingMessage( + mThreadId, rcsOutgoingMessageCreationParams)); - switch (type) { - case RCS_1_TO_1_TYPE: - return new Rcs1To1Thread(in); - case RCS_GROUP_TYPE: - return new RcsGroupThread(in); - default: - Log.e(RcsMessageStore.TAG, "Cannot unparcel RcsThread, wrong type: " + type); - } - return null; - } + return new RcsOutgoingMessage(messageId); + } + + /** + * Deletes an {@link RcsMessage} from this RcsThread and updates the storage. + * + * @param rcsMessage The message to delete from the thread + * @throws RcsMessageStoreException if the message could not be deleted + */ + @WorkerThread + public void deleteMessage(@NonNull RcsMessage rcsMessage) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.deleteMessage(rcsMessage.getId(), rcsMessage.isIncoming(), mThreadId, + isGroup())); + } + + /** + * Convenience function for loading all the {@link RcsMessage}s in this {@link RcsThread}. For + * a more detailed and paginated query, please use + * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParams)} + * + * @return Loads the {@link RcsMessage}s in this thread and returns them in an immutable list. + * @throws RcsMessageStoreException if the messages could not be read from the storage + */ + @WorkerThread + @NonNull + public RcsMessageQueryResult getMessages() throws RcsMessageStoreException { + RcsMessageQueryParams queryParameters = + new RcsMessageQueryParams.Builder().setThread(this).build(); + return RcsControllerCall.call(iRcs -> iRcs.getMessages(queryParameters)); + } - @Override - public RcsThread[] newArray(int size) { - return new RcsThread[0]; - } - }; + /** + * @return Returns whether this is a group thread or not + */ + public abstract boolean isGroup(); - @Override - public int describeContents() { - return 0; + /** + * @hide + */ + @VisibleForTesting + public int getThreadId() { + return mThreadId; } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mThreadId); + /** + * @hide + */ + public int getThreadType() { + return isGroup() ? THREAD_TYPE_GROUP : THREAD_TYPE_1_TO_1; } } diff --git a/telephony/java/android/telephony/ims/RcsThreadEvent.aidl b/telephony/java/android/telephony/ims/RcsThreadEvent.aidl deleted file mode 100644 index 4a40d8906bbb..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadEvent.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 2019, 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; - -parcelable RcsThreadEvent; diff --git a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl deleted file mode 100644 index 82d985df4c6c..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 2019, 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; - -parcelable RcsThreadIconChangedEvent; diff --git a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.java b/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.java deleted file mode 100644 index b308fef46435..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2019 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; - -import android.os.Parcel; - -/** - * An event that indicates an {@link RcsGroupThread}'s icon was changed. - * @hide - TODO(sahinc) make this public - */ -public class RcsThreadIconChangedEvent extends RcsThreadEvent { - public static final Creator<RcsThreadIconChangedEvent> CREATOR = - new Creator<RcsThreadIconChangedEvent>() { - @Override - public RcsThreadIconChangedEvent createFromParcel(Parcel in) { - return new RcsThreadIconChangedEvent(in); - } - - @Override - public RcsThreadIconChangedEvent[] newArray(int size) { - return new RcsThreadIconChangedEvent[size]; - } - }; - - protected RcsThreadIconChangedEvent(Parcel in) { - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - } -} diff --git a/telephony/java/android/telephony/ims/RcsThreadNameChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsThreadNameChangedEvent.aidl deleted file mode 100644 index 54a311d02958..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadNameChangedEvent.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 2019, 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; - -parcelable RcsThreadNameChangedEvent; diff --git a/telephony/java/android/telephony/ims/RcsThreadNameChangedEvent.java b/telephony/java/android/telephony/ims/RcsThreadNameChangedEvent.java deleted file mode 100644 index 6f5cfdf3b4c4..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadNameChangedEvent.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2019 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; - -import android.os.Parcel; - -/** - * An event that indicates an {@link RcsGroupThread}'s name was changed. - * @hide - TODO(sahinc) make this public - */ -public class RcsThreadNameChangedEvent extends RcsThreadEvent { - public static final Creator<RcsThreadNameChangedEvent> CREATOR = - new Creator<RcsThreadNameChangedEvent>() { - @Override - public RcsThreadNameChangedEvent createFromParcel(Parcel in) { - return new RcsThreadNameChangedEvent(in); - } - - @Override - public RcsThreadNameChangedEvent[] newArray(int size) { - return new RcsThreadNameChangedEvent[size]; - } - }; - - protected RcsThreadNameChangedEvent(Parcel in) { - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - } -} diff --git a/telephony/java/android/telephony/ims/RcsThreadParticipantJoinedEvent.aidl b/telephony/java/android/telephony/ims/RcsThreadParticipantJoinedEvent.aidl deleted file mode 100644 index 047a42466ee7..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadParticipantJoinedEvent.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 2019, 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; - -parcelable RcsThreadParticipantJoinedEvent; diff --git a/telephony/java/android/telephony/ims/RcsThreadParticipantJoinedEvent.java b/telephony/java/android/telephony/ims/RcsThreadParticipantJoinedEvent.java deleted file mode 100644 index 5c4073c430e7..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadParticipantJoinedEvent.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2019 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; - -import android.os.Parcel; - -/** - * An event that indicates an RCS participant has joined an {@link RcsGroupThread}. - * @hide - TODO(sahinc) make this public - */ -public class RcsThreadParticipantJoinedEvent extends RcsThreadEvent { - public static final Creator<RcsThreadParticipantJoinedEvent> CREATOR = - new Creator<RcsThreadParticipantJoinedEvent>() { - @Override - public RcsThreadParticipantJoinedEvent createFromParcel(Parcel in) { - return new RcsThreadParticipantJoinedEvent(in); - } - - @Override - public RcsThreadParticipantJoinedEvent[] newArray(int size) { - return new RcsThreadParticipantJoinedEvent[size]; - } - }; - - protected RcsThreadParticipantJoinedEvent(Parcel in) { - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - } -} diff --git a/telephony/java/android/telephony/ims/RcsThreadParticipantLeftEvent.aidl b/telephony/java/android/telephony/ims/RcsThreadParticipantLeftEvent.aidl deleted file mode 100644 index 52f9bbd3cd93..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadParticipantLeftEvent.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 2019, 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; - -parcelable RcsThreadParticipantLeftEvent; diff --git a/telephony/java/android/telephony/ims/RcsThreadParticipantLeftEvent.java b/telephony/java/android/telephony/ims/RcsThreadParticipantLeftEvent.java deleted file mode 100644 index 4bf86b90ebb7..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadParticipantLeftEvent.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2019 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; - -import android.os.Parcel; - -/** - * An event that indicates an RCS participant has left an {@link RcsGroupThread}. - * @hide - TODO(sahinc) make this public - */ -public class RcsThreadParticipantLeftEvent extends RcsThreadEvent { - public static final Creator<RcsThreadParticipantLeftEvent> CREATOR = - new Creator<RcsThreadParticipantLeftEvent>() { - @Override - public RcsThreadParticipantLeftEvent createFromParcel(Parcel in) { - return new RcsThreadParticipantLeftEvent(in); - } - - @Override - public RcsThreadParticipantLeftEvent[] newArray(int size) { - return new RcsThreadParticipantLeftEvent[size]; - } - }; - - protected RcsThreadParticipantLeftEvent(Parcel in) { - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - } -} diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.aidl b/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.aidl deleted file mode 100644 index 7bcebfa08fcb..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* -** -** Copyright 2018, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -package android.telephony.ims; - -parcelable RcsThreadQueryContinuationToken; diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.java b/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.java deleted file mode 100644 index 931e93dc8bf1..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.telephony.ims; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * A continuation token to provide for {@link RcsMessageStore#getRcsThreads}. Use this token to - * break large queries into manageable chunks - * @hide - TODO make this public - */ -public class RcsThreadQueryContinuationToken implements Parcelable { - protected RcsThreadQueryContinuationToken(Parcel in) { - } - - public static final Creator<RcsThreadQueryContinuationToken> CREATOR = - new Creator<RcsThreadQueryContinuationToken>() { - @Override - public RcsThreadQueryContinuationToken createFromParcel(Parcel in) { - return new RcsThreadQueryContinuationToken(in); - } - - @Override - public RcsThreadQueryContinuationToken[] newArray(int size) { - return new RcsThreadQueryContinuationToken[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - } -} diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryParameters.aidl b/telephony/java/android/telephony/ims/RcsThreadQueryParameters.aidl deleted file mode 100644 index feb2d4dec094..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadQueryParameters.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* -** -** Copyright 2018, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -package android.telephony.ims; - -parcelable RcsThreadQueryParameters; diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryParameters.java b/telephony/java/android/telephony/ims/RcsThreadQueryParameters.java deleted file mode 100644 index f2c4ab1884ca..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadQueryParameters.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.telephony.ims; - -import android.annotation.CheckResult; -import android.os.Parcel; -import android.os.Parcelable; - -import java.security.InvalidParameterException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Set; - -/** - * The parameters to pass into {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParameters)} in - * order to select a subset of {@link RcsThread}s present in the message store. - * @hide TODO - make the Builder and builder() public. The rest should stay internal only. - */ -public class RcsThreadQueryParameters implements Parcelable { - private final boolean mIsGroup; - private final Set<RcsParticipant> mRcsParticipants; - private final int mLimit; - private final boolean mIsAscending; - - RcsThreadQueryParameters(boolean isGroup, Set<RcsParticipant> participants, int limit, - boolean isAscending) { - mIsGroup = isGroup; - mRcsParticipants = participants; - mLimit = limit; - mIsAscending = isAscending; - } - - /** - * Returns a new builder to build a query with. - * TODO - make public - */ - public static Builder builder() { - return new Builder(); - } - - /** - * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get - * the list of participants. - * @hide - */ - public Set<RcsParticipant> getRcsParticipants() { - return mRcsParticipants; - } - - /** - * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get - * whether group threads should be queried - * @hide - */ - public boolean isGroupThread() { - return mIsGroup; - } - - /** - * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get - * the number of tuples the result query should be limited to. - */ - public int getLimit() { - return mLimit; - } - - /** - * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to - * determine the sort order. - */ - public boolean isAscending() { - return mIsAscending; - } - - /** - * A helper class to build the {@link RcsThreadQueryParameters}. - */ - public static class Builder { - private boolean mIsGroupThread; - private Set<RcsParticipant> mParticipants; - private int mLimit = 100; - private boolean mIsAscending; - - /** - * Package private constructor for {@link RcsThreadQueryParameters.Builder}. To obtain this, - * {@link RcsThreadQueryParameters#builder()} needs to be called. - */ - Builder() { - mParticipants = new HashSet<>(); - } - - /** - * Limits the query to only return group threads. - * @param isGroupThread Whether to limit the query result to group threads. - * @return The same instance of the builder to chain parameters. - */ - @CheckResult - public Builder isGroupThread(boolean isGroupThread) { - mIsGroupThread = isGroupThread; - return this; - } - - /** - * Limits the query to only return threads that contain the given participant. - * @param participant The participant that must be included in all of the returned threads. - * @return The same instance of the builder to chain parameters. - */ - @CheckResult - public Builder withParticipant(RcsParticipant participant) { - mParticipants.add(participant); - return this; - } - - /** - * Limits the query to only return threads that contain the given list of participants. - * @param participants An iterable list of participants that must be included in all of the - * returned threads. - * @return The same instance of the builder to chain parameters. - */ - @CheckResult - public Builder withParticipants(Iterable<RcsParticipant> participants) { - for (RcsParticipant participant : participants) { - mParticipants.add(participant); - } - return this; - } - - /** - * Desired number of threads to be returned from the query. Passing in 0 will return all - * existing threads at once. The limit defaults to 100. - * @param limit The number to limit the query result to. - * @return The same instance of the builder to chain parameters. - * @throws InvalidParameterException If the given limit is negative. - */ - @CheckResult - public Builder limitResultsTo(int limit) throws InvalidParameterException { - if (limit < 0) { - throw new InvalidParameterException("The query limit must be non-negative"); - } - - mLimit = limit; - return this; - } - - /** - * Sorts the results returned from the query via thread IDs. - * - * TODO - add sorting support for other fields - * - * @param isAscending whether to sort in ascending order or not - * @return The same instance of the builder to chain parameters. - */ - @CheckResult - public Builder sort(boolean isAscending) { - mIsAscending = isAscending; - return this; - } - - /** - * Builds the {@link RcsThreadQueryParameters} to use in - * {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParameters)} - * - * @return An instance of {@link RcsThreadQueryParameters} to use with the thread query. - */ - public RcsThreadQueryParameters build() { - return new RcsThreadQueryParameters( - mIsGroupThread, mParticipants, mLimit, mIsAscending); - } - } - - /** - * Parcelable boilerplate below. - */ - protected RcsThreadQueryParameters(Parcel in) { - mIsGroup = in.readBoolean(); - - ArrayList<RcsParticipant> participantArrayList = new ArrayList<>(); - in.readTypedList(participantArrayList, RcsParticipant.CREATOR); - mRcsParticipants = new HashSet<>(participantArrayList); - - mLimit = in.readInt(); - mIsAscending = in.readBoolean(); - } - - public static final Creator<RcsThreadQueryParameters> CREATOR = - new Creator<RcsThreadQueryParameters>() { - @Override - public RcsThreadQueryParameters createFromParcel(Parcel in) { - return new RcsThreadQueryParameters(in); - } - - @Override - public RcsThreadQueryParameters[] newArray(int size) { - return new RcsThreadQueryParameters[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeBoolean(mIsGroup); - dest.writeTypedList(new ArrayList<>(mRcsParticipants)); - dest.writeInt(mLimit); - dest.writeBoolean(mIsAscending); - } - -} diff --git a/telephony/java/android/telephony/ims/RcsFileTransferPart.aidl b/telephony/java/android/telephony/ims/RcsThreadQueryParams.aidl index eaf312877deb..3f351dc5efee 100644 --- a/telephony/java/android/telephony/ims/RcsFileTransferPart.aidl +++ b/telephony/java/android/telephony/ims/RcsThreadQueryParams.aidl @@ -17,4 +17,4 @@ package android.telephony.ims; -parcelable RcsFileTransferPart; +parcelable RcsThreadQueryParams; diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryParams.java b/telephony/java/android/telephony/ims/RcsThreadQueryParams.java new file mode 100644 index 000000000000..05a5a3917691 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsThreadQueryParams.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2019 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; + +import android.annotation.CheckResult; +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.security.InvalidParameterException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * The parameters to pass into {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParams)} in + * order to select a subset of {@link RcsThread}s present in the message store. + */ +public final class RcsThreadQueryParams implements Parcelable { + /** + * Bitmask flag to be used with {@link Builder#setThreadType(int)} to make + * {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParams)} return + * {@link RcsGroupThread}s. + */ + public static final int THREAD_TYPE_GROUP = 0x0001; + + /** + * Bitmask flag to be used with {@link Builder#setThreadType(int)} to make + * {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParams)} return + * {@link Rcs1To1Thread}s. + */ + public static final int THREAD_TYPE_1_TO_1 = 0x0002; + + // The type of threads to be filtered with the query + private final int mThreadType; + // The list of participants that are expected in the resulting threads + private final List<Integer> mRcsParticipantIds; + // The number of RcsThread's that should be returned with this query + private final int mLimit; + // The property which the result of the query should be sorted against + private final @SortingProperty int mSortingProperty; + // Whether the sorting should be done in ascending + private final boolean mIsAscending; + + /** + * Flag to be used with {@link Builder#setSortProperty(int)} to denote that the results should + * be sorted in the order of {@link RcsThread} creation time for faster results. + */ + public static final int SORT_BY_CREATION_ORDER = 0; + + /** + * Flag to be used with {@link Builder#setSortProperty(int)} to denote that the results should + * be sorted according to the timestamp of {@link RcsThread#getSnippet()} + */ + public static final int SORT_BY_TIMESTAMP = 1; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({SORT_BY_CREATION_ORDER, SORT_BY_TIMESTAMP}) + public @interface SortingProperty { + } + + /** + * @hide + */ + public static final String THREAD_QUERY_PARAMETERS_KEY = "thread_query_parameters"; + + RcsThreadQueryParams(int threadType, Set<RcsParticipant> participants, + int limit, int sortingProperty, boolean isAscending) { + mThreadType = threadType; + mRcsParticipantIds = convertParticipantSetToIdList(participants); + mLimit = limit; + mSortingProperty = sortingProperty; + mIsAscending = isAscending; + } + + private static List<Integer> convertParticipantSetToIdList(Set<RcsParticipant> participants) { + List<Integer> ids = new ArrayList<>(participants.size()); + for (RcsParticipant participant : participants) { + ids.add(participant.getId()); + } + return ids; + } + + /** + * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get + * the list of participant IDs. + * + * As we don't expose any integer ID's to API users, this should stay hidden + * + * @hide - not meant for public use + */ + public List<Integer> getRcsParticipantsIds() { + return Collections.unmodifiableList(mRcsParticipantIds); + } + + /** + * @return Returns the bitmask flag for types of {@link RcsThread}s that this query should + * return. + */ + public int getThreadType() { + return mThreadType; + } + + /** + * @return Returns the number of {@link RcsThread}s to be returned from the query. A value + * of 0 means there is no set limit. + */ + public int getLimit() { + return mLimit; + } + + /** + * @return Returns the property that will be used to sort the result against. + * @see SortingProperty + */ + public @SortingProperty int getSortingProperty() { + return mSortingProperty; + } + + /** + * @return Returns {@code true} if the result set will be sorted in ascending order, + * {@code false} if it will be sorted in descending order. + */ + public boolean getSortDirection() { + return mIsAscending; + } + + /** + * A helper class to build the {@link RcsThreadQueryParams}. + */ + public static class Builder { + private int mThreadType; + private Set<RcsParticipant> mParticipants; + private int mLimit = 100; + private @SortingProperty int mSortingProperty; + private boolean mIsAscending; + + /** + * Constructs a {@link RcsThreadQueryParams.Builder} to help build an + * {@link RcsThreadQueryParams} + */ + public Builder() { + mParticipants = new HashSet<>(); + } + + /** + * Limits the query to only return group threads. + * + * @param threadType Whether to limit the query result to group threads. + * @return The same instance of the builder to chain parameters. + * @see RcsThreadQueryParams#THREAD_TYPE_GROUP + * @see RcsThreadQueryParams#THREAD_TYPE_1_TO_1 + */ + @CheckResult + public Builder setThreadType(int threadType) { + mThreadType = threadType; + return this; + } + + /** + * Limits the query to only return threads that contain the given participant. If this + * property was not set, participants will not be taken into account while querying for + * threads. + * + * @param participant The participant that must be included in all of the returned threads. + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setParticipant(@NonNull RcsParticipant participant) { + mParticipants.add(participant); + return this; + } + + /** + * Limits the query to only return threads that contain the given list of participants. If + * this property was not set, participants will not be taken into account while querying + * for threads. + * + * @param participants An iterable list of participants that must be included in all of the + * returned threads. + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setParticipants(@NonNull List<RcsParticipant> participants) { + mParticipants.addAll(participants); + return this; + } + + /** + * Desired number of threads to be returned from the query. Passing in 0 will return all + * existing threads at once. The limit defaults to 100. + * + * @param limit The number to limit the query result to. + * @return The same instance of the builder to chain parameters. + * @throws InvalidParameterException If the given limit is negative. + */ + @CheckResult + public Builder setResultLimit(@IntRange(from = 0) int limit) + throws InvalidParameterException { + if (limit < 0) { + throw new InvalidParameterException("The query limit must be non-negative"); + } + + mLimit = limit; + return this; + } + + /** + * Sets the property where the results should be sorted against. Defaults to + * {@link SortingProperty#SORT_BY_CREATION_ORDER} + * + * @param sortingProperty whether to sort in ascending order or not + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setSortProperty(@SortingProperty int sortingProperty) { + mSortingProperty = sortingProperty; + return this; + } + + /** + * Sets whether the results should be sorted ascending or descending + * + * @param isAscending whether the results should be sorted ascending + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setSortDirection(boolean isAscending) { + mIsAscending = isAscending; + return this; + } + + /** + * Builds the {@link RcsThreadQueryParams} to use in + * {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParams)} + * + * @return An instance of {@link RcsThreadQueryParams} to use with the thread query. + */ + public RcsThreadQueryParams build() { + return new RcsThreadQueryParams(mThreadType, mParticipants, mLimit, + mSortingProperty, mIsAscending); + } + } + + /** + * Parcelable boilerplate below. + */ + private RcsThreadQueryParams(Parcel in) { + mThreadType = in.readInt(); + mRcsParticipantIds = new ArrayList<>(); + in.readList(mRcsParticipantIds, Integer.class.getClassLoader()); + mLimit = in.readInt(); + mSortingProperty = in.readInt(); + mIsAscending = in.readByte() == 1; + } + + public static final Creator<RcsThreadQueryParams> CREATOR = + new Creator<RcsThreadQueryParams>() { + @Override + public RcsThreadQueryParams createFromParcel(Parcel in) { + return new RcsThreadQueryParams(in); + } + + @Override + public RcsThreadQueryParams[] newArray(int size) { + return new RcsThreadQueryParams[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mThreadType); + dest.writeList(mRcsParticipantIds); + dest.writeInt(mLimit); + dest.writeInt(mSortingProperty); + dest.writeByte((byte) (mIsAscending ? 1 : 0)); + } +} diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryResult.aidl b/telephony/java/android/telephony/ims/RcsThreadQueryResult.aidl index 4b06529d1294..b1d5cf4c7211 100644 --- a/telephony/java/android/telephony/ims/RcsThreadQueryResult.aidl +++ b/telephony/java/android/telephony/ims/RcsThreadQueryResult.aidl @@ -1,19 +1,19 @@ /* -** -** Copyright 2018, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ + * + * Copyright 2019, 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; diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryResult.java b/telephony/java/android/telephony/ims/RcsThreadQueryResult.java index 47715f8410d6..1cac61d1aa64 100644 --- a/telephony/java/android/telephony/ims/RcsThreadQueryResult.java +++ b/telephony/java/android/telephony/ims/RcsThreadQueryResult.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,22 +16,28 @@ package android.telephony.ims; +import static android.provider.Telephony.RcsColumns.RcsUnifiedThreadColumns.THREAD_TYPE_1_TO_1; + +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; +import com.android.ims.RcsTypeIdPair; + +import java.util.ArrayList; import java.util.List; /** - * The result of a {@link RcsMessageStore#getRcsThreads(RcsThreadQueryContinuationToken, - * RcsThreadQueryParameters)} + * The result of a {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParams)} * call. This class allows getting the token for querying the next batch of threads in order to * prevent handling large amounts of data at once. - * - * @hide */ -public class RcsThreadQueryResult implements Parcelable { - private RcsThreadQueryContinuationToken mContinuationToken; - private List<RcsThread> mRcsThreads; +public final class RcsThreadQueryResult implements Parcelable { + // A token for the caller to continue their query for the next batch of results + private RcsQueryContinuationToken mContinuationToken; + // The list of thread IDs returned with this query + private List<RcsTypeIdPair> mRcsThreadIds; /** * Internal constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController} @@ -40,31 +46,47 @@ public class RcsThreadQueryResult implements Parcelable { * @hide */ public RcsThreadQueryResult( - RcsThreadQueryContinuationToken continuationToken, List<RcsThread> rcsThreads) { + RcsQueryContinuationToken continuationToken, + List<RcsTypeIdPair> rcsThreadIds) { mContinuationToken = continuationToken; - mRcsThreads = rcsThreads; + mRcsThreadIds = rcsThreadIds; } /** * Returns a token to call - * {@link RcsMessageStore#getRcsThreads(RcsThreadQueryContinuationToken)} + * {@link RcsMessageStore#getRcsThreads(RcsQueryContinuationToken)} * to get the next batch of {@link RcsThread}s. */ - public RcsThreadQueryContinuationToken nextChunkToken() { + @Nullable + public RcsQueryContinuationToken getContinuationToken() { return mContinuationToken; } /** * Returns all the RcsThreads in the current query result. Call {@link - * RcsMessageStore#getRcsThreads(RcsThreadQueryContinuationToken)} to get the next batch of + * RcsMessageStore#getRcsThreads(RcsQueryContinuationToken)} to get the next batch of * {@link RcsThread}s. */ + @NonNull public List<RcsThread> getThreads() { - return mRcsThreads; + List<RcsThread> rcsThreads = new ArrayList<>(); + + for (RcsTypeIdPair typeIdPair : mRcsThreadIds) { + if (typeIdPair.getType() == THREAD_TYPE_1_TO_1) { + rcsThreads.add(new Rcs1To1Thread(typeIdPair.getId())); + } else { + rcsThreads.add(new RcsGroupThread(typeIdPair.getId())); + } + } + + return rcsThreads; } - protected RcsThreadQueryResult(Parcel in) { - // TODO - implement + private RcsThreadQueryResult(Parcel in) { + mContinuationToken = in.readParcelable( + RcsQueryContinuationToken.class.getClassLoader()); + mRcsThreadIds = new ArrayList<>(); + in.readList(mRcsThreadIds, Integer.class.getClassLoader()); } public static final Creator<RcsThreadQueryResult> CREATOR = @@ -87,6 +109,7 @@ public class RcsThreadQueryResult implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - // TODO - implement + dest.writeParcelable(mContinuationToken, flags); + dest.writeList(mRcsThreadIds); } } diff --git a/telephony/java/android/telephony/ims/aidl/IRcs.aidl b/telephony/java/android/telephony/ims/aidl/IRcs.aidl index 0c958ba719f3..2478f8cff6b7 100644 --- a/telephony/java/android/telephony/ims/aidl/IRcs.aidl +++ b/telephony/java/android/telephony/ims/aidl/IRcs.aidl @@ -16,10 +16,19 @@ package android.telephony.ims.aidl; -import android.telephony.ims.RcsParticipant; -import android.telephony.ims.Rcs1To1Thread; -import android.telephony.ims.RcsThreadQueryContinuationToken; -import android.telephony.ims.RcsThreadQueryParameters; +import android.net.Uri; +import android.telephony.ims.RcsEventQueryParams; +import android.telephony.ims.RcsEventQueryResult; +import android.telephony.ims.RcsFileTransferCreationParams; +import android.telephony.ims.RcsIncomingMessageCreationParams; +import android.telephony.ims.RcsMessageSnippet; +import android.telephony.ims.RcsMessageQueryParams; +import android.telephony.ims.RcsMessageQueryResult; +import android.telephony.ims.RcsOutgoingMessageCreationParams; +import android.telephony.ims.RcsParticipantQueryParams; +import android.telephony.ims.RcsParticipantQueryResult; +import android.telephony.ims.RcsQueryContinuationToken; +import android.telephony.ims.RcsThreadQueryParams; import android.telephony.ims.RcsThreadQueryResult; /** @@ -27,23 +36,231 @@ import android.telephony.ims.RcsThreadQueryResult; * {@hide} */ interface IRcs { + ///////////////////////// // RcsMessageStore APIs - RcsThreadQueryResult getRcsThreads(in RcsThreadQueryParameters queryParameters); + ///////////////////////// + RcsThreadQueryResult getRcsThreads(in RcsThreadQueryParams queryParams); RcsThreadQueryResult getRcsThreadsWithToken( - in RcsThreadQueryContinuationToken continuationToken); + in RcsQueryContinuationToken continuationToken); - void deleteThread(int threadId); + RcsParticipantQueryResult getParticipants(in RcsParticipantQueryParams queryParams); - Rcs1To1Thread createRcs1To1Thread(in RcsParticipant participant); + RcsParticipantQueryResult getParticipantsWithToken( + in RcsQueryContinuationToken continuationToken); + RcsMessageQueryResult getMessages(in RcsMessageQueryParams queryParams); + + RcsMessageQueryResult getMessagesWithToken( + in RcsQueryContinuationToken continuationToken); + + RcsEventQueryResult getEvents(in RcsEventQueryParams queryParams); + + RcsEventQueryResult getEventsWithToken( + in RcsQueryContinuationToken continuationToken); + + // returns true if the thread was successfully deleted + boolean deleteThread(int threadId, int threadType); + + // Creates an Rcs1To1Thread and returns its row ID + int createRcs1To1Thread(int participantId); + + // Creates an RcsGroupThread and returns its row ID + int createGroupThread(in int[] participantIds, String groupName, in Uri groupIcon); + + ///////////////////////// // RcsThread APIs - int getMessageCount(int rcsThreadId); + ///////////////////////// + + // Creates a new RcsIncomingMessage on the given thread and returns its row ID + int addIncomingMessage(int rcsThreadId, + in RcsIncomingMessageCreationParams rcsIncomingMessageCreationParams); + + // Creates a new RcsOutgoingMessage on the given thread and returns its row ID + int addOutgoingMessage(int rcsThreadId, + in RcsOutgoingMessageCreationParams rcsOutgoingMessageCreationParams); + + // TODO: modify RcsProvider URI's to allow deleting a message without specifying its thread + void deleteMessage(int rcsMessageId, boolean isIncoming, int rcsThreadId, boolean isGroup); + + RcsMessageSnippet getMessageSnippet(int rcsThreadId); + + ///////////////////////// + // Rcs1To1Thread APIs + ///////////////////////// + void set1To1ThreadFallbackThreadId(int rcsThreadId, long fallbackId); + + long get1To1ThreadFallbackThreadId(int rcsThreadId); + + int get1To1ThreadOtherParticipantId(int rcsThreadId); + + ///////////////////////// + // RcsGroupThread APIs + ///////////////////////// + void setGroupThreadName(int rcsThreadId, String groupName); + + String getGroupThreadName(int rcsThreadId); + + void setGroupThreadIcon(int rcsThreadId, in Uri groupIcon); + + Uri getGroupThreadIcon(int rcsThreadId); + + void setGroupThreadOwner(int rcsThreadId, int participantId); + + int getGroupThreadOwner(int rcsThreadId); + + void setGroupThreadConferenceUri(int rcsThreadId, in Uri conferenceUri); + + Uri getGroupThreadConferenceUri(int rcsThreadId); + void addParticipantToGroupThread(int rcsThreadId, int participantId); + + void removeParticipantFromGroupThread(int rcsThreadId, int participantId); + + ///////////////////////// // RcsParticipant APIs - RcsParticipant createRcsParticipant(String canonicalAddress); + ///////////////////////// + + // Creates a new RcsParticipant and returns its rowId + int createRcsParticipant(String canonicalAddress, String alias); + + String getRcsParticipantCanonicalAddress(int participantId); + + String getRcsParticipantAlias(int participantId); + + void setRcsParticipantAlias(int id, String alias); + + String getRcsParticipantContactId(int participantId); + + void setRcsParticipantContactId(int participantId, String contactId); + + ///////////////////////// + // RcsMessage APIs + ///////////////////////// + void setMessageSubId(int messageId, boolean isIncoming, int subId); + + int getMessageSubId(int messageId, boolean isIncoming); + + void setMessageStatus(int messageId, boolean isIncoming, int status); + + int getMessageStatus(int messageId, boolean isIncoming); + + void setMessageOriginationTimestamp(int messageId, boolean isIncoming, long originationTimestamp); + + long getMessageOriginationTimestamp(int messageId, boolean isIncoming); + + void setGlobalMessageIdForMessage(int messageId, boolean isIncoming, String globalId); + + String getGlobalMessageIdForMessage(int messageId, boolean isIncoming); + + void setMessageArrivalTimestamp(int messageId, boolean isIncoming, long arrivalTimestamp); + + long getMessageArrivalTimestamp(int messageId, boolean isIncoming); + + void setMessageSeenTimestamp(int messageId, boolean isIncoming, long seenTimestamp); + + long getMessageSeenTimestamp(int messageId, boolean isIncoming); + + void setTextForMessage(int messageId, boolean isIncoming, String text); + + String getTextForMessage(int messageId, boolean isIncoming); + + void setLatitudeForMessage(int messageId, boolean isIncoming, double latitude); + + double getLatitudeForMessage(int messageId, boolean isIncoming); + + void setLongitudeForMessage(int messageId, boolean isIncoming, double longitude); + + double getLongitudeForMessage(int messageId, boolean isIncoming); + + // Returns the ID's of the file transfers attached to the given message + int[] getFileTransfersAttachedToMessage(int messageId, boolean isIncoming); + + int getSenderParticipant(int messageId); + + ///////////////////////// + // RcsOutgoingMessageDelivery APIs + ///////////////////////// + + // Returns the participant ID's that this message is intended to be delivered to + int[] getMessageRecipients(int messageId); + + long getOutgoingDeliveryDeliveredTimestamp(int messageId, int participantId); + + void setOutgoingDeliveryDeliveredTimestamp(int messageId, int participantId, long deliveredTimestamp); + + long getOutgoingDeliverySeenTimestamp(int messageId, int participantId); + + void setOutgoingDeliverySeenTimestamp(int messageId, int participantId, long seenTimestamp); + + int getOutgoingDeliveryStatus(int messageId, int participantId); + + void setOutgoingDeliveryStatus(int messageId, int participantId, int status); + + ///////////////////////// + // RcsFileTransferPart APIs + ///////////////////////// + + // Performs the initial write to storage and returns the row ID. + int storeFileTransfer(int messageId, boolean isIncoming, + in RcsFileTransferCreationParams fileTransferCreationParams); + + void deleteFileTransfer(int partId); + + void setFileTransferSessionId(int partId, String sessionId); + + String getFileTransferSessionId(int partId); + + void setFileTransferContentUri(int partId, in Uri contentUri); + + Uri getFileTransferContentUri(int partId); + + void setFileTransferContentType(int partId, String contentType); + + String getFileTransferContentType(int partId); + + void setFileTransferFileSize(int partId, long fileSize); + + long getFileTransferFileSize(int partId); + + void setFileTransferTransferOffset(int partId, long transferOffset); + + long getFileTransferTransferOffset(int partId); + + void setFileTransferStatus(int partId, int transferStatus); + + int getFileTransferStatus(int partId); + + void setFileTransferWidth(int partId, int width); + + int getFileTransferWidth(int partId); + + void setFileTransferHeight(int partId, int height); + + int getFileTransferHeight(int partId); + + void setFileTransferLength(int partId, long length); + + long getFileTransferLength(int partId); + + void setFileTransferPreviewUri(int partId, in Uri uri); + + Uri getFileTransferPreviewUri(int partId); + + void setFileTransferPreviewType(int partId, String type); + + String getFileTransferPreviewType(int partId); + + ///////////////////////// + // RcsEvent APIs + ///////////////////////// + int createGroupThreadNameChangedEvent(long timestamp, int threadId, int originationParticipantId, String newName); + + int createGroupThreadIconChangedEvent(long timestamp, int threadId, int originationParticipantId, in Uri newIcon); + + int createGroupThreadParticipantJoinedEvent(long timestamp, int threadId, int originationParticipantId, int participantId); - void updateRcsParticipantCanonicalAddress(int id, String canonicalAddress); + int createGroupThreadParticipantLeftEvent(long timestamp, int threadId, int originationParticipantId, int participantId); - void updateRcsParticipantAlias(int id, String alias); + int createParticipantAliasChangedEvent(long timestamp, int participantId, String newAlias); }
\ No newline at end of file diff --git a/telephony/java/android/telephony/mbms/GroupCallCallback.java b/telephony/java/android/telephony/mbms/GroupCallCallback.java index 77e36bbcf2ae..603f4e6d2030 100644 --- a/telephony/java/android/telephony/mbms/GroupCallCallback.java +++ b/telephony/java/android/telephony/mbms/GroupCallCallback.java @@ -57,7 +57,7 @@ public interface GroupCallCallback { * @param errorCode The error code. * @param message A human-readable message generated by the middleware for debugging purposes. */ - void onError(@GroupCallError int errorCode, @Nullable String message); + default void onError(@GroupCallError int errorCode, @Nullable String message) {} /** * Called to indicate this call has changed state. @@ -65,8 +65,8 @@ public interface GroupCallCallback { * See {@link GroupCall#STATE_STOPPED}, {@link GroupCall#STATE_STARTED} * and {@link GroupCall#STATE_STALLED}. */ - void onGroupCallStateChanged(@GroupCall.GroupCallState int state, - @GroupCall.GroupCallStateChangeReason int reason); + default void onGroupCallStateChanged(@GroupCall.GroupCallState int state, + @GroupCall.GroupCallStateChangeReason int reason) {} /** * Broadcast Signal Strength updated. @@ -78,5 +78,6 @@ public interface GroupCallCallback { * {@link #SIGNAL_STRENGTH_UNAVAILABLE} if broadcast is not available * for this call due to timing, geography or popularity. */ - void onBroadcastSignalStrengthUpdated(@IntRange(from = -1, to = 4) int signalStrength); + default void onBroadcastSignalStrengthUpdated( + @IntRange(from = -1, to = 4) int signalStrength) {} } diff --git a/telephony/java/android/telephony/mbms/MbmsGroupCallSessionCallback.java b/telephony/java/android/telephony/mbms/MbmsGroupCallSessionCallback.java index 04e7ba1af372..ac7e17271e34 100644 --- a/telephony/java/android/telephony/mbms/MbmsGroupCallSessionCallback.java +++ b/telephony/java/android/telephony/mbms/MbmsGroupCallSessionCallback.java @@ -57,7 +57,7 @@ public interface MbmsGroupCallSessionCallback { * @param errorCode The error code. * @param message A human-readable message generated by the middleware for debugging purposes. */ - void onError(@GroupCallError int errorCode, @Nullable String message); + default void onError(@GroupCallError int errorCode, @Nullable String message) {} /** * Indicates that the list of currently available SAIs has been updated. The app may use this @@ -70,8 +70,8 @@ public interface MbmsGroupCallSessionCallback { * @param availableSais A list of lists of available SAIS in neighboring cells, where each list * contains the available SAIs in an individual cell. */ - void onAvailableSaisUpdated(@NonNull List<Integer> currentSais, - @NonNull List<List<Integer>> availableSais); + default void onAvailableSaisUpdated(@NonNull List<Integer> currentSais, + @NonNull List<List<Integer>> availableSais) {} /** * Called soon after the app calls {@link MbmsGroupCallSession#create}. The information supplied @@ -85,7 +85,7 @@ public interface MbmsGroupCallSessionCallback { * @param interfaceName The interface name for the data link. * @param index The index for the data link. */ - void onServiceInterfaceAvailable(@NonNull String interfaceName, int index); + default void onServiceInterfaceAvailable(@NonNull String interfaceName, int index) {} /** * Called to indicate that the middleware has been initialized and is ready. @@ -95,5 +95,5 @@ public interface MbmsGroupCallSessionCallback { * delivered via {@link #onError(int, String)} with error code * {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}. */ - void onMiddlewareReady(); + default void onMiddlewareReady() {} } diff --git a/telephony/java/com/android/ims/ImsException.java b/telephony/java/com/android/ims/ImsException.java index f35e88672a23..fea763ed5785 100644 --- a/telephony/java/com/android/ims/ImsException.java +++ b/telephony/java/com/android/ims/ImsException.java @@ -21,8 +21,10 @@ import android.telephony.ims.ImsReasonInfo; /** * This class defines a general IMS-related exception. * + * @deprecated Use {@link android.telephony.ims.ImsException} instead. * @hide */ +@Deprecated public class ImsException extends Exception { /** diff --git a/telephony/java/com/android/ims/RcsTypeIdPair.java b/telephony/java/com/android/ims/RcsTypeIdPair.java new file mode 100644 index 000000000000..a5177354002e --- /dev/null +++ b/telephony/java/com/android/ims/RcsTypeIdPair.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2019 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.ims; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A utility class to pass RCS IDs and types in RPC calls + * + * @hide + */ +public class RcsTypeIdPair implements Parcelable { + private int mType; + private int mId; + + public RcsTypeIdPair(int type, int id) { + mType = type; + mId = id; + } + + public int getType() { + return mType; + } + + public void setType(int type) { + mType = type; + } + + public int getId() { + return mId; + } + + public void setId(int id) { + mId = id; + } + + public RcsTypeIdPair(Parcel in) { + mType = in.readInt(); + mId = in.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mType); + dest.writeInt(mId); + } + + public static final Creator<RcsTypeIdPair> CREATOR = + new Creator<RcsTypeIdPair>() { + @Override + public RcsTypeIdPair createFromParcel(Parcel in) { + return new RcsTypeIdPair(in); + } + + @Override + public RcsTypeIdPair[] newArray(int size) { + return new RcsTypeIdPair[size]; + } + }; +} diff --git a/telephony/java/com/android/internal/telephony/EncodeException.java b/telephony/java/com/android/internal/telephony/EncodeException.java index 4e3fac1a2aea..cdc853e09895 100644 --- a/telephony/java/com/android/internal/telephony/EncodeException.java +++ b/telephony/java/com/android/internal/telephony/EncodeException.java @@ -22,6 +22,12 @@ import android.annotation.UnsupportedAppUsage; * {@hide} */ public class EncodeException extends Exception { + + private int mError = ERROR_UNENCODABLE; + + public static final int ERROR_UNENCODABLE = 0; + public static final int ERROR_EXCEED_SIZE = 1; + public EncodeException() { super(); } @@ -31,9 +37,18 @@ public class EncodeException extends Exception { super(s); } + public EncodeException(String s, int error) { + super(s); + mError = error; + } + @UnsupportedAppUsage public EncodeException(char c) { super("Unencodable char: '" + c + "'"); } + + public int getError() { + return mError; + } } diff --git a/telephony/java/com/android/internal/telephony/GsmAlphabet.java b/telephony/java/com/android/internal/telephony/GsmAlphabet.java index 84c0e6453104..a774cdc8a280 100644 --- a/telephony/java/com/android/internal/telephony/GsmAlphabet.java +++ b/telephony/java/com/android/internal/telephony/GsmAlphabet.java @@ -388,7 +388,7 @@ public class GsmAlphabet { * GSM extension table * @return the encoded message * - * @throws EncodeException if String is too large to encode + * @throws EncodeException if String is too large to encode or any characters are unencodable */ @UnsupportedAppUsage public static byte[] stringToGsm7BitPacked(String data, int startingSeptetOffset, @@ -402,7 +402,8 @@ public class GsmAlphabet { } septetCount += startingSeptetOffset; if (septetCount > 255) { - throw new EncodeException("Payload cannot exceed 255 septets"); + throw new EncodeException( + "Payload cannot exceed 255 septets", EncodeException.ERROR_EXCEED_SIZE); } int byteCount = ((septetCount * 7) + 7) / 8; byte[] ret = new byte[byteCount + 1]; // Include space for one byte length prefix. diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl index 04ec3d1a3df3..a49d2d976d16 100755 --- a/telephony/java/com/android/internal/telephony/ISub.aidl +++ b/telephony/java/com/android/internal/telephony/ISub.aidl @@ -276,6 +276,11 @@ interface ISub { String getSubscriptionProperty(int subId, String propKey, String callingPackage); + boolean setSubscriptionEnabled(boolean enable, int subId); + + boolean isSubscriptionEnabled(int subId); + + int getEnabledSubscriptionId(int slotIndex); /** * Get the SIM state for the slot index * @return SIM state as the ordinal of IccCardConstants.State diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index a12792a8cfa6..e6a55585506c 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -765,7 +765,7 @@ interface ITelephony { * @param subId the id of the subscription. * @return CellNetworkScanResult containing status of scan and networks. */ - CellNetworkScanResult getCellNetworkScanResults(int subId); + CellNetworkScanResult getCellNetworkScanResults(int subId, String callingPackage); /** * Perform a radio network scan and return the id of this scan. @@ -774,10 +774,11 @@ interface ITelephony { * @param request Defines all the configs for network scan. * @param messenger Callback messages will be sent using this messenger. * @param binder the binder object instantiated in TelephonyManager. + * @param callingPackage the calling package * @return An id for this scan. */ int requestNetworkScan(int subId, in NetworkScanRequest request, in Messenger messenger, - in IBinder binder); + in IBinder binder, in String callingPackage); /** * Stop an existing radio network scan. @@ -1829,4 +1830,26 @@ interface ITelephony { * @hide */ boolean isMultisimCarrierRestricted(); + + /** + * Switch configs to enable multi-sim or switch back to single-sim + * @hide + */ + void switchMultiSimConfig(int numOfSims); + /** + * Get how many modems have been activated on the phone + * @hide + */ + int getNumOfActiveSims(); + + /** + * Get if reboot is required upon altering modems configurations + * @hide + */ + boolean isRebootRequiredForModemConfigChange(); + + /** + * Get the mapping from logical slots to physical slots. + */ + int[] getSlotsMapping(); } diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index f901c0e29f9b..77b797956cf5 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -128,32 +128,108 @@ public interface RILConstants { int OEM_ERROR_25 = 525; /* NETWORK_MODE_* See ril.h RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE */ - int NETWORK_MODE_WCDMA_PREF = 0; /* GSM/WCDMA (WCDMA preferred) */ - int NETWORK_MODE_GSM_ONLY = 1; /* GSM only */ - int NETWORK_MODE_WCDMA_ONLY = 2; /* WCDMA only */ - int NETWORK_MODE_GSM_UMTS = 3; /* GSM/WCDMA (auto mode, according to PRL) - AVAILABLE Application Settings menu*/ - int NETWORK_MODE_CDMA = 4; /* CDMA and EvDo (auto mode, according to PRL) - AVAILABLE Application Settings menu*/ - int NETWORK_MODE_CDMA_NO_EVDO = 5; /* CDMA only */ - int NETWORK_MODE_EVDO_NO_CDMA = 6; /* EvDo only */ - int NETWORK_MODE_GLOBAL = 7; /* GSM/WCDMA, CDMA, and EvDo (auto mode, according to PRL) - AVAILABLE Application Settings menu*/ - int NETWORK_MODE_LTE_CDMA_EVDO = 8; /* LTE, CDMA and EvDo */ - int NETWORK_MODE_LTE_GSM_WCDMA = 9; /* LTE, GSM/WCDMA */ - int NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA = 10; /* LTE, CDMA, EvDo, GSM/WCDMA */ - int NETWORK_MODE_LTE_ONLY = 11; /* LTE Only mode. */ - int NETWORK_MODE_LTE_WCDMA = 12; /* LTE/WCDMA */ - int NETWORK_MODE_TDSCDMA_ONLY = 13; /* TD-SCDMA only */ - int NETWORK_MODE_TDSCDMA_WCDMA = 14; /* TD-SCDMA and WCDMA */ - int NETWORK_MODE_LTE_TDSCDMA = 15; /* TD-SCDMA and LTE */ - int NETWORK_MODE_TDSCDMA_GSM = 16; /* TD-SCDMA and GSM */ - int NETWORK_MODE_LTE_TDSCDMA_GSM = 17; /* TD-SCDMA,GSM and LTE */ - int NETWORK_MODE_TDSCDMA_GSM_WCDMA = 18; /* TD-SCDMA, GSM/WCDMA */ - int NETWORK_MODE_LTE_TDSCDMA_WCDMA = 19; /* TD-SCDMA, WCDMA and LTE */ - int NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA = 20; /* TD-SCDMA, GSM/WCDMA and LTE */ - int NETWORK_MODE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = 21; /*TD-SCDMA,EvDo,CDMA,GSM/WCDMA*/ - int NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = 22; /* TD-SCDMA/LTE/GSM/WCDMA, CDMA, and EvDo */ + /** GSM, WCDMA (WCDMA preferred) */ + int NETWORK_MODE_WCDMA_PREF = 0; + + /** GSM only */ + int NETWORK_MODE_GSM_ONLY = 1; + + /** WCDMA only */ + int NETWORK_MODE_WCDMA_ONLY = 2; + + /** GSM, WCDMA (auto mode, according to PRL) */ + int NETWORK_MODE_GSM_UMTS = 3; + + /** CDMA and EvDo (auto mode, according to PRL) */ + int NETWORK_MODE_CDMA = 4; + + /** CDMA only */ + int NETWORK_MODE_CDMA_NO_EVDO = 5; + + /** EvDo only */ + int NETWORK_MODE_EVDO_NO_CDMA = 6; + + /** GSM, WCDMA, CDMA, and EvDo (auto mode, according to PRL) */ + int NETWORK_MODE_GLOBAL = 7; + + /** LTE, CDMA and EvDo */ + int NETWORK_MODE_LTE_CDMA_EVDO = 8; + + /** LTE, GSM and WCDMA */ + int NETWORK_MODE_LTE_GSM_WCDMA = 9; + + /** LTE, CDMA, EvDo, GSM, and WCDMA */ + int NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA = 10; + + /** LTE only mode. */ + int NETWORK_MODE_LTE_ONLY = 11; + + /** LTE and WCDMA */ + int NETWORK_MODE_LTE_WCDMA = 12; + + /** TD-SCDMA only */ + int NETWORK_MODE_TDSCDMA_ONLY = 13; + + /** TD-SCDMA and WCDMA */ + int NETWORK_MODE_TDSCDMA_WCDMA = 14; + + /** LTE and TD-SCDMA*/ + int NETWORK_MODE_LTE_TDSCDMA = 15; + + /** TD-SCDMA and GSM */ + int NETWORK_MODE_TDSCDMA_GSM = 16; + + /** TD-SCDMA, GSM and LTE */ + int NETWORK_MODE_LTE_TDSCDMA_GSM = 17; + + /** TD-SCDMA, GSM and WCDMA */ + int NETWORK_MODE_TDSCDMA_GSM_WCDMA = 18; + + /** LTE, TD-SCDMA and WCDMA */ + int NETWORK_MODE_LTE_TDSCDMA_WCDMA = 19; + + /** LTE, TD-SCDMA, GSM, and WCDMA */ + int NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA = 20; + + /** TD-SCDMA, CDMA, EVDO, GSM and WCDMA */ + int NETWORK_MODE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = 21; + + /** LTE, TDCSDMA, CDMA, EVDO, GSM and WCDMA */ + int NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = 22; + + /** NR 5G only mode */ + int NETWORK_MODE_NR_ONLY = 23; + + /** NR 5G, LTE */ + int NETWORK_MODE_NR_LTE = 24; + + /** NR 5G, LTE, CDMA and EvDo */ + int NETWORK_MODE_NR_LTE_CDMA_EVDO = 25; + + /** NR 5G, LTE, GSM and WCDMA */ + int NETWORK_MODE_NR_LTE_GSM_WCDMA = 26; + + /** NR 5G, LTE, CDMA, EvDo, GSM and WCDMA */ + int NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA = 27; + + /** NR 5G, LTE and WCDMA */ + int NETWORK_MODE_NR_LTE_WCDMA = 28; + + /** NR 5G, LTE and TDSCDMA */ + int NETWORK_MODE_NR_LTE_TDSCDMA = 29; + + /** NR 5G, LTE, TD-SCDMA and GSM */ + int NETWORK_MODE_NR_LTE_TDSCDMA_GSM = 30; + + /** NR 5G, LTE, TD-SCDMA, WCDMA */ + int NETWORK_MODE_NR_LTE_TDSCDMA_WCDMA = 31; + + /** NR 5G, LTE, TD-SCDMA, GSM and WCDMA */ + int NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA = 32; + + /** NR 5G, LTE, TD-SCDMA, CDMA, EVDO, GSM and WCDMA */ + int NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = 33; + int PREFERRED_NETWORK_MODE = Integer.parseInt(TelephonyManager.getTelephonyProperty(0, "ro.telephony.default_network", Integer.toString(NETWORK_MODE_WCDMA_PREF))); @@ -404,6 +480,7 @@ public interface RILConstants { int RIL_REQUEST_SET_PREFERRED_DATA_MODEM = 204; int RIL_REQUEST_EMERGENCY_DIAL = 205; int RIL_REQUEST_GET_PHONE_CAPABILITY = 206; + int RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG = 207; /* Responses begin */ int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; diff --git a/telephony/java/com/android/internal/telephony/SmsMessageBase.java b/telephony/java/com/android/internal/telephony/SmsMessageBase.java index 190eac4d8c02..ffdc4b676f90 100644 --- a/telephony/java/com/android/internal/telephony/SmsMessageBase.java +++ b/telephony/java/com/android/internal/telephony/SmsMessageBase.java @@ -41,6 +41,9 @@ public abstract class SmsMessageBase { @UnsupportedAppUsage protected SmsAddress mOriginatingAddress; + /** {@hide} The address of the receiver */ + protected SmsAddress mRecipientAddress; + /** {@hide} The message body as a string. May be null if the message isn't text */ @UnsupportedAppUsage protected String mMessageBody; @@ -457,4 +460,17 @@ public abstract class SmsMessageBase { return ted; } + + /** + * {@hide} + * Returns the receiver address of this SMS message in String + * form or null if unavailable + */ + public String getRecipientAddress() { + if (mRecipientAddress == null) { + return null; + } + + return mRecipientAddress.getAddressString(); + } } diff --git a/telephony/java/com/android/internal/telephony/TelephonyProperties.java b/telephony/java/com/android/internal/telephony/TelephonyProperties.java index 6567ea764b50..603c4c2870d7 100644 --- a/telephony/java/com/android/internal/telephony/TelephonyProperties.java +++ b/telephony/java/com/android/internal/telephony/TelephonyProperties.java @@ -194,6 +194,13 @@ public interface TelephonyProperties */ static final String PROPERTY_MULTI_SIM_CONFIG = "persist.radio.multisim.config"; + /** + * Property to indicate if reboot is required when changing modems configurations + * Type: String(true, false) default is false; most devices don't need reboot + */ + String PROPERTY_REBOOT_REQUIRED_ON_MODEM_CHANGE = + "persist.radio.reboot_on_modem_change"; + /** * Property to store default subscription. */ diff --git a/telephony/java/com/android/internal/telephony/cdma/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/BearerData.java index a4cd56b9e3e2..694cc69c2b3f 100644 --- a/telephony/java/com/android/internal/telephony/cdma/BearerData.java +++ b/telephony/java/com/android/internal/telephony/cdma/BearerData.java @@ -596,6 +596,45 @@ public final class BearerData { System.arraycopy(payload, 0, uData.payload, udhBytes, payload.length); } + private static void encode7bitAsciiEms(UserData uData, byte[] udhData, boolean force) + throws CodingException + { + try { + Rlog.d(LOG_TAG, "encode7bitAsciiEms"); + int udhBytes = udhData.length + 1; // Add length octet. + int udhSeptets = ((udhBytes * 8) + 6) / 7; + int paddingBits = (udhSeptets * 7) - (udhBytes * 8); + String msg = uData.payloadStr; + byte[] payload ; + int msgLen = msg.length(); + BitwiseOutputStream outStream = new BitwiseOutputStream(msgLen + + (paddingBits > 0 ? 1 : 0)); + outStream.write(paddingBits, 0); + for (int i = 0; i < msgLen; i++) { + int charCode = UserData.charToAscii.get(msg.charAt(i), -1); + if (charCode == -1) { + if (force) { + outStream.write(7, UserData.UNENCODABLE_7_BIT_CHAR); + } else { + throw new CodingException("cannot ASCII encode (" + msg.charAt(i) + ")"); + } + } else { + outStream.write(7, charCode); + } + } + payload = outStream.toByteArray(); + uData.msgEncoding = UserData.ENCODING_7BIT_ASCII; + uData.msgEncodingSet = true; + uData.numFields = udhSeptets + uData.payloadStr.length(); + uData.payload = new byte[udhBytes + payload.length]; + uData.payload[0] = (byte)udhData.length; + System.arraycopy(udhData, 0, uData.payload, 1, udhData.length); + System.arraycopy(payload, 0, uData.payload, udhBytes, payload.length); + } catch (BitwiseOutputStream.AccessException ex) { + throw new CodingException("7bit ASCII encode failed: " + ex); + } + } + private static void encodeEmsUserDataPayload(UserData uData) throws CodingException { @@ -605,6 +644,8 @@ public final class BearerData { encode7bitEms(uData, headerData, true); } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) { encode16bitEms(uData, headerData); + } else if (uData.msgEncoding == UserData.ENCODING_7BIT_ASCII) { + encode7bitAsciiEms(uData, headerData, true); } else { throw new CodingException("unsupported EMS user data encoding (" + uData.msgEncoding + ")"); @@ -1056,15 +1097,18 @@ public final class BearerData { throws CodingException { try { - offset *= 8; + int offsetBits = offset * 8; + int offsetSeptets = (offsetBits + 6) / 7; + numFields -= offsetSeptets; + StringBuffer strBuf = new StringBuffer(numFields); BitwiseInputStream inStream = new BitwiseInputStream(data); - int wantedBits = (offset * 8) + (numFields * 7); + int wantedBits = (offsetSeptets * 7) + (numFields * 7); if (inStream.available() < wantedBits) { throw new CodingException("insufficient data (wanted " + wantedBits + " bits, but only have " + inStream.available() + ")"); } - inStream.skip(offset); + inStream.skip(offsetSeptets * 7); for (int i = 0; i < numFields; i++) { int charCode = inStream.read(7); if ((charCode >= UserData.ASCII_MAP_BASE_INDEX) && diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java index 964a31304db5..a31fa0b6a725 100644 --- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java @@ -21,7 +21,6 @@ import android.os.SystemProperties; import android.telephony.PhoneNumberUtils; import android.telephony.SmsCbLocation; import android.telephony.SmsCbMessage; -import android.telephony.TelephonyManager; import android.telephony.cdma.CdmaSmsCbProgramData; import android.telephony.Rlog; import android.util.Log; @@ -602,18 +601,24 @@ public class SmsMessage extends SmsMessageBase { } else if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_DATA_NETWORK) { if (numberType == 2) - Rlog.e(LOG_TAG, "TODO: Originating Addr is email id"); + Rlog.e(LOG_TAG, "TODO: Addr is email id"); else Rlog.e(LOG_TAG, - "TODO: Originating Addr is data network address"); + "TODO: Addr is data network address"); } else { - Rlog.e(LOG_TAG, "Originating Addr is of incorrect type"); + Rlog.e(LOG_TAG, "Addr is of incorrect type"); } } else { Rlog.e(LOG_TAG, "Incorrect Digit mode"); } addr.origBytes = data; - Rlog.i(LOG_TAG, "Originating Addr=" + addr.toString()); + Rlog.pii(LOG_TAG, "Addr=" + addr.toString()); + mOriginatingAddress = addr; + if (parameterId == DESTINATION_ADDRESS) { + // Original address awlays indicates one sender's address for 3GPP2 + // Here add recipient address support along with 3GPP + mRecipientAddress = addr; + } break; case ORIGINATING_SUB_ADDRESS: case DESTINATION_SUB_ADDRESS: @@ -668,7 +673,7 @@ public class SmsMessage extends SmsMessageBase { } /** - * Parses a SMS message from its BearerData stream. (mobile-terminated only) + * Parses a SMS message from its BearerData stream. */ public void parseSms() { // Message Waiting Info Record defined in 3GPP2 C.S-0005, 3.7.5.6 @@ -698,16 +703,15 @@ public class SmsMessage extends SmsMessageBase { } if (mOriginatingAddress != null) { - mOriginatingAddress.address = new String(mOriginatingAddress.origBytes); - if (mOriginatingAddress.ton == CdmaSmsAddress.TON_INTERNATIONAL_OR_IP) { - if (mOriginatingAddress.address.charAt(0) != '+') { - mOriginatingAddress.address = "+" + mOriginatingAddress.address; - } - } + decodeSmsDisplayAddress(mOriginatingAddress); if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: " + mOriginatingAddress.address); } + if (mRecipientAddress != null) { + decodeSmsDisplayAddress(mRecipientAddress); + } + if (mBearerData.msgCenterTimeStamp != null) { mScTimeMillis = mBearerData.msgCenterTimeStamp.toMillis(true); } @@ -732,7 +736,8 @@ public class SmsMessage extends SmsMessageBase { status = mBearerData.errorClass << 8; status |= mBearerData.messageStatus; } - } else if (mBearerData.messageType != BearerData.MESSAGE_TYPE_DELIVER) { + } else if (mBearerData.messageType != BearerData.MESSAGE_TYPE_DELIVER + && mBearerData.messageType != BearerData.MESSAGE_TYPE_SUBMIT) { throw new RuntimeException("Unsupported message type: " + mBearerData.messageType); } @@ -744,10 +749,22 @@ public class SmsMessage extends SmsMessageBase { } } + private void decodeSmsDisplayAddress(SmsAddress addr) { + addr.address = new String(addr.origBytes); + if (addr.ton == CdmaSmsAddress.TON_INTERNATIONAL_OR_IP) { + if (addr.address.charAt(0) != '+') { + addr.address = "+" + addr.address; + } + } + Rlog.pii(LOG_TAG, " decodeSmsDisplayAddress = " + addr.address); + } + /** * Parses a broadcast SMS, possibly containing a CMAS alert. + * + * @param plmn the PLMN for a broadcast SMS */ - public SmsCbMessage parseBroadcastSms() { + public SmsCbMessage parseBroadcastSms(String plmn) { BearerData bData = BearerData.decode(mEnvelope.bearerData, mEnvelope.serviceCategory); if (bData == null) { Rlog.w(LOG_TAG, "BearerData.decode() returned null"); @@ -758,7 +775,6 @@ public class SmsMessage extends SmsMessageBase { Rlog.d(LOG_TAG, "MT raw BearerData = " + HexDump.toHexString(mEnvelope.bearerData)); } - String plmn = TelephonyManager.getDefault().getNetworkOperator(); SmsCbLocation location = new SmsCbLocation(plmn); return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP2, @@ -858,14 +874,15 @@ public class SmsMessage extends SmsMessageBase { bearerData.userData = userData; byte[] encodedBearerData = BearerData.encode(bearerData); + if (encodedBearerData == null) return null; if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) { Rlog.d(LOG_TAG, "MO (encoded) BearerData = " + bearerData); Rlog.d(LOG_TAG, "MO raw BearerData = '" + HexDump.toHexString(encodedBearerData) + "'"); } - if (encodedBearerData == null) return null; - int teleservice = bearerData.hasUserDataHeader ? - SmsEnvelope.TELESERVICE_WEMT : SmsEnvelope.TELESERVICE_WMT; + int teleservice = (bearerData.hasUserDataHeader + && userData.msgEncoding != UserData.ENCODING_7BIT_ASCII) + ? SmsEnvelope.TELESERVICE_WEMT : SmsEnvelope.TELESERVICE_WMT; SmsEnvelope envelope = new SmsEnvelope(); envelope.messageType = SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT; diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl index 14a36c8c840d..20169152539e 100644 --- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl +++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl @@ -31,7 +31,7 @@ interface IEuiccController { String callingPackage, in PendingIntent callbackIntent); oneway void getDefaultDownloadableSubscriptionList(int cardId, String callingPackage, in PendingIntent callbackIntent); - String getEid(int cardId); + String getEid(int cardId, String callingPackage); int getOtaStatus(int cardId); oneway void downloadSubscription(int cardId, in DownloadableSubscription subscription, boolean switchAfterDownload, String callingPackage, in Bundle resolvedBundle, diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java index 4f5bfa919135..19465a44e4e8 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java @@ -71,9 +71,6 @@ public class SmsMessage extends SmsMessageBase { // e.g. 23.040 9.2.2.1 private boolean mReplyPathPresent = false; - /** The address of the receiver. */ - private GsmSmsAddress mRecipientAddress; - /** * TP-Status - status of a previously submitted SMS. * This field applies to SMS-STATUS-REPORT messages. 0 indicates success; @@ -384,16 +381,22 @@ public class SmsMessage extends SmsMessageBase { } } } catch (EncodeException ex) { - // Encoding to the 7-bit alphabet failed. Let's see if we can - // send it as a UCS-2 encoded message - try { - userData = encodeUCS2(message, header); - encoding = ENCODING_16BIT; - } catch(UnsupportedEncodingException uex) { - Rlog.e(LOG_TAG, - "Implausible UnsupportedEncodingException ", - uex); + if (ex.getError() == EncodeException.ERROR_EXCEED_SIZE) { + Rlog.e(LOG_TAG, "Exceed size limitation EncodeException", ex); return null; + } else { + // Encoding to the 7-bit alphabet failed. Let's see if we can + // send it as a UCS-2 encoded message + try { + userData = encodeUCS2(message, header); + encoding = ENCODING_16BIT; + } catch (EncodeException ex1) { + Rlog.e(LOG_TAG, "Exceed size limitation EncodeException", ex1); + return null; + } catch (UnsupportedEncodingException uex) { + Rlog.e(LOG_TAG, "Implausible UnsupportedEncodingException ", uex); + return null; + } } } @@ -438,9 +441,10 @@ public class SmsMessage extends SmsMessageBase { * * @return encoded message as UCS2 * @throws UnsupportedEncodingException + * @throws EncodeException if String is too large to encode */ private static byte[] encodeUCS2(String message, byte[] header) - throws UnsupportedEncodingException { + throws UnsupportedEncodingException, EncodeException { byte[] userData, textPart; textPart = message.getBytes("utf-16be"); @@ -455,6 +459,10 @@ public class SmsMessage extends SmsMessageBase { else { userData = textPart; } + if (userData.length > 255) { + throw new EncodeException( + "Payload cannot exceed 255 bytes", EncodeException.ERROR_EXCEED_SIZE); + } byte[] ret = new byte[userData.length+1]; ret[0] = (byte) (userData.length & 0xff ); System.arraycopy(userData, 0, ret, 1, userData.length); diff --git a/test-base/api/TEST_MAPPING b/test-base/api/TEST_MAPPING new file mode 100644 index 000000000000..3535954bc019 --- /dev/null +++ b/test-base/api/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "CtsAndroidTestBase27ApiSignatureTestCases" + } + ] +} diff --git a/test-mock/api/TEST_MAPPING b/test-mock/api/TEST_MAPPING new file mode 100644 index 000000000000..d1bd9afaf287 --- /dev/null +++ b/test-mock/api/TEST_MAPPING @@ -0,0 +1,10 @@ +{ + "presubmit": [ + { + "name": "CtsAndroidTestMockCurrentApiSignatureTestCases" + }, + { + "name": "CtsCurrentApiSignatureTestCases" + } + ] +} diff --git a/test-runner/Android.mk b/test-runner/Android.mk deleted file mode 100644 index 18bde8517351..000000000000 --- a/test-runner/Android.mk +++ /dev/null @@ -1,20 +0,0 @@ -# -# Copyright (C) 2008 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -LOCAL_PATH:= $(call my-dir) - -# additionally, build unit tests in a separate .apk -include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/test-runner/api/TEST_MAPPING b/test-runner/api/TEST_MAPPING new file mode 100644 index 000000000000..76ade3c1e647 --- /dev/null +++ b/test-runner/api/TEST_MAPPING @@ -0,0 +1,10 @@ +{ + "presubmit": [ + { + "name": "CtsAndroidTestRunnerCurrentApiSignatureTestCases" + }, + { + "name": "CtsCurrentApiSignatureTestCases" + } + ] +} diff --git a/test-runner/tests/Android.bp b/test-runner/tests/Android.bp new file mode 100644 index 000000000000..03c73986118d --- /dev/null +++ b/test-runner/tests/Android.bp @@ -0,0 +1,40 @@ +// Copyright 2010, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test { + name: "FrameworkTestRunnerTests", + + // We only want this apk build for tests. + // + // Run the tests using the following commands: + // adb install -r ${ANDROID_PRODUCT_OUT}/data/app/FrameworkTestRunnerTests/FrameworkTestRunnerTests.apk + // adb shell am instrument \ + // -e notAnnotation android.test.suitebuilder.examples.error.RunAsPartOfSeparateTest \ + // -w com.android.frameworks.testrunner.tests/android.test.InstrumentationTestRunner \ + // + + libs: [ + "android.test.runner", + "android.test.base", + "android.test.mock", + ], + static_libs: ["junit"], + + // Include all test java files. + srcs: ["src/**/*.java"], + + // Because of android.test.mock. + platform_apis: true, + +} diff --git a/test-runner/tests/Android.mk b/test-runner/tests/Android.mk deleted file mode 100644 index f97d1c986b1c..000000000000 --- a/test-runner/tests/Android.mk +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2010, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -# We only want this apk build for tests. -# -# Run the tests using the following commands: -# adb install -r ${ANDROID_PRODUCT_OUT}/data/app/FrameworkTestRunnerTests/FrameworkTestRunnerTests.apk -# adb shell am instrument \ - -e notAnnotation android.test.suitebuilder.examples.error.RunAsPartOfSeparateTest \ - -w com.android.frameworks.testrunner.tests/android.test.InstrumentationTestRunner -# -LOCAL_MODULE_TAGS := tests - -LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base android.test.mock -LOCAL_STATIC_JAVA_LIBRARIES := junit - -# Include all test java files. -LOCAL_SRC_FILES := $(call all-java-files-under, src) - -LOCAL_PACKAGE_NAME := FrameworkTestRunnerTests -# Because of android.test.mock. -LOCAL_PRIVATE_PLATFORM_APIS := true - -include $(BUILD_PACKAGE) - diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadIconChangedEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadIconChangedEventTest.java new file mode 100644 index 000000000000..915a260f5c79 --- /dev/null +++ b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadIconChangedEventTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2019 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.tests.ims; + +import static com.google.common.truth.Truth.assertThat; + +import android.net.Uri; +import android.os.Parcel; +import android.support.test.runner.AndroidJUnit4; +import android.telephony.ims.RcsGroupThread; +import android.telephony.ims.RcsGroupThreadIconChangedEvent; +import android.telephony.ims.RcsParticipant; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class RcsGroupThreadIconChangedEventTest { + + @Test + public void testCanUnparcel() { + RcsGroupThread rcsGroupThread = new RcsGroupThread(1); + RcsParticipant rcsParticipant = new RcsParticipant(2); + Uri newIconUri = Uri.parse("content://new_icon"); + + RcsGroupThreadIconChangedEvent iconChangedEvent = + new RcsGroupThreadIconChangedEvent(1234567890, rcsGroupThread, rcsParticipant, + newIconUri); + + Parcel parcel = Parcel.obtain(); + iconChangedEvent.writeToParcel(parcel, iconChangedEvent.describeContents()); + + parcel.setDataPosition(0); + + iconChangedEvent = RcsGroupThreadIconChangedEvent.CREATOR.createFromParcel(parcel); + + assertThat(iconChangedEvent.getNewIcon()).isEqualTo(newIconUri); + assertThat(iconChangedEvent.getRcsGroupThread().getThreadId()).isEqualTo(1); + assertThat(iconChangedEvent.getOriginatingParticipant().getId()).isEqualTo(2); + assertThat(iconChangedEvent.getTimestamp()).isEqualTo(1234567890); + } +} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadNameChangedEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadNameChangedEventTest.java new file mode 100644 index 000000000000..1384c016daa8 --- /dev/null +++ b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadNameChangedEventTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2019 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.tests.ims; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; +import android.support.test.runner.AndroidJUnit4; +import android.telephony.ims.RcsGroupThread; +import android.telephony.ims.RcsGroupThreadNameChangedEvent; +import android.telephony.ims.RcsParticipant; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class RcsGroupThreadNameChangedEventTest { + @Test + public void testCanUnparcel() { + String newName = "new name"; + + RcsGroupThread rcsGroupThread = new RcsGroupThread(1); + RcsParticipant rcsParticipant = new RcsParticipant(2); + + RcsGroupThreadNameChangedEvent nameChangedEvent = + new RcsGroupThreadNameChangedEvent(1234567890, rcsGroupThread, rcsParticipant, + newName); + + Parcel parcel = Parcel.obtain(); + nameChangedEvent.writeToParcel(parcel, nameChangedEvent.describeContents()); + + parcel.setDataPosition(0); + + nameChangedEvent = RcsGroupThreadNameChangedEvent.CREATOR.createFromParcel( + parcel); + + assertThat(nameChangedEvent.getNewName()).isEqualTo(newName); + assertThat(nameChangedEvent.getRcsGroupThread().getThreadId()).isEqualTo(1); + assertThat(nameChangedEvent.getOriginatingParticipant().getId()).isEqualTo(2); + assertThat(nameChangedEvent.getTimestamp()).isEqualTo(1234567890); + } +} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantJoinedEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantJoinedEventTest.java new file mode 100644 index 000000000000..d0af7db90627 --- /dev/null +++ b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantJoinedEventTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019 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.tests.ims; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; +import android.support.test.runner.AndroidJUnit4; +import android.telephony.ims.RcsGroupThread; +import android.telephony.ims.RcsGroupThreadParticipantJoinedEvent; +import android.telephony.ims.RcsParticipant; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class RcsGroupThreadParticipantJoinedEventTest { + + @Test + public void testCanUnparcel() { + RcsGroupThread rcsGroupThread = new RcsGroupThread(1); + RcsParticipant rcsParticipant = new RcsParticipant(2); + + RcsGroupThreadParticipantJoinedEvent participantJoinedEvent = + new RcsGroupThreadParticipantJoinedEvent(1234567890, rcsGroupThread, rcsParticipant, + rcsParticipant); + + Parcel parcel = Parcel.obtain(); + participantJoinedEvent.writeToParcel(parcel, participantJoinedEvent.describeContents()); + + parcel.setDataPosition(0); + + participantJoinedEvent = RcsGroupThreadParticipantJoinedEvent.CREATOR.createFromParcel( + parcel); + + assertThat(participantJoinedEvent.getJoinedParticipant().getId()).isEqualTo(2); + assertThat(participantJoinedEvent.getRcsGroupThread().getThreadId()).isEqualTo(1); + assertThat(participantJoinedEvent.getOriginatingParticipant().getId()).isEqualTo(2); + assertThat(participantJoinedEvent.getTimestamp()).isEqualTo(1234567890); + } +} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantLeftEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantLeftEventTest.java new file mode 100644 index 000000000000..7ba5fa653258 --- /dev/null +++ b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantLeftEventTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2019 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.tests.ims; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; +import android.support.test.runner.AndroidJUnit4; +import android.telephony.ims.RcsGroupThread; +import android.telephony.ims.RcsGroupThreadParticipantLeftEvent; +import android.telephony.ims.RcsParticipant; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class RcsGroupThreadParticipantLeftEventTest { + @Test + public void testCanUnparcel() { + RcsGroupThread rcsGroupThread = new RcsGroupThread(1); + RcsParticipant rcsParticipant = new RcsParticipant(2); + + RcsGroupThreadParticipantLeftEvent participantLeftEvent = + new RcsGroupThreadParticipantLeftEvent(1234567890, rcsGroupThread, rcsParticipant, + rcsParticipant); + + Parcel parcel = Parcel.obtain(); + participantLeftEvent.writeToParcel(parcel, participantLeftEvent.describeContents()); + + parcel.setDataPosition(0); + + // create from parcel + parcel.setDataPosition(0); + participantLeftEvent = RcsGroupThreadParticipantLeftEvent.CREATOR.createFromParcel( + parcel); + assertThat(participantLeftEvent.getRcsGroupThread().getThreadId()).isEqualTo(1); + assertThat(participantLeftEvent.getLeavingParticipantId().getId()).isEqualTo(2); + assertThat(participantLeftEvent.getTimestamp()).isEqualTo(1234567890); + } +} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsMessageStoreTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsMessageStoreTest.java deleted file mode 100644 index 44277edcdb8c..000000000000 --- a/tests/RcsTests/src/com/android/tests/ims/RcsMessageStoreTest.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tests.ims; - -import android.support.test.runner.AndroidJUnit4; -import android.telephony.ims.RcsMessageStore; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class RcsMessageStoreTest { - //TODO(sahinc): Add meaningful tests once we have more of the implementation in place - @Test - public void testDeleteThreadDoesntCrash() { - RcsMessageStore mRcsMessageStore = new RcsMessageStore(); - mRcsMessageStore.deleteThread(0); - } -} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsParticipantAliasChangedEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantAliasChangedEventTest.java new file mode 100644 index 000000000000..3e2bbbf8256c --- /dev/null +++ b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantAliasChangedEventTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019 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.tests.ims; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; +import android.support.test.runner.AndroidJUnit4; +import android.telephony.ims.RcsParticipant; +import android.telephony.ims.RcsParticipantAliasChangedEvent; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class RcsParticipantAliasChangedEventTest { + private static final String OLD_ALIAS = "old alias"; + private static final String NEW_ALIAS = "new alias"; + private RcsParticipant mParticipant; + + @Before + public void setUp() { + mParticipant = new RcsParticipant(3); + } + + @Test + public void testCanUnparcel() { + RcsParticipantAliasChangedEvent aliasChangedEvent = + new RcsParticipantAliasChangedEvent(1234567890, mParticipant, NEW_ALIAS); + + Parcel parcel = Parcel.obtain(); + aliasChangedEvent.writeToParcel(parcel, aliasChangedEvent.describeContents()); + + parcel.setDataPosition(0); + + aliasChangedEvent = RcsParticipantAliasChangedEvent.CREATOR.createFromParcel( + parcel); + + assertThat(aliasChangedEvent.getParticipantId().getId()).isEqualTo(3); + assertThat(aliasChangedEvent.getNewAlias()).isEqualTo(NEW_ALIAS); + assertThat(aliasChangedEvent.getTimestamp()).isEqualTo(1234567890); + } +} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsParticipantQueryParamsTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantQueryParamsTest.java new file mode 100644 index 000000000000..6361a393187e --- /dev/null +++ b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantQueryParamsTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tests.ims; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; +import android.support.test.runner.AndroidJUnit4; +import android.telephony.ims.RcsParticipantQueryParams; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class RcsParticipantQueryParamsTest { + + @Test + public void testCanUnparcel() { + RcsParticipantQueryParams rcsParticipantQueryParams = + new RcsParticipantQueryParams.Builder() + .setAliasLike("%alias_") + .setCanonicalAddressLike("_canonical%") + .setSortProperty(RcsParticipantQueryParams.SORT_BY_CANONICAL_ADDRESS) + .setSortDirection(true) + .setResultLimit(432) + .build(); + + + Parcel parcel = Parcel.obtain(); + rcsParticipantQueryParams.writeToParcel(parcel, + rcsParticipantQueryParams.describeContents()); + + parcel.setDataPosition(0); + rcsParticipantQueryParams = RcsParticipantQueryParams.CREATOR.createFromParcel( + parcel); + + assertThat(rcsParticipantQueryParams.getAliasLike()).isEqualTo("%alias_"); + assertThat(rcsParticipantQueryParams.getCanonicalAddressLike()).contains("_canonical%"); + assertThat(rcsParticipantQueryParams.getLimit()).isEqualTo(432); + assertThat(rcsParticipantQueryParams.getSortingProperty()).isEqualTo( + RcsParticipantQueryParams.SORT_BY_CANONICAL_ADDRESS); + assertThat(rcsParticipantQueryParams.getSortDirection()).isTrue(); + } +} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsParticipantTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantTest.java deleted file mode 100644 index c402dbffc84b..000000000000 --- a/tests/RcsTests/src/com/android/tests/ims/RcsParticipantTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tests.ims; - -import static com.google.common.truth.Truth.assertThat; - -import android.os.Bundle; -import android.support.test.runner.AndroidJUnit4; -import android.telephony.ims.RcsParticipant; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class RcsParticipantTest { - private static final int ID = 123; - private static final String ALIAS = "alias"; - private static final String CANONICAL_ADDRESS = "+1234567890"; - - @Test - public void testCanUnparcel() { - RcsParticipant rcsParticipant = new RcsParticipant(ID, CANONICAL_ADDRESS); - rcsParticipant.setAlias(ALIAS); - - Bundle bundle = new Bundle(); - bundle.putParcelable("Some key", rcsParticipant); - rcsParticipant = bundle.getParcelable("Some key"); - - assertThat(rcsParticipant.getId()).isEqualTo(ID); - assertThat(rcsParticipant.getAlias()).isEqualTo(ALIAS); - assertThat(rcsParticipant.getCanonicalAddress()).isEqualTo(CANONICAL_ADDRESS); - } -} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParametersTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParametersTest.java deleted file mode 100644 index a890a389bdfc..000000000000 --- a/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParametersTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tests.ims; - -import static com.google.common.truth.Truth.assertThat; - -import android.os.Bundle; -import android.support.test.runner.AndroidJUnit4; -import android.telephony.ims.RcsParticipant; -import android.telephony.ims.RcsThreadQueryParameters; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; - -@RunWith(AndroidJUnit4.class) -public class RcsThreadQueryParametersTest { - private RcsThreadQueryParameters mRcsThreadQueryParameters; - @Mock RcsParticipant mMockParticipant; - - @Test - public void testUnparceling() { - String key = "some key"; - mRcsThreadQueryParameters = RcsThreadQueryParameters.builder() - .isGroupThread(true) - .withParticipant(mMockParticipant) - .limitResultsTo(50) - .sort(true) - .build(); - - Bundle bundle = new Bundle(); - bundle.putParcelable(key, mRcsThreadQueryParameters); - mRcsThreadQueryParameters = bundle.getParcelable(key); - - assertThat(mRcsThreadQueryParameters.isGroupThread()).isTrue(); - assertThat(mRcsThreadQueryParameters.getRcsParticipants()).contains(mMockParticipant); - assertThat(mRcsThreadQueryParameters.getLimit()).isEqualTo(50); - assertThat(mRcsThreadQueryParameters.isAscending()).isTrue(); - } -} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParamsTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParamsTest.java new file mode 100644 index 000000000000..beb4f8ad28e2 --- /dev/null +++ b/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParamsTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tests.ims; + +import static android.telephony.ims.RcsThreadQueryParams.SORT_BY_TIMESTAMP; +import static android.telephony.ims.RcsThreadQueryParams.THREAD_TYPE_GROUP; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; +import android.support.test.runner.AndroidJUnit4; +import android.telephony.ims.RcsParticipant; +import android.telephony.ims.RcsThreadQueryParams; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class RcsThreadQueryParamsTest { + + @Test + public void testCanUnparcel() { + RcsParticipant rcsParticipant = new RcsParticipant(1); + RcsThreadQueryParams rcsThreadQueryParams = new RcsThreadQueryParams.Builder() + .setThreadType(THREAD_TYPE_GROUP) + .setParticipant(rcsParticipant) + .setResultLimit(50) + .setSortProperty(SORT_BY_TIMESTAMP) + .setSortDirection(true) + .build(); + + Parcel parcel = Parcel.obtain(); + rcsThreadQueryParams.writeToParcel(parcel, rcsThreadQueryParams.describeContents()); + + parcel.setDataPosition(0); + rcsThreadQueryParams = RcsThreadQueryParams.CREATOR.createFromParcel(parcel); + + assertThat(rcsThreadQueryParams.getThreadType()).isEqualTo(THREAD_TYPE_GROUP); + assertThat(rcsThreadQueryParams.getRcsParticipantsIds()) + .contains(rcsParticipant.getId()); + assertThat(rcsThreadQueryParams.getLimit()).isEqualTo(50); + assertThat(rcsThreadQueryParams.getSortingProperty()).isEqualTo(SORT_BY_TIMESTAMP); + assertThat(rcsThreadQueryParams.getSortDirection()).isTrue(); + } +} diff --git a/tests/net/java/android/net/LinkPropertiesTest.java b/tests/net/java/android/net/LinkPropertiesTest.java index 299fbefc78e4..bdde0961909d 100644 --- a/tests/net/java/android/net/LinkPropertiesTest.java +++ b/tests/net/java/android/net/LinkPropertiesTest.java @@ -22,18 +22,15 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import android.net.IpPrefix; -import android.net.LinkAddress; -import android.net.LinkProperties; import android.net.LinkProperties.CompareResult; import android.net.LinkProperties.ProvisioningChange; -import android.net.RouteInfo; -import android.os.Parcel; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.system.OsConstants; import android.util.ArraySet; +import com.android.internal.util.TestUtils; + import org.junit.Test; import org.junit.runner.RunWith; @@ -849,18 +846,6 @@ public class LinkPropertiesTest { assertEquals(new ArraySet<>(expectRemoved), (new ArraySet<>(result.removed))); } - private void assertParcelingIsLossless(LinkProperties source) { - Parcel p = Parcel.obtain(); - source.writeToParcel(p, /* flags */ 0); - p.setDataPosition(0); - final byte[] marshalled = p.marshall(); - p = Parcel.obtain(); - p.unmarshall(marshalled, 0, marshalled.length); - p.setDataPosition(0); - LinkProperties dest = LinkProperties.CREATOR.createFromParcel(p); - assertEquals(source, dest); - } - @Test public void testLinkPropertiesParcelable() throws Exception { LinkProperties source = new LinkProperties(); @@ -882,12 +867,12 @@ public class LinkPropertiesTest { source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96")); - assertParcelingIsLossless(source); + TestUtils.assertParcelingIsLossless(source, LinkProperties.CREATOR); } @Test public void testParcelUninitialized() throws Exception { LinkProperties empty = new LinkProperties(); - assertParcelingIsLossless(empty); + TestUtils.assertParcelingIsLossless(empty, LinkProperties.CREATOR); } } diff --git a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java new file mode 100644 index 000000000000..1f2dd275bb7b --- /dev/null +++ b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import android.net.SocketKeepalive.InvalidPacketException; +import android.net.TcpKeepalivePacketData.TcpSocketInfo; + +import com.android.internal.util.TestUtils; + +import libcore.net.InetAddressUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.net.InetAddress; +import java.nio.ByteBuffer; + +@RunWith(JUnit4.class) +public final class TcpKeepalivePacketDataTest { + + @Before + public void setUp() {} + + @Test + public void testV4TcpKeepalivePacket() { + final InetAddress srcAddr = InetAddressUtils.parseNumericAddress("192.168.0.1"); + final InetAddress dstAddr = InetAddressUtils.parseNumericAddress("192.168.0.10"); + final int srcPort = 1234; + final int dstPort = 4321; + final int seq = 0x11111111; + final int ack = 0x22222222; + final int wnd = 8000; + final int wndScale = 2; + TcpKeepalivePacketData resultData = null; + TcpSocketInfo testInfo = new TcpSocketInfo( + srcAddr, srcPort, dstAddr, dstPort, seq, ack, wnd, wndScale); + try { + resultData = TcpKeepalivePacketData.tcpKeepalivePacket(testInfo); + } catch (InvalidPacketException e) { + fail("InvalidPacketException: " + e); + } + + assertEquals(testInfo.srcAddress, resultData.srcAddress); + assertEquals(testInfo.dstAddress, resultData.dstAddress); + assertEquals(testInfo.srcPort, resultData.srcPort); + assertEquals(testInfo.dstPort, resultData.dstPort); + assertEquals(testInfo.seq, resultData.tcpSeq); + assertEquals(testInfo.ack, resultData.tcpAck); + assertEquals(testInfo.rcvWndScale, resultData.tcpWndScale); + + TestUtils.assertParcelingIsLossless(resultData, TcpKeepalivePacketData.CREATOR); + + final byte[] packet = resultData.getPacket(); + // IP version and TOS. + ByteBuffer buf = ByteBuffer.wrap(packet); + assertEquals(buf.getShort(), 0x4500); + // Source IP address. + byte[] ip = new byte[4]; + buf = ByteBuffer.wrap(packet, 12, 4); + buf.get(ip); + assertArrayEquals(ip, srcAddr.getAddress()); + // Destination IP address. + buf = ByteBuffer.wrap(packet, 16, 4); + buf.get(ip); + assertArrayEquals(ip, dstAddr.getAddress()); + + buf = ByteBuffer.wrap(packet, 20, 12); + // Source port. + assertEquals(buf.getShort(), srcPort); + // Destination port. + assertEquals(buf.getShort(), dstPort); + // Sequence number. + assertEquals(buf.getInt(), seq); + // Ack. + assertEquals(buf.getInt(), ack); + // Window size. + buf = ByteBuffer.wrap(packet, 34, 2); + assertEquals(buf.getShort(), wnd >> wndScale); + } + + //TODO: add ipv6 test when ipv6 supported + + @Test + public void testParcel() throws Exception { + final InetAddress srcAddr = InetAddresses.parseNumericAddress("192.168.0.1"); + final InetAddress dstAddr = InetAddresses.parseNumericAddress("192.168.0.10"); + final int srcPort = 1234; + final int dstPort = 4321; + final int sequence = 0x11111111; + final int ack = 0x22222222; + final int wnd = 48_000; + final int wndScale = 2; + TcpKeepalivePacketData testData = null; + TcpKeepalivePacketDataParcelable resultData = null; + TcpSocketInfo testInfo = new TcpSocketInfo( + srcAddr, srcPort, dstAddr, dstPort, sequence, ack, wnd, wndScale); + testData = TcpKeepalivePacketData.tcpKeepalivePacket(testInfo); + resultData = testData.toStableParcelable(); + assertArrayEquals(resultData.srcAddress, srcAddr.getAddress()); + assertArrayEquals(resultData.dstAddress, dstAddr.getAddress()); + assertEquals(resultData.srcPort, srcPort); + assertEquals(resultData.dstPort, dstPort); + assertEquals(resultData.seq, sequence); + assertEquals(resultData.ack, ack); + } +} diff --git a/tests/net/java/com/android/internal/util/TestUtils.java b/tests/net/java/com/android/internal/util/TestUtils.java index 6db01d343756..7e5a1d3ad4d3 100644 --- a/tests/net/java/com/android/internal/util/TestUtils.java +++ b/tests/net/java/com/android/internal/util/TestUtils.java @@ -16,12 +16,15 @@ package com.android.internal.util; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; +import android.os.Parcel; +import android.os.Parcelable; public final class TestUtils { private TestUtils() { } @@ -50,4 +53,18 @@ public final class TestUtils { fail(handler.toString() + " did not become idle after " + timeoutMs + " ms"); } } + + // TODO : fetch the creator through reflection or something instead of passing it + public static <T extends Parcelable, C extends Parcelable.Creator<T>> + void assertParcelingIsLossless(T source, C creator) { + Parcel p = Parcel.obtain(); + source.writeToParcel(p, /* flags */ 0); + p.setDataPosition(0); + final byte[] marshalled = p.marshall(); + p = Parcel.obtain(); + p.unmarshall(marshalled, 0, marshalled.length); + p.setDataPosition(0); + T dest = creator.createFromParcel(p); + assertEquals(source, dest); + } } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 036c5dcc8926..d1a06925a902 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -20,6 +20,7 @@ import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; +import static android.net.ConnectivityManager.NETID_UNSET; import static android.net.ConnectivityManager.TYPE_ETHERNET; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA; @@ -123,7 +124,7 @@ import android.net.NetworkMisc; import android.net.NetworkParcelable; import android.net.NetworkRequest; import android.net.NetworkSpecifier; -import android.net.NetworkStack; +import android.net.NetworkStackClient; import android.net.NetworkUtils; import android.net.ProxyInfo; import android.net.RouteInfo; @@ -194,6 +195,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; @@ -244,7 +246,7 @@ public class ConnectivityServiceTest { @Mock INetworkStatsService mStatsService; @Mock INetworkPolicyManager mNpm; @Mock INetd mMockNetd; - @Mock NetworkStack mNetworkStack; + @Mock NetworkStackClient mNetworkStack; private ArgumentCaptor<String[]> mStringArrayCaptor = ArgumentCaptor.forClass(String[].class); @@ -500,17 +502,17 @@ public class ConnectivityServiceTest { public void unwanted() { mDisconnected.open(); } @Override - public void startPacketKeepalive(Message msg) { + public void startSocketKeepalive(Message msg) { int slot = msg.arg1; if (mExpectedKeepaliveSlot != null) { assertEquals((int) mExpectedKeepaliveSlot, slot); } - onPacketKeepaliveEvent(slot, mStartKeepaliveError); + onSocketKeepaliveEvent(slot, mStartKeepaliveError); } @Override - public void stopPacketKeepalive(Message msg) { - onPacketKeepaliveEvent(msg.arg1, mStopKeepaliveError); + public void stopSocketKeepalive(Message msg) { + onSocketKeepaliveEvent(msg.arg1, mStopKeepaliveError); } @Override @@ -553,7 +555,7 @@ public class ConnectivityServiceTest { if (mNmValidationRedirectUrl != null) { mNmCallbacks.showProvisioningNotification( - "test_provisioning_notif_action"); + "test_provisioning_notif_action", "com.android.test.package"); mNmProvNotificationRequested = true; } } catch (RemoteException e) { @@ -884,11 +886,14 @@ public class ConnectivityServiceTest { public void setUids(Set<UidRange> uids) { mNetworkCapabilities.setUids(uids); - updateCapabilities(); + updateCapabilities(null /* defaultNetwork */); } @Override public int getNetId() { + if (mMockNetworkAgent == null) { + return NETID_UNSET; + } return mMockNetworkAgent.getNetwork().netId; } @@ -906,15 +911,17 @@ public class ConnectivityServiceTest { mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities()); mConnected = true; mConfig = new VpnConfig(); + mConfig.isMetered = false; } @Override - public void updateCapabilities() { - if (!mConnected) return; - super.updateCapabilities(); - // Because super.updateCapabilities will update the capabilities of the agent but not - // the mock agent, the mock agent needs to know about them. + public NetworkCapabilities updateCapabilities(Network defaultNetwork) { + if (!mConnected) return null; + super.updateCapabilities(defaultNetwork); + // Because super.updateCapabilities will update the capabilities of the agent but + // not the mock agent, the mock agent needs to know about them. copyCapabilitiesToNetworkAgent(); + return new NetworkCapabilities(mNetworkCapabilities); } private void copyCapabilitiesToNetworkAgent() { @@ -1059,6 +1066,11 @@ public class ConnectivityServiceTest { } @Override + protected NetworkStackClient getNetworkStack() { + return mNetworkStack; + } + + @Override public WakeupMessage makeWakeupMessage( Context context, Handler handler, String cmdName, int cmd, Object obj) { return new FakeWakeupMessage(context, handler, cmdName, cmd, 0, 0, obj); @@ -3789,11 +3801,21 @@ public class ConnectivityServiceTest { } @Test - public void testNattSocketKeepalives() throws Exception { + public void testNattSocketKeepalives_SingleThreadExecutor() throws Exception { + final ExecutorService executorSingleThread = Executors.newSingleThreadExecutor(); + doTestNattSocketKeepalivesWithExecutor(executorSingleThread); + executorSingleThread.shutdown(); + } + + @Test + public void testNattSocketKeepalives_InlineExecutor() throws Exception { + final Executor executorInline = (Runnable r) -> r.run(); + doTestNattSocketKeepalivesWithExecutor(executorInline); + } + + private void doTestNattSocketKeepalivesWithExecutor(Executor executor) throws Exception { // TODO: 1. Move this outside of ConnectivityServiceTest. - // 2. Add helper function to test against newSingleThreadExecutor as well as inline - // executor. - // 3. Make test to verify that Nat-T keepalive socket is created by IpSecService. + // 2. Make test to verify that Nat-T keepalive socket is created by IpSecService. final int srcPort = 12345; final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); final InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35"); @@ -3807,8 +3829,6 @@ public class ConnectivityServiceTest { final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE); final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(srcPort); - final Executor executor = Executors.newSingleThreadExecutor(); - LinkProperties lp = new LinkProperties(); lp.setInterfaceName("wlan12"); lp.addLinkAddress(new LinkAddress(myIPv6, 64)); @@ -3925,6 +3945,12 @@ public class ConnectivityServiceTest { ka2.stop(); callback2.expectStopped(); + + testSocket.close(); + testSocket2.close(); + + mWiFiNetworkAgent.disconnect(); + waitFor(mWiFiNetworkAgent.getDisconnectedCV()); } @Test @@ -4648,6 +4674,7 @@ public class ConnectivityServiceTest { vpnNetworkAgent.connect(false); mMockVpn.connect(); + mMockVpn.setUnderlyingNetworks(new Network[0]); genericNetworkCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent); genericNotVpnNetworkCallback.assertNoCallback(); @@ -4680,6 +4707,7 @@ public class ConnectivityServiceTest { ranges.add(new UidRange(uid, uid)); mMockVpn.setUids(ranges); + vpnNetworkAgent.setUids(ranges); genericNetworkCallback.expectAvailableCallbacksValidated(vpnNetworkAgent); genericNotVpnNetworkCallback.assertNoCallback(); @@ -4713,12 +4741,11 @@ public class ConnectivityServiceTest { } @Test - public void testVpnWithAndWithoutInternet() { + public void testVpnWithoutInternet() { final int uid = Process.myUid(); final TestNetworkCallback defaultCallback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(defaultCallback); - defaultCallback.assertNoCallback(); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); @@ -4740,11 +4767,30 @@ public class ConnectivityServiceTest { vpnNetworkAgent.disconnect(); defaultCallback.assertNoCallback(); - vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); + mCm.unregisterNetworkCallback(defaultCallback); + } + + @Test + public void testVpnWithInternet() { + final int uid = Process.myUid(); + + final TestNetworkCallback defaultCallback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(defaultCallback); + + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + + defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); + final ArraySet<UidRange> ranges = new ArraySet<>(); + ranges.add(new UidRange(uid, uid)); mMockVpn.setNetworkAgent(vpnNetworkAgent); mMockVpn.setUids(ranges); vpnNetworkAgent.connect(true /* validated */, true /* hasInternet */); mMockVpn.connect(); + defaultCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); @@ -4752,14 +4798,6 @@ public class ConnectivityServiceTest { defaultCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent); defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); - vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); - ranges.clear(); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.setUids(ranges); - vpnNetworkAgent.connect(false /* validated */, true /* hasInternet */); - mMockVpn.connect(); - defaultCallback.assertNoCallback(); - mCm.unregisterNetworkCallback(defaultCallback); } @@ -4862,6 +4900,70 @@ public class ConnectivityServiceTest { } @Test + public void testNullUnderlyingNetworks() { + final int uid = Process.myUid(); + + final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback(); + final NetworkRequest vpnNetworkRequest = new NetworkRequest.Builder() + .removeCapability(NET_CAPABILITY_NOT_VPN) + .addTransportType(TRANSPORT_VPN) + .build(); + NetworkCapabilities nc; + mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback); + vpnNetworkCallback.assertNoCallback(); + + final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); + final ArraySet<UidRange> ranges = new ArraySet<>(); + ranges.add(new UidRange(uid, uid)); + mMockVpn.setNetworkAgent(vpnNetworkAgent); + mMockVpn.connect(); + mMockVpn.setUids(ranges); + vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */); + + vpnNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent); + nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork()); + assertTrue(nc.hasTransport(TRANSPORT_VPN)); + assertFalse(nc.hasTransport(TRANSPORT_CELLULAR)); + assertFalse(nc.hasTransport(TRANSPORT_WIFI)); + // By default, VPN is set to track default network (i.e. its underlying networks is null). + // In case of no default network, VPN is considered metered. + assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); + + // Connect to Cell; Cell is the default network. + mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + + vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN) + && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED), + vpnNetworkAgent); + + // Connect to WiFi; WiFi is the new default. + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + mWiFiNetworkAgent.connect(true); + + vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN) + && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) + && caps.hasCapability(NET_CAPABILITY_NOT_METERED), + vpnNetworkAgent); + + // Disconnect Cell. The default network did not change, so there shouldn't be any changes in + // the capabilities. + mCellNetworkAgent.disconnect(); + + // Disconnect wifi too. Now we have no default network. + mWiFiNetworkAgent.disconnect(); + + vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN) + && !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED), + vpnNetworkAgent); + + mMockVpn.disconnect(); + } + + @Test public void testNetworkBlockedStatus() { final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); final NetworkRequest cellRequest = new NetworkRequest.Builder() diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java index 9578ded1a089..aa6cbda0479d 100644 --- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java @@ -37,7 +37,6 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkMisc; -import android.net.NetworkStack; import android.os.INetworkManagementService; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -74,16 +73,12 @@ public class LingerMonitorTest { @Mock NetworkMisc mMisc; @Mock NetworkNotificationManager mNotifier; @Mock Resources mResources; - @Mock NetworkStack mNetworkStack; @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mCtx.getResources()).thenReturn(mResources); when(mCtx.getPackageName()).thenReturn("com.android.server.connectivity"); - when(mCtx.getSystemServiceName(NetworkStack.class)) - .thenReturn(Context.NETWORK_STACK_SERVICE); - when(mCtx.getSystemService(Context.NETWORK_STACK_SERVICE)).thenReturn(mNetworkStack); mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, HIGH_RATE_LIMIT); } diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java index b6356076db60..533d7ad2a472 100644 --- a/tests/net/java/com/android/server/connectivity/TetheringTest.java +++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java @@ -35,6 +35,7 @@ import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY; import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED; import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -194,10 +195,6 @@ public class TetheringTest { } public class MockIpServerDependencies extends IpServer.Dependencies { - MockIpServerDependencies() { - super(null); - } - @Override public RouterAdvertisementDaemon getRouterAdvertisementDaemon( InterfaceParams ifParams) { @@ -265,7 +262,7 @@ public class TetheringTest { } @Override - public IpServer.Dependencies getIpServerDependencies(Context context) { + public IpServer.Dependencies getIpServerDependencies() { return mIpServerDependencies; } @@ -274,6 +271,11 @@ public class TetheringTest { isTetheringSupportedCalls++; return true; } + + @Override + public int getDefaultDataSubscriptionId() { + return INVALID_SUBSCRIPTION_ID; + } } private static NetworkState buildMobileUpstreamState(boolean withIPv4, boolean withIPv6, diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index 5b17224e41e5..f169d6b5bee3 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -168,6 +168,8 @@ public class VpnTest { ApplicationInfo applicationInfo = new ApplicationInfo(); applicationInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT; when(mContext.getApplicationInfo()).thenReturn(applicationInfo); + when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) + .thenReturn(applicationInfo); doNothing().when(mNetService).registerObserver(any()); } @@ -544,23 +546,28 @@ public class VpnTest { final Network wifi = new Network(2); final Map<Network, NetworkCapabilities> networks = new HashMap<>(); - networks.put(mobile, new NetworkCapabilities() - .addTransportType(TRANSPORT_CELLULAR) - .addCapability(NET_CAPABILITY_INTERNET) - .addCapability(NET_CAPABILITY_NOT_METERED) - .addCapability(NET_CAPABILITY_NOT_CONGESTED) - .setLinkDownstreamBandwidthKbps(10)); - networks.put(wifi, new NetworkCapabilities() - .addTransportType(TRANSPORT_WIFI) - .addCapability(NET_CAPABILITY_INTERNET) - .addCapability(NET_CAPABILITY_NOT_ROAMING) - .addCapability(NET_CAPABILITY_NOT_CONGESTED) - .setLinkUpstreamBandwidthKbps(20)); + networks.put( + mobile, + new NetworkCapabilities() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_NOT_CONGESTED) + .setLinkDownstreamBandwidthKbps(10)); + networks.put( + wifi, + new NetworkCapabilities() + .addTransportType(TRANSPORT_WIFI) + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_NOT_METERED) + .addCapability(NET_CAPABILITY_NOT_ROAMING) + .addCapability(NET_CAPABILITY_NOT_CONGESTED) + .setLinkUpstreamBandwidthKbps(20)); setMockedNetworks(networks); final NetworkCapabilities caps = new NetworkCapabilities(); - Vpn.updateCapabilities(mConnectivityManager, new Network[] { }, caps); + Vpn.applyUnderlyingCapabilities( + mConnectivityManager, new Network[] {}, caps, false /* isAlwaysMetered */); assertTrue(caps.hasTransport(TRANSPORT_VPN)); assertFalse(caps.hasTransport(TRANSPORT_CELLULAR)); assertFalse(caps.hasTransport(TRANSPORT_WIFI)); @@ -570,17 +577,33 @@ public class VpnTest { assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); - Vpn.updateCapabilities(mConnectivityManager, new Network[] { mobile }, caps); + Vpn.applyUnderlyingCapabilities( + mConnectivityManager, + new Network[] {mobile}, + caps, + false /* isAlwaysMetered */); assertTrue(caps.hasTransport(TRANSPORT_VPN)); assertTrue(caps.hasTransport(TRANSPORT_CELLULAR)); assertFalse(caps.hasTransport(TRANSPORT_WIFI)); assertEquals(10, caps.getLinkDownstreamBandwidthKbps()); assertEquals(LINK_BANDWIDTH_UNSPECIFIED, caps.getLinkUpstreamBandwidthKbps()); - assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_METERED)); assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); - Vpn.updateCapabilities(mConnectivityManager, new Network[] { wifi }, caps); + Vpn.applyUnderlyingCapabilities( + mConnectivityManager, new Network[] {wifi}, caps, false /* isAlwaysMetered */); + assertTrue(caps.hasTransport(TRANSPORT_VPN)); + assertFalse(caps.hasTransport(TRANSPORT_CELLULAR)); + assertTrue(caps.hasTransport(TRANSPORT_WIFI)); + assertEquals(LINK_BANDWIDTH_UNSPECIFIED, caps.getLinkDownstreamBandwidthKbps()); + assertEquals(20, caps.getLinkUpstreamBandwidthKbps()); + assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); + assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); + + Vpn.applyUnderlyingCapabilities( + mConnectivityManager, new Network[] {wifi}, caps, true /* isAlwaysMetered */); assertTrue(caps.hasTransport(TRANSPORT_VPN)); assertFalse(caps.hasTransport(TRANSPORT_CELLULAR)); assertTrue(caps.hasTransport(TRANSPORT_WIFI)); @@ -590,7 +613,11 @@ public class VpnTest { assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); - Vpn.updateCapabilities(mConnectivityManager, new Network[] { mobile, wifi }, caps); + Vpn.applyUnderlyingCapabilities( + mConnectivityManager, + new Network[] {mobile, wifi}, + caps, + false /* isAlwaysMetered */); assertTrue(caps.hasTransport(TRANSPORT_VPN)); assertTrue(caps.hasTransport(TRANSPORT_CELLULAR)); assertTrue(caps.hasTransport(TRANSPORT_WIFI)); diff --git a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java index 0f72229d38e6..193f3806dbf6 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java @@ -16,8 +16,17 @@ package com.android.server.connectivity.tethering; +import static android.net.ConnectivityManager.TETHERING_USB; +import static android.net.ConnectivityManager.TETHERING_WIFI; +import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; +import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; +import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; + +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.Matchers.anyBoolean; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; @@ -27,12 +36,22 @@ import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.net.util.SharedLog; +import android.os.Bundle; +import android.os.Message; import android.os.PersistableBundle; +import android.os.ResultReceiver; +import android.os.test.TestLooper; +import android.provider.Settings; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.telephony.CarrierConfigManager; +import android.test.mock.MockContentResolver; import com.android.internal.R; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import com.android.internal.util.test.BroadcastInterceptingContext; +import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.connectivity.MockableSystemProperties; import org.junit.After; @@ -42,6 +61,10 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + @RunWith(AndroidJUnit4.class) @SmallTest public final class EntitlementManagerTest { @@ -51,7 +74,6 @@ public final class EntitlementManagerTest { @Mock private CarrierConfigManager mCarrierConfigManager; @Mock private Context mContext; - @Mock private ContentResolver mContent; @Mock private MockableSystemProperties mSystemProperties; @Mock private Resources mResources; @Mock private SharedLog mLog; @@ -59,15 +81,49 @@ public final class EntitlementManagerTest { // Like so many Android system APIs, these cannot be mocked because it is marked final. // We have to use the real versions. private final PersistableBundle mCarrierConfig = new PersistableBundle(); + private final TestLooper mLooper = new TestLooper(); + private Context mMockContext; + private MockContentResolver mContentResolver; + + private TestStateMachine mSM; + private WrappedEntitlementManager mEnMgr; - private EntitlementManager mEnMgr; + private class MockContext extends BroadcastInterceptingContext { + MockContext(Context base) { + super(base); + } + + @Override + public Resources getResources() { + return mResources; + } + + @Override + public ContentResolver getContentResolver() { + return mContentResolver; + } + } + + public class WrappedEntitlementManager extends EntitlementManager { + public int fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKONWN; + public boolean everRunUiEntitlement = false; + + public WrappedEntitlementManager(Context ctx, StateMachine target, + SharedLog log, MockableSystemProperties systemProperties) { + super(ctx, target, log, systemProperties); + } + + @Override + protected void runUiTetherProvisioning(int type, ResultReceiver receiver) { + everRunUiEntitlement = true; + receiver.send(fakeEntitlementResult, null); + } + } @Before public void setUp() { MockitoAnnotations.initMocks(this); - when(mContext.getResources()).thenReturn(mResources); - when(mContext.getContentResolver()).thenReturn(mContent); when(mResources.getStringArray(R.array.config_tether_dhcp_range)) .thenReturn(new String[0]); when(mResources.getStringArray(R.array.config_tether_usb_regexs)) @@ -80,12 +136,22 @@ public final class EntitlementManagerTest { .thenReturn(new int[0]); when(mLog.forSubComponent(anyString())).thenReturn(mLog); - mEnMgr = new EntitlementManager(mContext, mLog, mSystemProperties); - mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + mContentResolver = new MockContentResolver(); + mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + mMockContext = new MockContext(mContext); + mSM = new TestStateMachine(); + mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, mSystemProperties); + mEnMgr.updateConfiguration( + new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID)); } @After - public void tearDown() throws Exception {} + public void tearDown() throws Exception { + if (mSM != null) { + mSM.quit(); + mSM = null; + } + } private void setupForRequiredProvisioning() { // Produce some acceptable looking provision app setting if requested. @@ -104,7 +170,8 @@ public final class EntitlementManagerTest { @Test public void canRequireProvisioning() { setupForRequiredProvisioning(); - mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + mEnMgr.updateConfiguration( + new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID)); assertTrue(mEnMgr.isTetherProvisioningRequired()); } @@ -113,7 +180,8 @@ public final class EntitlementManagerTest { setupForRequiredProvisioning(); when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE)) .thenReturn(null); - mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + mEnMgr.updateConfiguration( + new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID)); // Couldn't get the CarrierConfigManager, but still had a declared provisioning app. // Therefore provisioning still be required. assertTrue(mEnMgr.isTetherProvisioningRequired()); @@ -123,7 +191,8 @@ public final class EntitlementManagerTest { public void toleratesCarrierConfigMissing() { setupForRequiredProvisioning(); when(mCarrierConfigManager.getConfig()).thenReturn(null); - mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + mEnMgr.updateConfiguration( + new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID)); // We still have a provisioning app configured, so still require provisioning. assertTrue(mEnMgr.isTetherProvisioningRequired()); } @@ -133,12 +202,146 @@ public final class EntitlementManagerTest { setupForRequiredProvisioning(); when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) .thenReturn(null); - mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + mEnMgr.updateConfiguration( + new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID)); assertFalse(mEnMgr.isTetherProvisioningRequired()); when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) .thenReturn(new String[] {"malformedApp"}); - mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + mEnMgr.updateConfiguration( + new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID)); assertFalse(mEnMgr.isTetherProvisioningRequired()); } + @Test + public void testGetLastEntitlementCacheValue() throws Exception { + final CountDownLatch mCallbacklatch = new CountDownLatch(1); + // 1. Entitlement check is not required. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.everRunUiEntitlement = false; + ResultReceiver receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_NO_ERROR, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_WIFI, receiver, true); + callbackTimeoutHelper(mCallbacklatch); + assertFalse(mEnMgr.everRunUiEntitlement); + + setupForRequiredProvisioning(); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, + INVALID_SUBSCRIPTION_ID)); + // 2. No cache value and don't need to run entitlement check. + mEnMgr.everRunUiEntitlement = false; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_ENTITLEMENT_UNKONWN, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_WIFI, receiver, false); + callbackTimeoutHelper(mCallbacklatch); + assertFalse(mEnMgr.everRunUiEntitlement); + // 3. No cache value and ui entitlement check is needed. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.everRunUiEntitlement = false; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_PROVISION_FAILED, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_WIFI, receiver, true); + mLooper.dispatchAll(); + callbackTimeoutHelper(mCallbacklatch); + assertTrue(mEnMgr.everRunUiEntitlement); + // 4. Cache value is TETHER_ERROR_PROVISION_FAILED and don't need to run entitlement check. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.everRunUiEntitlement = false; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_PROVISION_FAILED, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_WIFI, receiver, false); + callbackTimeoutHelper(mCallbacklatch); + assertFalse(mEnMgr.everRunUiEntitlement); + // 5. Cache value is TETHER_ERROR_PROVISION_FAILED and ui entitlement check is needed. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.everRunUiEntitlement = false; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_NO_ERROR, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_WIFI, receiver, true); + mLooper.dispatchAll(); + callbackTimeoutHelper(mCallbacklatch); + assertTrue(mEnMgr.everRunUiEntitlement); + // 6. Cache value is TETHER_ERROR_NO_ERROR. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.everRunUiEntitlement = false; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_NO_ERROR, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_WIFI, receiver, true); + callbackTimeoutHelper(mCallbacklatch); + assertFalse(mEnMgr.everRunUiEntitlement); + // 7. Test get value for other downstream type. + mEnMgr.everRunUiEntitlement = false; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_ENTITLEMENT_UNKONWN, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_USB, receiver, false); + callbackTimeoutHelper(mCallbacklatch); + assertFalse(mEnMgr.everRunUiEntitlement); + } + + void callbackTimeoutHelper(final CountDownLatch latch) throws Exception { + if (!latch.await(1, TimeUnit.SECONDS)) { + fail("Timout, fail to recieve callback"); + } + } + public class TestStateMachine extends StateMachine { + public final ArrayList<Message> messages = new ArrayList<>(); + private final State mLoggingState = + new EntitlementManagerTest.TestStateMachine.LoggingState(); + + class LoggingState extends State { + @Override public void enter() { + messages.clear(); + } + + @Override public void exit() { + messages.clear(); + } + + @Override public boolean processMessage(Message msg) { + messages.add(msg); + return false; + } + } + + public TestStateMachine() { + super("EntitlementManagerTest.TestStateMachine", mLooper.getLooper()); + addState(mLoggingState); + setInitialState(mLoggingState); + super.start(); + } + } } diff --git a/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java index 521778484d91..01b904d8f088 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java @@ -22,10 +22,12 @@ import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static com.android.server.connectivity.tethering.TetheringConfiguration.DUN_NOT_REQUIRED; import static com.android.server.connectivity.tethering.TetheringConfiguration.DUN_REQUIRED; import static com.android.server.connectivity.tethering.TetheringConfiguration.DUN_UNSPECIFIED; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -44,26 +46,39 @@ import android.test.mock.MockContentResolver; import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.internal.util.test.FakeSettingsProvider; -import java.util.Iterator; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Iterator; @RunWith(AndroidJUnit4.class) @SmallTest public class TetheringConfigurationTest { private final SharedLog mLog = new SharedLog("TetheringConfigurationTest"); + + private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; @Mock private Context mContext; @Mock private TelephonyManager mTelephonyManager; @Mock private Resources mResources; + @Mock private Resources mResourcesForSubId; private MockContentResolver mContentResolver; private Context mMockContext; private boolean mHasTelephonyManager; + private class MockTetheringConfiguration extends TetheringConfiguration { + MockTetheringConfiguration(Context ctx, SharedLog log, int id) { + super(ctx, log, id); + } + + @Override + protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) { + return mResourcesForSubId; + } + } + private class MockContext extends BroadcastInterceptingContext { MockContext(Context base) { super(base); @@ -99,6 +114,9 @@ public class TetheringConfigurationTest { .thenReturn(new String[0]); when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types)) .thenReturn(new int[0]); + when(mResources.getStringArray( + com.android.internal.R.array.config_mobile_hotspot_provision_app)) + .thenReturn(new String[0]); mContentResolver = new MockContentResolver(); mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); mMockContext = new MockContext(mContext); @@ -111,7 +129,8 @@ public class TetheringConfigurationTest { mHasTelephonyManager = true; when(mTelephonyManager.getTetherApnRequired()).thenReturn(DUN_REQUIRED); - final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext, mLog); + final TetheringConfiguration cfg = new TetheringConfiguration( + mMockContext, mLog, INVALID_SUBSCRIPTION_ID); assertTrue(cfg.isDunRequired); assertTrue(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN)); assertFalse(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE)); @@ -127,7 +146,8 @@ public class TetheringConfigurationTest { mHasTelephonyManager = true; when(mTelephonyManager.getTetherApnRequired()).thenReturn(DUN_NOT_REQUIRED); - final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext, mLog); + final TetheringConfiguration cfg = new TetheringConfiguration( + mMockContext, mLog, INVALID_SUBSCRIPTION_ID); assertFalse(cfg.isDunRequired); assertFalse(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN)); assertTrue(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE)); @@ -143,7 +163,8 @@ public class TetheringConfigurationTest { mHasTelephonyManager = false; when(mTelephonyManager.getTetherApnRequired()).thenReturn(DUN_UNSPECIFIED); - final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext, mLog); + final TetheringConfiguration cfg = new TetheringConfiguration( + mMockContext, mLog, INVALID_SUBSCRIPTION_ID); assertTrue(cfg.isDunRequired); assertTrue(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN)); // Just to prove we haven't clobbered Wi-Fi: @@ -160,7 +181,8 @@ public class TetheringConfigurationTest { mHasTelephonyManager = false; when(mTelephonyManager.getTetherApnRequired()).thenReturn(DUN_UNSPECIFIED); - final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext, mLog); + final TetheringConfiguration cfg = new TetheringConfiguration( + mMockContext, mLog, INVALID_SUBSCRIPTION_ID); final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator(); assertTrue(upstreamIterator.hasNext()); assertEquals(TYPE_ETHERNET, upstreamIterator.next().intValue()); @@ -181,7 +203,8 @@ public class TetheringConfigurationTest { mHasTelephonyManager = false; when(mTelephonyManager.getTetherApnRequired()).thenReturn(DUN_UNSPECIFIED); - final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext, mLog); + final TetheringConfiguration cfg = new TetheringConfiguration( + mMockContext, mLog, INVALID_SUBSCRIPTION_ID); final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator(); assertTrue(upstreamIterator.hasNext()); assertEquals(TYPE_ETHERNET, upstreamIterator.next().intValue()); @@ -199,7 +222,8 @@ public class TetheringConfigurationTest { mHasTelephonyManager = false; when(mTelephonyManager.getTetherApnRequired()).thenReturn(DUN_UNSPECIFIED); - final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext, mLog); + final TetheringConfiguration cfg = new TetheringConfiguration( + mMockContext, mLog, INVALID_SUBSCRIPTION_ID); final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator(); assertTrue(upstreamIterator.hasNext()); assertEquals(TYPE_WIFI, upstreamIterator.next().intValue()); @@ -214,7 +238,8 @@ public class TetheringConfigurationTest { public void testNewDhcpServerDisabled() { Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1); - final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext, mLog); + final TetheringConfiguration cfg = new TetheringConfiguration( + mMockContext, mLog, INVALID_SUBSCRIPTION_ID); assertTrue(cfg.enableLegacyDhcpServer); } @@ -222,7 +247,41 @@ public class TetheringConfigurationTest { public void testNewDhcpServerEnabled() { Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0); - final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext, mLog); + final TetheringConfiguration cfg = new TetheringConfiguration( + mMockContext, mLog, INVALID_SUBSCRIPTION_ID); assertFalse(cfg.enableLegacyDhcpServer); } + + @Test + public void testGetResourcesBySubId() { + setUpResourceForSubId(); + final TetheringConfiguration cfg = new TetheringConfiguration( + mMockContext, mLog, INVALID_SUBSCRIPTION_ID); + assertTrue(cfg.provisioningApp.length == 0); + final int anyValidSubId = 1; + final MockTetheringConfiguration mockCfg = + new MockTetheringConfiguration(mMockContext, mLog, anyValidSubId); + assertEquals(mockCfg.provisioningApp[0], PROVISIONING_APP_NAME[0]); + assertEquals(mockCfg.provisioningApp[1], PROVISIONING_APP_NAME[1]); + } + + private void setUpResourceForSubId() { + when(mResourcesForSubId.getStringArray( + com.android.internal.R.array.config_tether_dhcp_range)).thenReturn(new String[0]); + when(mResourcesForSubId.getStringArray( + com.android.internal.R.array.config_tether_usb_regexs)).thenReturn(new String[0]); + when(mResourcesForSubId.getStringArray( + com.android.internal.R.array.config_tether_wifi_regexs)) + .thenReturn(new String[]{ "test_wlan\\d" }); + when(mResourcesForSubId.getStringArray( + com.android.internal.R.array.config_tether_bluetooth_regexs)) + .thenReturn(new String[0]); + when(mResourcesForSubId.getIntArray( + com.android.internal.R.array.config_tether_upstream_types)) + .thenReturn(new int[0]); + when(mResourcesForSubId.getStringArray( + com.android.internal.R.array.config_mobile_hotspot_provision_app)) + .thenReturn(PROVISIONING_APP_NAME); + } + } diff --git a/tools/preload/Android.bp b/tools/preload/Android.bp new file mode 100644 index 000000000000..809ee474969a --- /dev/null +++ b/tools/preload/Android.bp @@ -0,0 +1,17 @@ +java_library_host { + name: "preload", + srcs: [ + "Compile.java", + "LoadedClass.java", + "MemoryUsage.java", + "Operation.java", + "Policy.java", + "PrintCsv.java", + "PrintHtmlDiff.java", + "PrintPsTree.java", + "Proc.java", + "Record.java", + "Root.java", + "WritePreloadedClassFile.java", + ], +} diff --git a/tools/preload/Android.mk b/tools/preload/Android.mk deleted file mode 100644 index 14a4547cccbf..000000000000 --- a/tools/preload/Android.mk +++ /dev/null @@ -1,23 +0,0 @@ -LOCAL_PATH:= $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ - Compile.java \ - LoadedClass.java \ - MemoryUsage.java \ - Operation.java \ - Policy.java \ - PrintCsv.java \ - PrintHtmlDiff.java \ - PrintPsTree.java \ - Proc.java \ - Record.java \ - Root.java \ - WritePreloadedClassFile.java - -LOCAL_MODULE:= preload - -include $(BUILD_HOST_JAVA_LIBRARY) - -include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tools/preload/loadclass/Android.bp b/tools/preload/loadclass/Android.bp new file mode 100644 index 000000000000..6f12015dae2b --- /dev/null +++ b/tools/preload/loadclass/Android.bp @@ -0,0 +1,4 @@ +java_test { + name: "loadclass", + srcs: ["**/*.java"], +} diff --git a/tools/preload/loadclass/Android.mk b/tools/preload/loadclass/Android.mk deleted file mode 100644 index 65828be617df..000000000000 --- a/tools/preload/loadclass/Android.mk +++ /dev/null @@ -1,9 +0,0 @@ -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := $(call all-subdir-java-files) -LOCAL_MODULE_TAGS := tests - -LOCAL_MODULE := loadclass - -include $(BUILD_JAVA_LIBRARY) |