diff options
286 files changed, 13997 insertions, 3353 deletions
diff --git a/Android.bp b/Android.bp index 225f86de7fef..8e174792f508 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", @@ -221,6 +220,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 +601,7 @@ java_defaults { ":storaged_aidl", ":vold_aidl", + ":gsiservice_aidl", ":installd_aidl", ":dumpstate_aidl", @@ -657,6 +658,7 @@ java_defaults { "frameworks/native/aidl/gui", "system/core/storaged/binder", "system/vold/binder", + "system/gsid/aidl", "system/bt/binder", "system/security/keystore/binder", ], @@ -830,6 +832,7 @@ 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", @@ -851,6 +854,7 @@ filegroup { "core/java/android/annotation/UnsupportedAppUsage.java", "core/java/android/net/DhcpResults.java", "core/java/android/util/LocalLog.java", + "core/java/com/android/internal/annotations/GuardedBy.java", "core/java/com/android/internal/annotations/VisibleForTesting.java", "core/java/com/android/internal/util/HexDump.java", "core/java/com/android/internal/util/IndentingPrintWriter.java", diff --git a/Android.mk b/Android.mk index e4053452a9c5..9c65948f4838 100644 --- a/Android.mk +++ b/Android.mk @@ -78,36 +78,10 @@ checkbuild: doc-comment-check-docs 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/current.txt b/api/current.txt index 7dbd9a9fb861..16274a92f994 100755 --- a/api/current.txt +++ b/api/current.txt @@ -6450,6 +6450,7 @@ package android.app.admin { method @Nullable public String[] getAccountTypesWithManagementDisabled(); method @Nullable public java.util.List<android.content.ComponentName> getActiveAdmins(); method @NonNull public java.util.Set<java.lang.String> getAffiliationIds(@NonNull android.content.ComponentName); + method @Nullable public java.util.List<java.lang.String> getAlwaysOnVpnLockdownWhitelist(@NonNull android.content.ComponentName); method @Nullable public String getAlwaysOnVpnPackage(@NonNull android.content.ComponentName); method @WorkerThread @NonNull public android.os.Bundle getApplicationRestrictions(@Nullable android.content.ComponentName, String); method @Deprecated @Nullable public String getApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName); @@ -6519,6 +6520,7 @@ package android.app.admin { method public boolean isActivePasswordSufficient(); method public boolean isAdminActive(@NonNull android.content.ComponentName); method public boolean isAffiliatedUser(); + method public boolean isAlwaysOnVpnLockdownEnabled(@NonNull android.content.ComponentName); method public boolean isApplicationHidden(@NonNull android.content.ComponentName, String); method public boolean isBackupServiceEnabled(@NonNull android.content.ComponentName); method @Deprecated public boolean isCallerApplicationRestrictionsManagingPackage(); @@ -6555,7 +6557,8 @@ package android.app.admin { method @Nullable public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrieveSecurityLogs(@NonNull android.content.ComponentName); method public void setAccountManagementDisabled(@NonNull android.content.ComponentName, String, boolean); method public void setAffiliationIds(@NonNull android.content.ComponentName, @NonNull java.util.Set<java.lang.String>); - method public void setAlwaysOnVpnPackage(@NonNull android.content.ComponentName, @Nullable String, boolean) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException; + method public void setAlwaysOnVpnPackage(@NonNull android.content.ComponentName, @Nullable String, boolean) throws android.content.pm.PackageManager.NameNotFoundException; + method public void setAlwaysOnVpnPackage(@NonNull android.content.ComponentName, @Nullable String, boolean, @Nullable java.util.List<java.lang.String>) throws android.content.pm.PackageManager.NameNotFoundException; method public boolean setApplicationHidden(@NonNull android.content.ComponentName, String, boolean); method @WorkerThread public void setApplicationRestrictions(@Nullable android.content.ComponentName, String, android.os.Bundle); method @Deprecated public void setApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName, @Nullable String) throws android.content.pm.PackageManager.NameNotFoundException; @@ -11197,7 +11200,7 @@ package android.content.pm { method public abstract java.util.List<android.content.pm.PermissionGroupInfo> getAllPermissionGroups(int); method public abstract android.graphics.drawable.Drawable getApplicationBanner(android.content.pm.ApplicationInfo); method public abstract android.graphics.drawable.Drawable getApplicationBanner(String) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract int getApplicationEnabledSetting(String); + method public abstract int getApplicationEnabledSetting(@NonNull String); method public abstract android.graphics.drawable.Drawable getApplicationIcon(android.content.pm.ApplicationInfo); method public abstract android.graphics.drawable.Drawable getApplicationIcon(String) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.pm.ApplicationInfo getApplicationInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException; @@ -11205,7 +11208,7 @@ package android.content.pm { method public abstract android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo); method public abstract android.graphics.drawable.Drawable getApplicationLogo(String) throws android.content.pm.PackageManager.NameNotFoundException; method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int); - method public abstract int getComponentEnabledSetting(android.content.ComponentName); + method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName); method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon(); method public abstract android.graphics.drawable.Drawable getDrawable(String, @DrawableRes int, android.content.pm.ApplicationInfo); method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int); @@ -11268,8 +11271,8 @@ package android.content.pm { method public abstract android.content.pm.ProviderInfo resolveContentProvider(String, int); method public abstract android.content.pm.ResolveInfo resolveService(android.content.Intent, int); method public abstract void setApplicationCategoryHint(@NonNull String, int); - method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(String, int, int); - method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int); + method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(@NonNull String, int, int); + method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setComponentEnabledSetting(@NonNull android.content.ComponentName, int, int); method public abstract void setInstallerPackageName(String, String); method public abstract void updateInstantAppCookie(@Nullable byte[]); method public abstract void verifyPendingInstall(int, int); @@ -27164,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"; @@ -27851,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[]); @@ -40406,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; @@ -40474,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; @@ -41153,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(); @@ -41189,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 @@ -41277,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"; } @@ -41855,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(); @@ -41882,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); @@ -41956,8 +41976,8 @@ package android.telecom { } public static final class VideoProfile.CameraCapabilities implements android.os.Parcelable { - ctor public VideoProfile.CameraCapabilities(int, int); - ctor public VideoProfile.CameraCapabilities(int, int, boolean, float); + ctor public VideoProfile.CameraCapabilities(@IntRange(from=0) int, @IntRange(from=0) int); + ctor public VideoProfile.CameraCapabilities(@IntRange(from=0) int, @IntRange(from=0) int, boolean, @FloatRange(from=1.0f) float); method public int describeContents(); method public int getHeight(); method public float getMaxZoom(); @@ -42075,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(); @@ -42217,6 +42237,9 @@ package android.telephony { field public static final String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int"; field public static final String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array"; field public static final String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool"; + field public static final String KEY_OPPORTUNISTIC_NETWORK_DATA_SWITCH_HYSTERESIS_TIME_LONG = "opportunistic_network_data_switch_hysteresis_time_long"; + field public static final String KEY_OPPORTUNISTIC_NETWORK_ENTRY_OR_EXIT_HYSTERESIS_TIME_LONG = "opportunistic_network_entry_or_exit_hysteresis_time_long"; + field public static final String KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_BANDWIDTH_INT = "opportunistic_network_entry_threshold_bandwidth_int"; field public static final String KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSRP_INT = "opportunistic_network_entry_threshold_rsrp_int"; field public static final String KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSSNR_INT = "opportunistic_network_entry_threshold_rssnr_int"; field public static final String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSRP_INT = "opportunistic_network_exit_threshold_rsrp_int"; @@ -42878,6 +42901,7 @@ package android.telephony { public class SubscriptionInfo implements android.os.Parcelable { method public android.graphics.Bitmap createIconBitmap(android.content.Context); method public int describeContents(); + method public int getCardId(); method public int getCarrierId(); method public CharSequence getCarrierName(); method public String getCountryIso(); @@ -42994,6 +43018,7 @@ package android.telephony { method public android.telephony.TelephonyManager createForSubscriptionId(int); method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_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(); @@ -43025,7 +43050,7 @@ package android.telephony { method public int getNetworkType(); method public int getPhoneCount(); method public int getPhoneType(); - method public int getPreferredOpportunisticDataSubscription(); + method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telephony.ServiceState getServiceState(); method @Nullable public android.telephony.SignalStrength getSignalStrength(); method public int getSimCarrierId(); @@ -43041,6 +43066,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getSubscriberId(); method public String getTypeAllocationCode(); method public String getTypeAllocationCode(int); + method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public java.util.List<android.telephony.UiccCardInfo> getUiccCardsInfo(); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVisualVoicemailPackageName(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailAlphaTag(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber(); @@ -43060,6 +43086,7 @@ 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(); @@ -43083,8 +43110,10 @@ 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"; field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE"; field public static final String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE"; field public static final String ACTION_SECRET_CODE = "android.telephony.action.SECRET_CODE"; @@ -43122,6 +43151,7 @@ package android.telephony { field public static final String EXTRA_INCOMING_NUMBER = "incoming_number"; field public static final String EXTRA_IS_REFRESH = "android.telephony.extra.IS_REFRESH"; field public static final String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT"; + field public static final String EXTRA_NETWORK_COUNTRY = "android.telephony.extra.NETWORK_COUNTRY"; field public static final String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT"; field public static final String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telephony.extra.PHONE_ACCOUNT_HANDLE"; field public static final String EXTRA_PRECISE_CARRIER_ID = "android.telephony.extra.PRECISE_CARRIER_ID"; @@ -43132,6 +43162,7 @@ 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 @@ -43199,6 +43230,18 @@ package android.telephony { method public void onResults(java.util.List<android.telephony.CellInfo>); } + public final class UiccCardInfo implements android.os.Parcelable { + ctor public UiccCardInfo(boolean, int, String, String, int); + method public int describeContents(); + method public int getCardId(); + method public String getEid(); + method public String getIccId(); + method public int getSlotIndex(); + method public boolean isEuicc(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.UiccCardInfo> CREATOR; + } + public abstract class VisualVoicemailService extends android.app.Service { ctor public VisualVoicemailService(); method public android.os.IBinder onBind(android.content.Intent); @@ -43575,9 +43618,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 } @@ -43635,10 +43678,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 { @@ -55510,13 +55553,13 @@ package java.io { ctor public ByteArrayOutputStream(int); method public void reset(); method public int size(); - method public byte[] toByteArray(); + method @NonNull public byte[] toByteArray(); method @NonNull public String toString(@NonNull String) throws java.io.UnsupportedEncodingException; method @Deprecated @NonNull public String toString(int); method public void write(int); - method public void write(byte[], int, int); + method public void write(@NonNull byte[], int, int); method public void writeTo(@NonNull java.io.OutputStream) throws java.io.IOException; - field protected byte[] buf; + field @NonNull protected byte[] buf; field protected int count; } @@ -55687,12 +55730,12 @@ package java.io { method public boolean isHidden(); method public long lastModified(); method public long length(); - method public String[] list(); - method public String[] list(@Nullable java.io.FilenameFilter); - method public java.io.File[] listFiles(); - method public java.io.File[] listFiles(@Nullable java.io.FilenameFilter); - method public java.io.File[] listFiles(@Nullable java.io.FileFilter); - method public static java.io.File[] listRoots(); + method @Nullable public String[] list(); + method @Nullable public String[] list(@Nullable java.io.FilenameFilter); + method @Nullable public java.io.File[] listFiles(); + method @Nullable public java.io.File[] listFiles(@Nullable java.io.FilenameFilter); + method @Nullable public java.io.File[] listFiles(@Nullable java.io.FileFilter); + method @NonNull public static java.io.File[] listRoots(); method public boolean mkdir(); method public boolean mkdirs(); method public boolean renameTo(@NonNull java.io.File); @@ -56181,8 +56224,8 @@ package java.io { method protected void clearError(); method public void close(); method public void flush(); - method @NonNull public java.io.PrintWriter format(@NonNull String, java.lang.Object...); - method @NonNull public java.io.PrintWriter format(@Nullable java.util.Locale, @NonNull String, java.lang.Object...); + method @NonNull public java.io.PrintWriter format(@NonNull String, @NonNull java.lang.Object...); + method @NonNull public java.io.PrintWriter format(@Nullable java.util.Locale, @NonNull String, @NonNull java.lang.Object...); method public void print(boolean); method public void print(char); method public void print(int); @@ -56192,8 +56235,8 @@ package java.io { method public void print(char[]); method public void print(@Nullable String); method public void print(@Nullable Object); - method @NonNull public java.io.PrintWriter printf(@NonNull String, java.lang.Object...); - method @NonNull public java.io.PrintWriter printf(@Nullable java.util.Locale, @NonNull String, java.lang.Object...); + method @NonNull public java.io.PrintWriter printf(@NonNull String, @NonNull java.lang.Object...); + method @NonNull public java.io.PrintWriter printf(@Nullable java.util.Locale, @NonNull String, @NonNull java.lang.Object...); method public void println(); method public void println(boolean); method public void println(char); @@ -57019,45 +57062,45 @@ package java.lang { method @NonNull public static Class<?> forName(@NonNull String) throws java.lang.ClassNotFoundException; method @NonNull public static Class<?> forName(@NonNull String, boolean, @Nullable ClassLoader) throws java.lang.ClassNotFoundException; method @Nullable public <A extends java.lang.annotation.Annotation> A getAnnotation(@NonNull Class<A>); - method public java.lang.annotation.Annotation[] getAnnotations(); + method @NonNull public java.lang.annotation.Annotation[] getAnnotations(); method @NonNull public <A extends java.lang.annotation.Annotation> A[] getAnnotationsByType(@NonNull Class<A>); method @Nullable public String getCanonicalName(); method @Nullable public ClassLoader getClassLoader(); - method public Class<?>[] getClasses(); + method @NonNull public Class<?>[] getClasses(); method @Nullable public Class<?> getComponentType(); - method @NonNull public java.lang.reflect.Constructor<T> getConstructor(Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException; - method public java.lang.reflect.Constructor<?>[] getConstructors() throws java.lang.SecurityException; + method @NonNull public java.lang.reflect.Constructor<T> getConstructor(@Nullable Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException; + method @NonNull public java.lang.reflect.Constructor<?>[] getConstructors() throws java.lang.SecurityException; method @Nullable public <A extends java.lang.annotation.Annotation> A getDeclaredAnnotation(@NonNull Class<A>); - method public java.lang.annotation.Annotation[] getDeclaredAnnotations(); - method public Class<?>[] getDeclaredClasses(); - method @NonNull public java.lang.reflect.Constructor<T> getDeclaredConstructor(Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException; - method public java.lang.reflect.Constructor<?>[] getDeclaredConstructors() throws java.lang.SecurityException; + method @NonNull public java.lang.annotation.Annotation[] getDeclaredAnnotations(); + method @NonNull public Class<?>[] getDeclaredClasses(); + method @NonNull public java.lang.reflect.Constructor<T> getDeclaredConstructor(@Nullable Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException; + method @NonNull public java.lang.reflect.Constructor<?>[] getDeclaredConstructors() throws java.lang.SecurityException; method @NonNull public java.lang.reflect.Field getDeclaredField(@NonNull String) throws java.lang.NoSuchFieldException; - method public java.lang.reflect.Field[] getDeclaredFields(); - method @NonNull public java.lang.reflect.Method getDeclaredMethod(@NonNull String, Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException; - method public java.lang.reflect.Method[] getDeclaredMethods() throws java.lang.SecurityException; + method @NonNull public java.lang.reflect.Field[] getDeclaredFields(); + method @NonNull public java.lang.reflect.Method getDeclaredMethod(@NonNull String, @Nullable Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException; + method @NonNull public java.lang.reflect.Method[] getDeclaredMethods() throws java.lang.SecurityException; method @Nullable public Class<?> getDeclaringClass(); method @Nullable public Class<?> getEnclosingClass(); method @Nullable public java.lang.reflect.Constructor<?> getEnclosingConstructor(); method @Nullable public java.lang.reflect.Method getEnclosingMethod(); - method public T[] getEnumConstants(); + method @Nullable public T[] getEnumConstants(); method @NonNull public java.lang.reflect.Field getField(@NonNull String) throws java.lang.NoSuchFieldException; - method public java.lang.reflect.Field[] getFields() throws java.lang.SecurityException; - method public java.lang.reflect.Type[] getGenericInterfaces(); + method @NonNull public java.lang.reflect.Field[] getFields() throws java.lang.SecurityException; + method @NonNull public java.lang.reflect.Type[] getGenericInterfaces(); method @Nullable public java.lang.reflect.Type getGenericSuperclass(); - method public Class<?>[] getInterfaces(); - method @NonNull public java.lang.reflect.Method getMethod(@NonNull String, Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException; - method public java.lang.reflect.Method[] getMethods() throws java.lang.SecurityException; + method @NonNull public Class<?>[] getInterfaces(); + method @NonNull public java.lang.reflect.Method getMethod(@NonNull String, @Nullable Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException; + method @NonNull public java.lang.reflect.Method[] getMethods() throws java.lang.SecurityException; method public int getModifiers(); method @NonNull public String getName(); method @Nullable public Package getPackage(); method @Nullable public java.security.ProtectionDomain getProtectionDomain(); method @Nullable public java.net.URL getResource(@NonNull String); method @Nullable public java.io.InputStream getResourceAsStream(@NonNull String); - method public Object[] getSigners(); + method @Nullable public Object[] getSigners(); method @NonNull public String getSimpleName(); method @Nullable public Class<? super T> getSuperclass(); - method public java.lang.reflect.TypeVariable<java.lang.Class<T>>[] getTypeParameters(); + method @NonNull public java.lang.reflect.TypeVariable<java.lang.Class<T>>[] getTypeParameters(); method public boolean isAnnotation(); method public boolean isAnonymousClass(); method public boolean isArray(); @@ -57944,8 +57987,8 @@ package java.lang { method @NonNull public static String copyValueOf(char[]); method public boolean endsWith(@NonNull String); method public boolean equalsIgnoreCase(@Nullable String); - method @NonNull public static String format(@NonNull String, java.lang.Object...); - method @NonNull public static String format(@NonNull java.util.Locale, @NonNull String, java.lang.Object...); + method @NonNull public static String format(@NonNull String, @NonNull java.lang.Object...); + method @NonNull public static String format(@NonNull java.util.Locale, @NonNull String, @NonNull java.lang.Object...); method @Deprecated public void getBytes(int, int, byte[], int); method public byte[] getBytes(@NonNull String) throws java.io.UnsupportedEncodingException; method public byte[] getBytes(@NonNull java.nio.charset.Charset); @@ -57957,7 +58000,7 @@ package java.lang { method public int indexOf(@NonNull String, int); method @NonNull public String intern(); method public boolean isEmpty(); - method @NonNull public static String join(@NonNull CharSequence, java.lang.CharSequence...); + method @NonNull public static String join(@NonNull CharSequence, @Nullable java.lang.CharSequence...); method @NonNull public static String join(@NonNull CharSequence, @NonNull Iterable<? extends java.lang.CharSequence>); method public int lastIndexOf(int); method public int lastIndexOf(int, int); @@ -57972,8 +58015,8 @@ package java.lang { method @NonNull public String replace(@NonNull CharSequence, @NonNull CharSequence); method @NonNull public String replaceAll(@NonNull String, @NonNull String); method @NonNull public String replaceFirst(@NonNull String, @NonNull String); - method public String[] split(@NonNull String, int); - method public String[] split(@NonNull String); + method @NonNull public String[] split(@NonNull String, int); + method @NonNull public String[] split(@NonNull String); method public boolean startsWith(@NonNull String, int); method public boolean startsWith(@NonNull String); method @NonNull public CharSequence subSequence(int, int); @@ -58174,7 +58217,7 @@ package java.lang { method public long getId(); method @NonNull public final String getName(); method public final int getPriority(); - method public StackTraceElement[] getStackTrace(); + method @NonNull public StackTraceElement[] getStackTrace(); method @NonNull public java.lang.Thread.State getState(); method @Nullable public final ThreadGroup getThreadGroup(); method @Nullable public java.lang.Thread.UncaughtExceptionHandler getUncaughtExceptionHandler(); @@ -58272,13 +58315,13 @@ package java.lang { method @Nullable public Throwable getCause(); method @Nullable public String getLocalizedMessage(); method @Nullable public String getMessage(); - method public StackTraceElement[] getStackTrace(); - method public final Throwable[] getSuppressed(); + method @NonNull public StackTraceElement[] getStackTrace(); + method @NonNull public final Throwable[] getSuppressed(); method @NonNull public Throwable initCause(@Nullable Throwable); method public void printStackTrace(); method public void printStackTrace(@NonNull java.io.PrintStream); method public void printStackTrace(@NonNull java.io.PrintWriter); - method public void setStackTrace(StackTraceElement[]); + method public void setStackTrace(@NonNull StackTraceElement[]); } public class TypeNotPresentException extends java.lang.RuntimeException { @@ -58601,8 +58644,8 @@ package java.lang.reflect { public class AccessibleObject implements java.lang.reflect.AnnotatedElement { ctor protected AccessibleObject(); method @Nullable public <T extends java.lang.annotation.Annotation> T getAnnotation(@NonNull Class<T>); - method public java.lang.annotation.Annotation[] getAnnotations(); - method public java.lang.annotation.Annotation[] getDeclaredAnnotations(); + method @NonNull public java.lang.annotation.Annotation[] getAnnotations(); + method @NonNull public java.lang.annotation.Annotation[] getDeclaredAnnotations(); method public boolean isAccessible(); method public static void setAccessible(java.lang.reflect.AccessibleObject[], boolean) throws java.lang.SecurityException; method public void setAccessible(boolean) throws java.lang.SecurityException; @@ -58610,10 +58653,10 @@ package java.lang.reflect { public interface AnnotatedElement { method @Nullable public <T extends java.lang.annotation.Annotation> T getAnnotation(@NonNull Class<T>); - method public java.lang.annotation.Annotation[] getAnnotations(); + method @NonNull public java.lang.annotation.Annotation[] getAnnotations(); method public default <T extends java.lang.annotation.Annotation> T[] getAnnotationsByType(@NonNull Class<T>); method @Nullable public default <T extends java.lang.annotation.Annotation> T getDeclaredAnnotation(@NonNull Class<T>); - method public java.lang.annotation.Annotation[] getDeclaredAnnotations(); + method @NonNull public java.lang.annotation.Annotation[] getDeclaredAnnotations(); method public default <T extends java.lang.annotation.Annotation> T[] getDeclaredAnnotationsByType(@NonNull Class<T>); method public default boolean isAnnotationPresent(@NonNull Class<? extends java.lang.annotation.Annotation>); } @@ -58648,20 +58691,20 @@ package java.lang.reflect { method public int getModifiers(); method @NonNull public String getName(); method public java.lang.annotation.Annotation[][] getParameterAnnotations(); - method public Class<?>[] getParameterTypes(); + method @NonNull public Class<?>[] getParameterTypes(); method public java.lang.reflect.TypeVariable<java.lang.reflect.Constructor<T>>[] getTypeParameters(); method @NonNull public T newInstance(java.lang.Object...) throws java.lang.IllegalAccessException, java.lang.IllegalArgumentException, java.lang.InstantiationException, java.lang.reflect.InvocationTargetException; method @NonNull public String toGenericString(); } public abstract class Executable extends java.lang.reflect.AccessibleObject implements java.lang.reflect.GenericDeclaration java.lang.reflect.Member { - method public abstract Class<?>[] getExceptionTypes(); - method public java.lang.reflect.Type[] getGenericExceptionTypes(); - method public java.lang.reflect.Type[] getGenericParameterTypes(); - method public abstract java.lang.annotation.Annotation[][] getParameterAnnotations(); + method @NonNull public abstract Class<?>[] getExceptionTypes(); + method @NonNull public java.lang.reflect.Type[] getGenericExceptionTypes(); + method @NonNull public java.lang.reflect.Type[] getGenericParameterTypes(); + method @NonNull public abstract java.lang.annotation.Annotation[][] getParameterAnnotations(); method public int getParameterCount(); - method public abstract Class<?>[] getParameterTypes(); - method public java.lang.reflect.Parameter[] getParameters(); + method @NonNull public abstract Class<?>[] getParameterTypes(); + method @NonNull public java.lang.reflect.Parameter[] getParameters(); method public final boolean isAnnotationPresent(@NonNull Class<? extends java.lang.annotation.Annotation>); method public boolean isSynthetic(); method public boolean isVarArgs(); @@ -58750,7 +58793,7 @@ package java.lang.reflect { method @NonNull public Class<?>[] getParameterTypes(); method @NonNull public Class<?> getReturnType(); method @NonNull public java.lang.reflect.TypeVariable<java.lang.reflect.Method>[] getTypeParameters(); - method @Nullable public Object invoke(@Nullable Object, java.lang.Object...) throws java.lang.IllegalAccessException, java.lang.IllegalArgumentException, java.lang.reflect.InvocationTargetException; + method @Nullable public Object invoke(@Nullable Object, @Nullable java.lang.Object...) throws java.lang.IllegalAccessException, java.lang.IllegalArgumentException, java.lang.reflect.InvocationTargetException; method public boolean isBridge(); method public boolean isDefault(); method @NonNull public String toGenericString(); @@ -58793,8 +58836,8 @@ package java.lang.reflect { public final class Parameter implements java.lang.reflect.AnnotatedElement { method @Nullable public <T extends java.lang.annotation.Annotation> T getAnnotation(@NonNull Class<T>); - method public java.lang.annotation.Annotation[] getAnnotations(); - method public java.lang.annotation.Annotation[] getDeclaredAnnotations(); + method @NonNull public java.lang.annotation.Annotation[] getAnnotations(); + method @NonNull public java.lang.annotation.Annotation[] getDeclaredAnnotations(); method @NonNull public java.lang.reflect.Executable getDeclaringExecutable(); method public int getModifiers(); method @NonNull public String getName(); @@ -58807,7 +58850,7 @@ package java.lang.reflect { } public interface ParameterizedType extends java.lang.reflect.Type { - method public java.lang.reflect.Type[] getActualTypeArguments(); + method @NonNull public java.lang.reflect.Type[] getActualTypeArguments(); method @Nullable public java.lang.reflect.Type getOwnerType(); method @NonNull public java.lang.reflect.Type getRawType(); } @@ -58815,9 +58858,9 @@ package java.lang.reflect { public class Proxy implements java.io.Serializable { ctor protected Proxy(@NonNull java.lang.reflect.InvocationHandler); method @NonNull public static java.lang.reflect.InvocationHandler getInvocationHandler(@NonNull Object) throws java.lang.IllegalArgumentException; - method @NonNull public static Class<?> getProxyClass(@Nullable ClassLoader, Class<?>...) throws java.lang.IllegalArgumentException; + method @NonNull public static Class<?> getProxyClass(@Nullable ClassLoader, @NonNull Class<?>...) throws java.lang.IllegalArgumentException; method public static boolean isProxyClass(@NonNull Class<?>); - method @NonNull public static Object newProxyInstance(@Nullable ClassLoader, Class<?>[], @NonNull java.lang.reflect.InvocationHandler) throws java.lang.IllegalArgumentException; + method @NonNull public static Object newProxyInstance(@Nullable ClassLoader, @NonNull Class<?>[], @NonNull java.lang.reflect.InvocationHandler) throws java.lang.IllegalArgumentException; field protected java.lang.reflect.InvocationHandler h; } @@ -58831,7 +58874,7 @@ package java.lang.reflect { } public interface TypeVariable<D extends java.lang.reflect.GenericDeclaration> extends java.lang.reflect.Type { - method public java.lang.reflect.Type[] getBounds(); + method @NonNull public java.lang.reflect.Type[] getBounds(); method @NonNull public D getGenericDeclaration(); method @NonNull public String getName(); } @@ -58843,8 +58886,8 @@ package java.lang.reflect { } public interface WildcardType extends java.lang.reflect.Type { - method public java.lang.reflect.Type[] getLowerBounds(); - method public java.lang.reflect.Type[] getUpperBounds(); + method @NonNull public java.lang.reflect.Type[] getLowerBounds(); + method @NonNull public java.lang.reflect.Type[] getUpperBounds(); } } @@ -59888,7 +59931,7 @@ package java.nio { public abstract class ByteBuffer extends java.nio.Buffer implements java.lang.Comparable<java.nio.ByteBuffer> { method @NonNull public static java.nio.ByteBuffer allocate(int); method @NonNull public static java.nio.ByteBuffer allocateDirect(int); - method public final byte[] array(); + method @NonNull public final byte[] array(); method public final int arrayOffset(); method @NonNull public abstract java.nio.CharBuffer asCharBuffer(); method @NonNull public abstract java.nio.DoubleBuffer asDoubleBuffer(); @@ -59902,8 +59945,8 @@ package java.nio { method @NonNull public abstract java.nio.ByteBuffer duplicate(); method public abstract byte get(); method public abstract byte get(int); - method @NonNull public java.nio.ByteBuffer get(byte[], int, int); - method @NonNull public java.nio.ByteBuffer get(byte[]); + method @NonNull public java.nio.ByteBuffer get(@NonNull byte[], int, int); + method @NonNull public java.nio.ByteBuffer get(@NonNull byte[]); method public abstract char getChar(); method public abstract char getChar(int); method public abstract double getDouble(); @@ -59922,8 +59965,8 @@ package java.nio { method @NonNull public abstract java.nio.ByteBuffer put(byte); method @NonNull public abstract java.nio.ByteBuffer put(int, byte); method @NonNull public java.nio.ByteBuffer put(@NonNull java.nio.ByteBuffer); - method @NonNull public java.nio.ByteBuffer put(byte[], int, int); - method @NonNull public final java.nio.ByteBuffer put(byte[]); + method @NonNull public java.nio.ByteBuffer put(@NonNull byte[], int, int); + method @NonNull public final java.nio.ByteBuffer put(@NonNull byte[]); method @NonNull public abstract java.nio.ByteBuffer putChar(char); method @NonNull public abstract java.nio.ByteBuffer putChar(int, char); method @NonNull public abstract java.nio.ByteBuffer putDouble(double); @@ -59937,8 +59980,8 @@ package java.nio { method @NonNull public abstract java.nio.ByteBuffer putShort(short); method @NonNull public abstract java.nio.ByteBuffer putShort(int, short); method @NonNull public abstract java.nio.ByteBuffer slice(); - method @NonNull public static java.nio.ByteBuffer wrap(byte[], int, int); - method @NonNull public static java.nio.ByteBuffer wrap(byte[]); + method @NonNull public static java.nio.ByteBuffer wrap(@NonNull byte[], int, int); + method @NonNull public static java.nio.ByteBuffer wrap(@NonNull byte[]); } public final class ByteOrder { @@ -61771,20 +61814,20 @@ package java.security { public abstract class MessageDigest extends java.security.MessageDigestSpi { ctor protected MessageDigest(@NonNull String); - method public byte[] digest(); - method public int digest(byte[], int, int) throws java.security.DigestException; - method public byte[] digest(byte[]); + method @NonNull public byte[] digest(); + method public int digest(@NonNull byte[], int, int) throws java.security.DigestException; + method @NonNull public byte[] digest(@NonNull byte[]); method @NonNull public final String getAlgorithm(); method public final int getDigestLength(); method @NonNull public static java.security.MessageDigest getInstance(@NonNull String) throws java.security.NoSuchAlgorithmException; method @NonNull public static java.security.MessageDigest getInstance(@NonNull String, @NonNull String) throws java.security.NoSuchAlgorithmException, java.security.NoSuchProviderException; method @NonNull public static java.security.MessageDigest getInstance(@NonNull String, @NonNull java.security.Provider) throws java.security.NoSuchAlgorithmException; method @NonNull public final java.security.Provider getProvider(); - method public static boolean isEqual(byte[], byte[]); + method public static boolean isEqual(@Nullable byte[], @Nullable byte[]); method public void reset(); method public void update(byte); - method public void update(byte[], int, int); - method public void update(byte[]); + method public void update(@NonNull byte[], int, int); + method public void update(@NonNull byte[]); method public final void update(@NonNull java.nio.ByteBuffer); } @@ -64391,7 +64434,7 @@ package java.text { method @NonNull public final StringBuffer format(@NonNull Object, @NonNull StringBuffer, @NonNull java.text.FieldPosition); method @NonNull public abstract StringBuffer format(@NonNull java.util.Date, @NonNull StringBuffer, @NonNull java.text.FieldPosition); method @NonNull public final String format(@NonNull java.util.Date); - method public static java.util.Locale[] getAvailableLocales(); + method @NonNull public static java.util.Locale[] getAvailableLocales(); method @NonNull public java.util.Calendar getCalendar(); method @NonNull public static final java.text.DateFormat getDateInstance(); method @NonNull public static final java.text.DateFormat getDateInstance(int); @@ -64631,7 +64674,7 @@ package java.text { method @NonNull public final String format(long); method @NonNull public abstract StringBuffer format(double, @NonNull StringBuffer, @NonNull java.text.FieldPosition); method @NonNull public abstract StringBuffer format(long, @NonNull StringBuffer, @NonNull java.text.FieldPosition); - method public static java.util.Locale[] getAvailableLocales(); + method @NonNull public static java.util.Locale[] getAvailableLocales(); method @Nullable public java.util.Currency getCurrency(); method @NonNull public static final java.text.NumberFormat getCurrencyInstance(); method @NonNull public static java.text.NumberFormat getCurrencyInstance(@NonNull java.util.Locale); @@ -66285,8 +66328,8 @@ package java.util { method public boolean remove(@Nullable Object); method public boolean removeAll(@NonNull java.util.Collection<?>); method public boolean retainAll(@NonNull java.util.Collection<?>); - method public Object[] toArray(); - method public <T> T[] toArray(T[]); + method @NonNull public Object[] toArray(); + method @NonNull public <T> T[] toArray(@NonNull T[]); } public abstract class AbstractList<E> extends java.util.AbstractCollection<E> implements java.util.List<E> { @@ -66395,161 +66438,161 @@ package java.util { } public class Arrays { - method @NonNull @java.lang.SafeVarargs public static <T> java.util.List<T> asList(T...); - method public static int binarySearch(long[], long); - method public static int binarySearch(long[], int, int, long); - method public static int binarySearch(int[], int); - method public static int binarySearch(int[], int, int, int); - method public static int binarySearch(short[], short); - method public static int binarySearch(short[], int, int, short); - method public static int binarySearch(char[], char); - method public static int binarySearch(char[], int, int, char); - method public static int binarySearch(byte[], byte); - method public static int binarySearch(byte[], int, int, byte); - method public static int binarySearch(double[], double); - method public static int binarySearch(double[], int, int, double); - method public static int binarySearch(float[], float); - method public static int binarySearch(float[], int, int, float); - method public static int binarySearch(Object[], @NonNull Object); - method public static int binarySearch(Object[], int, int, @NonNull Object); - method public static <T> int binarySearch(T[], T, @Nullable java.util.Comparator<? super T>); - method public static <T> int binarySearch(T[], int, int, T, @Nullable java.util.Comparator<? super T>); - method public static <T> T[] copyOf(T[], int); - method public static <T, U> T[] copyOf(U[], int, @NonNull Class<? extends T[]>); - method public static byte[] copyOf(byte[], int); - method public static short[] copyOf(short[], int); - method public static int[] copyOf(int[], int); - method public static long[] copyOf(long[], int); - method public static char[] copyOf(char[], int); - method public static float[] copyOf(float[], int); - method public static double[] copyOf(double[], int); - method public static boolean[] copyOf(boolean[], int); - method public static <T> T[] copyOfRange(T[], int, int); - method public static <T, U> T[] copyOfRange(U[], int, int, @NonNull Class<? extends T[]>); - method public static byte[] copyOfRange(byte[], int, int); - method public static short[] copyOfRange(short[], int, int); - method public static int[] copyOfRange(int[], int, int); - method public static long[] copyOfRange(long[], int, int); - method public static char[] copyOfRange(char[], int, int); - method public static float[] copyOfRange(float[], int, int); - method public static double[] copyOfRange(double[], int, int); - method public static boolean[] copyOfRange(boolean[], int, int); - method public static boolean deepEquals(Object[], Object[]); - method public static int deepHashCode(Object[]); - method @NonNull public static String deepToString(Object[]); - method public static boolean equals(long[], long[]); - method public static boolean equals(int[], int[]); - method public static boolean equals(short[], short[]); - method public static boolean equals(char[], char[]); - method public static boolean equals(byte[], byte[]); - method public static boolean equals(boolean[], boolean[]); - method public static boolean equals(double[], double[]); - method public static boolean equals(float[], float[]); - method public static boolean equals(Object[], Object[]); - method public static void fill(long[], long); - method public static void fill(long[], int, int, long); - method public static void fill(int[], int); - method public static void fill(int[], int, int, int); - method public static void fill(short[], short); - method public static void fill(short[], int, int, short); - method public static void fill(char[], char); - method public static void fill(char[], int, int, char); - method public static void fill(byte[], byte); - method public static void fill(byte[], int, int, byte); - method public static void fill(boolean[], boolean); - method public static void fill(boolean[], int, int, boolean); - method public static void fill(double[], double); - method public static void fill(double[], int, int, double); - method public static void fill(float[], float); - method public static void fill(float[], int, int, float); - method public static void fill(Object[], @Nullable Object); - method public static void fill(Object[], int, int, @Nullable Object); - method public static int hashCode(long[]); - method public static int hashCode(int[]); - method public static int hashCode(short[]); - method public static int hashCode(char[]); - method public static int hashCode(byte[]); - method public static int hashCode(boolean[]); - method public static int hashCode(float[]); - method public static int hashCode(double[]); - method public static int hashCode(Object[]); - method public static <T> void parallelPrefix(T[], @NonNull java.util.function.BinaryOperator<T>); - method public static <T> void parallelPrefix(T[], int, int, @NonNull java.util.function.BinaryOperator<T>); - method public static void parallelPrefix(long[], @NonNull java.util.function.LongBinaryOperator); - method public static void parallelPrefix(long[], int, int, @NonNull java.util.function.LongBinaryOperator); - method public static void parallelPrefix(double[], @NonNull java.util.function.DoubleBinaryOperator); - method public static void parallelPrefix(double[], int, int, @NonNull java.util.function.DoubleBinaryOperator); - method public static void parallelPrefix(int[], @NonNull java.util.function.IntBinaryOperator); - method public static void parallelPrefix(int[], int, int, @NonNull java.util.function.IntBinaryOperator); - method public static <T> void parallelSetAll(T[], @NonNull java.util.function.IntFunction<? extends T>); - method public static void parallelSetAll(int[], @NonNull java.util.function.IntUnaryOperator); - method public static void parallelSetAll(long[], @NonNull java.util.function.IntToLongFunction); - method public static void parallelSetAll(double[], @NonNull java.util.function.IntToDoubleFunction); - method public static void parallelSort(byte[]); - method public static void parallelSort(byte[], int, int); - method public static void parallelSort(char[]); - method public static void parallelSort(char[], int, int); - method public static void parallelSort(short[]); - method public static void parallelSort(short[], int, int); - method public static void parallelSort(int[]); - method public static void parallelSort(int[], int, int); - method public static void parallelSort(long[]); - method public static void parallelSort(long[], int, int); - method public static void parallelSort(float[]); - method public static void parallelSort(float[], int, int); - method public static void parallelSort(double[]); - method public static void parallelSort(double[], int, int); - method public static <T extends java.lang.Comparable<? super T>> void parallelSort(T[]); - method public static <T extends java.lang.Comparable<? super T>> void parallelSort(T[], int, int); - method public static <T> void parallelSort(T[], @Nullable java.util.Comparator<? super T>); - method public static <T> void parallelSort(T[], int, int, @Nullable java.util.Comparator<? super T>); - method public static <T> void setAll(T[], @NonNull java.util.function.IntFunction<? extends T>); - method public static void setAll(int[], @NonNull java.util.function.IntUnaryOperator); - method public static void setAll(long[], @NonNull java.util.function.IntToLongFunction); - method public static void setAll(double[], @NonNull java.util.function.IntToDoubleFunction); - method public static void sort(int[]); - method public static void sort(int[], int, int); - method public static void sort(long[]); - method public static void sort(long[], int, int); - method public static void sort(short[]); - method public static void sort(short[], int, int); - method public static void sort(char[]); - method public static void sort(char[], int, int); - method public static void sort(byte[]); - method public static void sort(byte[], int, int); - method public static void sort(float[]); - method public static void sort(float[], int, int); - method public static void sort(double[]); - method public static void sort(double[], int, int); - method public static void sort(Object[]); - method public static void sort(Object[], int, int); - method public static <T> void sort(T[], @Nullable java.util.Comparator<? super T>); - method public static <T> void sort(T[], int, int, @Nullable java.util.Comparator<? super T>); - method @NonNull public static <T> java.util.Spliterator<T> spliterator(T[]); - method @NonNull public static <T> java.util.Spliterator<T> spliterator(T[], int, int); - method @NonNull public static java.util.Spliterator.OfInt spliterator(int[]); - method @NonNull public static java.util.Spliterator.OfInt spliterator(int[], int, int); - method @NonNull public static java.util.Spliterator.OfLong spliterator(long[]); - method @NonNull public static java.util.Spliterator.OfLong spliterator(long[], int, int); - method @NonNull public static java.util.Spliterator.OfDouble spliterator(double[]); - method @NonNull public static java.util.Spliterator.OfDouble spliterator(double[], int, int); - method @NonNull public static <T> java.util.stream.Stream<T> stream(T[]); - method @NonNull public static <T> java.util.stream.Stream<T> stream(T[], int, int); - method @NonNull public static java.util.stream.IntStream stream(int[]); - method @NonNull public static java.util.stream.IntStream stream(int[], int, int); - method @NonNull public static java.util.stream.LongStream stream(long[]); - method @NonNull public static java.util.stream.LongStream stream(long[], int, int); - method @NonNull public static java.util.stream.DoubleStream stream(double[]); - method @NonNull public static java.util.stream.DoubleStream stream(double[], int, int); - method @NonNull public static String toString(long[]); - method @NonNull public static String toString(int[]); - method @NonNull public static String toString(short[]); - method @NonNull public static String toString(char[]); - method @NonNull public static String toString(byte[]); - method @NonNull public static String toString(boolean[]); - method @NonNull public static String toString(float[]); - method @NonNull public static String toString(double[]); - method @NonNull public static String toString(Object[]); + method @NonNull @java.lang.SafeVarargs public static <T> java.util.List<T> asList(@NonNull T...); + method public static int binarySearch(@NonNull long[], long); + method public static int binarySearch(@NonNull long[], int, int, long); + method public static int binarySearch(@NonNull int[], int); + method public static int binarySearch(@NonNull int[], int, int, int); + method public static int binarySearch(@NonNull short[], short); + method public static int binarySearch(@NonNull short[], int, int, short); + method public static int binarySearch(@NonNull char[], char); + method public static int binarySearch(@NonNull char[], int, int, char); + method public static int binarySearch(@NonNull byte[], byte); + method public static int binarySearch(@NonNull byte[], int, int, byte); + method public static int binarySearch(@NonNull double[], double); + method public static int binarySearch(@NonNull double[], int, int, double); + method public static int binarySearch(@NonNull float[], float); + method public static int binarySearch(@NonNull float[], int, int, float); + method public static int binarySearch(@NonNull Object[], @NonNull Object); + method public static int binarySearch(@NonNull Object[], int, int, @NonNull Object); + method public static <T> int binarySearch(@NonNull T[], T, @Nullable java.util.Comparator<? super T>); + method public static <T> int binarySearch(@NonNull T[], int, int, T, @Nullable java.util.Comparator<? super T>); + method @NonNull public static <T> T[] copyOf(@NonNull T[], int); + method @NonNull public static <T, U> T[] copyOf(@NonNull U[], int, @NonNull Class<? extends T[]>); + method @NonNull public static byte[] copyOf(@NonNull byte[], int); + method @NonNull public static short[] copyOf(@NonNull short[], int); + method @NonNull public static int[] copyOf(@NonNull int[], int); + method @NonNull public static long[] copyOf(@NonNull long[], int); + method @NonNull public static char[] copyOf(@NonNull char[], int); + method @NonNull public static float[] copyOf(@NonNull float[], int); + method @NonNull public static double[] copyOf(@NonNull double[], int); + method @NonNull public static boolean[] copyOf(@NonNull boolean[], int); + method @NonNull public static <T> T[] copyOfRange(@NonNull T[], int, int); + method @NonNull public static <T, U> T[] copyOfRange(@NonNull U[], int, int, @NonNull Class<? extends T[]>); + method @NonNull public static byte[] copyOfRange(@NonNull byte[], int, int); + method @NonNull public static short[] copyOfRange(@NonNull short[], int, int); + method @NonNull public static int[] copyOfRange(@NonNull int[], int, int); + method @NonNull public static long[] copyOfRange(@NonNull long[], int, int); + method @NonNull public static char[] copyOfRange(@NonNull char[], int, int); + method @NonNull public static float[] copyOfRange(@NonNull float[], int, int); + method @NonNull public static double[] copyOfRange(@NonNull double[], int, int); + method @NonNull public static boolean[] copyOfRange(@NonNull boolean[], int, int); + method public static boolean deepEquals(@Nullable Object[], @Nullable Object[]); + method public static int deepHashCode(@Nullable Object[]); + method @NonNull public static String deepToString(@Nullable Object[]); + method public static boolean equals(@Nullable long[], @Nullable long[]); + method public static boolean equals(@Nullable int[], @Nullable int[]); + method public static boolean equals(@Nullable short[], @Nullable short[]); + method public static boolean equals(@Nullable char[], @Nullable char[]); + method public static boolean equals(@Nullable byte[], @Nullable byte[]); + method public static boolean equals(@Nullable boolean[], @Nullable boolean[]); + method public static boolean equals(@Nullable double[], @Nullable double[]); + method public static boolean equals(@Nullable float[], @Nullable float[]); + method public static boolean equals(@Nullable Object[], @Nullable Object[]); + method public static void fill(@NonNull long[], long); + method public static void fill(@NonNull long[], int, int, long); + method public static void fill(@NonNull int[], int); + method public static void fill(@NonNull int[], int, int, int); + method public static void fill(@NonNull short[], short); + method public static void fill(@NonNull short[], int, int, short); + method public static void fill(@NonNull char[], char); + method public static void fill(@NonNull char[], int, int, char); + method public static void fill(@NonNull byte[], byte); + method public static void fill(@NonNull byte[], int, int, byte); + method public static void fill(@NonNull boolean[], boolean); + method public static void fill(@NonNull boolean[], int, int, boolean); + method public static void fill(@NonNull double[], double); + method public static void fill(@NonNull double[], int, int, double); + method public static void fill(@NonNull float[], float); + method public static void fill(@NonNull float[], int, int, float); + method public static void fill(@NonNull Object[], @Nullable Object); + method public static void fill(@NonNull Object[], int, int, @Nullable Object); + method public static int hashCode(@Nullable long[]); + method public static int hashCode(@Nullable int[]); + method public static int hashCode(@Nullable short[]); + method public static int hashCode(@Nullable char[]); + method public static int hashCode(@Nullable byte[]); + method public static int hashCode(@Nullable boolean[]); + method public static int hashCode(@Nullable float[]); + method public static int hashCode(@Nullable double[]); + method public static int hashCode(@Nullable Object[]); + method public static <T> void parallelPrefix(@NonNull T[], @NonNull java.util.function.BinaryOperator<T>); + method public static <T> void parallelPrefix(@NonNull T[], int, int, @NonNull java.util.function.BinaryOperator<T>); + method public static void parallelPrefix(@NonNull long[], @NonNull java.util.function.LongBinaryOperator); + method public static void parallelPrefix(@NonNull long[], int, int, @NonNull java.util.function.LongBinaryOperator); + method public static void parallelPrefix(@NonNull double[], @NonNull java.util.function.DoubleBinaryOperator); + method public static void parallelPrefix(@NonNull double[], int, int, @NonNull java.util.function.DoubleBinaryOperator); + method public static void parallelPrefix(@NonNull int[], @NonNull java.util.function.IntBinaryOperator); + method public static void parallelPrefix(@NonNull int[], int, int, @NonNull java.util.function.IntBinaryOperator); + method public static <T> void parallelSetAll(@NonNull T[], @NonNull java.util.function.IntFunction<? extends T>); + method public static void parallelSetAll(@NonNull int[], @NonNull java.util.function.IntUnaryOperator); + method public static void parallelSetAll(@NonNull long[], @NonNull java.util.function.IntToLongFunction); + method public static void parallelSetAll(@NonNull double[], @NonNull java.util.function.IntToDoubleFunction); + method public static void parallelSort(@NonNull byte[]); + method public static void parallelSort(@NonNull byte[], int, int); + method public static void parallelSort(@NonNull char[]); + method public static void parallelSort(@NonNull char[], int, int); + method public static void parallelSort(@NonNull short[]); + method public static void parallelSort(@NonNull short[], int, int); + method public static void parallelSort(@NonNull int[]); + method public static void parallelSort(@NonNull int[], int, int); + method public static void parallelSort(@NonNull long[]); + method public static void parallelSort(@NonNull long[], int, int); + method public static void parallelSort(@NonNull float[]); + method public static void parallelSort(@NonNull float[], int, int); + method public static void parallelSort(@NonNull double[]); + method public static void parallelSort(@NonNull double[], int, int); + method public static <T extends java.lang.Comparable<? super T>> void parallelSort(@NonNull T[]); + method public static <T extends java.lang.Comparable<? super T>> void parallelSort(@NonNull T[], int, int); + method public static <T> void parallelSort(@NonNull T[], @Nullable java.util.Comparator<? super T>); + method public static <T> void parallelSort(@NonNull T[], int, int, @Nullable java.util.Comparator<? super T>); + method public static <T> void setAll(@NonNull T[], @NonNull java.util.function.IntFunction<? extends T>); + method public static void setAll(@NonNull int[], @NonNull java.util.function.IntUnaryOperator); + method public static void setAll(@NonNull long[], @NonNull java.util.function.IntToLongFunction); + method public static void setAll(@NonNull double[], @NonNull java.util.function.IntToDoubleFunction); + method public static void sort(@NonNull int[]); + method public static void sort(@NonNull int[], int, int); + method public static void sort(@NonNull long[]); + method public static void sort(@NonNull long[], int, int); + method public static void sort(@NonNull short[]); + method public static void sort(@NonNull short[], int, int); + method public static void sort(@NonNull char[]); + method public static void sort(@NonNull char[], int, int); + method public static void sort(@NonNull byte[]); + method public static void sort(@NonNull byte[], int, int); + method public static void sort(@NonNull float[]); + method public static void sort(@NonNull float[], int, int); + method public static void sort(@NonNull double[]); + method public static void sort(@NonNull double[], int, int); + method public static void sort(@NonNull Object[]); + method public static void sort(@NonNull Object[], int, int); + method public static <T> void sort(@NonNull T[], @Nullable java.util.Comparator<? super T>); + method public static <T> void sort(@NonNull T[], int, int, @Nullable java.util.Comparator<? super T>); + method @NonNull public static <T> java.util.Spliterator<T> spliterator(@NonNull T[]); + method @NonNull public static <T> java.util.Spliterator<T> spliterator(@NonNull T[], int, int); + method @NonNull public static java.util.Spliterator.OfInt spliterator(@NonNull int[]); + method @NonNull public static java.util.Spliterator.OfInt spliterator(@NonNull int[], int, int); + method @NonNull public static java.util.Spliterator.OfLong spliterator(@NonNull long[]); + method @NonNull public static java.util.Spliterator.OfLong spliterator(@NonNull long[], int, int); + method @NonNull public static java.util.Spliterator.OfDouble spliterator(@NonNull double[]); + method @NonNull public static java.util.Spliterator.OfDouble spliterator(@NonNull double[], int, int); + method @NonNull public static <T> java.util.stream.Stream<T> stream(@NonNull T[]); + method @NonNull public static <T> java.util.stream.Stream<T> stream(@NonNull T[], int, int); + method @NonNull public static java.util.stream.IntStream stream(@NonNull int[]); + method @NonNull public static java.util.stream.IntStream stream(@NonNull int[], int, int); + method @NonNull public static java.util.stream.LongStream stream(@NonNull long[]); + method @NonNull public static java.util.stream.LongStream stream(@NonNull long[], int, int); + method @NonNull public static java.util.stream.DoubleStream stream(@NonNull double[]); + method @NonNull public static java.util.stream.DoubleStream stream(@NonNull double[], int, int); + method @NonNull public static String toString(@Nullable long[]); + method @NonNull public static String toString(@Nullable int[]); + method @NonNull public static String toString(@Nullable short[]); + method @NonNull public static String toString(@Nullable char[]); + method @NonNull public static String toString(@Nullable byte[]); + method @NonNull public static String toString(@Nullable boolean[]); + method @NonNull public static String toString(@Nullable float[]); + method @NonNull public static String toString(@Nullable double[]); + method @NonNull public static String toString(@Nullable Object[]); } public class Base64 { @@ -66633,7 +66676,7 @@ package java.util { method public int getActualMaximum(int); method public int getActualMinimum(int); method @NonNull public static java.util.Set<java.lang.String> getAvailableCalendarTypes(); - method public static java.util.Locale[] getAvailableLocales(); + method @NonNull public static java.util.Locale[] getAvailableLocales(); method @NonNull public String getCalendarType(); method @Nullable public String getDisplayName(int, int, @NonNull java.util.Locale); method @Nullable public java.util.Map<java.lang.String,java.lang.Integer> getDisplayNames(int, int, @NonNull java.util.Locale); @@ -66721,8 +66764,8 @@ package java.util { field public static final int YEAR = 1; // 0x1 field public static final int ZONE_OFFSET = 15; // 0xf field protected boolean areFieldsSet; - field protected int[] fields; - field protected boolean[] isSet; + field @NonNull protected int[] fields; + field @NonNull protected boolean[] isSet; field protected boolean isTimeSet; field protected long time; } @@ -66733,7 +66776,7 @@ package java.util { method @NonNull public java.util.Calendar.Builder set(int, int); method @NonNull public java.util.Calendar.Builder setCalendarType(@NonNull String); method @NonNull public java.util.Calendar.Builder setDate(int, int, int); - method @NonNull public java.util.Calendar.Builder setFields(int...); + method @NonNull public java.util.Calendar.Builder setFields(@NonNull int...); method @NonNull public java.util.Calendar.Builder setInstant(long); method @NonNull public java.util.Calendar.Builder setInstant(@NonNull java.util.Date); method @NonNull public java.util.Calendar.Builder setLenient(boolean); @@ -66763,12 +66806,12 @@ package java.util { method public int size(); method @NonNull public default java.util.Spliterator<E> spliterator(); method @NonNull public default java.util.stream.Stream<E> stream(); - method public Object[] toArray(); - method public <T> T[] toArray(T[]); + method @NonNull public Object[] toArray(); + method @NonNull public <T> T[] toArray(@NonNull T[]); } public class Collections { - method @java.lang.SafeVarargs public static <T> boolean addAll(@NonNull java.util.Collection<? super T>, T...); + method @java.lang.SafeVarargs public static <T> boolean addAll(@NonNull java.util.Collection<? super T>, @NonNull T...); method @NonNull public static <T> java.util.Queue<T> asLifoQueue(@NonNull java.util.Deque<T>); method public static <T> int binarySearch(@NonNull java.util.List<? extends java.lang.Comparable<? super T>>, @NonNull T); method public static <T> int binarySearch(@NonNull java.util.List<? extends T>, T, @Nullable java.util.Comparator<? super T>); @@ -67286,7 +67329,7 @@ package java.util { method @NonNull public static java.util.List<java.lang.String> filterTags(@NonNull java.util.List<java.util.Locale.LanguageRange>, @NonNull java.util.Collection<java.lang.String>, @NonNull java.util.Locale.FilteringMode); method @NonNull public static java.util.List<java.lang.String> filterTags(@NonNull java.util.List<java.util.Locale.LanguageRange>, @NonNull java.util.Collection<java.lang.String>); method @NonNull public static java.util.Locale forLanguageTag(@NonNull String); - method public static java.util.Locale[] getAvailableLocales(); + method @NonNull public static java.util.Locale[] getAvailableLocales(); method @NonNull public String getCountry(); method @NonNull public static java.util.Locale getDefault(); method @NonNull public static java.util.Locale getDefault(@NonNull java.util.Locale.Category); @@ -67304,8 +67347,8 @@ package java.util { method @NonNull public java.util.Set<java.lang.Character> getExtensionKeys(); method @NonNull public String getISO3Country() throws java.util.MissingResourceException; method @NonNull public String getISO3Language() throws java.util.MissingResourceException; - method public static String[] getISOCountries(); - method public static String[] getISOLanguages(); + method @NonNull public static String[] getISOCountries(); + method @NonNull public static String[] getISOLanguages(); method @NonNull public String getLanguage(); method @NonNull public String getScript(); method @NonNull public java.util.Set<java.lang.String> getUnicodeLocaleAttributes(); @@ -67499,7 +67542,7 @@ package java.util { method public static <T> int compare(T, T, @NonNull java.util.Comparator<? super T>); method public static boolean deepEquals(@Nullable Object, @Nullable Object); method public static boolean equals(@Nullable Object, @Nullable Object); - method public static int hash(java.lang.Object...); + method public static int hash(@Nullable java.lang.Object...); method public static int hashCode(@Nullable Object); method public static boolean isNull(@Nullable Object); method public static boolean nonNull(@Nullable Object); @@ -68164,7 +68207,7 @@ package java.util { method public void addElement(E); method public int capacity(); method @NonNull public Object clone(); - method public void copyInto(Object[]); + method public void copyInto(@NonNull Object[]); method public E elementAt(int); method @NonNull public java.util.Enumeration<E> elements(); method public void ensureCapacity(int); @@ -68184,7 +68227,7 @@ package java.util { method public void trimToSize(); field protected int capacityIncrement; field protected int elementCount; - field protected Object[] elementData; + field @NonNull protected Object[] elementData; } public class WeakHashMap<K, V> extends java.util.AbstractMap<K,V> implements java.util.Map<K,V> { @@ -68575,7 +68618,7 @@ package java.util.concurrent { public class CopyOnWriteArrayList<E> implements java.lang.Cloneable java.util.List<E> java.util.RandomAccess java.io.Serializable { ctor public CopyOnWriteArrayList(); ctor public CopyOnWriteArrayList(@NonNull java.util.Collection<? extends E>); - ctor public CopyOnWriteArrayList(E[]); + ctor public CopyOnWriteArrayList(@NonNull E[]); method public boolean add(E); method public void add(int, E); method public boolean addAll(@NonNull java.util.Collection<? extends E>); @@ -68603,8 +68646,8 @@ package java.util.concurrent { method public E set(int, E); method public int size(); method @NonNull public java.util.List<E> subList(int, int); - method public Object[] toArray(); - method public <T> T[] toArray(T[]); + method @NonNull public Object[] toArray(); + method @NonNull public <T> T[] toArray(@NonNull T[]); } public class CopyOnWriteArraySet<E> extends java.util.AbstractSet<E> implements java.io.Serializable { @@ -70356,7 +70399,7 @@ package java.util.logging { method public void config(@NonNull java.util.function.Supplier<java.lang.String>); method public void entering(@Nullable String, @Nullable String); method public void entering(@Nullable String, @Nullable String, @Nullable Object); - method public void entering(@Nullable String, @Nullable String, Object[]); + method public void entering(@Nullable String, @Nullable String, @Nullable Object[]); method public void exiting(@Nullable String, @Nullable String); method public void exiting(@Nullable String, @Nullable String, @Nullable Object); method public void fine(@Nullable String); @@ -70369,7 +70412,7 @@ package java.util.logging { method @NonNull public static java.util.logging.Logger getAnonymousLogger(@Nullable String); method @Nullable public java.util.logging.Filter getFilter(); method @NonNull public static final java.util.logging.Logger getGlobal(); - method public java.util.logging.Handler[] getHandlers(); + method @NonNull public java.util.logging.Handler[] getHandlers(); method @Nullable public java.util.logging.Level getLevel(); method @NonNull public static java.util.logging.Logger getLogger(@NonNull String); method @NonNull public static java.util.logging.Logger getLogger(@NonNull String, @Nullable String); @@ -70385,7 +70428,7 @@ package java.util.logging { method public void log(@NonNull java.util.logging.Level, @Nullable String); method public void log(@NonNull java.util.logging.Level, @NonNull java.util.function.Supplier<java.lang.String>); method public void log(@NonNull java.util.logging.Level, @Nullable String, @Nullable Object); - method public void log(@NonNull java.util.logging.Level, @Nullable String, Object[]); + method public void log(@NonNull java.util.logging.Level, @Nullable String, @Nullable Object[]); method public void log(@NonNull java.util.logging.Level, @Nullable String, @Nullable Throwable); method public void log(@NonNull java.util.logging.Level, @Nullable Throwable, @NonNull java.util.function.Supplier<java.lang.String>); method public void logp(@NonNull java.util.logging.Level, @Nullable String, @Nullable String, @Nullable String); @@ -70396,8 +70439,8 @@ package java.util.logging { method public void logp(@NonNull java.util.logging.Level, @Nullable String, @Nullable String, @Nullable Throwable, @NonNull java.util.function.Supplier<java.lang.String>); method @Deprecated public void logrb(@NonNull java.util.logging.Level, @Nullable String, @Nullable String, @Nullable String, @Nullable String); method @Deprecated public void logrb(@NonNull java.util.logging.Level, @Nullable String, @Nullable String, @Nullable String, @Nullable String, @Nullable Object); - method @Deprecated public void logrb(@NonNull java.util.logging.Level, @Nullable String, @Nullable String, @Nullable String, @Nullable String, Object[]); - method public void logrb(@NonNull java.util.logging.Level, @Nullable String, @Nullable String, @Nullable java.util.ResourceBundle, @Nullable String, java.lang.Object...); + method @Deprecated public void logrb(@NonNull java.util.logging.Level, @Nullable String, @Nullable String, @Nullable String, @Nullable String, @Nullable Object[]); + method public void logrb(@NonNull java.util.logging.Level, @Nullable String, @Nullable String, @Nullable java.util.ResourceBundle, @Nullable String, @Nullable java.lang.Object...); method @Deprecated public void logrb(@NonNull java.util.logging.Level, @Nullable String, @Nullable String, @Nullable String, @Nullable String, @Nullable Throwable); method public void logrb(@NonNull java.util.logging.Level, @Nullable String, @Nullable String, @Nullable java.util.ResourceBundle, @Nullable String, @Nullable Throwable); method public void removeHandler(@Nullable java.util.logging.Handler) throws java.lang.SecurityException; @@ -70659,8 +70702,8 @@ package java.util.regex { method public static boolean matches(@NonNull String, @NonNull CharSequence); method @NonNull public String pattern(); method @NonNull public static String quote(@NonNull String); - method public String[] split(@NonNull CharSequence, int); - method public String[] split(@NonNull CharSequence); + method @NonNull public String[] split(@NonNull CharSequence, int); + method @NonNull public String[] split(@NonNull CharSequence); method @NonNull public java.util.stream.Stream<java.lang.String> splitAsStream(@NonNull CharSequence); field public static final int CANON_EQ = 128; // 0x80 field public static final int CASE_INSENSITIVE = 2; // 0x2 diff --git a/api/system-current.txt b/api/system-current.txt index e0801b7acbe1..981789c735f3 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"; @@ -3058,6 +3060,7 @@ package android.net { public class CaptivePortal implements android.os.Parcelable { ctor public CaptivePortal(android.os.IBinder); + method public void logEvent(int, String); method public void useNetwork(); field public static final int APP_RETURN_DISMISSED = 0; // 0x0 field public static final int APP_RETURN_UNWANTED = 1; // 0x1 @@ -3065,11 +3068,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.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); @@ -3078,6 +3084,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 { @@ -3086,6 +3095,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); @@ -3185,6 +3199,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(); @@ -4303,6 +4321,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(); @@ -6049,6 +6068,7 @@ package android.telephony { public class PhoneStateListener { method public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes); method public void onCallDisconnectCauseChanged(int, int); + method public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo); method public void onPreciseCallStateChanged(android.telephony.PreciseCallState); method public void onPreciseDataConnectionStateChanged(android.telephony.PreciseDataConnectionState); method public void onRadioPowerStateChanged(int); @@ -6056,6 +6076,7 @@ package android.telephony { method public void onVoiceActivationStateChanged(int); field public static final int LISTEN_CALL_ATTRIBUTES_CHANGED = 67108864; // 0x4000000 field public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000 + field public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 134217728; // 0x8000000 field public static final int LISTEN_PRECISE_CALL_STATE = 2048; // 0x800 field public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000 field public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 8388608; // 0x800000 @@ -6226,7 +6247,6 @@ package android.telephony { public class SubscriptionInfo implements android.os.Parcelable { method @Nullable public java.util.List<android.telephony.UiccAccessRule> getAccessRules(); - method public int getCardId(); method public int getProfileClass(); } @@ -6286,7 +6306,6 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void enableVideoCalling(boolean); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getAidForAppType(int); method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.service.carrier.CarrierIdentifier> getAllowedCarriers(int); - method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getCardIdForDefaultEuicc(); method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent); method public java.util.List<java.lang.String> getCarrierPackageNamesForIntentAndPhone(android.content.Intent, int); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.CarrierRestrictionRules getCarrierRestrictionRules(); @@ -6303,14 +6322,12 @@ 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 @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.MODIFY_PHONE_STATE) public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms(); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.UiccCardInfo[] getUiccCardsInfo(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.UiccSlotInfo[] getUiccSlotsInfo(); method @Nullable public android.os.Bundle getVisualVoicemailSettings(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoiceActivationState(); @@ -6319,6 +6336,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isCurrentPotentialEmergencyNumber(@NonNull String); method public boolean isDataConnectivityPossible(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle(); + 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 @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging(); @@ -6335,6 +6353,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int); 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 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); @@ -6348,6 +6367,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"; @@ -6355,34 +6375,12 @@ 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 int INVALID_CARD_ID = -1; // 0xffffffff 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 @@ -6433,18 +6431,6 @@ package android.telephony { field public static final android.os.Parcelable.Creator<android.telephony.UiccAccessRule> CREATOR; } - public class UiccCardInfo implements android.os.Parcelable { - ctor public UiccCardInfo(boolean, int, String, String, int); - method public int describeContents(); - method public int getCardId(); - method public String getEid(); - method public String getIccId(); - method public int getSlotIndex(); - method public boolean isEuicc(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.telephony.UiccCardInfo> CREATOR; - } - public class UiccSlotInfo implements android.os.Parcelable { ctor public UiccSlotInfo(boolean, boolean, String, int, int, boolean); method public int describeContents(); @@ -6855,6 +6841,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(); @@ -6872,7 +6868,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(); @@ -6881,8 +6877,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); @@ -7317,11 +7313,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 af455fb4abb6..01127249c1fc 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -600,6 +600,7 @@ package android.net { public class CaptivePortal implements android.os.Parcelable { ctor public CaptivePortal(android.os.IBinder); + method public void logEvent(int, String); method public void useNetwork(); field public static final int APP_RETURN_DISMISSED = 0; // 0x0 field public static final int APP_RETURN_UNWANTED = 1; // 0x1 @@ -607,6 +608,7 @@ package android.net { } public class ConnectivityManager { + method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(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"; } @@ -668,6 +670,10 @@ package android.net { method public boolean satisfiedByNetworkCapabilities(android.net.NetworkCapabilities); } + 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(); @@ -1472,7 +1478,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 @@ -1790,6 +1795,10 @@ package android.view { method public boolean isSystemGroup(); } + public abstract class LayoutInflater { + method public void setPrecompiledLayoutsEnabledForTesting(boolean); + } + public final class MotionEvent extends android.view.InputEvent implements android.os.Parcelable { method public void setActionButton(int); method public void setButtonState(int); diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 30536099456a..3da5e0c3192e 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -23,9 +23,11 @@ option java_outer_classname = "AtomsProto"; import "frameworks/base/cmds/statsd/src/atom_field_options.proto"; import "frameworks/base/core/proto/android/app/enums.proto"; import "frameworks/base/core/proto/android/app/job/enums.proto"; +import "frameworks/base/core/proto/android/bluetooth/a2dp/enums.proto"; import "frameworks/base/core/proto/android/bluetooth/enums.proto"; import "frameworks/base/core/proto/android/bluetooth/hci/enums.proto"; import "frameworks/base/core/proto/android/bluetooth/hfp/enums.proto"; +import "frameworks/base/core/proto/android/bluetooth/smp/enums.proto"; import "frameworks/base/core/proto/android/net/networkcapabilities.proto"; import "frameworks/base/core/proto/android/os/enums.proto"; import "frameworks/base/core/proto/android/server/connectivity/data_stall_event.proto"; @@ -142,6 +144,24 @@ message Atom { NfcHceTransactionOccurred nfc_hce_transaction_occurred = 139; SeStateChanged se_state_changed = 140; SeOmapiReported se_omapi_reported = 141; + BluetoothActiveDeviceChanged bluetooth_active_device_changed = 151; + BluetoothA2dpPlaybackStateChanged bluetooth_a2dp_playback_state_changed = 152; + BluetoothA2dpCodecConfigChanged bluetooth_a2dp_codec_config_changed = 153; + BluetoothA2dpCodecCapabilityChanged bluetooth_a2dp_codec_capability_changed = 154; + BluetoothA2dpAudioUnderrunReported bluetooth_a2dp_audio_underrun_reported = 155; + BluetoothA2dpAudioOverrunReported bluetooth_a2dp_audio_overrun_reported = 156; + BluetoothDeviceRssiReported bluetooth_device_rssi_reported = 157; + BluetoothDeviceFailedContactCounterReported bluetooth_device_failed_contact_counter_reported = 158; + BluetoothDeviceTxPowerLevelReported bluetooth_device_tx_power_level_reported = 159; + BluetoothHciTimeoutReported bluetooth_hci_timeout_reported = 160; + BluetoothQualityReportReported bluetooth_quality_report_reported = 161; + BluetoothManufacturerInfoReported bluetooth_device_info_reported = 162; + BluetoothRemoteVersionInfoReported bluetooth_remote_version_info_reported = 163; + BluetoothSdpAttributeReported bluetooth_sdp_attribute_reported = 164; + BluetoothBondStateChanged bluetooth_bond_state_changed = 165; + BluetoothClassicPairingEventReported bluetooth_classic_pairing_event_reported = 166; + BluetoothSmpPairingEventReported bluetooth_smp_pairing_event_reported = 167; + BluetoothSocketConnectionStateChanged bluetooth_socket_connection_state_changed = 171; } // Pulled events will start at field 10000. @@ -1084,6 +1104,27 @@ message BluetoothScoConnectionStateChanged { optional android.bluetooth.hfp.ScoCodec codec = 3; } +/** + * Logged when active device of a profile changes + * + * Logged from: + * packages/apps/Bluetooth/src/com/android/bluetooth/a2dp/A2dpService.java + * packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetService.java + * packages/apps/Bluetooth/src/com/android/bluetooth/hearingaid/HearingAidService.java + */ +message BluetoothActiveDeviceChanged { + // The profile whose active device has changed. Eg. A2DP, HEADSET, HEARING_AID + // From android.bluetooth.BluetoothProfile + optional int32 bt_profile = 1; + // An identifier that can be used to match events for this new active 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 there is no active device for this profile + optional bytes obfuscated_id = 2 [(android.os.statsd.log_mode) = MODE_BYTES]; +} + // Logs when there is an event affecting Bluetooth device's link layer connection. // - This event is triggered when there is a related HCI command or event // - Users of this metrics can deduce Bluetooth device's connection state from these events @@ -1167,6 +1208,546 @@ message BluetoothLinkLayerConnectionEvent { optional android.bluetooth.hci.StatusEnum reason_code = 9; } +/** + * Logs when there is a change in Bluetooth A2DP playback state + * + * Logged from: + * packages/apps/Bluetooth/src/com/android/bluetooth/a2dp/A2dpService.java + */ +message BluetoothA2dpPlaybackStateChanged { + // 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 the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Current playback state + // Default: PLAYBACK_STATE_UNKNOWN + optional android.bluetooth.a2dp.PlaybackStateEnum playback_state = 2; + // Current audio coding mode + // Default: AUDIO_CODING_MODE_UNKNOWN + optional android.bluetooth.a2dp.AudioCodingModeEnum audio_coding_mode = 3; +} + +/** + * Logs when there is a change in A2DP codec config for a particular remote device + * + * Logged from: + * frameworks/base/core/java/android/bluetooth/BluetoothCodecConfig.java + * packages/apps/Bluetooth/src/com/android/bluetooth/a2dp/A2dpService.java + */ +message BluetoothA2dpCodecConfigChanged { + // 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 the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Type of codec as defined by various SOURCE_CODEC_TYPE_* constants in BluetoothCodecConfig + // Default SOURCE_CODEC_TYPE_INVALID + optional int32 codec_type = 2; + // Codec priroity, the higher the more preferred, -1 for disabled + // Default: CODEC_PRIORITY_DEFAULT + optional int32 codec_priority = 3; + // Sample rate in Hz as defined by various SAMPLE_RATE_* constants in BluetoothCodecConfig + // Default: SAMPLE_RATE_NONE + optional int32 sample_rate = 4; + // Bits per sample as defined by various BITS_PER_SAMPLE_* constants in BluetoothCodecConfig + // Default: BITS_PER_SAMPLE_NONE + optional int32 bits_per_sample = 5; + // Channel mode as defined by various CHANNEL_MODE_* constants in BluetoothCodecConfig + // Default: CHANNEL_MODE_NONE + optional int32 channel_mode = 6; + // Codec specific values + // Default 0 + optional int64 codec_specific_1 = 7; + optional int64 codec_specific_2 = 8; + optional int64 codec_specific_3 = 9; + optional int64 codec_specific_4 = 10; +} + +/** + * Logs when there is a change in selectable A2DP codec capability for a paricular remote device + * Each codec's capability is logged separately due to statsd restriction + * + * Logged from: + * frameworks/base/core/java/android/bluetooth/BluetoothCodecConfig.java + * packages/apps/Bluetooth/src/com/android/bluetooth/a2dp/A2dpService.java + */ +message BluetoothA2dpCodecCapabilityChanged { + // 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 the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Type of codec as defined by various SOURCE_CODEC_TYPE_* constants in BluetoothCodecConfig + // Default SOURCE_CODEC_TYPE_INVALID + optional int32 codec_type = 2; + // Codec priroity, the higher the more preferred, -1 for disabled + // Default: CODEC_PRIORITY_DEFAULT + optional int32 codec_priority = 3; + // A bit field of supported sample rates as defined by various SAMPLE_RATE_* constants + // in BluetoothCodecConfig + // Default: empty and SAMPLE_RATE_NONE for individual item + optional int32 sample_rate = 4; + // A bit field of supported bits per sample as defined by various BITS_PER_SAMPLE_* constants + // in BluetoothCodecConfig + // Default: empty and BITS_PER_SAMPLE_NONE for individual item + optional int32 bits_per_sample = 5; + // A bit field of supported channel mode as defined by various CHANNEL_MODE_* constants in + // BluetoothCodecConfig + // Default: empty and CHANNEL_MODE_NONE for individual item + optional int32 channel_mode = 6; + // Codec specific values + // Default 0 + optional int64 codec_specific_1 = 7; + optional int64 codec_specific_2 = 8; + optional int64 codec_specific_3 = 9; + optional int64 codec_specific_4 = 10; +} + +/** + * Logs when A2DP failed to read from PCM source. + * This typically happens when audio HAL cannot supply A2DP with data fast enough for encoding. + * + * Logged from: + * system/bt + */ +message BluetoothA2dpAudioUnderrunReported { + // 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 the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Encoding interval in nanoseconds + // Default: 0 + optional int64 encoding_interval_nanos = 2; + // Number of bytes of PCM data that could not be read from the source + // Default: 0 + optional int32 num_missing_pcm_bytes = 3; +} + +/** + * Logs when A2DP failed send encoded data to the remote device fast enough such that the transmit + * buffer queue is full and we have to drop data + * + * Logged from: + * system/bt + */ +message BluetoothA2dpAudioOverrunReported { + // 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 the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Encoding interval in nanoseconds + // Default: 0 + optional int64 encoding_interval_nanos = 2; + // Number of buffers dropped in this event + // Each buffer is encoded in one encoding interval and consists of multiple encoded frames + // Default: 0 + optional int32 num_dropped_buffers = 3; + // Number of encoded buffers dropped in this event + // Default 0 + optional int32 num_dropped_encoded_frames = 4; + // Number of encoded bytes dropped in this event + // Default: 0 + optional int32 num_dropped_encoded_bytes = 5; +} + +/** + * Logs when we receive reports regarding a device's RSSI value + * + * Logged from: + * system/bt + */ +message BluetoothDeviceRssiReported { + // 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 the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Connection handle of this connection if available + // Range: 0x0000 - 0x0EFF (12 bits) + // Default: 0xFFFF if the handle is unknown + optional int32 connection_handle = 2; + // HCI command status code if this is triggerred by hci_cmd + // Default: STATUS_UNKNOWN + optional android.bluetooth.hci.StatusEnum hci_status = 3; + // BR/EDR + // Range: -128 ≤ N ≤ 127 (signed integer) + // Units: dB + // LE: + // Range: -127 to 20, 127 (signed integer) + // Units: dBm + // Invalid when an out of range value is reported + optional int32 rssi = 4; +} + +/** + * Logs when we receive reports regarding how many consecutive failed contacts for a connection + * + * Logged from: + * system/bt + */ +message BluetoothDeviceFailedContactCounterReported { + // 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 the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Connection handle of this connection if available + // Range: 0x0000 - 0x0EFF (12 bits) + // Default: 0xFFFF if the handle is unknown + optional int32 connection_handle = 2; + // HCI command status code if this is triggerred by hci_cmd + // Default: STATUS_UNKNOWN + optional android.bluetooth.hci.StatusEnum cmd_status = 3; + // Number of consecutive failed contacts for a connection corresponding to the Handle + // Range: uint16_t, 0-0xFFFF + // Default: 0xFFFFF + optional int32 failed_contact_counter = 4; +} + +/** + * Logs when we receive reports regarding the tranmit power level used for a specific connection + * + * Logged from: + * system/bt + */ +message BluetoothDeviceTxPowerLevelReported { + // 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 the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Connection handle of this connection if available + // Range: 0x0000 - 0x0EFF (12 bits) + // Default: 0xFFFF if the handle is unknown + optional int32 connection_handle = 2; + // HCI command status code if this is triggered by hci_cmd + // Default: STATUS_UNKNOWN + optional android.bluetooth.hci.StatusEnum hci_status = 3; + // Range: -30 ≤ N ≤ 20 + // Units: dBm + // Invalid when an out of range value is reported + optional int32 transmit_power_level = 4; +} + +/** + * Logs when Bluetooth controller failed to reply with command status within a timeout period after + * receiving an HCI command from the host + * + * Logged from: system/bt + */ +message BluetoothHciTimeoutReported { + // HCI command associated with this event + // Default: CMD_UNKNOWN + optional android.bluetooth.hci.CommandEnum hci_command = 1; +} + +/** + * Logs when we receive Bluetooth Link Quality Report event from the controller + * See Android Bluetooth HCI specification for more details + * + * Note: all count and bytes field are counted since last event + * + * Logged from: system/bt + */ +message BluetoothQualityReportReported { + // Quality report ID + // Original type: uint8_t + // Default: BQR_ID_UNKNOWN + optional android.bluetooth.hci.BqrIdEnum quality_report_id = 1; + // Packet type of the connection + // Original type: uint8_t + // Default: BQR_PACKET_TYPE_UNKNOWN + optional android.bluetooth.hci.BqrPacketTypeEnum packet_types = 2; + // Connection handle of the connection + // Original type: uint16_t + optional int32 connection_handle = 3; + // Performing Role for the connection + // Original type: uint8_t + optional int32 connection_role = 4; + // Current Transmit Power Level for the connection. This value is the same as the controller's + // response to the HCI_Read_Transmit_Power_Level HCI command + // Original type: uint8_t + optional int32 tx_power_level = 5; + // Received Signal Strength Indication (RSSI) value for the connection. This value is an + // absolute receiver signal strength value + // Original type: int8_t + optional int32 rssi = 6; + // Signal-to-Noise Ratio (SNR) value for the connection. It is the average SNR of all the + // channels used by the link currently + // Original type: uint8_t + optional int32 snr = 7; + // Indicates the number of unused channels in AFH_channel_map + // Original type: uint8_t + optional int32 unused_afh_channel_count = 8; + // Indicates the number of the channels which are interfered and quality is bad but are still + // selected for AFH + // Original type: uint8_t + optional int32 afh_select_unideal_channel_count = 9; + // Current Link Supervision Timeout Setting + // Unit: N * 0.3125 ms (1 Bluetooth Clock) + // Original type: uint16_t + optional int32 lsto = 10; + // Piconet Clock for the specified Connection_Handle. This value is the same as the controller's + // response to HCI_Read_Clock HCI command with the parameter "Which_Clock" of + // 0x01 (Piconet Clock) + // Unit: N * 0.3125 ms (1 Bluetooth Clock) + // Original type: uint32_t + optional int64 connection_piconet_clock = 11; + // The count of retransmission + // Original type: uint32_t + optional int64 retransmission_count = 12; + // The count of no RX + // Original type: uint32_t + optional int64 no_rx_count = 13; + // The count of NAK (Negative Acknowledge) + // Original type: uint32_t + optional int64 nak_count = 14; + // Controller timestamp of last TX ACK + // Unit: N * 0.3125 ms (1 Bluetooth Clock) + // Original type: uint32_t + optional int64 last_tx_ack_timestamp = 15; + // The count of Flow-off (STOP) + // Original type: uint32_t + optional int64 flow_off_count = 16; + // Controller timestamp of last Flow-on (GO) + // Unit: N * 0.3125 ms (1 Bluetooth Clock) + // Original type: uint32_t + optional int64 last_flow_on_timestamp = 17; + // Buffer overflow count (how many bytes of TX data are dropped) since the last event + // Original type: uint32_t + optional int64 buffer_overflow_bytes = 18; + // Buffer underflow count (in byte) since last event + // Original type: uint32_t + optional int64 buffer_underflow_bytes = 19; +} + +/** + * Logs when a Bluetooth device's manufacturer information is learnt by the Bluetooth stack + * + * Notes: + * - Each event can be partially filled as we might learn different pieces of device + * information at different time + * - Multiple device info events can be combined to give more complete picture + * - When multiple device info events tries to describe the same information, the + * later one wins + * + * Logged from: + * packages/apps/Bluetooth + */ +message BluetoothManufacturerInfoReported { + // 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 the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Where is this device info obtained from + optional android.bluetooth.DeviceInfoSrcEnum source_type = 2; + // Name of the data source + // For EXTERNAL: package name of the data source + // For INTERNAL: null for general case, component name otherwise + optional string source_name = 3; + // Name of the manufacturer of this device + optional string manufacturer = 4; + // Model of this device + optional string model = 5; + // Hardware version of this device + optional string hardware_version = 6; + // Software version of this device + optional string software_version = 7; +} + +/** + * Logs when we receive Bluetooth Read Remote Version Information Complete Event from the remote + * device, as documented by the Bluetooth Core HCI specification + * Reference: https://www.bluetooth.com/specifications/bluetooth-core-specification + * Vol 2, Part E, Page 1118 + * + * Logged from: + * system/bt + */ +message BluetoothRemoteVersionInfoReported { + // Connection handle of the connection + // Original type: uint16_t + optional int32 connection_handle = 1; + // HCI command status code + // Default: STATUS_UNKNOWN + optional android.bluetooth.hci.StatusEnum hci_status = 2; + // 1 byte Version of current LMP in the remote controller + optional int32 lmp_version = 3; + // 2 bytes LMP manufacturer code of the remote controller + // https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers + optional int32 lmp_manufacturer_code = 4; + // 4 bytes subversion of the LMP in the remote controller + optional int32 lmp_subversion = 5; +} + +/** + * Logs when certain Bluetooth SDP attributes are discovered + * Constant definitions are from: + * https://www.bluetooth.com/specifications/assigned-numbers/service-discovery + * + * Current logged attributes: + * - BluetoothProfileDescriptorList + * - Supported Features Bitmask + * + * Logged from: + * system/bt + */ +message BluetoothSdpAttributeReported { + // 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 + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Short form UUIDs used to identify Bluetooth protocols, profiles, and service classes + // Original type: uint16_t + optional int32 protocol_uuid = 2; + // Short form UUIDs used to identify Bluetooth SDP attribute types + // Original type: uint16_t + optional int32 attribute_id = 3; + // Attribute value for the particular attribute + optional bytes attribute_value = 4 [(android.os.statsd.log_mode) = MODE_BYTES]; +} + +/** + * Logs when bond state of a Bluetooth device changes + * + * Logged from: + * frameworks/base/core/java/android/bluetooth/BluetoothDevice.java + * packages/apps/Bluetooth/src/com/android/bluetooth/btservice/BondStateMachine.java + */ +message BluetoothBondStateChanged { + // 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 the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Preferred transport type to remote dual mode device + // Default: TRANSPORT_AUTO means no preference + optional android.bluetooth.TransportTypeEnum transport = 2; + // The type of this Bluetooth device (Classic, LE, or Dual mode) + // Default: UNKNOWN + optional android.bluetooth.DeviceTypeEnum type = 3; + // Current bond state (NONE, BONDING, BONDED) + // Default: BOND_STATE_UNKNOWN + optional android.bluetooth.BondStateEnum bond_state = 4; + // Bonding sub state + // Default: BOND_SUB_STATE_UNKNOWN + optional android.bluetooth.BondSubStateEnum bonding_sub_state = 5; + // Unbond Reason + // Default: UNBOND_REASON_UNKNOWN + optional android.bluetooth.UnbondReasonEnum unbond_reason = 6; +} + +/** + * Logs there is an event related Bluetooth classic pairing + * + * Logged from: + * system/bt + */ +message BluetoothClassicPairingEventReported { + // 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 the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Connection handle of this connection if available + // Range: 0x0000 - 0x0EFF (12 bits) + // Default: 0xFFFF if the handle is unknown + optional int32 connection_handle = 2; + // HCI command associated with this event + // Default: CMD_UNKNOWN + optional android.bluetooth.hci.CommandEnum hci_cmd = 3; + // HCI event associated with this event + // Default: EVT_UNKNOWN + optional android.bluetooth.hci.EventEnum hci_event = 4; + // HCI command status code if this is triggerred by hci_cmd + // Default: STATUS_UNKNOWN + optional android.bluetooth.hci.StatusEnum cmd_status = 5; + // HCI reason code associated with this event + // Default: STATUS_UNKNOWN + optional android.bluetooth.hci.StatusEnum reason_code = 6; +} + +/** + * Logs when there is an event related to Bluetooth Security Manager Protocol (SMP) + * + * Logged from: + * system/bt + */ +message BluetoothSmpPairingEventReported { + // 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 the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // SMP command sent or received over L2CAP + // Default: CMD_UNKNOWN + optional android.bluetooth.smp.CommandEnum smp_command = 2; + // Whether this command is sent or received + // Default: DIRECTION_UNKNOWN + optional android.bluetooth.DirectionEnum direction = 3; + // SMP failure reason code + // Default: PAIRING_FAIL_REASON_DEFAULT + optional android.bluetooth.smp.PairingFailReasonEnum smp_fail_reason = 4; +} + +/** + * 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 the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Port of this socket + // 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; +} /** * Logs when something is plugged into or removed from the USB-C connector. diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index b2951df63ebd..c42a2bce2c48 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; @@ -114,11 +113,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; @@ -1058,12 +1059,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/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 1b08ecd32fca..18a006ffff31 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -4464,11 +4464,16 @@ public class DevicePolicyManager { } /** + * Service-specific error code used in implementation of {@code setAlwaysOnVpnPackage} methods. + * @hide + */ + public static final int ERROR_VPN_PACKAGE_NOT_FOUND = 1; + + /** * Called by a device or profile owner to configure an always-on VPN connection through a * specific application for the current user. This connection is automatically granted and * persisted after a reboot. - * <p> - * To support the always-on feature, an app must + * <p> To support the always-on feature, an app must * <ul> * <li>declare a {@link android.net.VpnService} in its manifest, guarded by * {@link android.Manifest.permission#BIND_VPN_SERVICE};</li> @@ -4477,25 +4482,61 @@ public class DevicePolicyManager { * {@link android.net.VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}.</li> * </ul> * The call will fail if called with the package name of an unsupported VPN app. + * <p> Enabling lockdown via {@code lockdownEnabled} argument carries the risk that any failure + * of the VPN provider could break networking for all apps. * * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} to * remove an existing always-on VPN configuration. * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or - * {@code false} otherwise. This carries the risk that any failure of the VPN provider - * could break networking for all apps. This has no effect when clearing. + * {@code false} otherwise. This has no effect when clearing. * @throws SecurityException if {@code admin} is not a device or a profile owner. * @throws NameNotFoundException if {@code vpnPackage} is not installed. * @throws UnsupportedOperationException if {@code vpnPackage} exists but does not support being * set as always-on, or if always-on VPN is not available. + * @see #setAlwaysOnVpnPackage(ComponentName, String, boolean, List) */ public void setAlwaysOnVpnPackage(@NonNull ComponentName admin, @Nullable String vpnPackage, - boolean lockdownEnabled) - throws NameNotFoundException, UnsupportedOperationException { + boolean lockdownEnabled) throws NameNotFoundException { + setAlwaysOnVpnPackage(admin, vpnPackage, lockdownEnabled, Collections.emptyList()); + } + + /** + * A version of {@link #setAlwaysOnVpnPackage(ComponentName, String, boolean)} that allows the + * admin to specify a set of apps that should be able to access the network directly when VPN + * is not connected. When VPN connects these apps switch over to VPN if allowed to use that VPN. + * System apps can always bypass VPN. + * <p> Note that the system doesn't update the whitelist when packages are installed or + * uninstalled, the admin app must call this method to keep the list up to date. + * + * @param vpnPackage package name for an installed VPN app on the device, or {@code null} + * to remove an existing always-on VPN configuration + * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or + * {@code false} otherwise. This has no effect when clearing. + * @param lockdownWhitelist Packages that will be able to access the network directly when VPN + * is in lockdown mode but not connected. Has no effect when clearing. + * @throws SecurityException if {@code admin} is not a device or a profile + * owner. + * @throws NameNotFoundException if {@code vpnPackage} or one of + * {@code lockdownWhitelist} is not installed. + * @throws UnsupportedOperationException if {@code vpnPackage} exists but does + * not support being set as always-on, or if always-on VPN is not + * available. + */ + public void setAlwaysOnVpnPackage(@NonNull ComponentName admin, @Nullable String vpnPackage, + boolean lockdownEnabled, @Nullable List<String> lockdownWhitelist) + throws NameNotFoundException { throwIfParentInstance("setAlwaysOnVpnPackage"); if (mService != null) { try { - if (!mService.setAlwaysOnVpnPackage(admin, vpnPackage, lockdownEnabled)) { - throw new NameNotFoundException(vpnPackage); + mService.setAlwaysOnVpnPackage( + admin, vpnPackage, lockdownEnabled, lockdownWhitelist); + } catch (ServiceSpecificException e) { + switch (e.errorCode) { + case ERROR_VPN_PACKAGE_NOT_FOUND: + throw new NameNotFoundException(e.getMessage()); + default: + throw new RuntimeException( + "Unknown error setting always-on VPN: " + e.errorCode, e); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -4504,6 +4545,51 @@ public class DevicePolicyManager { } /** + * Called by device or profile owner to query whether current always-on VPN is configured in + * lockdown mode. Returns {@code false} when no always-on configuration is set. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * + * @throws SecurityException if {@code admin} is not a device or a profile owner. + * + * @see #setAlwaysOnVpnPackage(ComponentName, String, boolean) + */ + public boolean isAlwaysOnVpnLockdownEnabled(@NonNull ComponentName admin) { + throwIfParentInstance("isAlwaysOnVpnLockdownEnabled"); + if (mService != null) { + try { + return mService.isAlwaysOnVpnLockdownEnabled(admin); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** + * Called by device or profile owner to query the list of packages that are allowed to access + * the network directly when always-on VPN is in lockdown mode but not connected. Returns + * {@code null} when always-on VPN is not active or not in lockdown mode. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * + * @throws SecurityException if {@code admin} is not a device or a profile owner. + * + * @see #setAlwaysOnVpnPackage(ComponentName, String, boolean, List) + */ + public @Nullable List<String> getAlwaysOnVpnLockdownWhitelist(@NonNull ComponentName admin) { + throwIfParentInstance("getAlwaysOnVpnLockdownWhitelist"); + if (mService != null) { + try { + return mService.getAlwaysOnVpnLockdownWhitelist(admin); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return null; + } + + /** * Called by a device or profile owner to read the name of the package administering an * always-on VPN connection for the current user. If there is no such package, or the always-on * VPN is provided by the system instead of by an application, {@code null} will be returned. diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 37508cdc1119..00463028a685 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -182,8 +182,10 @@ interface IDevicePolicyManager { void setCertInstallerPackage(in ComponentName who, String installerPackage); String getCertInstallerPackage(in ComponentName who); - boolean setAlwaysOnVpnPackage(in ComponentName who, String vpnPackage, boolean lockdown); + boolean setAlwaysOnVpnPackage(in ComponentName who, String vpnPackage, boolean lockdown, in List<String> lockdownWhitelist); String getAlwaysOnVpnPackage(in ComponentName who); + boolean isAlwaysOnVpnLockdownEnabled(in ComponentName who); + List<String> getAlwaysOnVpnLockdownWhitelist(in ComponentName who); void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity); void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName); 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/content/Context.java b/core/java/android/content/Context.java index 8959540ff7c3..493aac6e5c40 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3087,7 +3087,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 {} @@ -4290,19 +4289,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/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 2130c39a8905..92c757ceaa29 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -5501,7 +5501,7 @@ public abstract class PackageManager { * @param newState The new enabled state for the component. * @param flags Optional behavior flags. */ - public abstract void setComponentEnabledSetting(ComponentName componentName, + public abstract void setComponentEnabledSetting(@NonNull ComponentName componentName, @EnabledState int newState, @EnabledFlags int flags); /** @@ -5515,7 +5515,7 @@ public abstract class PackageManager { * @return Returns the current enabled state for the component. */ public abstract @EnabledState int getComponentEnabledSetting( - ComponentName componentName); + @NonNull ComponentName componentName); /** * Set the enabled setting for an application @@ -5528,7 +5528,7 @@ public abstract class PackageManager { * @param newState The new enabled state for the application. * @param flags Optional behavior flags. */ - public abstract void setApplicationEnabledSetting(String packageName, + public abstract void setApplicationEnabledSetting(@NonNull String packageName, @EnabledState int newState, @EnabledFlags int flags); /** @@ -5542,7 +5542,7 @@ public abstract class PackageManager { * @return Returns the current enabled state for the application. * @throws IllegalArgumentException if the named package does not exist. */ - public abstract @EnabledState int getApplicationEnabledSetting(String packageName); + public abstract @EnabledState int getApplicationEnabledSetting(@NonNull String packageName); /** * Flush the package restrictions for a given user to disk. This forces the package restrictions 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/CaptivePortal.java b/core/java/android/net/CaptivePortal.java index 3b0126673779..3ab35e1eebf0 100644 --- a/core/java/android/net/CaptivePortal.java +++ b/core/java/android/net/CaptivePortal.java @@ -117,4 +117,17 @@ public class CaptivePortal implements Parcelable { } catch (RemoteException e) { } } + + /** + * Log a captive portal login event. + * @hide + */ + @SystemApi + @TestApi + public void logEvent(int eventId, String packageName) { + try { + ICaptivePortal.Stub.asInterface(mBinder).logEvent(eventId, packageName); + } catch (RemoteException e) { + } + } } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 3bae12e93745..68ac46c874ff 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"; /** @@ -1033,34 +1038,6 @@ public class ConnectivityManager { } } - /** - * Configures an always-on VPN connection through a specific application. - * This connection is automatically granted and persisted after a reboot. - * - * <p>The designated package should declare a {@link VpnService} in its - * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE}, - * otherwise the call will fail. - * - * @param userId The identifier of the user to set an always-on VPN for. - * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} - * to remove an existing always-on VPN configuration. - * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or - * {@code false} otherwise. - * @return {@code true} if the package is set as always-on VPN controller; - * {@code false} otherwise. - * @hide - */ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) - public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, - boolean lockdownEnabled) { - try { - return mService.setAlwaysOnVpnPackage( - userId, vpnPackage, lockdownEnabled, /* whitelist */ null); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - /** * Returns the package name of the currently set always-on VPN application. * If there is no always-on VPN set, or the VPN is provided by the system instead @@ -1271,9 +1248,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(); @@ -1844,7 +1825,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) { @@ -1909,7 +1890,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, @@ -1938,6 +1920,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 @@ -1953,6 +1937,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. @@ -2609,6 +2621,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; @@ -2631,9 +2644,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 @@ -2656,6 +2673,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 @@ -3831,6 +3907,25 @@ 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 appExtras Extras to include in the app start intent. + * @hide + */ + @SystemApi + @TestApi + @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) + public void startCaptivePortalApp(Bundle appExtras) { + try { + mService.startCaptivePortalAppInternal(appExtras); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Determine whether the device is configured to avoid bad wifi. * @hide */ @@ -3970,10 +4065,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)) { + return false; } - if (NetworkUtils.bindProcessToNetwork(netId)) { + + if (!isSameNetId) { // Set HTTP proxy system properties to match network. // TODO: Deprecate this static method and replace it with a non-static version. try { @@ -3987,10 +4089,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/DhcpResults.java b/core/java/android/net/DhcpResults.java index 6c291c25dbdf..6f9e65fdf12c 100644 --- a/core/java/android/net/DhcpResults.java +++ b/core/java/android/net/DhcpResults.java @@ -17,6 +17,7 @@ package android.net; import android.annotation.UnsupportedAppUsage; +import android.net.shared.InetAddressUtils; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -73,19 +74,21 @@ public final class DhcpResults implements Parcelable { public StaticIpConfiguration toStaticIpConfiguration() { final StaticIpConfiguration s = new StaticIpConfiguration(); // All these except dnsServers are immutable, so no need to make copies. - s.ipAddress = ipAddress; - s.gateway = gateway; - s.dnsServers.addAll(dnsServers); - s.domains = domains; + s.setIpAddress(ipAddress); + s.setGateway(gateway); + for (InetAddress addr : dnsServers) { + s.addDnsServer(addr); + } + s.setDomains(domains); return s; } public DhcpResults(StaticIpConfiguration source) { if (source != null) { - ipAddress = source.ipAddress; - gateway = source.gateway; - dnsServers.addAll(source.dnsServers); - domains = source.domains; + ipAddress = source.getIpAddress(); + gateway = source.getGateway(); + dnsServers.addAll(source.getDnsServers()); + domains = source.getDomains(); } } @@ -177,7 +180,7 @@ public final class DhcpResults implements Parcelable { toStaticIpConfiguration().writeToParcel(dest, flags); dest.writeInt(leaseDuration); dest.writeInt(mtu); - NetworkUtils.parcelInetAddress(dest, serverAddress, flags); + InetAddressUtils.parcelInetAddress(dest, serverAddress, flags); dest.writeString(vendorInfo); } @@ -191,7 +194,7 @@ public final class DhcpResults implements Parcelable { final DhcpResults dhcpResults = new DhcpResults(s); dhcpResults.leaseDuration = in.readInt(); dhcpResults.mtu = in.readInt(); - dhcpResults.serverAddress = (Inet4Address) NetworkUtils.unparcelInetAddress(in); + dhcpResults.serverAddress = (Inet4Address) InetAddressUtils.unparcelInetAddress(in); dhcpResults.vendorInfo = in.readString(); return dhcpResults; } @@ -200,7 +203,7 @@ public final class DhcpResults implements Parcelable { // Not part of the superclass because they're only used by the JNI iterface to the DHCP daemon. public boolean setIpAddress(String addrString, int prefixLength) { try { - Inet4Address addr = (Inet4Address) NetworkUtils.numericToInetAddress(addrString); + Inet4Address addr = (Inet4Address) InetAddresses.parseNumericAddress(addrString); ipAddress = new LinkAddress(addr, prefixLength); } catch (IllegalArgumentException|ClassCastException e) { Log.e(TAG, "setIpAddress failed with addrString " + addrString + "/" + prefixLength); @@ -211,7 +214,7 @@ public final class DhcpResults implements Parcelable { public boolean setGateway(String addrString) { try { - gateway = NetworkUtils.numericToInetAddress(addrString); + gateway = InetAddresses.parseNumericAddress(addrString); } catch (IllegalArgumentException e) { Log.e(TAG, "setGateway failed with addrString " + addrString); return true; @@ -222,7 +225,7 @@ public final class DhcpResults implements Parcelable { public boolean addDns(String addrString) { if (TextUtils.isEmpty(addrString) == false) { try { - dnsServers.add(NetworkUtils.numericToInetAddress(addrString)); + dnsServers.add(InetAddresses.parseNumericAddress(addrString)); } catch (IllegalArgumentException e) { Log.e(TAG, "addDns failed with addrString " + addrString); return true; diff --git a/core/java/android/net/ICaptivePortal.aidl b/core/java/android/net/ICaptivePortal.aidl index 56ae57dc0e8d..707b4f699873 100644 --- a/core/java/android/net/ICaptivePortal.aidl +++ b/core/java/android/net/ICaptivePortal.aidl @@ -22,4 +22,5 @@ package android.net; */ oneway interface ICaptivePortal { void appResponse(int response); + void logEvent(int eventId, String packageName); } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index fd7360fd4c17..92a5839ac2af 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 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/INetworkMonitorCallbacks.aidl b/core/java/android/net/INetworkMonitorCallbacks.aidl index 0bc25750129b..a8682f9ddd3b 100644 --- a/core/java/android/net/INetworkMonitorCallbacks.aidl +++ b/core/java/android/net/INetworkMonitorCallbacks.aidl @@ -26,4 +26,5 @@ oneway interface INetworkMonitorCallbacks { void notifyPrivateDnsConfigResolved(in PrivateDnsConfigParcel config); void showProvisioningNotification(String action); void hideProvisioningNotification(); + void logCaptivePortalLoginEvent(int eventId, String packageName); }
\ No newline at end of file diff --git a/core/java/android/net/INetworkStackConnector.aidl b/core/java/android/net/INetworkStackConnector.aidl index 8b64f1c7c45a..e052488f38c8 100644 --- a/core/java/android/net/INetworkStackConnector.aidl +++ b/core/java/android/net/INetworkStackConnector.aidl @@ -16,6 +16,7 @@ package android.net; import android.net.INetworkMonitorCallbacks; +import android.net.NetworkParcelable; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpServerCallbacks; import android.net.ip.IIpClientCallbacks; @@ -24,6 +25,7 @@ import android.net.ip.IIpClientCallbacks; oneway interface INetworkStackConnector { void makeDhcpServer(in String ifName, in DhcpServingParamsParcel params, in IDhcpServerCallbacks cb); - void makeNetworkMonitor(int netId, String name, in INetworkMonitorCallbacks cb); + void makeNetworkMonitor(in NetworkParcelable network, String name, + in INetworkMonitorCallbacks cb); void makeIpClient(in String ifName, in IIpClientCallbacks callbacks); }
\ No newline at end of file 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/NetworkStack.java b/core/java/android/net/NetworkStack.java index d277034650a1..b6cd6359384a 100644 --- a/core/java/android/net/NetworkStack.java +++ b/core/java/android/net/NetworkStack.java @@ -20,7 +20,9 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -46,9 +48,22 @@ import java.util.ArrayList; * @hide */ @SystemService(Context.NETWORK_STACK_SERVICE) +@SystemApi +@TestApi public class NetworkStack { private static final String TAG = NetworkStack.class.getSimpleName(); + /** + * Permission granted only to the NetworkStack APK, defined in NetworkStackStub with signature + * protection level. + * @hide + */ + @SystemApi + @TestApi + public static final String PERMISSION_MAINLINE_NETWORK_STACK = + "android.permission.MAINLINE_NETWORK_STACK"; + + /** @hide */ public static final String NETWORKSTACK_PACKAGE_NAME = "com.android.mainline.networkstack"; private static final int NETWORKSTACK_TIMEOUT_MS = 10_000; @@ -66,12 +81,14 @@ public class NetworkStack { void onNetworkStackConnected(INetworkStackConnector connector); } + /** @hide */ public NetworkStack() { } /** * Create a DHCP server according to the specified parameters. * * <p>The server will be returned asynchronously through the provided callbacks. + * @hide */ public void makeDhcpServer(final String ifName, final DhcpServingParamsParcel params, final IDhcpServerCallbacks cb) { @@ -88,6 +105,7 @@ public class NetworkStack { * Create an IpClient on the specified interface. * * <p>The IpClient will be returned asynchronously through the provided callbacks. + * @hide */ public void makeIpClient(String ifName, IIpClientCallbacks cb) { requestConnector(connector -> { @@ -103,11 +121,13 @@ public class NetworkStack { * Create a NetworkMonitor. * * <p>The INetworkMonitor will be returned asynchronously through the provided callbacks. + * @hide */ - public void makeNetworkMonitor(Network network, String name, INetworkMonitorCallbacks cb) { + public void makeNetworkMonitor( + NetworkParcelable network, String name, INetworkMonitorCallbacks cb) { requestConnector(connector -> { try { - connector.makeNetworkMonitor(network.netId, name, cb); + connector.makeNetworkMonitor(network, name, cb); } catch (RemoteException e) { e.rethrowFromSystemServer(); } @@ -152,6 +172,7 @@ public class NetworkStack { * 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. + * @hide */ public void start(Context context) { mNetworkStackStartRequested = true; @@ -222,7 +243,7 @@ public class NetworkStack { private void requestConnector(@NonNull NetworkStackCallback request) { // TODO: PID check. final int caller = Binder.getCallingUid(); - if (caller != Process.SYSTEM_UID && caller != Process.BLUETOOTH_UID) { + 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."); diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index 44170b584faf..0ae29b125149 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -19,7 +19,6 @@ package android.net; import android.annotation.UnsupportedAppUsage; import android.net.shared.Inet4AddressUtils; import android.os.Build; -import android.os.Parcel; import android.system.ErrnoException; import android.util.Log; import android.util.Pair; @@ -72,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. @@ -171,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)} @@ -247,32 +268,6 @@ public class NetworkUtils { } /** - * Writes an InetAddress to a parcel. The address may be null. This is likely faster than - * calling writeSerializable. - */ - protected static void parcelInetAddress(Parcel parcel, InetAddress address, int flags) { - byte[] addressArray = (address != null) ? address.getAddress() : null; - parcel.writeByteArray(addressArray); - } - - /** - * Reads an InetAddress from a parcel. Returns null if the address that was written was null - * or if the data is invalid. - */ - protected static InetAddress unparcelInetAddress(Parcel in) { - byte[] addressArray = in.createByteArray(); - if (addressArray == null) { - return null; - } - try { - return InetAddress.getByAddress(addressArray); - } catch (UnknownHostException e) { - return null; - } - } - - - /** * Masks a raw IP address byte array with the specified prefix length. */ public static void maskRawAddress(byte[] array, int prefixLength) { 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/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java index 25bae3c57423..99cf3a99f57a 100644 --- a/core/java/android/net/StaticIpConfiguration.java +++ b/core/java/android/net/StaticIpConfiguration.java @@ -19,6 +19,7 @@ package android.net; import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; +import android.net.shared.InetAddressUtils; import android.os.Parcel; import android.os.Parcelable; @@ -232,10 +233,10 @@ public final class StaticIpConfiguration implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeParcelable(ipAddress, flags); - NetworkUtils.parcelInetAddress(dest, gateway, flags); + InetAddressUtils.parcelInetAddress(dest, gateway, flags); dest.writeInt(dnsServers.size()); for (InetAddress dnsServer : dnsServers) { - NetworkUtils.parcelInetAddress(dest, dnsServer, flags); + InetAddressUtils.parcelInetAddress(dest, dnsServer, flags); } dest.writeString(domains); } @@ -244,11 +245,11 @@ public final class StaticIpConfiguration implements Parcelable { public static StaticIpConfiguration readFromParcel(Parcel in) { final StaticIpConfiguration s = new StaticIpConfiguration(); s.ipAddress = in.readParcelable(null); - s.gateway = NetworkUtils.unparcelInetAddress(in); + s.gateway = InetAddressUtils.unparcelInetAddress(in); s.dnsServers.clear(); int size = in.readInt(); for (int i = 0; i < size; i++) { - s.dnsServers.add(NetworkUtils.unparcelInetAddress(in)); + s.dnsServers.add(InetAddressUtils.unparcelInetAddress(in)); } s.domains = in.readString(); return s; 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/net/metrics/DhcpClientEvent.java b/core/java/android/net/metrics/DhcpClientEvent.java index 2a942eedcb47..3008115e063b 100644 --- a/core/java/android/net/metrics/DhcpClientEvent.java +++ b/core/java/android/net/metrics/DhcpClientEvent.java @@ -31,10 +31,6 @@ import android.os.Parcelable; public final class DhcpClientEvent implements IpConnectivityLog.Event { // Names for recording DhcpClient pseudo-state transitions. - /** {@hide} Represents transitions from DhcpInitState to DhcpBoundState */ - public static final String INITIAL_BOUND = "InitialBoundState"; - /** {@hide} Represents transitions from and to DhcpBoundState via DhcpRenewingState */ - public static final String RENEWING_BOUND = "RenewingBoundState"; /** @hide */ public final String msg; diff --git a/packages/NetworkStack/src/android/net/util/FdEventsReader.java b/core/java/android/net/shared/FdEventsReader.java index 8bbf449f6374..bffbfb115886 100644 --- a/packages/NetworkStack/src/android/net/util/FdEventsReader.java +++ b/core/java/android/net/shared/FdEventsReader.java @@ -14,22 +14,22 @@ * limitations under the License. */ -package android.net.util; +package android.net.shared; -import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; 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; import android.system.ErrnoException; import android.system.OsConstants; -import libcore.io.IoUtils; - import java.io.FileDescriptor; +import java.io.IOException; /** @@ -63,6 +63,7 @@ import java.io.FileDescriptor; * All public methods MUST only be called from the same thread with which * the Handler constructor argument is associated. * + * @param <BufferType> the type of the buffer used to read data. * @hide */ public abstract class FdEventsReader<BufferType> { @@ -80,7 +81,10 @@ public abstract class FdEventsReader<BufferType> { private long mPacketsReceived; protected static void closeFd(FileDescriptor fd) { - IoUtils.closeQuietly(fd); + try { + SocketUtils.closeSocket(fd); + } catch (IOException ignored) { + } } protected FdEventsReader(@NonNull Handler h, @NonNull BufferType buffer) { @@ -89,6 +93,7 @@ public abstract class FdEventsReader<BufferType> { mBuffer = buffer; } + /** Start this FdEventsReader. */ public void start() { if (onCorrectThread()) { createAndRegisterFd(); @@ -100,6 +105,7 @@ public abstract class FdEventsReader<BufferType> { } } + /** Stop this FdEventsReader and destroy the file descriptor. */ public void stop() { if (onCorrectThread()) { unregisterAndDestroyFd(); @@ -112,22 +118,29 @@ public abstract class FdEventsReader<BufferType> { } @NonNull - public Handler getHandler() { return mHandler; } + public Handler getHandler() { + return mHandler; + } protected abstract int recvBufSize(@NonNull BufferType buffer); - public int recvBufSize() { return recvBufSize(mBuffer); } + /** Returns the size of the receive buffer. */ + public int recvBufSize() { + return recvBufSize(mBuffer); + } /** * Get the number of successful calls to {@link #readPacket(FileDescriptor, Object)}. * * <p>A call was successful if {@link #readPacket(FileDescriptor, Object)} returned a value > 0. */ - public final long numPacketsReceived() { return mPacketsReceived; } + public final long numPacketsReceived() { + return mPacketsReceived; + } /** - * Subclasses MUST create the listening socket here, including setting - * all desired socket options, interface or address/port binding, etc. + * Subclasses MUST create the listening socket here, including setting all desired socket + * options, interface or address/port binding, etc. The socket MUST be created nonblocking. */ @Nullable protected abstract FileDescriptor createFd(); @@ -171,10 +184,6 @@ public abstract class FdEventsReader<BufferType> { try { mFd = createFd(); - if (mFd != null) { - // Force the socket to be non-blocking. - IoUtils.setBlocking(mFd, false); - } } catch (Exception e) { logError("Failed to create socket: ", e); closeFd(mFd); @@ -199,7 +208,9 @@ public abstract class FdEventsReader<BufferType> { onStart(); } - private boolean isRunning() { return (mFd != null) && mFd.valid(); } + private boolean isRunning() { + return (mFd != null) && mFd.valid(); + } // Keep trying to read until we get EAGAIN/EWOULDBLOCK or some fatal error. private boolean handleInput() { diff --git a/core/java/android/net/shared/InetAddressUtils.java b/core/java/android/net/shared/InetAddressUtils.java new file mode 100644 index 000000000000..c9ee3a7cce4b --- /dev/null +++ b/core/java/android/net/shared/InetAddressUtils.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2012 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.shared; + +import android.os.Parcel; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * Collection of utilities to interact with {@link InetAddress} + * @hide + */ +public class InetAddressUtils { + + /** + * Writes an InetAddress to a parcel. The address may be null. This is likely faster than + * calling writeSerializable. + * @hide + */ + public static void parcelInetAddress(Parcel parcel, InetAddress address, int flags) { + byte[] addressArray = (address != null) ? address.getAddress() : null; + parcel.writeByteArray(addressArray); + } + + /** + * Reads an InetAddress from a parcel. Returns null if the address that was written was null + * or if the data is invalid. + * @hide + */ + public static InetAddress unparcelInetAddress(Parcel in) { + byte[] addressArray = in.createByteArray(); + if (addressArray == null) { + return null; + } + try { + return InetAddress.getByAddress(addressArray); + } catch (UnknownHostException e) { + return null; + } + } + + private InetAddressUtils() {} +} diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java index 141d33bc4145..1f3369376b10 100644 --- a/core/java/android/os/AsyncTask.java +++ b/core/java/android/os/AsyncTask.java @@ -19,6 +19,7 @@ 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; @@ -70,7 +71,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 +159,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> @@ -388,6 +398,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 +418,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 +430,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 +451,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 +486,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 +525,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/OWNERS b/core/java/android/os/OWNERS new file mode 100644 index 000000000000..b568f157c01d --- /dev/null +++ b/core/java/android/os/OWNERS @@ -0,0 +1,2 @@ +# Zygote +per-file ZygoteProcess.java = chriswailes@google.com, ngeoffray@google.com, sehr@google.com, narayan@google.com, maco@google.com 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/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/service/dreams/OWNERS b/core/java/android/service/dreams/OWNERS index 3c9bbf8797ea..426f002ad236 100644 --- a/core/java/android/service/dreams/OWNERS +++ b/core/java/android/service/dreams/OWNERS @@ -1,3 +1,3 @@ -dsandler@google.com +dsandler@android.com michaelwr@google.com roosa@google.com diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index f2259b045c0a..2ee72bffc9ec 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -20,6 +20,7 @@ import android.annotation.LayoutRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemService; +import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Resources; @@ -389,9 +390,13 @@ public abstract class LayoutInflater { } private void initPrecompiledViews() { + initPrecompiledViews( + SystemProperties.getBoolean(USE_PRECOMPILED_LAYOUT_SYSTEM_PROPERTY, false)); + } + + private void initPrecompiledViews(boolean enablePrecompiledViews) { + mUseCompiledView = enablePrecompiledViews; try { - mUseCompiledView = - SystemProperties.getBoolean(USE_PRECOMPILED_LAYOUT_SYSTEM_PROPERTY, false); if (mUseCompiledView) { mPrecompiledClassLoader = mContext.getClassLoader(); String dexFile = mContext.getCodeCacheDir() + COMPILED_VIEW_DEX_FILE_NAME; @@ -409,6 +414,17 @@ public abstract class LayoutInflater { } mUseCompiledView = false; } + if (!mUseCompiledView) { + mPrecompiledClassLoader = null; + } + } + + /** + * @hide for use by CTS tests + */ + @TestApi + public void setPrecompiledLayoutsEnabledForTesting(boolean enablePrecompiledLayouts) { + initPrecompiledViews(enablePrecompiledLayouts); } /** 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/OWNERS b/core/java/com/android/internal/os/OWNERS new file mode 100644 index 000000000000..928310549e6e --- /dev/null +++ b/core/java/com/android/internal/os/OWNERS @@ -0,0 +1 @@ +per-file ZygoteArguments.java,ZygoteConnection.java,ZygoteInit.java,Zygote.java,ZygoteServer.java = chriswailes@google.com, ngeoffray@google.com, sehr@google.com, narayan@google.com, maco@google.com diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 3859b951ed45..1048cb4e6e3a 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -120,8 +120,6 @@ public final class Zygote { * */ protected static FileDescriptor sBlastulaPoolEventFD; - private static final ZygoteHooks VM_HOOKS = new ZygoteHooks(); - /** * An extraArg passed when a zygote process is forking a child-zygote, specifying a name * in the abstract socket namespace. This socket name is what the new child zygote @@ -213,7 +211,7 @@ public final class Zygote { public static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir) { - VM_HOOKS.preFork(); + ZygoteHooks.preFork(); // Resets nice priority for zygote process. resetNicePriority(); int pid = nativeForkAndSpecialize( @@ -226,7 +224,7 @@ public final class Zygote { // Note that this event ends at the end of handleChildProc, Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork"); } - VM_HOOKS.postForkCommon(); + ZygoteHooks.postForkCommon(); return pid; } @@ -275,7 +273,7 @@ public final class Zygote { * * TODO (chriswailes): Look into moving this to immediately after the fork. */ - VM_HOOKS.postForkCommon(); + ZygoteHooks.postForkCommon(); } private static native void nativeSpecializeBlastula(int uid, int gid, int[] gids, @@ -312,7 +310,7 @@ public final class Zygote { */ public static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) { - VM_HOOKS.preFork(); + ZygoteHooks.preFork(); // Resets nice priority for zygote process. resetNicePriority(); int pid = nativeForkSystemServer( @@ -322,7 +320,7 @@ public final class Zygote { if (pid == 0) { Trace.setTracingEnabled(true, runtimeFlags); } - VM_HOOKS.postForkCommon(); + ZygoteHooks.postForkCommon(); return pid; } @@ -390,7 +388,7 @@ public final class Zygote { // Disable some VM functionality and reset some system values // before forking. - VM_HOOKS.preFork(); + ZygoteHooks.preFork(); resetNicePriority(); while (blastulaPoolCount++ < sBlastulaPoolMax) { @@ -403,7 +401,7 @@ public final class Zygote { // Re-enable runtime services for the Zygote. Blastula services // are re-enabled in specializeBlastula. - VM_HOOKS.postForkCommon(); + ZygoteHooks.postForkCommon(); Log.i("zygote", "Filled the blastula pool. New blastulas: " + numBlastulasToSpawn); } @@ -817,12 +815,12 @@ public final class Zygote { private static void callPostForkSystemServerHooks() { // SystemServer specific post fork hooks run before child post fork hooks. - VM_HOOKS.postForkSystemServer(); + ZygoteHooks.postForkSystemServer(); } private static void callPostForkChildHooks(int runtimeFlags, boolean isSystemServer, boolean isZygote, String instructionSet) { - VM_HOOKS.postForkChild(runtimeFlags, isSystemServer, isZygote, instructionSet); + ZygoteHooks.postForkChild(runtimeFlags, isSystemServer, isZygote, instructionSet); } /** diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index ab356a6f9a99..2bb075989f35 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -26,6 +26,7 @@ import static android.system.OsConstants.STDOUT_FILENO; import static com.android.internal.os.ZygoteConnectionConstants.CONNECTION_TIMEOUT_MILLIS; import static com.android.internal.os.ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS; +import android.metrics.LogMaker; import android.net.Credentials; import android.net.LocalSocket; import android.os.Process; @@ -35,6 +36,9 @@ import android.system.Os; import android.system.StructPollfd; import android.util.Log; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; + import dalvik.system.VMRuntime; import libcore.io.IoUtils; @@ -311,9 +315,43 @@ class ZygoteConnection { } } - private void handleHiddenApiAccessLogSampleRate(int percent) { + private class HiddenApiUsageLogger implements VMRuntime.HiddenApiUsageLogger { + + private final MetricsLogger mMetricsLogger = new MetricsLogger(); + + public void hiddenApiUsed(String packageName, String signature, + int accessMethod, boolean accessDenied) { + int accessMethodMetric = HiddenApiUsageLogger.ACCESS_METHOD_NONE; + switch(accessMethod) { + case HiddenApiUsageLogger.ACCESS_METHOD_NONE: + accessMethodMetric = MetricsEvent.ACCESS_METHOD_NONE; + break; + case HiddenApiUsageLogger.ACCESS_METHOD_REFLECTION: + accessMethodMetric = MetricsEvent.ACCESS_METHOD_REFLECTION; + break; + case HiddenApiUsageLogger.ACCESS_METHOD_JNI: + accessMethodMetric = MetricsEvent.ACCESS_METHOD_JNI; + break; + case HiddenApiUsageLogger.ACCESS_METHOD_LINKING: + accessMethodMetric = MetricsEvent.ACCESS_METHOD_LINKING; + break; + } + LogMaker logMaker = new LogMaker(MetricsEvent.ACTION_HIDDEN_API_ACCESSED) + .setPackageName(packageName) + .addTaggedData(MetricsEvent.FIELD_HIDDEN_API_SIGNATURE, signature) + .addTaggedData(MetricsEvent.FIELD_HIDDEN_API_ACCESS_METHOD, + accessMethodMetric); + if (accessDenied) { + logMaker.addTaggedData(MetricsEvent.FIELD_HIDDEN_API_ACCESS_DENIED, 1); + } + mMetricsLogger.write(logMaker); + } + } + + private void handleHiddenApiAccessLogSampleRate(int samplingRate) { try { - ZygoteInit.setHiddenApiAccessLogSampleRate(percent); + ZygoteInit.setHiddenApiAccessLogSampleRate(samplingRate); + ZygoteInit.setHiddenApiUsageLogger(new HiddenApiUsageLogger()); 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 e3e55ed28c6f..9f23797d6ccc 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -533,6 +533,14 @@ public class ZygoteInit { } /** + * Sets the implementation to be used for logging hidden API accesses + * @param logger the implementation of the VMRuntime.HiddenApiUsageLogger interface + */ + public static void setHiddenApiUsageLogger(VMRuntime.HiddenApiUsageLogger logger) { + VMRuntime.getRuntime().setHiddenApiUsageLogger(logger); + } + + /** * Creates a PathClassLoader for the given class path that is associated with a shared * namespace, i.e., this classloader can access platform-private native libraries. The * classloader will use java.library.path as the native library path. diff --git a/core/jni/OWNERS b/core/jni/OWNERS index a365a566f038..774c2242e144 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -3,4 +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 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_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp index e64d2afe7bf3..ee11b6162db0 100644 --- a/core/jni/android_os_VintfObject.cpp +++ b/core/jni/android_os_VintfObject.cpp @@ -96,7 +96,7 @@ static jobjectArray android_os_VintfObject_report(JNIEnv* env, jclass) return toJavaStringArray(env, cStrings); } -static jint verify(JNIEnv* env, jobjectArray packageInfo, android::vintf::CheckFlags::Type checks) { +static jint android_os_VintfObject_verify(JNIEnv* env, jclass, jobjectArray packageInfo) { std::vector<std::string> cPackageInfo; if (packageInfo) { size_t count = env->GetArrayLength(packageInfo); @@ -109,18 +109,19 @@ static jint verify(JNIEnv* env, jobjectArray packageInfo, android::vintf::CheckF } } std::string error; - int32_t status = VintfObject::CheckCompatibility(cPackageInfo, &error, checks); + int32_t status = VintfObject::CheckCompatibility(cPackageInfo, &error); if (status) LOG(WARNING) << "VintfObject.verify() returns " << status << ": " << error; return status; } -static jint android_os_VintfObject_verify(JNIEnv* env, jclass, jobjectArray packageInfo) { - return verify(env, packageInfo, ::android::vintf::CheckFlags::ENABLE_ALL_CHECKS); -} - static jint android_os_VintfObject_verifyWithoutAvb(JNIEnv* env, jclass) { - return verify(env, nullptr, ::android::vintf::CheckFlags::DISABLE_AVB_CHECK); + std::string error; + int32_t status = VintfObject::CheckCompatibility({}, &error, + ::android::vintf::CheckFlags::DISABLE_AVB_CHECK); + if (status) + LOG(WARNING) << "VintfObject.verifyWithoutAvb() returns " << status << ": " << error; + return status; } static jobjectArray android_os_VintfObject_getHalNamesAndVersions(JNIEnv* env, jclass) { 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..3012c906df7b 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -1305,15 +1305,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.bp b/core/proto/Android.bp index 80cc2d4cab94..3b891d6b4947 100644 --- a/core/proto/Android.bp +++ b/core/proto/Android.bp @@ -21,7 +21,10 @@ cc_library_static { type: "lite", }, srcs: [ + "android/bluetooth/a2dp/enums.proto", "android/bluetooth/enums.proto", "android/bluetooth/hci/enums.proto", + "android/bluetooth/hfp/enums.proto", + "android/bluetooth/smp/enums.proto", ], } diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsMessageStoreTest.java b/core/proto/android/bluetooth/a2dp/enums.proto index 44277edcdb8c..5a025bdd6c10 100644 --- a/tests/RcsTests/src/com/android/tests/ims/RcsMessageStoreTest.java +++ b/core/proto/android/bluetooth/a2dp/enums.proto @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * 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. @@ -13,20 +13,23 @@ * 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; +syntax = "proto2"; +package android.bluetooth.a2dp; -import org.junit.Test; -import org.junit.runner.RunWith; +option java_outer_classname = "BluetoothA2dpProtoEnums"; +option java_multiple_files = true; -@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); - } +// A2dp playback state enum, defined from: +// frameworks/base/core/java/android/bluetooth/BluetoothA2dp.java +enum PlaybackStateEnum { + PLAYBACK_STATE_UNKNOWN = 0; + PLAYBACK_STATE_PLAYING = 10; + PLAYBACK_STATE_NOT_PLAYING = 11; +} + +enum AudioCodingModeEnum { + AUDIO_CODING_MODE_UNKNOWN = 0; + AUDIO_CODING_MODE_HARDWARE = 1; + AUDIO_CODING_MODE_SOFTWARE = 2; } diff --git a/core/proto/android/bluetooth/enums.proto b/core/proto/android/bluetooth/enums.proto index 76c240ecff4d..5b5c9c28b4a0 100644 --- a/core/proto/android/bluetooth/enums.proto +++ b/core/proto/android/bluetooth/enums.proto @@ -56,3 +56,79 @@ enum LinkTypeEnum { LINK_TYPE_ACL = 0x01; LINK_TYPE_ESCO = 0x02; } + +enum DeviceInfoSrcEnum { + DEVICE_INFO_SRC_UNKNOWN = 0; + // Within Android Bluetooth stack + DEVICE_INFO_INTERNAL = 1; + // Outside Android Bluetooth stack + DEVICE_INFO_EXTERNAL = 2; +} + +enum DeviceTypeEnum { + DEVICE_TYPE_UNKNOWN = 0; + DEVICE_TYPE_CLASSIC = 1; + DEVICE_TYPE_LE = 2; + DEVICE_TYPE_DUAL = 3; +} + +// Defined in frameworks/base/core/java/android/bluetooth/BluetoothDevice.java +enum TransportTypeEnum { + TRANSPORT_TYPE_AUTO = 0; + TRANSPORT_TYPE_BREDR = 1; + TRANSPORT_TYPE_LE = 2; +} + +// Bond state enum +// Defined in frameworks/base/core/java/android/bluetooth/BluetoothDevice.java +enum BondStateEnum { + BOND_STATE_UNKNOWN = 0; + BOND_STATE_NONE = 10; + BOND_STATE_BONDING = 11; + BOND_STATE_BONDED = 12; +} + +// Sub states within the bonding general state +enum BondSubStateEnum { + BOND_SUB_STATE_UNKNOWN = 0; + BOND_SUB_STATE_LOCAL_OOB_DATA_PROVIDED = 1; + BOND_SUB_STATE_LOCAL_PIN_REQUESTED = 2; + BOND_SUB_STATE_LOCAL_PIN_REPLIED = 3; + BOND_SUB_STATE_LOCAL_SSP_REQUESTED = 4; + BOND_SUB_STATE_LOCAL_SSP_REPLIED = 5; +} + +enum UnbondReasonEnum { + UNBOND_REASON_UNKNOWN = 0; + UNBOND_REASON_AUTH_FAILED = 1; + UNBOND_REASON_AUTH_REJECTED = 2; + UNBOND_REASON_AUTH_CANCELED = 3; + UNBOND_REASON_REMOTE_DEVICE_DOWN = 4; + UNBOND_REASON_DISCOVERY_IN_PROGRESS = 5; + UNBOND_REASON_AUTH_TIMEOUT = 6; + UNBOND_REASON_REPEATED_ATTEMPTS = 7; + 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; +} diff --git a/core/proto/android/bluetooth/hci/enums.proto b/core/proto/android/bluetooth/hci/enums.proto index e1d96bbce68a..ef894e548351 100644 --- a/core/proto/android/bluetooth/hci/enums.proto +++ b/core/proto/android/bluetooth/hci/enums.proto @@ -351,7 +351,7 @@ enum EventEnum { EVT_COMMAND_COMPLETE = 0x0E; EVT_COMMAND_STATUS = 0x0F; EVT_HARDWARE_ERROR = 0x10; - EVT_FLUSH_OCCURED = 0x11; + EVT_FLUSH_OCCURRED = 0x11; EVT_ROLE_CHANGE = 0x12; EVT_NUM_COMPL_DATA_PKTS = 0x13; EVT_MODE_CHANGE = 0x14; @@ -517,3 +517,43 @@ enum StatusEnum { STATUS_CLB_DATA_TOO_BIG = 0x43; STATUS_OPERATION_CANCELED_BY_HOST = 0x44; // Not currently used in system/bt } + +enum BqrIdEnum { + BQR_ID_UNKNOWN = 0x00; + BQR_ID_MONITOR_MODE = 0x01; + BQR_ID_APPROACH_LSTO = 0x02; + BQR_ID_A2DP_AUDIO_CHOPPY = 0x03; + BQR_ID_SCO_VOICE_CHOPPY = 0x04; +} + +enum BqrPacketTypeEnum { + BQR_PACKET_TYPE_UNKNOWN = 0x00; + BQR_PACKET_TYPE_ID = 0x01; + BQR_PACKET_TYPE_NULL = 0x02; + BQR_PACKET_TYPE_POLL = 0x03; + BQR_PACKET_TYPE_FHS = 0x04; + BQR_PACKET_TYPE_HV1 = 0x05; + BQR_PACKET_TYPE_HV2 = 0x06; + BQR_PACKET_TYPE_HV3 = 0x07; + BQR_PACKET_TYPE_DV = 0x08; + BQR_PACKET_TYPE_EV3 = 0x09; + BQR_PACKET_TYPE_EV4 = 0x0A; + BQR_PACKET_TYPE_EV5 = 0x0B; + BQR_PACKET_TYPE_2EV3 = 0x0C; + BQR_PACKET_TYPE_2EV5 = 0x0D; + BQR_PACKET_TYPE_3EV3 = 0x0E; + BQR_PACKET_TYPE_3EV5 = 0x0F; + BQR_PACKET_TYPE_DM1 = 0x10; + BQR_PACKET_TYPE_DH1 = 0x11; + BQR_PACKET_TYPE_DM3 = 0x12; + BQR_PACKET_TYPE_DH3 = 0x13; + BQR_PACKET_TYPE_DM5 = 0x14; + BQR_PACKET_TYPE_DH5 = 0x15; + BQR_PACKET_TYPE_AUX1 = 0x16; + BQR_PACKET_TYPE_2DH1 = 0x17; + BQR_PACKET_TYPE_2DH3 = 0x18; + BQR_PACKET_TYPE_2DH5 = 0x19; + BQR_PACKET_TYPE_3DH1 = 0x1A; + BQR_PACKET_TYPE_3DH3 = 0x1B; + BQR_PACKET_TYPE_3DH5 = 0x1C; +} diff --git a/core/proto/android/bluetooth/smp/enums.proto b/core/proto/android/bluetooth/smp/enums.proto new file mode 100644 index 000000000000..c6747b78dc29 --- /dev/null +++ b/core/proto/android/bluetooth/smp/enums.proto @@ -0,0 +1,58 @@ +/* + * 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. + */ + +syntax = "proto2"; +package android.bluetooth.smp; + +option java_outer_classname = "BluetoothSmpProtoEnums"; +option java_multiple_files = true; + +// SMP Pairing command codes +enum CommandEnum { + CMD_UNKNOWN = 0x00; + CMD_PAIRING_REQUEST = 0x01; + CMD_PAIRING_RESPONSE = 0x02; + CMD_PAIRING_CONFIRM = 0x03; + CMD_PAIRING_RANDOM = 0x04; + CMD_PAIRING_FAILED = 0x05; + CMD_ENCRYPTION_INFON = 0x06; + CMD_MASTER_IDENTIFICATION = 0x07; + CMD_IDENTITY_INFO = 0x08; + CMD_IDENTITY_ADDR_INFO = 0x09; + CMD_SIGNING_INFO = 0x0A; + CMD_SECURITY_REQUEST = 0x0B; + CMD_PAIRING_PUBLIC_KEY = 0x0C; + CMD_PAIRING_DHKEY_CHECK = 0x0D; + CMD_PAIRING_KEYPRESS_INFO = 0x0E; +} + +enum PairingFailReasonEnum { + PAIRING_FAIL_REASON_RESERVED = 0x00; + PAIRING_FAIL_REASON_PASSKEY_ENTRY = 0x01; + PAIRING_FAIL_REASON_OOB = 0x02; + PAIRING_FAIL_REASON_AUTH_REQ = 0x03; + PAIRING_FAIL_REASON_CONFIRM_VALUE = 0x04; + PAIRING_FAIL_REASON_PAIR_NOT_SUPPORT = 0x05; + PAIRING_FAIL_REASON_ENC_KEY_SIZE = 0x06; + PAIRING_FAIL_REASON_INVALID_CMD = 0x07; + PAIRING_FAIL_REASON_UNSPECIFIED = 0x08; + PAIRING_FAIL_REASON_REPEATED_ATTEMPTS = 0x09; + PAIRING_FAIL_REASON_INVALID_PARAMETERS = 0x0A; + PAIRING_FAIL_REASON_DHKEY_CHK = 0x0B; + PAIRING_FAIL_REASON_NUMERIC_COMPARISON = 0x0C; + PAIRING_FAIL_REASON_CLASSIC_PAIRING_IN_PROGR = 0x0D; + PAIRING_FAIL_REASON_XTRANS_DERIVE_NOT_ALLOW = 0x0E; +}
\ 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/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..9a148e4e7b79 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"/> 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/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/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..4ac652acb18f --- /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: "platform", + 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..e15dca046006 100644 --- a/packages/CaptivePortalLogin/AndroidManifest.xml +++ b/packages/CaptivePortalLogin/AndroidManifest.xml @@ -21,7 +21,8 @@ <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.ACCESS_WIFI_STATE" /> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.NETWORK_SETTINGS" /> <uses-permission android:name="android.permission.NETWORK_BYPASS_PRIVATE_DNS" /> 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 d03e5e3102ed..9b70ff368afb 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; @@ -59,7 +60,6 @@ import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; -import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import java.io.IOException; @@ -96,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; @@ -106,11 +107,12 @@ public class CaptivePortalLoginActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + 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); - mCaptivePortal = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL); mUserAgent = getIntent().getStringExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT); mUrl = getUrl(); @@ -151,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. @@ -190,9 +191,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()) { @@ -333,7 +337,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() { @@ -586,19 +594,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); + } } } @@ -619,11 +626,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) { @@ -636,7 +662,7 @@ public class CaptivePortalLoginActivity extends Activity { } private void logMetricsEvent(int event) { - MetricsLogger.action(this, event, getPackageName()); + mCaptivePortal.logEvent(event, getPackageName()); } private static final SparseArray<String> SSL_ERRORS = new SparseArray<>(); diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp index 9b0d896e483e..d6565936c860 100644 --- a/packages/NetworkStack/Android.bp +++ b/packages/NetworkStack/Android.bp @@ -18,6 +18,7 @@ // system server on devices that run the stack there java_library { name: "NetworkStackLib", + sdk_version: "system_current", installable: true, srcs: [ "src/**/*.java", @@ -25,18 +26,20 @@ java_library { ":services-networkstack-shared-srcs", ], static_libs: [ - "services-netlink-lib", + "netd_aidl_interface-java", + "networkstack-aidl-interfaces-java", ] } // Updatable network stack packaged as an application android_app { name: "NetworkStack", - platform_apis: true, + sdk_version: "system_current", certificate: "platform", privileged: true, static_libs: [ "NetworkStackLib" ], 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..ac55bfa1aed7 100644 --- a/packages/NetworkStack/AndroidManifest.xml +++ b/packages/NetworkStack/AndroidManifest.xml @@ -25,6 +25,8 @@ <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" /> + <!-- Signature permission defined in NetworkStackStub --> + <uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK" /> <!-- 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" /> 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/src/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java index 08452bbbe433..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,7 +39,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.net.LinkAddress; import android.net.LinkProperties; -import android.net.NetworkUtils; +import android.net.TcpKeepalivePacketDataParcelable; import android.net.apf.ApfGenerator.IllegalInstructionException; import android.net.apf.ApfGenerator.Register; import android.net.ip.IpClient.IpClientCallbacksWrapper; @@ -47,6 +48,8 @@ import android.net.metrics.ApfStats; import android.net.metrics.IpConnectivityLog; import android.net.metrics.RaEvent; import android.net.util.InterfaceParams; +import android.net.util.NetworkStackUtils; +import android.net.util.SocketUtils; import android.os.PowerManager; import android.os.SystemClock; import android.system.ErrnoException; @@ -54,14 +57,13 @@ 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; import com.android.internal.util.HexDump; import com.android.internal.util.IndentingPrintWriter; -import libcore.io.IoBridge; - import java.io.FileDescriptor; import java.io.IOException; import java.net.Inet4Address; @@ -150,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. @@ -200,10 +204,8 @@ public class ApfFilter { public void halt() { mStopped = true; - try { - // Interrupts the read() call the thread is blocked in. - IoBridge.closeAndSignalBlockedThreads(mSocket); - } catch (IOException ignored) {} + // Interrupts the read() call the thread is blocked in. + NetworkStackUtils.closeSocketQuietly(mSocket); } @Override @@ -288,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 @@ -308,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; @@ -470,8 +475,8 @@ public class ApfFilter { socket = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IPV6); SocketAddress addr = makePacketSocketAddress( (short) ETH_P_IPV6, mInterfaceParams.index); - Os.bind(socket, addr); - NetworkUtils.attachRaFilter(socket, mApfCapabilities.apfPacketFormat); + SocketUtils.bindSocket(socket, addr); + SocketUtils.attachRaFilter(socket, mApfCapabilities.apfPacketFormat); } catch(SocketException|ErrnoException e) { Log.e(TAG, "Error starting filter", e); return; @@ -791,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; } @@ -850,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 @@ -983,6 +1124,8 @@ public class ApfFilter { // drop // if it's IPv4 broadcast: // drop + // if keepalive ack + // drop // pass if (mMulticastFilter) { @@ -1026,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); @@ -1033,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 @@ -1040,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 @@ -1060,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); @@ -1115,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); + } } /** @@ -1492,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. @@ -1544,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/DhcpClient.java b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java index 12eecc070a06..b0e8da9a7fb7 100644 --- a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java @@ -28,6 +28,7 @@ import static android.net.dhcp.DhcpPacket.DHCP_SUBNET_MASK; import static android.net.dhcp.DhcpPacket.DHCP_VENDOR_INFO; import static android.net.dhcp.DhcpPacket.INADDR_ANY; import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST; +import static android.net.util.NetworkStackUtils.closeSocketQuietly; import static android.net.util.SocketUtils.makePacketSocketAddress; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_PACKET; @@ -44,7 +45,6 @@ import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY; import android.content.Context; import android.net.DhcpResults; -import android.net.NetworkUtils; import android.net.TrafficStats; import android.net.ip.IpClient; import android.net.metrics.DhcpClientEvent; @@ -66,8 +66,6 @@ import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.internal.util.WakeupMessage; -import libcore.io.IoBridge; - import java.io.FileDescriptor; import java.io.IOException; import java.net.Inet4Address; @@ -108,6 +106,12 @@ public class DhcpClient extends StateMachine { private static final boolean MSG_DBG = false; private static final boolean PACKET_DBG = false; + // Metrics events: must be kept in sync with server-side aggregation code. + /** Represents transitions from DhcpInitState to DhcpBoundState */ + private static final String EVENT_INITIAL_BOUND = "InitialBoundState"; + /** Represents transitions from and to DhcpBoundState via DhcpRenewingState */ + private static final String EVENT_RENEWING_BOUND = "RenewingBoundState"; + // Timers and timeouts. private static final int SECONDS = 1000; private static final int FIRST_TIMEOUT_MS = 2 * SECONDS; @@ -313,8 +317,8 @@ public class DhcpClient extends StateMachine { try { mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP); SocketAddress addr = makePacketSocketAddress((short) ETH_P_IP, mIface.index); - Os.bind(mPacketSock, addr); - NetworkUtils.attachDhcpFilter(mPacketSock); + SocketUtils.bindSocket(mPacketSock, addr); + SocketUtils.attachDhcpFilter(mPacketSock); } catch(SocketException|ErrnoException e) { Log.e(TAG, "Error creating packet socket", e); return false; @@ -350,15 +354,9 @@ public class DhcpClient extends StateMachine { } } - private static void closeQuietly(FileDescriptor fd) { - try { - IoBridge.closeAndSignalBlockedThreads(fd); - } catch (IOException ignored) {} - } - private void closeSockets() { - closeQuietly(mUdpSock); - closeQuietly(mPacketSock); + closeSocketQuietly(mUdpSock); + closeSocketQuietly(mPacketSock); } class ReceiveThread extends Thread { @@ -414,7 +412,8 @@ public class DhcpClient extends StateMachine { try { if (encap == DhcpPacket.ENCAP_L2) { if (DBG) Log.d(TAG, "Broadcasting " + description); - Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr); + SocketUtils.sendTo( + mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr); } else if (encap == DhcpPacket.ENCAP_BOOTP && to.equals(INADDR_BROADCAST)) { if (DBG) Log.d(TAG, "Broadcasting " + description); // We only send L3-encapped broadcasts in DhcpRebindingState, @@ -928,9 +927,9 @@ public class DhcpClient extends StateMachine { private void logTimeToBoundState() { long now = SystemClock.elapsedRealtime(); if (mLastBoundExitTime > mLastInitEnterTime) { - logState(DhcpClientEvent.RENEWING_BOUND, (int)(now - mLastBoundExitTime)); + logState(EVENT_RENEWING_BOUND, (int) (now - mLastBoundExitTime)); } else { - logState(DhcpClientEvent.INITIAL_BOUND, (int)(now - mLastInitEnterTime)); + logState(EVENT_INITIAL_BOUND, (int) (now - mLastInitEnterTime)); } } } @@ -1021,7 +1020,7 @@ public class DhcpClient extends StateMachine { // We need to broadcast and possibly reconnect the socket to a // completely different server. - closeQuietly(mUdpSock); + closeSocketQuietly(mUdpSock); if (!initUdpSocket()) { Log.e(TAG, "Failed to recreate UDP socket"); transitionTo(mDhcpInitState); diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpPacketListener.java b/packages/NetworkStack/src/android/net/dhcp/DhcpPacketListener.java index dce8b619494e..96d1a287ef09 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.util.FdEventsReader; +import android.net.shared.FdEventsReader; import android.os.Handler; import android.system.Os; @@ -64,7 +64,7 @@ abstract class DhcpPacketListener extends FdEventsReader<DhcpPacketListener.Payl @Override protected int readPacket(@NonNull FileDescriptor fd, @NonNull Payload packetBuffer) throws Exception { - final InetSocketAddress addr = new InetSocketAddress(); + final InetSocketAddress addr = new InetSocketAddress(0); final int read = Os.recvfrom( fd, packetBuffer.mBytes, 0, packetBuffer.mBytes.length, 0 /* flags */, addr); diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java index beabd3eb3152..cd993e93998b 100644 --- a/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java @@ -28,6 +28,7 @@ import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.IPPROTO_UDP; import static android.system.OsConstants.SOCK_DGRAM; +import static android.system.OsConstants.SOCK_NONBLOCK; import static android.system.OsConstants.SOL_SOCKET; import static android.system.OsConstants.SO_BROADCAST; import static android.system.OsConstants.SO_REUSEADDR; @@ -43,7 +44,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.net.INetworkStackStatusCallback; import android.net.MacAddress; -import android.net.NetworkUtils; import android.net.TrafficStats; import android.net.util.SharedLog; import android.net.util.SocketUtils; @@ -207,7 +207,7 @@ public class DhcpServer extends IDhcpServer.Stub { @Override public void addArpEntry(@NonNull Inet4Address ipv4Addr, @NonNull MacAddress ethAddr, @NonNull String ifname, @NonNull FileDescriptor fd) throws IOException { - NetworkUtils.addArpEntry(ipv4Addr, ethAddr, ifname, fd); + SocketUtils.addArpEntry(ipv4Addr, ethAddr, ifname, fd); } @Override @@ -630,7 +630,7 @@ public class DhcpServer extends IDhcpServer.Stub { // TODO: have and use an API to set a socket tag without going through the thread tag final int oldTag = TrafficStats.getAndSetThreadStatsTag(TAG_SYSTEM_DHCP_SERVER); try { - mSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + mSocket = Os.socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP); SocketUtils.bindSocketToInterface(mSocket, mIfName); Os.setsockoptInt(mSocket, SOL_SOCKET, SO_REUSEADDR, 1); Os.setsockoptInt(mSocket, SOL_SOCKET, SO_BROADCAST, 1); diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java index 31ce95b11ba9..3cd2aa40dfb2 100644 --- a/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java @@ -209,7 +209,7 @@ public class DhcpServingParams { * but it must always be set explicitly before building the {@link DhcpServingParams}. */ public Builder setDefaultRouters(@NonNull Inet4Address... defaultRouters) { - return setDefaultRouters(new ArraySet<>(Arrays.asList(defaultRouters))); + return setDefaultRouters(makeArraySet(defaultRouters)); } /** @@ -239,7 +239,7 @@ public class DhcpServingParams { * building the {@link DhcpServingParams}. */ public Builder setDnsServers(@NonNull Inet4Address... dnsServers) { - return setDnsServers(new ArraySet<>(Arrays.asList(dnsServers))); + return setDnsServers(makeArraySet(dnsServers)); } /** @@ -269,7 +269,7 @@ public class DhcpServingParams { * and do not need to be set here. */ public Builder setExcludedAddrs(@NonNull Inet4Address... excludedAddrs) { - return setExcludedAddrs(new ArraySet<>(Arrays.asList(excludedAddrs))); + return setExcludedAddrs(makeArraySet(excludedAddrs)); } /** @@ -368,4 +368,10 @@ public class DhcpServingParams { static IpPrefix makeIpPrefix(@NonNull LinkAddress addr) { return new IpPrefix(addr.getAddress(), addr.getPrefixLength()); } + + private static <T> ArraySet<T> makeArraySet(T[] elements) { + final ArraySet<T> set = new ArraySet<>(elements.length); + set.addAll(Arrays.asList(elements)); + return set; + } } diff --git a/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java b/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java index 385dd52e4576..649257ae3b5f 100644 --- a/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java +++ b/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java @@ -20,12 +20,13 @@ import static android.net.util.SocketUtils.makePacketSocketAddress; import static android.system.OsConstants.AF_PACKET; import static android.system.OsConstants.ARPHRD_ETHER; import static android.system.OsConstants.ETH_P_ALL; +import static android.system.OsConstants.SOCK_NONBLOCK; import static android.system.OsConstants.SOCK_RAW; -import android.net.NetworkUtils; import android.net.util.ConnectivityPacketSummary; import android.net.util.InterfaceParams; import android.net.util.PacketReader; +import android.net.util.SocketUtils; import android.os.Handler; import android.system.ErrnoException; import android.system.Os; @@ -33,7 +34,7 @@ import android.text.TextUtils; import android.util.LocalLog; import android.util.Log; -import libcore.util.HexEncoding; +import com.android.internal.util.HexDump; import java.io.FileDescriptor; import java.io.IOException; @@ -101,9 +102,10 @@ public class ConnectivityPacketTracker { protected FileDescriptor createFd() { FileDescriptor s = null; try { - s = Os.socket(AF_PACKET, SOCK_RAW, 0); - NetworkUtils.attachControlPacketFilter(s, ARPHRD_ETHER); - Os.bind(s, makePacketSocketAddress((short) ETH_P_ALL, mInterface.index)); + s = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0); + SocketUtils.attachControlPacketFilter(s, ARPHRD_ETHER); + SocketUtils.bindSocket( + s, makePacketSocketAddress((short) ETH_P_ALL, mInterface.index)); } catch (ErrnoException | IOException e) { logError("Failed to create packet tracking socket: ", e); closeFd(s); @@ -119,8 +121,7 @@ public class ConnectivityPacketTracker { if (summary == null) return; if (DBG) Log.d(mTag, summary); - addLogEntry(summary + - "\n[" + new String(HexEncoding.encode(recvbuf, 0, length)) + "]"); + addLogEntry(summary + "\n[" + HexDump.toHexString(recvbuf, 0, length) + "]"); } @Override diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java index 4315d34ba447..9e5991298834 100644 --- a/packages/NetworkStack/src/android/net/ip/IpClient.java +++ b/packages/NetworkStack/src/android/net/ip/IpClient.java @@ -16,12 +16,14 @@ package android.net.ip; +import static android.net.RouteInfo.RTN_UNICAST; import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable; import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable; 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; @@ -33,10 +35,10 @@ 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; -import android.net.ip.IIpClientCallbacks; import android.net.metrics.IpConnectivityLog; import android.net.metrics.IpManagerEvent; import android.net.shared.InitialConfiguration; @@ -52,7 +54,6 @@ import android.util.LocalLog; import android.util.Log; import android.util.SparseArray; -import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IState; import com.android.internal.util.IndentingPrintWriter; @@ -293,6 +294,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 @@ -523,6 +526,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() { @@ -645,6 +658,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) { @@ -992,7 +1021,7 @@ public class IpClient extends StateMachine { // specified in the InitialConfiguration have been observed with Netlink. if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) { for (IpPrefix prefix : config.directlyConnectedRoutes) { - newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName)); + newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName, RTN_UNICAST)); } } addAllReachableDnsServers(newLp, config.dnsServers); @@ -1093,7 +1122,7 @@ public class IpClient extends StateMachine { // If we have a StaticIpConfiguration attempt to apply it and // handle the result accordingly. if (mConfiguration.mStaticIpConfig != null) { - if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) { + if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.getIpAddress())) { handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig)); } else { return false; @@ -1348,10 +1377,8 @@ public class IpClient extends StateMachine { apfConfig.apfCapabilities = mConfiguration.mApfCapabilities; apfConfig.multicastFilter = mMulticastFiltering; // Get the Configuration for ApfFilter from Context - apfConfig.ieee802_3Filter = - mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames); - apfConfig.ethTypeBlackList = - mContext.getResources().getIntArray(R.array.config_apfEthTypeBlackList); + apfConfig.ieee802_3Filter = ApfCapabilities.getApfDrop8023Frames(mContext); + apfConfig.ethTypeBlackList = ApfCapabilities.getApfEthTypeBlackList(mContext); mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback); // TODO: investigate the effects of any multicast filtering racing/interfering with the // rest of this IP configuration startup. @@ -1515,6 +1542,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/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java index 2e6ff243a628..b29d61793a61 100644 --- a/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java +++ b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java @@ -20,6 +20,10 @@ import static android.net.netlink.NetlinkConstants.RTM_DELNEIGH; import static android.net.netlink.NetlinkConstants.hexify; import static android.net.netlink.NetlinkConstants.stringForNlMsgType; import static android.net.util.SocketUtils.makeNetlinkSocketAddress; +import static android.system.OsConstants.AF_NETLINK; +import static android.system.OsConstants.NETLINK_ROUTE; +import static android.system.OsConstants.SOCK_DGRAM; +import static android.system.OsConstants.SOCK_NONBLOCK; import android.net.MacAddress; import android.net.netlink.NetlinkErrorMessage; @@ -27,8 +31,10 @@ import android.net.netlink.NetlinkMessage; import android.net.netlink.NetlinkSocket; import android.net.netlink.RtNetlinkNeighborMessage; import android.net.netlink.StructNdMsg; +import android.net.util.NetworkStackUtils; import android.net.util.PacketReader; import android.net.util.SharedLog; +import android.net.util.SocketUtils; import android.os.Handler; import android.os.SystemClock; import android.system.ErrnoException; @@ -36,8 +42,6 @@ import android.system.Os; import android.system.OsConstants; import android.util.Log; -import libcore.io.IoUtils; - import java.io.FileDescriptor; import java.net.InetAddress; import java.net.SocketAddress; @@ -77,7 +81,7 @@ public class IpNeighborMonitor extends PacketReader { 1, ip, StructNdMsg.NUD_PROBE, ifIndex, null); try { - NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_ROUTE, msg); + NetlinkSocket.sendOneShotKernelMessage(NETLINK_ROUTE, msg); } catch (ErrnoException e) { Log.e(TAG, "Error " + msgSnippet + ": " + e); return -e.errno; @@ -145,8 +149,8 @@ public class IpNeighborMonitor extends PacketReader { FileDescriptor fd = null; try { - fd = NetlinkSocket.forProto(OsConstants.NETLINK_ROUTE); - Os.bind(fd, makeNetlinkSocketAddress(0, OsConstants.RTMGRP_NEIGH)); + fd = Os.socket(AF_NETLINK, SOCK_DGRAM | SOCK_NONBLOCK, NETLINK_ROUTE); + SocketUtils.bindSocket(fd, makeNetlinkSocketAddress(0, OsConstants.RTMGRP_NEIGH)); NetlinkSocket.connectToKernel(fd); if (VDBG) { @@ -155,7 +159,7 @@ public class IpNeighborMonitor extends PacketReader { } } catch (ErrnoException|SocketException e) { logError("Failed to create rtnetlink socket", e); - IoUtils.closeQuietly(fd); + NetworkStackUtils.closeSocketQuietly(fd); return null; } diff --git a/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java index 6dcf0c0d1626..98123a5c7261 100644 --- a/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java +++ b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java @@ -16,6 +16,9 @@ package android.net.util; +import java.io.FileDescriptor; +import java.io.IOException; + /** * Collection of utilities for the network stack. */ @@ -27,4 +30,14 @@ public class NetworkStackUtils { public static <T> boolean isEmpty(T[] array) { return array == null || array.length == 0; } + + /** + * Close a socket, ignoring any exception while closing. + */ + public static void closeSocketQuietly(FileDescriptor fd) { + try { + SocketUtils.closeSocket(fd); + } catch (IOException ignored) { + } + } } diff --git a/packages/NetworkStack/src/android/net/util/PacketReader.java b/packages/NetworkStack/src/android/net/util/PacketReader.java index 4aec6b6753a6..94b1e9f2e14e 100644 --- a/packages/NetworkStack/src/android/net/util/PacketReader.java +++ b/packages/NetworkStack/src/android/net/util/PacketReader.java @@ -18,6 +18,7 @@ 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/NetworkObserverRegistry.java b/packages/NetworkStack/src/com/android/server/NetworkObserverRegistry.java index 4f55779f473b..6fb4b0d79a64 100644 --- a/packages/NetworkStack/src/com/android/server/NetworkObserverRegistry.java +++ b/packages/NetworkStack/src/com/android/server/NetworkObserverRegistry.java @@ -15,6 +15,8 @@ */ package com.android.server; +import static android.net.RouteInfo.RTN_UNICAST; + import android.annotation.NonNull; import android.net.INetd; import android.net.INetdUnsolicitedEventListener; @@ -169,7 +171,7 @@ public class NetworkObserverRegistry extends INetdUnsolicitedEventListener.Stub public void onRouteChanged(boolean updated, String route, String gateway, String ifName) { final RouteInfo processRoute = new RouteInfo(new IpPrefix(route), ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway), - ifName); + ifName, RTN_UNICAST); if (updated) { invokeForAllObservers(o -> o.onRouteUpdated(processRoute)); } else { diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java index 7405c471808a..cedcb84e9d08 100644 --- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java +++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java @@ -19,6 +19,7 @@ package com.android.server; import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR; +import static android.net.shared.NetworkParcelableUtil.fromStableParcelable; import static com.android.server.util.PermissionUtil.checkDumpPermission; import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission; @@ -34,7 +35,7 @@ import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkStackConnector; import android.net.Network; -import android.net.NetworkRequest; +import android.net.NetworkParcelable; import android.net.PrivateDnsConfigParcel; import android.net.dhcp.DhcpServer; import android.net.dhcp.DhcpServingParams; @@ -150,13 +151,12 @@ public class NetworkStackService extends Service { } @Override - public void makeNetworkMonitor(int netId, String name, INetworkMonitorCallbacks cb) + public void makeNetworkMonitor( + NetworkParcelable network, String name, INetworkMonitorCallbacks cb) throws RemoteException { - final Network network = new Network(netId, false /* privateDnsBypass */); - final NetworkRequest defaultRequest = mCm.getDefaultRequest(); - final SharedLog log = addValidationLogs(network, name); - final NetworkMonitor nm = new NetworkMonitor( - mContext, cb, network, defaultRequest, log); + final Network parsedNetwork = fromStableParcelable(network); + final SharedLog log = addValidationLogs(parsedNetwork, name); + final NetworkMonitor nm = new NetworkMonitor(mContext, cb, parsedNetwork, log); cb.onNetworkMonitorCreated(new NetworkMonitorImpl(nm)); } diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java index 96eaa505389d..0d6d080b6dc2 100644 --- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java +++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java @@ -47,7 +47,6 @@ import android.net.INetworkMonitorCallbacks; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; -import android.net.NetworkRequest; import android.net.ProxyInfo; import android.net.TrafficStats; import android.net.Uri; @@ -62,6 +61,7 @@ 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; @@ -110,6 +110,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. @@ -310,14 +312,14 @@ public class NetworkMonitor extends StateMachine { private long mLastProbeTime; public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network, - NetworkRequest defaultRequest, SharedLog validationLog) { - this(context, cb, network, defaultRequest, new IpConnectivityLog(), validationLog, + SharedLog validationLog) { + this(context, cb, network, new IpConnectivityLog(), validationLog, Dependencies.DEFAULT); } @VisibleForTesting protected NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network, - NetworkRequest defaultRequest, IpConnectivityLog logger, SharedLog validationLogs, + IpConnectivityLog logger, SharedLog validationLogs, Dependencies deps) { // Add suffix indicating which NetworkMonitor we're talking about. super(TAG + "/" + network.toString()); @@ -369,8 +371,7 @@ public class NetworkMonitor extends StateMachine { // we can ever fetch them. // TODO: Delete ASAP. mLinkProperties = new LinkProperties(); - mNetworkCapabilities = new NetworkCapabilities(); - mNetworkCapabilities.clearAll(); + mNetworkCapabilities = new NetworkCapabilities(null); } /** @@ -674,33 +675,40 @@ 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, + appExtras.putParcelable( + ConnectivityManager.EXTRA_NETWORK, new Network(mNetwork)); + appExtras.putParcelable(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, + PERMISSION_NETWORK_SETTINGS, "CaptivePortal"); } sendMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, response); } + + @Override + public void logEvent(int eventId, String packageName) + throws RemoteException { + mContext.enforceCallingPermission( + PERMISSION_NETWORK_SETTINGS, + "CaptivePortal"); + mCallback.logCaptivePortalLoginEvent(eventId, packageName); + } })); 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(appExtras); return HANDLED; default: return NOT_HANDLED; 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..4a09b3e205a6 100644 --- a/packages/NetworkStack/tests/Android.bp +++ b/packages/NetworkStack/tests/Android.bp @@ -49,6 +49,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..a4a100000d12 100644 --- a/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java +++ b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java @@ -39,13 +39,14 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.SocketKeepalive; +import android.net.TcpKeepalivePacketData; +import android.net.TcpKeepalivePacketData.TcpSocketInfo; 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 +1004,31 @@ 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 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,200 @@ 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 TcpSocketInfo v4Tsi = new TcpSocketInfo( + srcAddr, srcPort, dstAddr, dstPort, seqNum, ackNum, window, windowScale); + final TcpKeepalivePacketData ipv4TcpKeepalivePacket = + TcpKeepalivePacketData.tcpKeepalivePacket(v4Tsi); + + apfFilter.addKeepalivePacketFilter(slot1, ipv4TcpKeepalivePacket.toStableParcelable()); + 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)); + + // 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 TcpSocketInfo v6Tsi = new TcpSocketInfo( + srcAddr, srcPort, dstAddr, dstPort, seqNum, ackNum, window, windowScale); + final TcpKeepalivePacketData ipv6TcpKeepalivePacket = + TcpKeepalivePacketData.tcpKeepalivePacket(v6Tsi); + apfFilter.addKeepalivePacketFilter(slot1, ipv6TcpKeepalivePacket.toStableParcelable()); + 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, ipv4TcpKeepalivePacket.toStableParcelable()); + apfFilter.addKeepalivePacketFilter(slot2, ipv6TcpKeepalivePacket.toStableParcelable()); + 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)); + // 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)); + // Verify IPv4 packet from another address is passed + assertPass(program, + ipv4Packet(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, anotherSrcPort, + anotherDstPort, anotherSeqNum, anotherAckNum)); + + // 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 (SocketKeepalive.InvalidPacketException 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)); + 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)); + assertPass(program, + ipv6Packet(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, srcPort, + dstPort, anotherSeqNum, anotherAckNum)); + + apfFilter.shutdown(); + } + + private static byte[] ipv4Packet(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_IP); + packet.put(IPV4_VERSION_IHL_OFFSET, (byte) 0x45); + put(packet, IPV4_SRC_ADDR_OFFSET, sip); + put(packet, IPV4_DEST_ADDR_OFFSET, tip); + 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); + return packet.array(); + } + + private static byte[] ipv4Packet(byte[] sip, byte[] tip, 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(ipv4Packet(sip, tip, sport, dport, seq, ack)); + packet.putShort(IPV4_TOTAL_LENGTH_OFFSET, (short) totalLength); + // TCP header length 5, reserved 3 bits, NS=0 + packet.put(IPV4_TCP_HEADER_LENGTH_OFFSET, (byte) 0x50); + 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/util/PacketReaderTest.java b/packages/NetworkStack/tests/src/android/net/util/PacketReaderTest.java index dced7435ee74..6e11c409e104 100644 --- a/packages/NetworkStack/tests/src/android/net/util/PacketReaderTest.java +++ b/packages/NetworkStack/tests/src/android/net/util/PacketReaderTest.java @@ -17,7 +17,13 @@ package android.net.util; import static android.net.util.PacketReader.DEFAULT_RECV_BUF_SIZE; -import static android.system.OsConstants.*; +import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.SOCK_DGRAM; +import static android.system.OsConstants.SOCK_NONBLOCK; +import static android.system.OsConstants.SOL_SOCKET; +import static android.system.OsConstants.SO_SNDTIMEO; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -31,10 +37,12 @@ import android.system.ErrnoException; import android.system.Os; import android.system.StructTimeval; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.UncheckedIOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.Inet6Address; @@ -45,13 +53,6 @@ import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import org.junit.runner.RunWith; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import libcore.io.IoBridge; - /** * Tests for PacketReader. * @@ -80,7 +81,7 @@ public class PacketReaderTest { protected FileDescriptor createFd() { FileDescriptor s = null; try { - s = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + s = Os.socket(AF_INET6, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP); Os.bind(s, LOOPBACK6, 0); mLocalSockName = (InetSocketAddress) Os.getsockname(s); Os.setsockoptTimeval(s, SOL_SOCKET, SO_SNDTIMEO, TIMEO); 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 d31fa7732e66..d11bb64213c3 100644 --- a/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java +++ b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java @@ -21,7 +21,6 @@ import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL; 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; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; @@ -51,7 +50,6 @@ import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; -import android.net.NetworkRequest; import android.net.captiveportal.CaptivePortalProbeResult; import android.net.metrics.IpConnectivityLog; import android.net.util.SharedLog; @@ -103,7 +101,6 @@ public class NetworkMonitorTest { private @Mock NetworkMonitor.Dependencies mDependencies; private @Mock INetworkMonitorCallbacks mCallbacks; private @Spy Network mNetwork = new Network(TEST_NETID); - private NetworkRequest mRequest; private static final int TEST_NETID = 4242; @@ -178,10 +175,6 @@ public class NetworkMonitorTest { InetAddresses.parseNumericAddress("192.168.0.0") }).when(mNetwork).getAllByName(any()); - mRequest = new NetworkRequest.Builder() - .addCapability(NET_CAPABILITY_INTERNET) - .addCapability(NET_CAPABILITY_NOT_RESTRICTED) - .build(); // Default values. Individual tests can override these. when(mCm.getLinkProperties(any())).thenReturn(TEST_LINKPROPERTIES); when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES); @@ -195,9 +188,9 @@ public class NetworkMonitorTest { private class WrappedNetworkMonitor extends NetworkMonitor { private long mProbeTime = 0; - WrappedNetworkMonitor(Context context, Network network, NetworkRequest defaultRequest, - IpConnectivityLog logger, Dependencies deps) { - super(context, mCallbacks, network, defaultRequest, logger, + WrappedNetworkMonitor(Context context, Network network, IpConnectivityLog logger, + Dependencies deps) { + super(context, mCallbacks, network, logger, new SharedLog("test_nm"), deps); } @@ -213,7 +206,7 @@ public class NetworkMonitorTest { private WrappedNetworkMonitor makeMeteredWrappedNetworkMonitor() { final WrappedNetworkMonitor nm = new WrappedNetworkMonitor( - mContext, mNetwork, mRequest, mLogger, mDependencies); + mContext, mNetwork, mLogger, mDependencies); when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES); nm.start(); waitForIdle(nm.getHandler()); @@ -222,7 +215,7 @@ public class NetworkMonitorTest { private WrappedNetworkMonitor makeNotMeteredWrappedNetworkMonitor() { final WrappedNetworkMonitor nm = new WrappedNetworkMonitor( - mContext, mNetwork, mRequest, mLogger, mDependencies); + mContext, mNetwork, mLogger, mDependencies); when(mCm.getNetworkCapabilities(any())).thenReturn(NOT_METERED_CAPABILITIES); nm.start(); waitForIdle(nm.getHandler()); @@ -231,7 +224,7 @@ public class NetworkMonitorTest { private NetworkMonitor makeMonitor() { final NetworkMonitor nm = new NetworkMonitor( - mContext, mCallbacks, mNetwork, mRequest, mLogger, mValidationLogger, + mContext, mCallbacks, mNetwork, mLogger, mValidationLogger, mDependencies); nm.start(); waitForIdle(nm.getHandler()); diff --git a/packages/NetworkStackPermissionStub/Android.bp b/packages/NetworkStackPermissionStub/Android.bp new file mode 100644 index 000000000000..94870c919dfa --- /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: "platform", + privileged: true, + manifest: "AndroidManifest.xml", +} diff --git a/packages/NetworkStackPermissionStub/AndroidManifest.xml b/packages/NetworkStackPermissionStub/AndroidManifest.xml new file mode 100644 index 000000000000..2ccf5ff1a01a --- /dev/null +++ b/packages/NetworkStackPermissionStub/AndroidManifest.xml @@ -0,0 +1,36 @@ +<?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.mainline.networkstack.permissionstub"> + <!-- + 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/OWNERS b/packages/SettingsLib/OWNERS index d188c65be883..d87908738b56 100644 --- a/packages/SettingsLib/OWNERS +++ b/packages/SettingsLib/OWNERS @@ -4,7 +4,7 @@ asargent@google.com dehboxturtle@google.com dhnishi@google.com dling@google.com -dsandler@google.com +dsandler@android.com evanlaird@google.com jackqdyulei@google.com jmonk@google.com 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/SystemUI/OWNERS b/packages/SystemUI/OWNERS index 2a3cf3ebeedf..3f6ebf0fe09d 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -1,6 +1,6 @@ set noparent -dsandler@google.com +dsandler@android.com adamcohen@google.com asc@google.com 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/packages/VpnDialogs/AndroidManifest.xml b/packages/VpnDialogs/AndroidManifest.xml index 8172e717850b..1d0b9b6d83db 100644 --- a/packages/VpnDialogs/AndroidManifest.xml +++ b/packages/VpnDialogs/AndroidManifest.xml @@ -20,6 +20,7 @@ package="com.android.vpndialogs"> <uses-permission android:name="android.permission.CONTROL_VPN" /> + <uses-permission android:name="android.permission.CONTROL_ALWAYS_ON_VPN" /> <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" /> <application android:label="VpnDialogs" 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/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 1519c1785070..f2d4ae24c047 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -40,6 +40,7 @@ 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; import static android.system.OsConstants.IPPROTO_TCP; import static android.system.OsConstants.IPPROTO_UDP; @@ -59,7 +60,6 @@ import android.content.res.Configuration; import android.database.ContentObserver; import android.net.ConnectionInfo; import android.net.ConnectivityManager; -import android.net.ConnectivityManager.PacketKeepalive; import android.net.IConnectivityManager; import android.net.IIpConnectivityMetrics; import android.net.INetd; @@ -92,16 +92,17 @@ 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; import android.net.metrics.IpConnectivityLog; import android.net.metrics.NetworkEvent; import android.net.netlink.InetDiagMessage; -import android.net.shared.NetdService; import android.net.shared.NetworkMonitorUtils; import android.net.shared.PrivateDnsConfig; import android.net.util.MultinetworkPolicyTracker; +import android.net.util.NetdService; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -144,6 +145,7 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; +import com.android.internal.logging.MetricsLogger; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnInfo; @@ -1830,14 +1832,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() { @@ -1857,19 +1865,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() { @@ -1879,9 +1890,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() { @@ -1892,20 +1903,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() { @@ -2485,8 +2492,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; } } @@ -2683,6 +2690,11 @@ 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) { @@ -2846,8 +2858,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); @@ -3224,6 +3235,25 @@ 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 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(Bundle appExtras) { + mContext.checkCallingOrSelfPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); + + final Intent appIntent = new Intent(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN); + appIntent.putExtras(appExtras); + appIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); + + Binder.withCleanCallingIdentity(() -> + mContext.startActivityAsUser(appIntent, UserHandle.CURRENT)); + } + public boolean avoidBadWifi() { return mMultinetworkPolicyTracker.getAvoidBadWifi(); } @@ -3439,12 +3469,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; @@ -3636,6 +3666,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 @@ -5012,8 +5056,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (DBG) log("registerNetworkAgent " + nai); final long token = Binder.clearCallingIdentity(); try { - mContext.getSystemService(NetworkStack.class) - .makeNetworkMonitor(nai.network, name, new NetworkMonitorCallbacks(nai)); + mContext.getSystemService(NetworkStack.class).makeNetworkMonitor( + toStableParcelable(nai.network), name, new NetworkMonitorCallbacks(nai)); } finally { Binder.restoreCallingIdentity(token); } @@ -6294,7 +6338,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 @@ -6309,9 +6353,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/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index 371276fbd2ae..126bf6556538 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -44,7 +44,7 @@ import android.net.LinkAddress; import android.net.Network; import android.net.NetworkUtils; import android.net.TrafficStats; -import android.net.shared.NetdService; +import android.net.util.NetdService; import android.os.Binder; import android.os.IBinder; import android.os.ParcelFileDescriptor; diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 2f66fd7dc7c7..da4df22d7b02 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -47,8 +47,10 @@ import android.app.ActivityManager; import android.content.Context; import android.net.ConnectivityManager; import android.net.INetd; +import android.net.INetdUnsolicitedEventListener; import android.net.INetworkManagementEventObserver; import android.net.ITetheringStatsProvider; +import android.net.InetAddresses; import android.net.InterfaceConfiguration; import android.net.InterfaceConfigurationParcel; import android.net.IpPrefix; @@ -60,8 +62,7 @@ import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.TetherStatsParcel; import android.net.UidRange; -import android.net.shared.NetdService; -import android.net.shared.NetworkObserverRegistry; +import android.net.util.NetdService; import android.os.BatteryStats; import android.os.Binder; import android.os.Handler; @@ -205,13 +206,16 @@ public class NetworkManagementService extends INetworkManagementService.Stub private INetd mNetdService; - private NMSNetworkObserverRegistry mNetworkObserverRegistry; + private final NetdUnsolicitedEventListener mNetdUnsolicitedEventListener; private IBatteryStats mBatteryStats; private final Thread mThread; private CountDownLatch mConnectedSignal = new CountDownLatch(1); + private final RemoteCallbackList<INetworkManagementEventObserver> mObservers = + new RemoteCallbackList<>(); + private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory(); @GuardedBy("mTetheringStatsProviders") @@ -321,6 +325,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub mDaemonHandler = new Handler(FgThread.get().getLooper()); + mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener(); + // Add ourself to the Watchdog monitors. Watchdog.getInstance().addMonitor(this); @@ -339,7 +345,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub mFgHandler = null; mThread = null; mServices = null; - mNetworkObserverRegistry = null; + mNetdUnsolicitedEventListener = null; } static NetworkManagementService create(Context context, String socket, SystemServices services) @@ -387,12 +393,14 @@ public class NetworkManagementService extends INetworkManagementService.Stub @Override public void registerObserver(INetworkManagementEventObserver observer) { - mNetworkObserverRegistry.registerObserver(observer); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + mObservers.register(observer); } @Override public void unregisterObserver(INetworkManagementEventObserver observer) { - mNetworkObserverRegistry.unregisterObserver(observer); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + mObservers.unregister(observer); } @FunctionalInterface @@ -400,97 +408,123 @@ public class NetworkManagementService extends INetworkManagementService.Stub public void sendCallback(INetworkManagementEventObserver o) throws RemoteException; } - private class NMSNetworkObserverRegistry extends NetworkObserverRegistry { - NMSNetworkObserverRegistry(Context context, Handler handler, INetd netd) - throws RemoteException { - super(context, handler, netd); - } - - /** - * Notify our observers of a change in the data activity state of the interface - */ - @Override - public void notifyInterfaceClassActivity(int type, boolean isActive, long tsNanos, - int uid, boolean fromRadio) { - final boolean isMobile = ConnectivityManager.isNetworkTypeMobile(type); - int powerState = isActive - ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH - : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; - - if (isMobile) { - if (!fromRadio) { - if (mMobileActivityFromRadio) { - // If this call is not coming from a report from the radio itself, but we - // have previously received reports from the radio, then we will take the - // power state to just be whatever the radio last reported. - powerState = mLastPowerStateFromRadio; - } - } else { - mMobileActivityFromRadio = true; - } - if (mLastPowerStateFromRadio != powerState) { - mLastPowerStateFromRadio = powerState; - try { - getBatteryStats().noteMobileRadioPowerState(powerState, tsNanos, uid); - } catch (RemoteException e) { - } + private void invokeForAllObservers(NetworkManagementEventCallback eventCallback) { + final int length = mObservers.beginBroadcast(); + try { + for (int i = 0; i < length; i++) { + try { + eventCallback.sendCallback(mObservers.getBroadcastItem(i)); + } catch (RemoteException | RuntimeException e) { } } + } finally { + mObservers.finishBroadcast(); + } + } - if (ConnectivityManager.isNetworkTypeWifi(type)) { - if (mLastPowerStateFromWifi != powerState) { - mLastPowerStateFromWifi = powerState; - try { - getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos, uid); - } catch (RemoteException e) { - } - } - } + /** + * Notify our observers of an interface status change + */ + private void notifyInterfaceStatusChanged(String iface, boolean up) { + invokeForAllObservers(o -> o.interfaceStatusChanged(iface, up)); + } - if (!isMobile || fromRadio || !mMobileActivityFromRadio) { - // Report the change in data activity. We don't do this if this is a change - // on the mobile network, that is not coming from the radio itself, and we - // have previously seen change reports from the radio. In that case only - // the radio is the authority for the current state. - final boolean active = isActive; - super.notifyInterfaceClassActivity(type, isActive, tsNanos, uid, fromRadio); - } + /** + * Notify our observers of an interface link state change + * (typically, an Ethernet cable has been plugged-in or unplugged). + */ + private void notifyInterfaceLinkStateChanged(String iface, boolean up) { + invokeForAllObservers(o -> o.interfaceLinkStateChanged(iface, up)); + } + + /** + * Notify our observers of an interface addition. + */ + private void notifyInterfaceAdded(String iface) { + invokeForAllObservers(o -> o.interfaceAdded(iface)); + } + + /** + * Notify our observers of an interface removal. + */ + private void notifyInterfaceRemoved(String iface) { + // netd already clears out quota and alerts for removed ifaces; update + // our sanity-checking state. + mActiveAlerts.remove(iface); + mActiveQuotas.remove(iface); + invokeForAllObservers(o -> o.interfaceRemoved(iface)); + } - boolean report = false; - synchronized (mIdleTimerLock) { - if (mActiveIdleTimers.isEmpty()) { - // If there are no idle timers, we are not monitoring activity, so we - // are always considered active. - isActive = true; + /** + * Notify our observers of a limit reached. + */ + private void notifyLimitReached(String limitName, String iface) { + invokeForAllObservers(o -> o.limitReached(limitName, iface)); + } + + /** + * Notify our observers of a change in the data activity state of the interface + */ + private void notifyInterfaceClassActivity(int type, boolean isActive, long tsNanos, + int uid, boolean fromRadio) { + final boolean isMobile = ConnectivityManager.isNetworkTypeMobile(type); + int powerState = isActive + ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH + : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; + if (isMobile) { + if (!fromRadio) { + if (mMobileActivityFromRadio) { + // If this call is not coming from a report from the radio itself, but we + // have previously received reports from the radio, then we will take the + // power state to just be whatever the radio last reported. + powerState = mLastPowerStateFromRadio; } - if (mNetworkActive != isActive) { - mNetworkActive = isActive; - report = isActive; + } else { + mMobileActivityFromRadio = true; + } + if (mLastPowerStateFromRadio != powerState) { + mLastPowerStateFromRadio = powerState; + try { + getBatteryStats().noteMobileRadioPowerState(powerState, tsNanos, uid); + } catch (RemoteException e) { } } - if (report) { - reportNetworkActive(); + } + + if (ConnectivityManager.isNetworkTypeWifi(type)) { + if (mLastPowerStateFromWifi != powerState) { + mLastPowerStateFromWifi = powerState; + try { + getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos, uid); + } catch (RemoteException e) { + } } } - /** - * Notify our observers of an interface removal. - */ - @Override - public void notifyInterfaceRemoved(String iface) { - // netd already clears out quota and alerts for removed ifaces; update - // our sanity-checking state. - mActiveAlerts.remove(iface); - mActiveQuotas.remove(iface); - super.notifyInterfaceRemoved(iface); + if (!isMobile || fromRadio || !mMobileActivityFromRadio) { + // Report the change in data activity. We don't do this if this is a change + // on the mobile network, that is not coming from the radio itself, and we + // have previously seen change reports from the radio. In that case only + // the radio is the authority for the current state. + final boolean active = isActive; + invokeForAllObservers(o -> o.interfaceClassDataActivityChanged( + Integer.toString(type), active, tsNanos)); } - @Override - public void onStrictCleartextDetected(int uid, String hex) throws RemoteException { - // Don't need to post to mDaemonHandler because the only thing - // that notifyCleartextNetwork does is post to a handler - ActivityManager.getService().notifyCleartextNetwork(uid, - HexDump.hexStringToByteArray(hex)); + boolean report = false; + synchronized (mIdleTimerLock) { + if (mActiveIdleTimers.isEmpty()) { + // If there are no idle timers, we are not monitoring activity, so we + // are always considered active. + isActive = true; + } + if (mNetworkActive != isActive) { + mNetworkActive = isActive; + report = isActive; + } + } + if (report) { + reportNetworkActive(); } } @@ -519,8 +553,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub return; } // No current code examines the interface parameter in a global alert. Just pass null. - mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyLimitReached( - LIMIT_GLOBAL_ALERT, null)); + mDaemonHandler.post(() -> notifyLimitReached(LIMIT_GLOBAL_ALERT, null)); } } @@ -552,11 +585,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub private void connectNativeNetdService() { mNetdService = mServices.getNetd(); try { - mNetworkObserverRegistry = new NMSNetworkObserverRegistry( - mContext, mDaemonHandler, mNetdService); - if (DBG) Slog.d(TAG, "Registered NetworkObserverRegistry"); + mNetdService.registerUnsolicitedEventListener(mNetdUnsolicitedEventListener); + if (DBG) Slog.d(TAG, "Register unsolicited event listener"); } catch (RemoteException | ServiceSpecificException e) { - Slog.wtf(TAG, "Failed to register NetworkObserverRegistry: " + e); + Slog.e(TAG, "Failed to set Netd unsolicited event listener " + e); } } @@ -660,6 +692,118 @@ public class NetworkManagementService extends INetworkManagementService.Stub } + /** + * Notify our observers of a new or updated interface address. + */ + private void notifyAddressUpdated(String iface, LinkAddress address) { + invokeForAllObservers(o -> o.addressUpdated(iface, address)); + } + + /** + * Notify our observers of a deleted interface address. + */ + private void notifyAddressRemoved(String iface, LinkAddress address) { + invokeForAllObservers(o -> o.addressRemoved(iface, address)); + } + + /** + * Notify our observers of DNS server information received. + */ + private void notifyInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) { + invokeForAllObservers(o -> o.interfaceDnsServerInfo(iface, lifetime, addresses)); + } + + /** + * Notify our observers of a route change. + */ + private void notifyRouteChange(boolean updated, RouteInfo route) { + if (updated) { + invokeForAllObservers(o -> o.routeUpdated(route)); + } else { + invokeForAllObservers(o -> o.routeRemoved(route)); + } + } + + private class NetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub { + @Override + public void onInterfaceClassActivityChanged(boolean isActive, + int label, long timestamp, int uid) throws RemoteException { + final long timestampNanos; + if (timestamp <= 0) { + timestampNanos = SystemClock.elapsedRealtimeNanos(); + } else { + timestampNanos = timestamp; + } + mDaemonHandler.post(() -> + notifyInterfaceClassActivity(label, isActive, timestampNanos, uid, false)); + } + + @Override + public void onQuotaLimitReached(String alertName, String ifName) + throws RemoteException { + mDaemonHandler.post(() -> notifyLimitReached(alertName, ifName)); + } + + @Override + public void onInterfaceDnsServerInfo(String ifName, + long lifetime, String[] servers) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceDnsServerInfo(ifName, lifetime, servers)); + } + + @Override + public void onInterfaceAddressUpdated(String addr, + String ifName, int flags, int scope) throws RemoteException { + final LinkAddress address = new LinkAddress(addr, flags, scope); + mDaemonHandler.post(() -> notifyAddressUpdated(ifName, address)); + } + + @Override + public void onInterfaceAddressRemoved(String addr, + String ifName, int flags, int scope) throws RemoteException { + final LinkAddress address = new LinkAddress(addr, flags, scope); + mDaemonHandler.post(() -> notifyAddressRemoved(ifName, address)); + } + + @Override + public void onInterfaceAdded(String ifName) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceAdded(ifName)); + } + + @Override + public void onInterfaceRemoved(String ifName) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceRemoved(ifName)); + } + + @Override + public void onInterfaceChanged(String ifName, boolean up) + throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceStatusChanged(ifName, up)); + } + + @Override + public void onInterfaceLinkStateChanged(String ifName, boolean up) + throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up)); + } + + @Override + public void onRouteChanged(boolean updated, + String route, String gateway, String ifName) throws RemoteException { + final RouteInfo processRoute = new RouteInfo(new IpPrefix(route), + ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway), + ifName); + mDaemonHandler.post(() -> notifyRouteChange(updated, processRoute)); + } + + @Override + public void onStrictCleartextDetected(int uid, String hex) throws RemoteException { + // Don't need to post to mDaemonHandler because the only thing + // that notifyCleartextNetwork does is post to a handler + ActivityManager.getService().notifyCleartextNetwork(uid, + HexDump.hexStringToByteArray(hex)); + } + } + // // Netd Callback handling // @@ -708,18 +852,16 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException(errorMessage); } if (cooked[2].equals("added")) { - mNetworkObserverRegistry.notifyInterfaceAdded(cooked[3]); + notifyInterfaceAdded(cooked[3]); return true; } else if (cooked[2].equals("removed")) { - mNetworkObserverRegistry.notifyInterfaceRemoved(cooked[3]); + notifyInterfaceRemoved(cooked[3]); return true; } else if (cooked[2].equals("changed") && cooked.length == 5) { - mNetworkObserverRegistry.notifyInterfaceStatusChanged( - cooked[3], cooked[4].equals("up")); + notifyInterfaceStatusChanged(cooked[3], cooked[4].equals("up")); return true; } else if (cooked[2].equals("linkstate") && cooked.length == 5) { - mNetworkObserverRegistry.notifyInterfaceLinkStateChanged( - cooked[3], cooked[4].equals("up")); + notifyInterfaceLinkStateChanged(cooked[3], cooked[4].equals("up")); return true; } throw new IllegalStateException(errorMessage); @@ -733,7 +875,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException(errorMessage); } if (cooked[2].equals("alert")) { - mNetworkObserverRegistry.notifyLimitReached(cooked[3], cooked[4]); + notifyLimitReached(cooked[3], cooked[4]); return true; } throw new IllegalStateException(errorMessage); @@ -759,9 +901,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub timestampNanos = SystemClock.elapsedRealtimeNanos(); } boolean isActive = cooked[2].equals("active"); - mNetworkObserverRegistry.notifyInterfaceClassActivity( - Integer.parseInt(cooked[3]), isActive, - timestampNanos, processUid, false); + notifyInterfaceClassActivity(Integer.parseInt(cooked[3]), + isActive, timestampNanos, processUid, false); return true; // break; case NetdResponseCode.InterfaceAddressChange: @@ -787,9 +928,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub } if (cooked[2].equals("updated")) { - mNetworkObserverRegistry.notifyAddressUpdated(iface, address); + notifyAddressUpdated(iface, address); } else { - mNetworkObserverRegistry.notifyAddressRemoved(iface, address); + notifyAddressRemoved(iface, address); } return true; // break; @@ -809,8 +950,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException(errorMessage); } String[] servers = cooked[5].split(","); - mNetworkObserverRegistry.notifyInterfaceDnsServerInfo( - cooked[3], lifetime, servers); + notifyInterfaceDnsServerInfo(cooked[3], lifetime, servers); } return true; // break; @@ -849,8 +989,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub InetAddress gateway = null; if (via != null) gateway = InetAddress.parseNumericAddress(via); RouteInfo route = new RouteInfo(new IpPrefix(cooked[3]), gateway, dev); - mNetworkObserverRegistry.notifyRouteChange( - cooked[2].equals("updated"), route); + notifyRouteChange(cooked[2].equals("updated"), route); return true; } catch (IllegalArgumentException e) {} } @@ -1313,8 +1452,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub if (ConnectivityManager.isNetworkTypeMobile(type)) { mNetworkActive = false; } - mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyInterfaceClassActivity( - type, true /* isActive */, SystemClock.elapsedRealtimeNanos(), -1, false)); + mDaemonHandler.post(() -> notifyInterfaceClassActivity(type, true, + SystemClock.elapsedRealtimeNanos(), -1, false)); } } @@ -1337,9 +1476,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException(e); } mActiveIdleTimers.remove(iface); - mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyInterfaceClassActivity( - params.type, false /* isActive */, SystemClock.elapsedRealtimeNanos(), -1, - false)); + mDaemonHandler.post(() -> notifyInterfaceClassActivity(params.type, false, + SystemClock.elapsedRealtimeNanos(), -1, false)); } } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index cbfcd6035d5e..43af36f86f3d 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -52,6 +52,7 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; import android.telephony.emergency.EmergencyNumber; +import android.telephony.ims.ImsReasonInfo; import android.util.LocalLog; import android.util.StatsLog; @@ -227,6 +228,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private int mCallDisconnectCause = DisconnectCause.NOT_VALID; + private List<ImsReasonInfo> mImsReasonInfo = null; + private int mCallPreciseDisconnectCause = PreciseDisconnectCause.NOT_VALID; private boolean mCarrierNetworkChangeState = false; @@ -377,6 +380,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mCellLocation = new Bundle[numPhones]; mCellInfo = new ArrayList<List<CellInfo>>(); mSrvccState = new int[numPhones]; + mImsReasonInfo = new ArrayList<ImsReasonInfo>(); mPhysicalChannelConfigs = new ArrayList<List<PhysicalChannelConfig>>(); mEmergencyNumberList = new HashMap<>(); for (int i = 0; i < numPhones; i++) { @@ -394,6 +398,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mCallForwarding[i] = false; mCellLocation[i] = new Bundle(); mCellInfo.add(i, null); + mImsReasonInfo.add(i, null); mSrvccState[i] = TelephonyManager.SRVCC_STATE_HANDOVER_NONE; mPhysicalChannelConfigs.add(i, new ArrayList<PhysicalChannelConfig>()); } @@ -739,6 +744,13 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } + if ((events & PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES) != 0) { + try { + r.callback.onImsCallDisconnectCauseChanged(mImsReasonInfo.get(phoneId)); + } catch (RemoteException ex) { + remove(r.binder); + } + } if ((events & PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE) != 0) { try { r.callback.onPreciseDataConnectionStateChanged( @@ -1591,6 +1603,34 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } + public void notifyImsDisconnectCause(int subId, ImsReasonInfo imsReasonInfo) { + if (!checkNotifyPermission("notifyImsCallDisconnectCause()")) { + return; + } + int phoneId = SubscriptionManager.getPhoneId(subId); + synchronized (mRecords) { + if (validatePhoneId(phoneId)) { + mImsReasonInfo.set(phoneId, imsReasonInfo); + for (Record r : mRecords) { + if (r.matchPhoneStateListenerEvent( + PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES) + && idMatch(r.subId, subId, phoneId)) { + try { + if (DBG_LOC) { + log("notifyImsCallDisconnectCause: mImsReasonInfo=" + + imsReasonInfo + " r=" + r); + } + r.callback.onImsCallDisconnectCauseChanged(mImsReasonInfo.get(phoneId)); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } + } + handleRemoveListLocked(); + } + } + public void notifyPreciseDataConnectionFailed(String apnType, String apn, @DataFailCause.FailCause int failCause) { if (!checkNotifyPermission("notifyPreciseDataConnectionFailed()")) { @@ -1627,7 +1667,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { int phoneId = SubscriptionManager.getPhoneId(subId); synchronized (mRecords) { if (validatePhoneId(phoneId)) { - mSrvccState[phoneId] = state; + mSrvccState[phoneId] = state; for (Record r : mRecords) { if (r.matchPhoneStateListenerEvent( PhoneStateListener.LISTEN_SRVCC_STATE_CHANGED) && @@ -1838,6 +1878,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println("mDataConnectionState=" + mDataConnectionState[i]); pw.println("mCellLocation=" + mCellLocation[i]); pw.println("mCellInfo=" + mCellInfo.get(i)); + pw.println("mImsCallDisconnectCause=" + mImsReasonInfo.get(i).toString()); pw.decreaseIndent(); } pw.println("mPreciseDataConnectionState=" + mPreciseDataConnectionState); @@ -2127,6 +2168,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null); } + if ((events & PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES) != 0) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.READ_PRECISE_PHONE_STATE, null); + } + return true; } 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..a14fd17209e8 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -121,7 +121,6 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; - /** * @hide * @@ -223,7 +222,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) -> { @@ -470,6 +470,7 @@ public class Tethering extends BaseNetworkObserver { } else { sendTetherResult(receiver, resultCode); } + mEntitlementMgr.updateEntitlementCacheValue(type, resultCode); } }; @@ -1662,6 +1663,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. diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 250884431440..9141ccb387bd 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); @@ -267,8 +269,11 @@ public class Vpn { public void updateCapabilities() { final Network[] underlyingNetworks = (mConfig != null) ? mConfig.underlyingNetworks : null; + // Only apps targeting Q and above can explicitly declare themselves as metered. + final boolean isAlwaysMetered = + mIsPackageTargetingAtLeastQ && (mConfig == null || mConfig.isMetered); updateCapabilities(mContext.getSystemService(ConnectivityManager.class), underlyingNetworks, - mNetworkCapabilities); + mNetworkCapabilities, isAlwaysMetered); if (mNetworkAgent != null) { mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); @@ -277,11 +282,13 @@ public class Vpn { @VisibleForTesting public static void updateCapabilities(ConnectivityManager cm, Network[] underlyingNetworks, - NetworkCapabilities caps) { + 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; @@ -724,6 +731,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 +797,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; } @@ -1076,6 +1099,8 @@ public class Vpn { // as rules are deleted. This prevents data leakage as the rules are moved over. agentDisconnect(oldNetworkAgent); } + // Set up VPN's capabilities such as meteredness. + updateCapabilities(); if (oldConnection != null) { mContext.unbindService(oldConnection); @@ -1776,6 +1801,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/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java index a42efe960ff9..6d6f81eb98e6 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java @@ -81,8 +81,8 @@ 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); } } diff --git a/services/core/java/com/android/server/dreams/OWNERS b/services/core/java/com/android/server/dreams/OWNERS index 3c9bbf8797ea..426f002ad236 100644 --- a/services/core/java/com/android/server/dreams/OWNERS +++ b/services/core/java/com/android/server/dreams/OWNERS @@ -1,3 +1,3 @@ -dsandler@google.com +dsandler@android.com michaelwr@google.com roosa@google.com 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..33b8641c145e 100644 --- a/services/core/java/com/android/server/pm/OWNERS +++ b/services/core/java/com/android/server/pm/OWNERS @@ -51,6 +51,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/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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 8f5d36abcf2c..31e1e358ee6d 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -1852,7 +1852,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } AlarmManager getAlarmManager() { - return (AlarmManager) mContext.getSystemService(AlarmManager.class); + return mContext.getSystemService(AlarmManager.class); + } + + ConnectivityManager getConnectivityManager() { + return mContext.getSystemService(ConnectivityManager.class); } IWindowManager getIWindowManager() { @@ -5881,7 +5885,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * @throws UnsupportedOperationException if the package does not support being set as always-on. */ @Override - public boolean setAlwaysOnVpnPackage(ComponentName admin, String vpnPackage, boolean lockdown) + public boolean setAlwaysOnVpnPackage(ComponentName admin, String vpnPackage, boolean lockdown, + List<String> lockdownWhitelist) throws SecurityException { enforceProfileOrDeviceOwner(admin); @@ -5889,11 +5894,23 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final long token = mInjector.binderClearCallingIdentity(); try { if (vpnPackage != null && !isPackageInstalledForUser(vpnPackage, userId)) { - return false; + Slog.w(LOG_TAG, "Non-existent VPN package specified: " + vpnPackage); + throw new ServiceSpecificException( + DevicePolicyManager.ERROR_VPN_PACKAGE_NOT_FOUND, vpnPackage); + } + + if (vpnPackage != null && lockdown && lockdownWhitelist != null) { + for (String packageName : lockdownWhitelist) { + if (!isPackageInstalledForUser(packageName, userId)) { + Slog.w(LOG_TAG, "Non-existent package in VPN whitelist: " + packageName); + throw new ServiceSpecificException( + DevicePolicyManager.ERROR_VPN_PACKAGE_NOT_FOUND, packageName); + } + } } - ConnectivityManager connectivityManager = (ConnectivityManager) - mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - if (!connectivityManager.setAlwaysOnVpnPackageForUser(userId, vpnPackage, lockdown)) { + // If some package is uninstalled after the check above, it will be ignored by CM. + if (!mInjector.getConnectivityManager().setAlwaysOnVpnPackageForUser( + userId, vpnPackage, lockdown, lockdownWhitelist)) { throw new UnsupportedOperationException(); } } finally { @@ -5903,16 +5920,40 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public String getAlwaysOnVpnPackage(ComponentName admin) + public String getAlwaysOnVpnPackage(ComponentName admin) throws SecurityException { + enforceProfileOrDeviceOwner(admin); + + final int userId = mInjector.userHandleGetCallingUserId(); + final long token = mInjector.binderClearCallingIdentity(); + try { + return mInjector.getConnectivityManager().getAlwaysOnVpnPackageForUser(userId); + } finally { + mInjector.binderRestoreCallingIdentity(token); + } + } + + @Override + public boolean isAlwaysOnVpnLockdownEnabled(ComponentName admin) throws SecurityException { + enforceProfileOrDeviceOwner(admin); + + final int userId = mInjector.userHandleGetCallingUserId(); + final long token = mInjector.binderClearCallingIdentity(); + try { + return mInjector.getConnectivityManager().isVpnLockdownEnabled(userId); + } finally { + mInjector.binderRestoreCallingIdentity(token); + } + } + + @Override + public List<String> getAlwaysOnVpnLockdownWhitelist(ComponentName admin) throws SecurityException { enforceProfileOrDeviceOwner(admin); final int userId = mInjector.userHandleGetCallingUserId(); final long token = mInjector.binderClearCallingIdentity(); - try{ - ConnectivityManager connectivityManager = (ConnectivityManager) - mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - return connectivityManager.getAlwaysOnVpnPackageForUser(userId); + try { + return mInjector.getConnectivityManager().getVpnLockdownWhitelist(userId); } finally { mInjector.binderRestoreCallingIdentity(token); } @@ -6382,9 +6423,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } long token = mInjector.binderClearCallingIdentity(); try { - ConnectivityManager connectivityManager = (ConnectivityManager) - mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - connectivityManager.setGlobalProxy(proxyInfo); + mInjector.getConnectivityManager().setGlobalProxy(proxyInfo); } finally { mInjector.binderRestoreCallingIdentity(token); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 3ecbd47cf12e..2338fffbf26a 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -238,8 +238,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 +756,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 +866,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); @@ -1310,14 +1314,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 30c7de57b73e..638ec95ec544 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -9,12 +9,6 @@ filegroup { "java/android/net/ip/InterfaceController.java", // TODO: move to NetworkStack with tethering "java/android/net/util/InterfaceParams.java", // TODO: move to NetworkStack with IpServer "java/android/net/shared/*.java", - ], -} - -java_library { - name: "services-netlink-lib", - srcs: [ "java/android/net/netlink/*.java", - ] + ], } diff --git a/services/net/java/android/net/ip/InterfaceController.java b/services/net/java/android/net/ip/InterfaceController.java index b3af67cdbdc3..970bc9cf667b 100644 --- a/services/net/java/android/net/ip/InterfaceController.java +++ b/services/net/java/android/net/ip/InterfaceController.java @@ -17,7 +17,6 @@ package android.net.ip; import android.net.INetd; -import android.net.InterfaceConfiguration; import android.net.InterfaceConfigurationParcel; import android.net.LinkAddress; import android.net.util.SharedLog; @@ -49,14 +48,18 @@ public class InterfaceController { mLog = log; } - private boolean setInterfaceConfig(InterfaceConfiguration config) { - final InterfaceConfigurationParcel cfgParcel = config.toParcel(mIfName); - + private boolean setInterfaceAddress(LinkAddress addr) { + final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel(); + ifConfig.ifName = mIfName; + ifConfig.ipv4Addr = addr.getAddress().getHostAddress(); + ifConfig.prefixLength = addr.getPrefixLength(); + ifConfig.hwAddr = ""; + ifConfig.flags = new String[0]; try { - mNetd.interfaceSetCfg(cfgParcel); + mNetd.interfaceSetCfg(ifConfig); } catch (RemoteException | ServiceSpecificException e) { logError("Setting IPv4 address to %s/%d failed: %s", - cfgParcel.ipv4Addr, cfgParcel.prefixLength, e); + ifConfig.ipv4Addr, ifConfig.prefixLength, e); return false; } return true; @@ -69,18 +72,14 @@ public class InterfaceController { if (!(address.getAddress() instanceof Inet4Address)) { return false; } - final InterfaceConfiguration ifConfig = new InterfaceConfiguration(); - ifConfig.setLinkAddress(address); - return setInterfaceConfig(ifConfig); + return setInterfaceAddress(address); } /** * Clear the IPv4Address of the interface. */ public boolean clearIPv4Address() { - final InterfaceConfiguration ifConfig = new InterfaceConfiguration(); - ifConfig.setLinkAddress(new LinkAddress("0.0.0.0/0")); - return setInterfaceConfig(ifConfig); + return setInterfaceAddress(new LinkAddress("0.0.0.0/0")); } private boolean setEnableIPv6(boolean enabled) { diff --git a/services/net/java/android/net/ip/IpServer.java b/services/net/java/android/net/ip/IpServer.java index f7360f52225f..7910c9a69310 100644 --- a/services/net/java/android/net/ip/IpServer.java +++ b/services/net/java/android/net/ip/IpServer.java @@ -40,7 +40,7 @@ import android.net.dhcp.IDhcpServer; import android.net.ip.RouterAdvertisementDaemon.RaParams; import android.net.util.InterfaceParams; import android.net.util.InterfaceSet; -import android.net.shared.NetdService; +import android.net.util.NetdService; import android.net.util.SharedLog; import android.os.INetworkManagementService; import android.os.Looper; diff --git a/services/net/java/android/net/netlink/NetlinkSocket.java b/services/net/java/android/net/netlink/NetlinkSocket.java index 2a98d90e5577..16f72bd53e15 100644 --- a/services/net/java/android/net/netlink/NetlinkSocket.java +++ b/services/net/java/android/net/netlink/NetlinkSocket.java @@ -27,14 +27,13 @@ import static android.system.OsConstants.SO_RCVBUF; import static android.system.OsConstants.SO_RCVTIMEO; import static android.system.OsConstants.SO_SNDTIMEO; +import android.net.util.SocketUtils; import android.system.ErrnoException; import android.system.Os; -import android.system.StructTimeval; import android.util.Log; -import libcore.io.IoUtils; - import java.io.FileDescriptor; +import java.io.IOException; import java.io.InterruptedIOException; import java.net.SocketException; import java.nio.ByteBuffer; @@ -95,7 +94,11 @@ public class NetlinkSocket { Log.e(TAG, errPrefix, e); throw new ErrnoException(errPrefix, EIO, e); } finally { - IoUtils.closeQuietly(fd); + try { + SocketUtils.closeSocket(fd); + } catch (IOException e) { + // Nothing we can do here + } } } @@ -106,7 +109,7 @@ public class NetlinkSocket { } public static void connectToKernel(FileDescriptor fd) throws ErrnoException, SocketException { - Os.connect(fd, makeNetlinkSocketAddress(0, 0)); + SocketUtils.connectSocket(fd, makeNetlinkSocketAddress(0, 0)); } private static void checkTimeout(long timeoutMs) { @@ -125,7 +128,7 @@ public class NetlinkSocket { throws ErrnoException, IllegalArgumentException, InterruptedIOException { checkTimeout(timeoutMs); - Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(timeoutMs)); + SocketUtils.setSocketTimeValueOption(fd, SOL_SOCKET, SO_RCVTIMEO, timeoutMs); ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize); int length = Os.read(fd, byteBuffer); @@ -148,7 +151,7 @@ public class NetlinkSocket { FileDescriptor fd, byte[] bytes, int offset, int count, long timeoutMs) throws ErrnoException, IllegalArgumentException, InterruptedIOException { checkTimeout(timeoutMs); - Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(timeoutMs)); + SocketUtils.setSocketTimeValueOption(fd, SOL_SOCKET, SO_SNDTIMEO, timeoutMs); return Os.write(fd, bytes, offset, count); } } diff --git a/services/net/java/android/net/shared/InitialConfiguration.java b/services/net/java/android/net/shared/InitialConfiguration.java index bc2373f4aabc..4ad71381da04 100644 --- a/services/net/java/android/net/shared/InitialConfiguration.java +++ b/services/net/java/android/net/shared/InitialConfiguration.java @@ -20,6 +20,7 @@ import static android.net.shared.ParcelableUtil.fromParcelableArray; import static android.net.shared.ParcelableUtil.toParcelableArray; import static android.text.TextUtils.join; +import android.net.InetAddresses; import android.net.InitialConfigurationParcelable; import android.net.IpPrefix; import android.net.IpPrefixParcelable; @@ -27,7 +28,7 @@ import android.net.LinkAddress; import android.net.LinkAddressParcelable; import android.net.RouteInfo; -import java.net.Inet6Address; +import java.net.Inet4Address; import java.net.InetAddress; import java.util.HashSet; import java.util.List; @@ -43,6 +44,8 @@ public class InitialConfiguration { private static final int RFC6177_MIN_PREFIX_LENGTH = 48; private static final int RFC7421_PREFIX_LENGTH = 64; + public static final InetAddress INET6_ANY = InetAddresses.parseNumericAddress("::"); + /** * Create a InitialConfiguration that is a copy of the specified configuration. */ @@ -102,7 +105,7 @@ public class InitialConfiguration { return false; } // There no more than one IPv4 address - if (ipAddresses.stream().filter(LinkAddress::isIPv4).count() > 1) { + if (ipAddresses.stream().filter(InitialConfiguration::isIPv4).count() > 1) { return false; } @@ -184,11 +187,11 @@ public class InitialConfiguration { } private static boolean isPrefixLengthCompliant(LinkAddress addr) { - return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength()); + return isIPv4(addr) || isCompliantIPv6PrefixLength(addr.getPrefixLength()); } private static boolean isPrefixLengthCompliant(IpPrefix prefix) { - return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength()); + return isIPv4(prefix) || isCompliantIPv6PrefixLength(prefix.getPrefixLength()); } private static boolean isCompliantIPv6PrefixLength(int prefixLength) { @@ -196,8 +199,16 @@ public class InitialConfiguration { && (prefixLength <= RFC7421_PREFIX_LENGTH); } + private static boolean isIPv4(IpPrefix prefix) { + return prefix.getAddress() instanceof Inet4Address; + } + + private static boolean isIPv4(LinkAddress addr) { + return addr.getAddress() instanceof Inet4Address; + } + private static boolean isIPv6DefaultRoute(IpPrefix prefix) { - return prefix.getAddress().equals(Inet6Address.ANY); + return prefix.getAddress().equals(INET6_ANY); } private static boolean isIPv6GUA(LinkAddress addr) { diff --git a/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java b/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java index 00073503886a..1f0525e4da88 100644 --- a/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java +++ b/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java @@ -44,11 +44,11 @@ public final class IpConfigurationParcelableUtil { @Nullable StaticIpConfiguration config) { if (config == null) return null; final StaticIpConfigurationParcelable p = new StaticIpConfigurationParcelable(); - p.ipAddress = LinkPropertiesParcelableUtil.toStableParcelable(config.ipAddress); - p.gateway = parcelAddress(config.gateway); + p.ipAddress = LinkPropertiesParcelableUtil.toStableParcelable(config.getIpAddress()); + p.gateway = parcelAddress(config.getGateway()); p.dnsServers = toParcelableArray( - config.dnsServers, IpConfigurationParcelableUtil::parcelAddress, String.class); - p.domains = config.domains; + config.getDnsServers(), IpConfigurationParcelableUtil::parcelAddress, String.class); + p.domains = config.getDomains(); return p; } @@ -59,11 +59,13 @@ public final class IpConfigurationParcelableUtil { @Nullable StaticIpConfigurationParcelable p) { if (p == null) return null; final StaticIpConfiguration config = new StaticIpConfiguration(); - config.ipAddress = LinkPropertiesParcelableUtil.fromStableParcelable(p.ipAddress); - config.gateway = unparcelAddress(p.gateway); - config.dnsServers.addAll(fromParcelableArray( - p.dnsServers, IpConfigurationParcelableUtil::unparcelAddress)); - config.domains = p.domains; + config.setIpAddress(LinkPropertiesParcelableUtil.fromStableParcelable(p.ipAddress)); + config.setGateway(unparcelAddress(p.gateway)); + for (InetAddress addr : fromParcelableArray( + p.dnsServers, IpConfigurationParcelableUtil::unparcelAddress)) { + config.addDnsServer(addr); + } + config.setDomains(p.domains); return config; } diff --git a/services/net/java/android/net/shared/NetworkObserverRegistry.java b/services/net/java/android/net/shared/NetworkObserverRegistry.java deleted file mode 100644 index 36945f5de2c5..000000000000 --- a/services/net/java/android/net/shared/NetworkObserverRegistry.java +++ /dev/null @@ -1,255 +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.net.shared; - -import static android.Manifest.permission.NETWORK_STACK; - -import android.content.Context; -import android.net.INetd; -import android.net.INetdUnsolicitedEventListener; -import android.net.INetworkManagementEventObserver; -import android.net.InetAddresses; -import android.net.IpPrefix; -import android.net.LinkAddress; -import android.net.RouteInfo; -import android.os.Handler; -import android.os.RemoteCallbackList; -import android.os.RemoteException; -import android.os.SystemClock; - -/** - * A class for reporting network events to clients. - * - * Implements INetdUnsolicitedEventListener and registers with netd, and relays those events to - * all INetworkManagementEventObserver objects that have registered with it. - * - * TODO: Make the notifyXyz methods protected once subclasses (e.g., the NetworkManagementService - * subclass) no longer call them directly. - * - * TODO: change from RemoteCallbackList to direct in-process callbacks. - */ -public class NetworkObserverRegistry extends INetdUnsolicitedEventListener.Stub { - - private final Context mContext; - private final Handler mDaemonHandler; - private static final String TAG = "NetworkObserverRegistry"; - - /** - * Constructs a new instance and registers it with netd. - * This method should only be called once since netd will reject multiple registrations from - * the same process. - */ - public NetworkObserverRegistry(Context context, Handler handler, INetd netd) - throws RemoteException { - mContext = context; - mDaemonHandler = handler; - netd.registerUnsolicitedEventListener(this); - } - - private final RemoteCallbackList<INetworkManagementEventObserver> mObservers = - new RemoteCallbackList<>(); - - /** - * Registers the specified observer and start sending callbacks to it. - * This method may be called on any thread. - */ - public void registerObserver(INetworkManagementEventObserver observer) { - mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG); - mObservers.register(observer); - } - - /** - * Unregisters the specified observer and stop sending callbacks to it. - * This method may be called on any thread. - */ - public void unregisterObserver(INetworkManagementEventObserver observer) { - mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG); - mObservers.unregister(observer); - } - - @FunctionalInterface - private interface NetworkManagementEventCallback { - void sendCallback(INetworkManagementEventObserver o) throws RemoteException; - } - - private void invokeForAllObservers(NetworkManagementEventCallback eventCallback) { - final int length = mObservers.beginBroadcast(); - try { - for (int i = 0; i < length; i++) { - try { - eventCallback.sendCallback(mObservers.getBroadcastItem(i)); - } catch (RemoteException | RuntimeException e) { - } - } - } finally { - mObservers.finishBroadcast(); - } - } - - /** - * Notify our observers of a change in the data activity state of the interface - */ - public void notifyInterfaceClassActivity(int type, boolean isActive, long tsNanos, - int uid, boolean fromRadio) { - invokeForAllObservers(o -> o.interfaceClassDataActivityChanged( - Integer.toString(type), isActive, tsNanos)); - } - - @Override - public void onInterfaceClassActivityChanged(boolean isActive, - int label, long timestamp, int uid) throws RemoteException { - final long timestampNanos; - if (timestamp <= 0) { - timestampNanos = SystemClock.elapsedRealtimeNanos(); - } else { - timestampNanos = timestamp; - } - mDaemonHandler.post(() -> notifyInterfaceClassActivity(label, isActive, - timestampNanos, uid, false)); - } - - /** - * Notify our observers of a limit reached. - */ - @Override - public void onQuotaLimitReached(String alertName, String ifName) throws RemoteException { - mDaemonHandler.post(() -> notifyLimitReached(alertName, ifName)); - } - - /** - * Notify our observers of a limit reached. - */ - public void notifyLimitReached(String limitName, String iface) { - invokeForAllObservers(o -> o.limitReached(limitName, iface)); - } - - @Override - public void onInterfaceDnsServerInfo(String ifName, - long lifetime, String[] servers) throws RemoteException { - mDaemonHandler.post(() -> notifyInterfaceDnsServerInfo(ifName, lifetime, servers)); - } - - /** - * Notify our observers of DNS server information received. - */ - public void notifyInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) { - invokeForAllObservers(o -> o.interfaceDnsServerInfo(iface, lifetime, addresses)); - } - - @Override - public void onInterfaceAddressUpdated(String addr, - String ifName, int flags, int scope) throws RemoteException { - final LinkAddress address = new LinkAddress(addr, flags, scope); - mDaemonHandler.post(() -> notifyAddressUpdated(ifName, address)); - } - - /** - * Notify our observers of a new or updated interface address. - */ - public void notifyAddressUpdated(String iface, LinkAddress address) { - invokeForAllObservers(o -> o.addressUpdated(iface, address)); - } - - @Override - public void onInterfaceAddressRemoved(String addr, - String ifName, int flags, int scope) throws RemoteException { - final LinkAddress address = new LinkAddress(addr, flags, scope); - mDaemonHandler.post(() -> notifyAddressRemoved(ifName, address)); - } - - /** - * Notify our observers of a deleted interface address. - */ - public void notifyAddressRemoved(String iface, LinkAddress address) { - invokeForAllObservers(o -> o.addressRemoved(iface, address)); - } - - - @Override - public void onInterfaceAdded(String ifName) throws RemoteException { - mDaemonHandler.post(() -> notifyInterfaceAdded(ifName)); - } - - /** - * Notify our observers of an interface addition. - */ - public void notifyInterfaceAdded(String iface) { - invokeForAllObservers(o -> o.interfaceAdded(iface)); - } - - @Override - public void onInterfaceRemoved(String ifName) throws RemoteException { - mDaemonHandler.post(() -> notifyInterfaceRemoved(ifName)); - } - - /** - * Notify our observers of an interface removal. - */ - public void notifyInterfaceRemoved(String iface) { - invokeForAllObservers(o -> o.interfaceRemoved(iface)); - } - - @Override - public void onInterfaceChanged(String ifName, boolean up) throws RemoteException { - mDaemonHandler.post(() -> notifyInterfaceStatusChanged(ifName, up)); - } - - /** - * Notify our observers of an interface status change - */ - public void notifyInterfaceStatusChanged(String iface, boolean up) { - invokeForAllObservers(o -> o.interfaceStatusChanged(iface, up)); - } - - @Override - public void onInterfaceLinkStateChanged(String ifName, boolean up) throws RemoteException { - mDaemonHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up)); - } - - /** - * Notify our observers of an interface link state change - * (typically, an Ethernet cable has been plugged-in or unplugged). - */ - public void notifyInterfaceLinkStateChanged(String iface, boolean up) { - invokeForAllObservers(o -> o.interfaceLinkStateChanged(iface, up)); - } - - @Override - public void onRouteChanged(boolean updated, - String route, String gateway, String ifName) throws RemoteException { - final RouteInfo processRoute = new RouteInfo(new IpPrefix(route), - ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway), - ifName); - mDaemonHandler.post(() -> notifyRouteChange(updated, processRoute)); - } - - /** - * Notify our observers of a route change. - */ - public void notifyRouteChange(boolean updated, RouteInfo route) { - if (updated) { - invokeForAllObservers(o -> o.routeUpdated(route)); - } else { - invokeForAllObservers(o -> o.routeRemoved(route)); - } - } - - @Override - public void onStrictCleartextDetected(int uid, String hex) throws RemoteException { - // Don't do anything here because this is not a method of INetworkManagementEventObserver. - // Only the NMS subclass will implement this. - } -} diff --git a/services/net/java/android/net/shared/NetdService.java b/services/net/java/android/net/util/NetdService.java index f5ae72587294..d4cd5bd7ba18 100644 --- a/services/net/java/android/net/shared/NetdService.java +++ b/services/net/java/android/net/util/NetdService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.net.shared; +package android.net.util; import android.content.Context; import android.net.INetd; 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/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/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..268e70fe5d12 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1397,8 +1397,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 +1425,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 +1452,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 +1976,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/android/telecom/VideoProfile.java b/telecomm/java/android/telecom/VideoProfile.java index 7b2306128b7b..157f12c9f105 100644 --- a/telecomm/java/android/telecom/VideoProfile.java +++ b/telecomm/java/android/telecom/VideoProfile.java @@ -16,7 +16,9 @@ package android.telecom; +import android.annotation.FloatRange; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; @@ -364,7 +366,7 @@ public class VideoProfile implements Parcelable { * @param width The width of the camera video (in pixels). * @param height The height of the camera video (in pixels). */ - public CameraCapabilities(int width, int height) { + public CameraCapabilities(@IntRange(from = 0) int width, @IntRange(from = 0) int height) { this(width, height, false, 1.0f); } @@ -376,7 +378,8 @@ public class VideoProfile implements Parcelable { * @param zoomSupported True when camera supports zoom. * @param maxZoom Maximum zoom supported by camera. */ - public CameraCapabilities(int width, int height, boolean zoomSupported, float maxZoom) { + public CameraCapabilities(@IntRange(from = 0) int width, @IntRange(from = 0) int height, + boolean zoomSupported, @FloatRange(from = 1.0f) float maxZoom) { mWidth = width; mHeight = height; mZoomSupported = zoomSupported; 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..ac34cea30f55 100644 --- a/telephony/java/android/provider/Telephony.java +++ b/telephony/java/android/provider/Telephony.java @@ -2054,6 +2054,608 @@ 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 { + /** + * 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); + } + } + + /** * Contains all MMS messages. */ public static final class Mms implements BaseMmsColumns { 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 26cba773c9cc..190e82b6cc5b 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2371,33 +2371,54 @@ public class CarrierConfigManager { "support_emergency_dialer_shortcut_bool"; /** - * Controls RSRP threshold at which AlternativeNetworkService will decide whether + * Controls RSRP threshold at which OpportunisticNetworkService will decide whether * the opportunistic network is good enough for internet data. */ public static final String KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSRP_INT = "opportunistic_network_entry_threshold_rsrp_int"; /** - * Controls RSSNR threshold at which AlternativeNetworkService will decide whether + * Controls RSSNR threshold at which OpportunisticNetworkService will decide whether * the opportunistic network is good enough for internet data. */ public static final String KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSSNR_INT = "opportunistic_network_entry_threshold_rssnr_int"; /** - * Controls RSRP threshold below which AlternativeNetworkService will decide whether + * Controls RSRP threshold below which OpportunisticNetworkService will decide whether * the opportunistic network available is not good enough for internet data. */ public static final String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSRP_INT = "opportunistic_network_exit_threshold_rsrp_int"; /** - * Controls RSSNR threshold below which AlternativeNetworkService will decide whether + * Controls RSSNR threshold below which OpportunisticNetworkService will decide whether * the opportunistic network available is not good enough for internet data. */ public static final String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT = "opportunistic_network_exit_threshold_rssnr_int"; + /** + * Controls bandwidth threshold in Kbps at which OpportunisticNetworkService will decide whether + * the opportunistic network is good enough for internet data. + */ + public static final String KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_BANDWIDTH_INT = + "opportunistic_network_entry_threshold_bandwidth_int"; + + /** + * Controls hysteresis time in milli seconds for which OpportunisticNetworkService + * will wait before attaching to a network. + */ + public static final String KEY_OPPORTUNISTIC_NETWORK_ENTRY_OR_EXIT_HYSTERESIS_TIME_LONG = + "opportunistic_network_entry_or_exit_hysteresis_time_long"; + + /** + * Controls hysteresis time in milli seconds for which OpportunisticNetworkService + * will wait before switching data to a network. + */ + public static final String KEY_OPPORTUNISTIC_NETWORK_DATA_SWITCH_HYSTERESIS_TIME_LONG = + "opportunistic_network_data_switch_hysteresis_time_long"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -2767,6 +2788,12 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSSNR_INT, 45); /* Default value is minimum RSSNR level needed for SIGNAL_STRENGTH_MODERATE */ sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT, 10); + /* Default value is 1024 kbps */ + sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_BANDWIDTH_INT, 1024); + /* Default value is 10 seconds */ + sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_ENTRY_OR_EXIT_HYSTERESIS_TIME_LONG, 10000); + /* Default value is 10 seconds. */ + sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_DATA_SWITCH_HYSTERESIS_TIME_LONG, 10000); } /** 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/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/NetworkService.java b/telephony/java/android/telephony/NetworkService.java index 4bca404d9444..6c45cc4ef3b8 100644 --- a/telephony/java/android/telephony/NetworkService.java +++ b/telephony/java/android/telephony/NetworkService.java @@ -16,6 +16,7 @@ package android.telephony; +import android.annotation.NonNull; import android.annotation.SystemApi; import android.app.Service; import android.content.Intent; @@ -112,13 +113,13 @@ public abstract class NetworkService extends Service { mSlotId, 0, null).sendToTarget(); } - private void registerForStateChanged(INetworkServiceCallback callback) { + private void registerForStateChanged(@NonNull INetworkServiceCallback callback) { synchronized (mNetworkRegistrationStateChangedCallbacks) { mNetworkRegistrationStateChangedCallbacks.add(callback); } } - private void unregisterForStateChanged(INetworkServiceCallback callback) { + private void unregisterForStateChanged(@NonNull INetworkServiceCallback callback) { synchronized (mNetworkRegistrationStateChangedCallbacks) { mNetworkRegistrationStateChangedCallbacks.remove(callback); } diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java index af324debbd57..fea1b7b08a20 100644 --- a/telephony/java/android/telephony/PhoneStateListener.java +++ b/telephony/java/android/telephony/PhoneStateListener.java @@ -28,6 +28,7 @@ import android.os.Handler; import android.os.HandlerExecutor; import android.os.Looper; import android.telephony.emergency.EmergencyNumber; +import android.telephony.ims.ImsReasonInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.IPhoneStateListener; @@ -347,6 +348,20 @@ public class PhoneStateListener { @SystemApi public static final int LISTEN_CALL_ATTRIBUTES_CHANGED = 0x04000000; + /** + * Listen for IMS call disconnect causes which contains + * {@link android.telephony.ims.ImsReasonInfo} + * + * {@more} + * Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE + * READ_PRECISE_PHONE_STATE} + * + * @see #onImsCallDisconnectCauseChanged(ImsReasonInfo) + * @hide + */ + @SystemApi + public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 0x08000000; + /* * Subscription used to listen to the phone state changes * @hide @@ -578,6 +593,17 @@ public class PhoneStateListener { } /** + * Callback invoked when Ims call disconnect cause changes. + * @param imsReasonInfo {@link ImsReasonInfo} contains details on why IMS call failed. + * + * @hide + */ + @SystemApi + public void onImsCallDisconnectCauseChanged(@NonNull ImsReasonInfo imsReasonInfo) { + // default implementation empty + } + + /** * Callback invoked when data connection state changes with precise information. * @param dataConnectionState {@link PreciseDataConnectionState} * @@ -981,6 +1007,16 @@ public class PhoneStateListener { Binder.withCleanCallingIdentity( () -> mExecutor.execute(() -> psl.onPreferredDataSubIdChanged(subId))); } + + public void onImsCallDisconnectCauseChanged(ImsReasonInfo disconnectCause) { + PhoneStateListener psl = mPhoneStateListenerWeakRef.get(); + if (psl == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute( + () -> psl.onImsCallDisconnectCauseChanged(disconnectCause))); + + } } 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..0d94c4dd3686 100644 --- a/telephony/java/android/telephony/RadioAccessFamily.java +++ b/telephony/java/android/telephony/RadioAccessFamily.java @@ -57,6 +57,9 @@ public class RadioAccessFamily implements Parcelable { public static final int RAF_LTE = TelephonyManager.NETWORK_TYPE_BITMASK_LTE; public static final int RAF_LTE_CA = TelephonyManager.NETWORK_TYPE_BITMASK_LTE_CA; + // 5G + public static final int RAF_NR = TelephonyManager.NETWORK_TYPE_BITMASK_NR; + // Grouping of RAFs // 2G private static final int GSM = RAF_GSM | RAF_GPRS | RAF_EDGE; @@ -68,6 +71,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; @@ -160,84 +166,78 @@ public class RadioAccessFamily implements Parcelable { @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 +250,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 +275,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 +373,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; } } diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 44756307a4b3..33178766f3a3 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -540,7 +540,7 @@ public class ServiceState implements Parcelable { * * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public int getDataRegState() { return mDataRegState; } 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/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index 4e4ef4d6c236..443f90844c50 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -577,10 +577,10 @@ public class SubscriptionInfo implements Parcelable { } /** - * @return the cardId of the SIM card which contains the subscription. - * @hide + * Returns the card ID of the SIM card which contains the subscription (see + * {@link UiccCardInfo#getCardId()}. + * @return the cardId */ - @SystemApi public int getCardId() { return this.mCardId; } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 2461aad80246..148563ac8029 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -227,10 +227,9 @@ public class TelephonyManager { public static final int SRVCC_STATE_HANDOVER_CANCELED = 3; /** - * An invalid card identifier. - * @hide + * An invalid UICC card identifier. See {@link #getCardIdForDefaultEuicc()} and + * {@link UiccCardInfo#getCardId()}. */ - @SystemApi public static final int INVALID_CARD_ID = -1; /** @hide */ @@ -350,41 +349,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; } @@ -1353,6 +1341,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 @@ -3118,14 +3138,8 @@ public class TelephonyManager { * unique to a device, and always refer to the same UICC or eUICC card unless the device goes * through a factory reset. * - * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} - * * @return card ID of the default eUICC card. - * @hide */ - @SystemApi - @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getCardIdForDefaultEuicc() { try { ITelephony telephony = getITelephony(); @@ -3139,25 +3153,37 @@ public class TelephonyManager { } /** - * Gets information about currently inserted UICCs and eUICCs. See {@link UiccCardInfo} for more - * details on the kind of information available. - * - * @return UiccCardInfo an array of UiccCardInfo objects, representing information on the - * currently inserted UICCs and eUICCs. + * Gets information about currently inserted UICCs and enabled eUICCs. + * <p> + * Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * <p> + * If the caller has carrier priviliges on any active subscription, then they have permission to + * get simple information like the card ID ({@link UiccCardInfo#getCardId()}), whether the card + * is an eUICC ({@link UiccCardInfo#isEuicc()}), and the slot index where the card is inserted + * ({@link UiccCardInfo#getSlotIndex()}). + * <p> + * To get private information such as the EID ({@link UiccCardInfo#getEid()}) or ICCID + * ({@link UiccCardInfo#getIccId()}), the caller must have carrier priviliges on that specific + * UICC or eUICC card. + * <p> + * See {@link UiccCardInfo} for more details on the kind of information available. * - * @hide + * @return a list of UiccCardInfo objects, representing information on the currently inserted + * UICCs and eUICCs. Each UiccCardInfo in the list will have private information filtered out if + * the caller does not have adequate permissions for that card. */ - @SystemApi @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public UiccCardInfo[] getUiccCardsInfo() { + public List<UiccCardInfo> getUiccCardsInfo() { try { ITelephony telephony = getITelephony(); if (telephony == null) { - return null; + Log.e(TAG, "Error in getUiccCardsInfo: unable to connect to Telephony service."); + return new ArrayList<UiccCardInfo>(); } - return telephony.getUiccCardsInfo(); + return telephony.getUiccCardsInfo(mContext.getOpPackageName()); } catch (RemoteException e) { - return null; + Log.e(TAG, "Error in getUiccCardsInfo: " + e); + return new ArrayList<UiccCardInfo>(); } } @@ -6242,197 +6268,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) { @@ -7803,9 +7890,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(); @@ -8949,6 +9034,9 @@ public class TelephonyManager { @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int slotIndex, List<CarrierIdentifier> carriers) { + if (carriers == null || !SubscriptionManager.isValidPhoneId(slotIndex)) { + return -1; + } // Execute the method setCarrierRestrictionRules with an empty excluded list and // indicating priority for the allowed list. CarrierRestrictionRules carrierRestrictionRules = CarrierRestrictionRules.newBuilder() @@ -8959,7 +9047,7 @@ public class TelephonyManager { int result = setCarrierRestrictionRules(carrierRestrictionRules); - // Convert boolean result into int, as required by this method. + // Convert result into int, as required by this method. if (result == SET_CARRIER_RESTRICTION_SUCCESS) { return carriers.size(); } else { @@ -9052,9 +9140,11 @@ public class TelephonyManager { @SystemApi @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public List<CarrierIdentifier> getAllowedCarriers(int slotIndex) { - CarrierRestrictionRules carrierRestrictionRule = getCarrierRestrictionRules(); - if (carrierRestrictionRule != null) { - return carrierRestrictionRule.getAllowedCarriers(); + if (SubscriptionManager.isValidPhoneId(slotIndex)) { + CarrierRestrictionRules carrierRestrictionRule = getCarrierRestrictionRules(); + if (carrierRestrictionRule != null) { + return carrierRestrictionRule.getAllowedCarriers(); + } } return new ArrayList<CarrierIdentifier>(0); } @@ -9509,10 +9599,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; @@ -9893,12 +9983,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; @@ -9971,4 +10066,120 @@ public class TelephonyManager { } return ret; } + + /** + * Indicate if the user is allowed to use multiple SIM cards at the same time to register + * on the network (e.g. Dual Standby or Dual Active) when the device supports it, or if the + * usage is restricted. This API is used to prevent usage of multiple SIM card, based on + * policies of the carrier. + * <p>Note: the API does not prevent access to the SIM cards for operations that don't require + * access to the network. + * + * @param isMultisimCarrierRestricted true if usage of multiple SIMs is restricted, false + * otherwise. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void setMultisimCarrierRestriction(boolean isMultisimCarrierRestricted) { + try { + ITelephony service = getITelephony(); + if (service != null) { + service.setMultisimCarrierRestriction(isMultisimCarrierRestricted); + } + } catch (RemoteException e) { + Log.e(TAG, "setMultisimCarrierRestriction RemoteException", e); + } + } + + /** + * Returns if the usage of multiple SIM cards at the same time to register on the network + * (e.g. Dual Standby or Dual Active) is restricted. + * + * @return true if usage of multiple SIMs is restricted, false otherwise. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public boolean isMultisimCarrierRestricted() { + try { + ITelephony service = getITelephony(); + if (service != null) { + return service.isMultisimCarrierRestricted(); + } + } catch (RemoteException e) { + Log.e(TAG, "isMultisimCarrierRestricted RemoteException", e); + } + return true; + } + + /** + * Broadcast intent action for network country code changes. + * + * <p> + * The {@link #EXTRA_NETWORK_COUNTRY} extra indicates the country code of the current + * network returned by {@link #getNetworkCountryIso()}. + * + * @see #EXTRA_NETWORK_COUNTRY + * @see #getNetworkCountryIso() + */ + public static final String ACTION_NETWORK_COUNTRY_CHANGED = + "android.telephony.action.NETWORK_COUNTRY_CHANGED"; + + /** + * The extra used with an {@link #ACTION_NETWORK_COUNTRY_CHANGED} to specify the + * the country code in ISO 3166 format. + * <p class="note"> + * Retrieve with {@link android.content.Intent#getStringExtra(String)}. + */ + 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. + * @Return {@code True} if reboot is required after making changes to modem configurations, + * otherwise return {@code False}. + * + * @hide + */ + @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/UiccCardInfo.java b/telephony/java/android/telephony/UiccCardInfo.java index 45e4704e8894..19f357a14687 100644 --- a/telephony/java/android/telephony/UiccCardInfo.java +++ b/telephony/java/android/telephony/UiccCardInfo.java @@ -15,7 +15,6 @@ */ package android.telephony; -import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -23,10 +22,8 @@ import java.util.Objects; /** * The UiccCardInfo represents information about a currently inserted UICC or embedded eUICC. - * @hide */ -@SystemApi -public class UiccCardInfo implements Parcelable { +public final class UiccCardInfo implements Parcelable { private final boolean mIsEuicc; private final int mCardId; @@ -95,6 +92,9 @@ public class UiccCardInfo implements Parcelable { /** * Get the embedded ID (EID) of the eUICC. If the UiccCardInfo is not an eUICC * (see {@link #isEuicc()}), returns null. + * <p> + * Note that this field may be omitted if the caller does not have the correct permissions + * (see {@link TelephonyManager#getUiccCardsInfo()}). */ public String getEid() { if (!mIsEuicc) { @@ -105,6 +105,9 @@ public class UiccCardInfo implements Parcelable { /** * Get the ICCID of the UICC. + * <p> + * Note that this field may be omitted if the caller does not have the correct permissions + * (see {@link TelephonyManager#getUiccCardsInfo()}). */ public String getIccId() { return mIccId; @@ -117,6 +120,16 @@ public class UiccCardInfo implements Parcelable { return mSlotIndex; } + /** + * Returns a copy of the UiccCardinfo with the clears the EID and ICCID set to null. These + * values are generally private and require carrier privileges to view. + * + * @hide + */ + public UiccCardInfo getUnprivileged() { + return new UiccCardInfo(mIsEuicc, mCardId, null, null, mSlotIndex); + } + @Override public boolean equals(Object obj) { if (this == obj) { diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java index 74d1e838f186..79572b9706a9 100644 --- a/telephony/java/android/telephony/data/DataService.java +++ b/telephony/java/android/telephony/data/DataService.java @@ -157,7 +157,10 @@ public abstract class DataService extends Service { @Nullable LinkProperties linkProperties, @Nullable DataServiceCallback callback) { // The default implementation is to return unsupported. - callback.onSetupDataCallComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED, null); + if (callback != null) { + callback.onSetupDataCallComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED, + null); + } } /** @@ -176,7 +179,9 @@ public abstract class DataService extends Service { public void deactivateDataCall(int cid, @DeactivateDataReason int reason, @Nullable DataServiceCallback callback) { // The default implementation is to return unsupported. - callback.onDeactivateDataCallComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED); + if (callback != null) { + callback.onDeactivateDataCallComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED); + } } /** @@ -190,7 +195,10 @@ public abstract class DataService extends Service { public void setInitialAttachApn(DataProfile dataProfile, boolean isRoaming, @Nullable DataServiceCallback callback) { // The default implementation is to return unsupported. - callback.onSetInitialAttachApnComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED); + if (callback != null) { + callback.onSetInitialAttachApnComplete( + DataServiceCallback.RESULT_ERROR_UNSUPPORTED); + } } /** @@ -206,7 +214,9 @@ public abstract class DataService extends Service { public void setDataProfile(List<DataProfile> dps, boolean isRoaming, @Nullable DataServiceCallback callback) { // The default implementation is to return unsupported. - callback.onSetDataProfileComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED); + if (callback != null) { + callback.onSetDataProfileComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED); + } } /** diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index a5c0442948ac..40c6f70f5112 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 (!refreshCardIdIfInvalid()) { 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 (!refreshCardIdIfInvalid()) { 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 (!refreshCardIdIfInvalid()) { 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 (!refreshCardIdIfInvalid()) { 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 (!refreshCardIdIfInvalid()) { 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 (!refreshCardIdIfInvalid()) { 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 (!refreshCardIdIfInvalid()) { 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 (!refreshCardIdIfInvalid()) { 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 (!refreshCardIdIfInvalid()) { 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 (!refreshCardIdIfInvalid()) { 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 (!refreshCardIdIfInvalid()) { sendUnavailableError(callbackIntent); return; } @@ -636,7 +656,7 @@ public class EuiccManager { * @hide */ public void retainSubscriptionsForFactoryReset(PendingIntent callbackIntent) { - if (!isEnabled()) { + if (!refreshCardIdIfInvalid()) { sendUnavailableError(callbackIntent); return; } @@ -647,6 +667,19 @@ public class EuiccManager { } } + private boolean refreshCardIdIfInvalid() { + if (!isEnabled()) { + return false; + } + // Refresh mCardId if it's invalid. + if (mCardId == TelephonyManager.INVALID_CARD_ID) { + TelephonyManager tm = (TelephonyManager) + mContext.getSystemService(Context.TELEPHONY_SERVICE); + mCardId = tm.getCardIdForDefaultEuicc(); + } + return true; + } + private static void sendUnavailableError(PendingIntent callbackIntent) { try { callbackIntent.send(EMBEDDED_SUBSCRIPTION_RESULT_ERROR); 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..cc28ee0758c2 100644 --- a/telephony/java/android/telephony/ims/Rcs1To1Thread.java +++ b/telephony/java/android/telephony/ims/Rcs1To1Thread.java @@ -15,42 +15,72 @@ */ 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) + * + * @hide - TODO(109759350) make this public */ 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/RcsThreadQueryContinuationToken.java b/telephony/java/android/telephony/ims/RcsEvent.java index 931e93dc8bf1..744ac76a7828 100644 --- a/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.java +++ b/telephony/java/android/telephony/ims/RcsEvent.java @@ -13,40 +13,48 @@ * 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 + * The base class for events that can happen on {@link RcsParticipant}s and {@link RcsThread}s. + * @hide - TODO(109759350) make this public */ -public class RcsThreadQueryContinuationToken implements Parcelable { - protected RcsThreadQueryContinuationToken(Parcel in) { +public abstract class RcsEvent implements Parcelable { + protected long mTimestamp; + + protected RcsEvent(long timestamp) { + mTimestamp = timestamp; } - public static final Creator<RcsThreadQueryContinuationToken> CREATOR = - new Creator<RcsThreadQueryContinuationToken>() { - @Override - public RcsThreadQueryContinuationToken createFromParcel(Parcel in) { - return new RcsThreadQueryContinuationToken(in); - } + /** + * @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; + } - @Override - public RcsThreadQueryContinuationToken[] newArray(int size) { - return new RcsThreadQueryContinuationToken[size]; - } - }; + /** + * Persists the event to the data store + * + * @hide + */ + abstract void persist() throws RcsMessageStoreException; - @Override - public int describeContents() { - return 0; + RcsEvent(Parcel in) { + mTimestamp = in.readLong(); } @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mTimestamp); + } + + @Override + public int describeContents() { + return 0; } } diff --git a/telephony/java/android/telephony/ims/RcsGroupThread.aidl b/telephony/java/android/telephony/ims/RcsEventQueryParameters.aidl index c4ce5299e512..9a3600bbae90 100644 --- a/telephony/java/android/telephony/ims/RcsGroupThread.aidl +++ b/telephony/java/android/telephony/ims/RcsEventQueryParameters.aidl @@ -17,4 +17,4 @@ package android.telephony.ims; -parcelable RcsGroupThread; +parcelable RcsEventQueryParameters; diff --git a/telephony/java/android/telephony/ims/RcsEventQueryParameters.java b/telephony/java/android/telephony/ims/RcsEventQueryParameters.java new file mode 100644 index 000000000000..6aee56f56f45 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsEventQueryParameters.java @@ -0,0 +1,322 @@ +/* + * 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(RcsEventQueryParameters)} in order to select a + * subset of {@link RcsEvent}s present in the message store. + * + * @hide TODO - make the Builder and builder() public. The rest should stay internal only. + */ +public class RcsEventQueryParameters implements Parcelable { + /** + * Flag to be used with {@link Builder#setEventType(int)} to make + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} 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(RcsEventQueryParameters)} 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(RcsEventQueryParameters)} 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(RcsEventQueryParameters)} 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(RcsEventQueryParameters)} 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(RcsEventQueryParameters)} 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(RcsEventQueryParameters)} 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; + + RcsEventQueryParameters(@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 RcsEventQueryParameters} is + * set to query for. + */ + public @EventType int getEventType() { + return mEventType; + } + + /** + * @return Returns the type of {@link RcsEvent}s that this {@link RcsEventQueryParameters} is + * set to query for. + */ + 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 RcsEventQueryParameters}. + */ + 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 RcsEventQueryParameters} to be used in + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} + */ + 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 RcsEventQueryParameters.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 RcsEventQueryParameters} to use in + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} + * + * @return An instance of {@link RcsEventQueryParameters} to use with the event query. + */ + public RcsEventQueryParameters build() { + return new RcsEventQueryParameters(mEventType, mThreadId, mSortingProperty, + mIsAscending, mLimit); + } + } + + protected RcsEventQueryParameters(Parcel in) { + mEventType = in.readInt(); + mThreadId = in.readInt(); + mSortingProperty = in.readInt(); + mIsAscending = in.readBoolean(); + mLimit = in.readInt(); + } + + public static final Creator<RcsEventQueryParameters> CREATOR = + new Creator<RcsEventQueryParameters>() { + @Override + public RcsEventQueryParameters createFromParcel(Parcel in) { + return new RcsEventQueryParameters(in); + } + + @Override + public RcsEventQueryParameters[] newArray(int size) { + return new RcsEventQueryParameters[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/RcsIncomingMessage.aidl b/telephony/java/android/telephony/ims/RcsEventQueryResult.aidl index 6552a82c9072..7d133350973c 100644 --- a/telephony/java/android/telephony/ims/RcsIncomingMessage.aidl +++ b/telephony/java/android/telephony/ims/RcsEventQueryResult.aidl @@ -17,4 +17,4 @@ package android.telephony.ims; -parcelable RcsIncomingMessage; +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..27898ab0d9a2 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsEventQueryResult.java @@ -0,0 +1,90 @@ +/* + * 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(RcsEventQueryParameters)} + * 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. + * + * @hide + */ +public 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; + } + + protected RcsEventQueryResult(Parcel in) { + } + + 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/RcsFileTransferCreationParameters.aidl b/telephony/java/android/telephony/ims/RcsFileTransferCreationParameters.aidl new file mode 100644 index 000000000000..5fec021525af --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsFileTransferCreationParameters.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 RcsFileTransferCreationParameters; diff --git a/telephony/java/android/telephony/ims/RcsFileTransferCreationParameters.java b/telephony/java/android/telephony/ims/RcsFileTransferCreationParameters.java new file mode 100644 index 000000000000..bd7cb4bc90bc --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsFileTransferCreationParameters.java @@ -0,0 +1,360 @@ +/* + * 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(RcsFileTransferCreationParameters)} create an + * {@link RcsFileTransferPart} and save it into storage. + * + * @hide - TODO(109759350) make this public + */ +public class RcsFileTransferCreationParameters 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 + */ + RcsFileTransferCreationParameters(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 RcsFileTransferCreationParameters} + */ + 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 RcsFileTransferCreationParameters} with the given + * parameters. + * + * @return The same instance of {@link Builder} to chain methods + * @see RcsMessage#insertFileTransfer(RcsFileTransferCreationParameters) + */ + public RcsFileTransferCreationParameters build() { + return new RcsFileTransferCreationParameters(this); + } + } + + protected RcsFileTransferCreationParameters(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<RcsFileTransferCreationParameters> CREATOR = + new Creator<RcsFileTransferCreationParameters>() { + @Override + public RcsFileTransferCreationParameters createFromParcel(Parcel in) { + return new RcsFileTransferCreationParameters(in); + } + + @Override + public RcsFileTransferCreationParameters[] newArray(int size) { + return new RcsFileTransferCreationParameters[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..2eadc4a1724e 100644 --- a/telephony/java/android/telephony/ims/RcsFileTransferPart.java +++ b/telephony/java/android/telephony/ims/RcsFileTransferPart.java @@ -15,34 +15,346 @@ */ 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) + * + * @hide - TODO(109759350) make this public */ -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; + + /** + * 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)); + } - @Override - public RcsFileTransferPart[] newArray(int size) { - return new RcsFileTransferPart[size]; - } - }; + /** + * 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..6f6258e3d894 100644 --- a/telephony/java/android/telephony/ims/RcsGroupThread.java +++ b/telephony/java/android/telephony/ims/RcsGroupThread.java @@ -15,38 +15,191 @@ */ 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. + * or leave. Please see Section 6 (Group Chat) - GSMA RCC.71 (RCS Universal Profile Service + * Definition Document) + * * @hide - TODO(sahinc) make this public */ 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 { + RcsParticipantQueryParameters queryParameters = + new RcsParticipantQueryParameters.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/Rcs1To1Thread.aidl b/telephony/java/android/telephony/ims/RcsGroupThreadEvent.aidl index 9fdc41d2bd5f..77a23722f080 100644 --- a/telephony/java/android/telephony/ims/Rcs1To1Thread.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 Rcs1To1Thread; +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..a18437b8366e --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsGroupThreadEvent.java @@ -0,0 +1,65 @@ +/* + * 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}. + * + * @hide - TODO(109759350) make this public + */ +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); + } + + RcsGroupThreadEvent(Parcel in) { + super(in); + mRcsGroupThreadId = in.readInt(); + mOriginatingParticipantId = in.readInt(); + } + + @Override + 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..7beed3ba7ec2 --- /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; + +/** + * 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) + * + * @hide - TODO(109759350) make this public + */ +public class RcsGroupThreadIconChangedEvent extends RcsGroupThreadEvent { + 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]; + } + }; + + protected 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..0d2ea4febfbc --- /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; + +/** + * 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) + * + * @hide - TODO(109759350) make this public + */ +public class RcsGroupThreadNameChangedEvent extends RcsGroupThreadEvent { + private 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]; + } + }; + + protected 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..2adafc7e1bb1 --- /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; + +/** + * 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) + * + * @hide - TODO(109759350) make this public + */ +public class RcsGroupThreadParticipantJoinedEvent extends RcsGroupThreadEvent { + private 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]; + } + }; + + protected 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..87c1852964ee --- /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; + +/** + * 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) + * + * @hide - TODO(109759350) make this public + */ +public class RcsGroupThreadParticipantLeftEvent extends RcsGroupThreadEvent { + private 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]; + } + }; + + protected 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..2ea115bd675b 100644 --- a/telephony/java/android/telephony/ims/RcsIncomingMessage.java +++ b/telephony/java/android/telephony/ims/RcsIncomingMessage.java @@ -15,34 +15,82 @@ */ 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 + * + * @hide - TODO(109759350) 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/RcsIncomingMessageCreationParameters.aidl b/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParameters.aidl new file mode 100644 index 000000000000..76073c22af0c --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParameters.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 RcsIncomingMessageCreationParameters; diff --git a/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParameters.java b/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParameters.java new file mode 100644 index 000000000000..acde8af0d295 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParameters.java @@ -0,0 +1,180 @@ +/* + * 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 RcsIncomingMessageCreationParameters} is a collection of parameters that should be passed + * into {@link RcsThread#addIncomingMessage(RcsIncomingMessageCreationParameters)} to generate an + * {@link RcsIncomingMessage} on that {@link RcsThread} + * + * @hide TODO:make public + */ +public class RcsIncomingMessageCreationParameters extends RcsMessageCreationParameters 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 RcsIncomingMessageCreationParameters} + * + * @see RcsThread#addIncomingMessage(RcsIncomingMessageCreationParameters) + */ + public static class Builder extends RcsMessageCreationParameters.Builder { + private RcsParticipant mSenderParticipant; + private long mArrivalTimestamp; + private long mSeenTimestamp; + + /** + * Creates a {@link Builder} to create an instance of + * {@link RcsIncomingMessageCreationParameters} + * + * @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 RcsIncomingMessageCreationParameters} to create a new + * {@link RcsIncomingMessage} + */ + public RcsIncomingMessageCreationParameters build() { + return new RcsIncomingMessageCreationParameters(this); + } + } + + private RcsIncomingMessageCreationParameters(Builder builder) { + super(builder); + mArrivalTimestamp = builder.mArrivalTimestamp; + mSeenTimestamp = builder.mSeenTimestamp; + mSenderParticipantId = builder.mSenderParticipant.getId(); + } + + protected RcsIncomingMessageCreationParameters(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<RcsIncomingMessageCreationParameters> CREATOR = + new Creator<RcsIncomingMessageCreationParameters>() { + @Override + public RcsIncomingMessageCreationParameters createFromParcel(Parcel in) { + return new RcsIncomingMessageCreationParameters(in); + } + + @Override + public RcsIncomingMessageCreationParameters[] newArray(int size) { + return new RcsIncomingMessageCreationParameters[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeLong(mArrivalTimestamp); + dest.writeLong(mSeenTimestamp); + dest.writeInt(mSenderParticipantId); + } +} diff --git a/telephony/java/android/telephony/ims/RcsLocationPart.aidl b/telephony/java/android/telephony/ims/RcsLocationPart.aidl deleted file mode 100644 index 4fe5ca97a30d..000000000000 --- a/telephony/java/android/telephony/ims/RcsLocationPart.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 RcsLocationPart; 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..e84d4ed9e7be 100644 --- a/telephony/java/android/telephony/ims/RcsManager.java +++ b/telephony/java/android/telephony/ims/RcsManager.java @@ -28,7 +28,7 @@ public class RcsManager { 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..b70a9a7973f5 100644 --- a/telephony/java/android/telephony/ims/RcsMessage.java +++ b/telephony/java/android/telephony/ims/RcsMessage.java @@ -15,11 +15,314 @@ */ 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 + * + * @hide - TODO(109759350) 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; + + protected 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( + RcsFileTransferCreationParameters 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/RcsMessageCreationParameters.aidl b/telephony/java/android/telephony/ims/RcsMessageCreationParameters.aidl new file mode 100644 index 000000000000..5774d00ca934 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsMessageCreationParameters.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 RcsMessageCreationParameters; diff --git a/telephony/java/android/telephony/ims/RcsMessageCreationParameters.java b/telephony/java/android/telephony/ims/RcsMessageCreationParameters.java new file mode 100644 index 000000000000..ff3f33ed2e1b --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsMessageCreationParameters.java @@ -0,0 +1,265 @@ +/* + * 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; +import android.os.Parcelable; +import android.telephony.SubscriptionInfo; + +/** + * The collection of parameters to be passed into + * {@link RcsThread#addIncomingMessage(RcsIncomingMessageCreationParameters)} and + * {@link RcsThread#addOutgoingMessage(RcsMessageCreationParameters)} to create and persist + * {@link RcsMessage}s on an {@link RcsThread} + * + * @hide TODO - make public + */ +public class RcsMessageCreationParameters implements Parcelable { + // 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 RcsIncomingMessageCreationParameters + */ + 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; + + /** + * Creates a new {@link Builder} to create an instance of + * {@link RcsMessageCreationParameters}. + * + * @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 SubscriptionInfo#getSubscriptionId() + */ + 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; + } + + /** + * @hide + */ + public RcsMessageCreationParameters build() { + return new RcsMessageCreationParameters(this); + } + } + + protected RcsMessageCreationParameters(Builder builder) { + mRcsMessageGlobalId = builder.mRcsMessageGlobalId; + mSubId = builder.mSubId; + mMessageStatus = builder.mMessageStatus; + mOriginationTimestamp = builder.mOriginationTimestamp; + mText = builder.mText; + mLatitude = builder.mLatitude; + mLongitude = builder.mLongitude; + } + + protected RcsMessageCreationParameters(Parcel in) { + mRcsMessageGlobalId = in.readString(); + mSubId = in.readInt(); + mMessageStatus = in.readInt(); + mOriginationTimestamp = in.readLong(); + mText = in.readString(); + mLatitude = in.readDouble(); + mLongitude = in.readDouble(); + } + + public static final Creator<RcsMessageCreationParameters> CREATOR = + new Creator<RcsMessageCreationParameters>() { + @Override + public RcsMessageCreationParameters createFromParcel(Parcel in) { + return new RcsMessageCreationParameters(in); + } + + @Override + public RcsMessageCreationParameters[] newArray(int size) { + return new RcsMessageCreationParameters[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + 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/RcsMessageQueryParameters.aidl b/telephony/java/android/telephony/ims/RcsMessageQueryParameters.aidl new file mode 100644 index 000000000000..c325c23ba9bd --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsMessageQueryParameters.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 RcsMessageQueryParameters; diff --git a/telephony/java/android/telephony/ims/RcsMessageQueryParameters.java b/telephony/java/android/telephony/ims/RcsMessageQueryParameters.java new file mode 100644 index 000000000000..c964cf9af7c5 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsMessageQueryParameters.java @@ -0,0 +1,361 @@ +/* + * 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(RcsMessageQueryParameters)} in order to select a + * subset of {@link RcsMessage}s present in the message store. + * + * @hide TODO - make the Builder and builder() public. The rest should stay internal only. + */ +public class RcsMessageQueryParameters 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(RcsMessageQueryParameters)} 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(RcsMessageQueryParameters)} 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(RcsMessageQueryParameters)} 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(RcsMessageQueryParameters)} 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; + + RcsMessageQueryParameters(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 RcsMessageQueryParameters} + * 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 RcsMessageQueryParameters}. + */ + 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 RcsMessageQueryParameters} to be used in + * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParameters)} + * + */ + 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 RcsMessageQueryParameters#MESSAGE_TYPE_INCOMING + * @see RcsMessageQueryParameters#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 RcsMessageQueryParameters#MESSAGES_WITH_FILE_TRANSFERS + * @see RcsMessageQueryParameters#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 RcsMessageQueryParameters.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 RcsMessageQueryParameters} to use in + * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParameters)} + * + * @return An instance of {@link RcsMessageQueryParameters} to use with the message + * query. + */ + public RcsMessageQueryParameters build() { + return new RcsMessageQueryParameters(mMessageType, mFileTransferPresence, mMessageLike, + mThreadId, mSortingProperty, mIsAscending, mLimit); + } + } + + /** + * Parcelable boilerplate below. + */ + protected RcsMessageQueryParameters(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<RcsMessageQueryParameters> CREATOR = + new Creator<RcsMessageQueryParameters>() { + @Override + public RcsMessageQueryParameters createFromParcel(Parcel in) { + return new RcsMessageQueryParameters(in); + } + + @Override + public RcsMessageQueryParameters[] newArray(int size) { + return new RcsMessageQueryParameters[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/RcsFileTransferPart.aidl b/telephony/java/android/telephony/ims/RcsMessageQueryResult.aidl index eaf312877deb..a73ba50b6591 100644 --- a/telephony/java/android/telephony/ims/RcsFileTransferPart.aidl +++ b/telephony/java/android/telephony/ims/RcsMessageQueryResult.aidl @@ -17,4 +17,4 @@ package android.telephony.ims; -parcelable RcsFileTransferPart; +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..c3846fdebf2e --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsMessageQueryResult.java @@ -0,0 +1,115 @@ +/* + * 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(RcsMessageQueryParameters)} + * 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. + * + * @hide + */ +public 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; + } + + protected 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..9399c2003827 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsMessageSnippet.java @@ -0,0 +1,98 @@ +/* + * 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} + * + * @hide TODO: make public + */ +public 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; + } + + protected 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..c8c36a8e479b 100644 --- a/telephony/java/android/telephony/ims/RcsMessageStore.java +++ b/telephony/java/android/telephony/ims/RcsMessageStore.java @@ -16,106 +16,223 @@ 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 RcsThreadQueryParameters 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 RcsParticipantQueryParameters 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)); + } - return null; + /** + * 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 RcsMessageQueryParameters 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 RcsEventQueryParameters queryParameters) throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getEvents(queryParameters)); + } + + /** + * 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..e158f1a55aec 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,23 @@ * 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} + * + * @hide TODO: make public */ -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..0bd55ec2d25a 100644 --- a/telephony/java/android/telephony/ims/RcsOutgoingMessage.java +++ b/telephony/java/android/telephony/ims/RcsOutgoingMessage.java @@ -15,34 +15,53 @@ */ 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 + * + * @hide - TODO(109759350) 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/RcsOutgoingMessageDelivery.java b/telephony/java/android/telephony/ims/RcsOutgoingMessageDelivery.java new file mode 100644 index 000000000000..b93f892df295 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsOutgoingMessageDelivery.java @@ -0,0 +1,131 @@ +/* + * 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. + * + * @hide - TODO(109759350) make this public + */ +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..ce0ad2c4749b 100644 --- a/telephony/java/android/telephony/ims/RcsParticipant.java +++ b/telephony/java/android/telephony/ims/RcsParticipant.java @@ -15,33 +15,17 @@ */ 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 + * + * @hide - TODO(109759350) 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 +33,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..04cdf86df9c0 100644 --- a/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEvent.java +++ b/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEvent.java @@ -15,27 +15,93 @@ */ package android.telephony.ims; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Parcel; /** - * 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) + * + * @hide - TODO(109759350) make this public */ -public class RcsParticipantAliasChangedEvent extends RcsParticipantEvent { +public class RcsParticipantAliasChangedEvent extends RcsEvent { + // The ID of the participant that changed their alias + private int mParticipantId; + // The new alias of the above participant + private 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) { + super(in); + mNewAlias = in.readString(); + mParticipantId = in.readInt(); } @Override @@ -45,5 +111,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/RcsParticipantQueryParameters.aidl b/telephony/java/android/telephony/ims/RcsParticipantQueryParameters.aidl new file mode 100644 index 000000000000..ea8288cd9f6a --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsParticipantQueryParameters.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 RcsParticipantQueryParameters; diff --git a/telephony/java/android/telephony/ims/RcsParticipantQueryParameters.java b/telephony/java/android/telephony/ims/RcsParticipantQueryParameters.java new file mode 100644 index 000000000000..3611fc187fc4 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsParticipantQueryParameters.java @@ -0,0 +1,310 @@ +/* + * 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(RcsParticipantQueryParameters)} 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 RcsParticipantQueryParameters 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"; + + RcsParticipantQueryParameters(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 RcsParticipantQueryParameters}. + */ + 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 RcsParticipantQueryParameters} to be used in + * {@link RcsMessageStore#getRcsParticipants(RcsParticipantQueryParameters)} + */ + 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 RcsParticipantQueryParameters.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 RcsParticipantQueryParameters} to use in + * {@link RcsMessageStore#getRcsParticipants(RcsParticipantQueryParameters)} + * + * @return An instance of {@link RcsParticipantQueryParameters} to use with the participant + * query. + */ + public RcsParticipantQueryParameters build() { + return new RcsParticipantQueryParameters(mThreadId, mAliasLike, mCanonicalAddressLike, + mSortingProperty, mIsAscending, mLimit); + } + } + + /** + * Parcelable boilerplate below. + */ + protected RcsParticipantQueryParameters(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<RcsParticipantQueryParameters> CREATOR = + new Creator<RcsParticipantQueryParameters>() { + @Override + public RcsParticipantQueryParameters createFromParcel(Parcel in) { + return new RcsParticipantQueryParameters(in); + } + + @Override + public RcsParticipantQueryParameters[] newArray(int size) { + return new RcsParticipantQueryParameters[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..2f4ab465a6cf --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsParticipantQueryResult.java @@ -0,0 +1,105 @@ +/* + * 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(RcsParticipantQueryParameters)} + * 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. + * + * @hide + */ +public 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; + } + + protected 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..e880651d6cdb --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsQueryContinuationToken.java @@ -0,0 +1,151 @@ +/* + * 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; + +/** + * This interface allows using the same implementation for continuation token usage in + * {@link com.android.providers.telephony.RcsProvider} + * @hide - TODO make getQueryType() and types public - the rest should stay internal + */ +public 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; + } + + protected 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..238f5e7ce625 100644 --- a/telephony/java/android/telephony/ims/RcsThread.java +++ b/telephony/java/android/telephony/ims/RcsThread.java @@ -16,60 +16,117 @@ 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 + * + * @hide - TODO(109759350) 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 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 RcsIncomingMessageCreationParameters rcsIncomingMessageCreationParameters) + throws RcsMessageStoreException { + return new RcsIncomingMessage(RcsControllerCall.call(iRcs -> iRcs.addIncomingMessage( + mThreadId, rcsIncomingMessageCreationParameters))); } - 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 RcsMessageCreationParameters rcsMessageCreationParameters) + throws RcsMessageStoreException { + int messageId = RcsControllerCall.call(iRcs -> iRcs.addOutgoingMessage( + mThreadId, rcsMessageCreationParameters)); - 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(RcsMessageQueryParameters)} + * + * @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 { + RcsMessageQueryParameters queryParameters = + new RcsMessageQueryParameters.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/RcsThreadQueryParameters.aidl b/telephony/java/android/telephony/ims/RcsThreadQueryParameters.aidl index feb2d4dec094..52e73ce02407 100644 --- a/telephony/java/android/telephony/ims/RcsThreadQueryParameters.aidl +++ b/telephony/java/android/telephony/ims/RcsThreadQueryParameters.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/RcsThreadQueryParameters.java b/telephony/java/android/telephony/ims/RcsThreadQueryParameters.java index f2c4ab1884ca..4aa42073b8f8 100644 --- a/telephony/java/android/telephony/ims/RcsThreadQueryParameters.java +++ b/telephony/java/android/telephony/ims/RcsThreadQueryParameters.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. @@ -17,72 +17,133 @@ 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(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; + /** + * Bitmask flag to be used with {@link Builder#setThreadType(int)} to make + * {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParameters)} 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(RcsThreadQueryParameters)} 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; - RcsThreadQueryParameters(boolean isGroup, Set<RcsParticipant> participants, int limit, - boolean isAscending) { - mIsGroup = isGroup; - mRcsParticipants = participants; - mLimit = limit; - mIsAscending = isAscending; - } + /** + * 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; /** - * Returns a new builder to build a query with. - * TODO - make public + * 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 Builder builder() { - return new Builder(); + public static final int SORT_BY_TIMESTAMP = 1; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({SORT_BY_CREATION_ORDER, SORT_BY_TIMESTAMP}) + public @interface SortingProperty { } /** - * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get - * the list of participants. * @hide */ - public Set<RcsParticipant> getRcsParticipants() { - return mRcsParticipants; + public static final String THREAD_QUERY_PARAMETERS_KEY = "thread_query_parameters"; + + RcsThreadQueryParameters(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 - * whether group threads should be queried - * @hide + * 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 boolean isGroupThread() { - return mIsGroup; + public List<Integer> getRcsParticipantsIds() { + return Collections.unmodifiableList(mRcsParticipantIds); } /** - * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get - * the number of tuples the result query should be limited to. + * @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; } /** - * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to - * determine the sort order. + * @return Returns the property that will be used to sort the result against. + * @see SortingProperty */ - public boolean isAscending() { + 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; } @@ -90,64 +151,74 @@ public class RcsThreadQueryParameters implements Parcelable { * A helper class to build the {@link RcsThreadQueryParameters}. */ public static class Builder { - private boolean mIsGroupThread; + private int mThreadType; private Set<RcsParticipant> mParticipants; private int mLimit = 100; + private @SortingProperty int mSortingProperty; private boolean mIsAscending; /** - * Package private constructor for {@link RcsThreadQueryParameters.Builder}. To obtain this, - * {@link RcsThreadQueryParameters#builder()} needs to be called. + * Constructs a {@link RcsThreadQueryParameters.Builder} to help build an + * {@link RcsThreadQueryParameters} */ - Builder() { + public Builder() { mParticipants = new HashSet<>(); } /** * Limits the query to only return group threads. - * @param isGroupThread Whether to limit the query result to group threads. + * + * @param threadType Whether to limit the query result to group threads. * @return The same instance of the builder to chain parameters. + * @see RcsThreadQueryParameters#THREAD_TYPE_GROUP + * @see RcsThreadQueryParameters#THREAD_TYPE_1_TO_1 */ @CheckResult - public Builder isGroupThread(boolean isGroupThread) { - mIsGroupThread = isGroupThread; + public Builder setThreadType(int threadType) { + mThreadType = threadType; return this; } /** - * Limits the query to only return threads that contain the given participant. + * 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 withParticipant(RcsParticipant participant) { + 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. + * 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 withParticipants(Iterable<RcsParticipant> participants) { - for (RcsParticipant participant : participants) { - mParticipants.add(participant); - } + 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 limitResultsTo(int limit) throws InvalidParameterException { + public Builder setResultLimit(@IntRange(from = 0) int limit) + throws InvalidParameterException { if (limit < 0) { throw new InvalidParameterException("The query limit must be non-negative"); } @@ -157,15 +228,26 @@ public class RcsThreadQueryParameters implements Parcelable { } /** - * Sorts the results returned from the query via thread IDs. + * Sets the property where the results should be sorted against. Defaults to + * {@link SortingProperty#SORT_BY_CREATION_ORDER} * - * TODO - add sorting support for other fields + * @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 to sort in ascending order or not + * @param isAscending whether the results should be sorted ascending * @return The same instance of the builder to chain parameters. */ @CheckResult - public Builder sort(boolean isAscending) { + public Builder setSortDirection(boolean isAscending) { mIsAscending = isAscending; return this; } @@ -177,8 +259,8 @@ public class RcsThreadQueryParameters implements Parcelable { * @return An instance of {@link RcsThreadQueryParameters} to use with the thread query. */ public RcsThreadQueryParameters build() { - return new RcsThreadQueryParameters( - mIsGroupThread, mParticipants, mLimit, mIsAscending); + return new RcsThreadQueryParameters(mThreadType, mParticipants, mLimit, + mSortingProperty, mIsAscending); } } @@ -186,14 +268,12 @@ public class RcsThreadQueryParameters implements Parcelable { * Parcelable boilerplate below. */ protected RcsThreadQueryParameters(Parcel in) { - mIsGroup = in.readBoolean(); - - ArrayList<RcsParticipant> participantArrayList = new ArrayList<>(); - in.readTypedList(participantArrayList, RcsParticipant.CREATOR); - mRcsParticipants = new HashSet<>(participantArrayList); - + mThreadType = in.readInt(); + mRcsParticipantIds = new ArrayList<>(); + in.readList(mRcsParticipantIds, Integer.class.getClassLoader()); mLimit = in.readInt(); - mIsAscending = in.readBoolean(); + mSortingProperty = in.readInt(); + mIsAscending = in.readByte() == 1; } public static final Creator<RcsThreadQueryParameters> CREATOR = @@ -216,10 +296,10 @@ public class RcsThreadQueryParameters implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeBoolean(mIsGroup); - dest.writeTypedList(new ArrayList<>(mRcsParticipants)); + dest.writeInt(mThreadType); + dest.writeList(mRcsParticipantIds); dest.writeInt(mLimit); - dest.writeBoolean(mIsAscending); + 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..6515933fff0e 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,30 @@ 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(RcsThreadQueryParameters)} * 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; + // 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 +48,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 + mContinuationToken = in.readParcelable( + RcsQueryContinuationToken.class.getClassLoader()); + mRcsThreadIds = new ArrayList<>(); + in.readList(mRcsThreadIds, Integer.class.getClassLoader()); } public static final Creator<RcsThreadQueryResult> CREATOR = @@ -87,6 +111,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..a399786dec54 100644 --- a/telephony/java/android/telephony/ims/aidl/IRcs.aidl +++ b/telephony/java/android/telephony/ims/aidl/IRcs.aidl @@ -16,9 +16,18 @@ package android.telephony.ims.aidl; -import android.telephony.ims.RcsParticipant; -import android.telephony.ims.Rcs1To1Thread; -import android.telephony.ims.RcsThreadQueryContinuationToken; +import android.net.Uri; +import android.telephony.ims.RcsEventQueryParameters; +import android.telephony.ims.RcsEventQueryResult; +import android.telephony.ims.RcsFileTransferCreationParameters; +import android.telephony.ims.RcsIncomingMessageCreationParameters; +import android.telephony.ims.RcsMessageCreationParameters; +import android.telephony.ims.RcsMessageSnippet; +import android.telephony.ims.RcsMessageQueryParameters; +import android.telephony.ims.RcsMessageQueryResult; +import android.telephony.ims.RcsParticipantQueryParameters; +import android.telephony.ims.RcsParticipantQueryResult; +import android.telephony.ims.RcsQueryContinuationToken; import android.telephony.ims.RcsThreadQueryParameters; 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 getRcsThreadsWithToken( - in RcsThreadQueryContinuationToken continuationToken); + in RcsQueryContinuationToken continuationToken); - void deleteThread(int threadId); + RcsParticipantQueryResult getParticipants(in RcsParticipantQueryParameters queryParameters); - Rcs1To1Thread createRcs1To1Thread(in RcsParticipant participant); + RcsParticipantQueryResult getParticipantsWithToken( + in RcsQueryContinuationToken continuationToken); + RcsMessageQueryResult getMessages(in RcsMessageQueryParameters queryParameters); + + RcsMessageQueryResult getMessagesWithToken( + in RcsQueryContinuationToken continuationToken); + + RcsEventQueryResult getEvents(in RcsEventQueryParameters queryParameters); + + 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 RcsIncomingMessageCreationParameters rcsIncomingMessageCreationParameters); + + // Creates a new RcsOutgoingMessage on the given thread and returns its row ID + int addOutgoingMessage(int rcsThreadId, + in RcsMessageCreationParameters rcsMessageCreationParameters); + + // 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 RcsFileTransferCreationParameters fileTransferCreationParameters); + + 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/IPhoneStateListener.aidl b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl index 3dbebe832fac..322ce45797ec 100644 --- a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl +++ b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl @@ -27,6 +27,7 @@ import android.telephony.PreciseDataConnectionState; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.emergency.EmergencyNumber; +import android.telephony.ims.ImsReasonInfo; oneway interface IPhoneStateListener { void onServiceStateChanged(in ServiceState serviceState); @@ -58,5 +59,6 @@ oneway interface IPhoneStateListener { void onCallAttributesChanged(in CallAttributes callAttributes); void onEmergencyNumberListChanged(in Map emergencyNumberList); void onCallDisconnectCauseChanged(in int disconnectCause, in int preciseDisconnectCause); + void onImsCallDisconnectCauseChanged(in ImsReasonInfo imsReasonInfo); } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index d381514a3eec..c7061dfe25f1 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1484,25 +1484,34 @@ interface ITelephony { * Get the card ID of the default eUICC card. If there is no eUICC, returns * {@link #INVALID_CARD_ID}. * - * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} - * * @param subId subscription ID used for authentication * @param callingPackage package making the call * @return card ID of the default eUICC card. - * @hide */ - int getCardIdForDefaultEuicc(int subId, String callingPackage); + int getCardIdForDefaultEuicc(int subId, String callingPackage); /** - * Gets information about currently inserted UICCs and eUICCs. See {@link UiccCardInfo} for more - * details on the kind of information available. - * - * @return UiccCardInfo an array of UiccCardInfo objects, representing information on the - * currently inserted UICCs and eUICCs. + * Gets information about currently inserted UICCs and enabled eUICCs. + * <p> + * Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * <p> + * If the caller has carrier priviliges on any active subscription, then they have permission to + * get simple information like the card ID ({@link UiccCardInfo#getCardId()}), whether the card + * is an eUICC ({@link UiccCardInfo#isEuicc()}), and the slot index where the card is inserted + * ({@link UiccCardInfo#getSlotIndex()}). + * <p> + * To get private information such as the EID ({@link UiccCardInfo#getEid()}) or ICCID + * ({@link UiccCardInfo#getIccId()}), the caller must have carrier priviliges on that specific + * UICC or eUICC card. + * <p> + * See {@link UiccCardInfo} for more details on the kind of information available. * - * @hide + * @param callingPackage package making the call, used to evaluate carrier privileges + * @return a list of UiccCardInfo objects, representing information on the currently inserted + * UICCs and eUICCs. Each UiccCardInfo in the list will have private information filtered out if + * the caller does not have adequate permissions for that card. */ - UiccCardInfo[] getUiccCardsInfo(); + List<UiccCardInfo> getUiccCardsInfo(String callingPackage); /** * Get slot info for all the UICC slots. @@ -1808,4 +1817,31 @@ interface ITelephony { * Enable or disable a logical modem stack associated with the slotIndex. */ boolean enableModemForSlot(int slotIndex, boolean enable); + + /** + * Indicate if the enablement of multi SIM functionality is restricted. + * @hide + */ + void setMultisimCarrierRestriction(boolean isMultisimCarrierRestricted); + + /** + * Returns if the usage of multiple SIM cards at the same time is restricted. + * @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 + */ + boolean isRebootRequiredForModemConfigChange(); } diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl index 2be1f419db4d..e9eba324acb0 100644 --- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -22,6 +22,7 @@ import android.net.NetworkCapabilities; import android.os.Bundle; import android.telephony.CallQuality; import android.telephony.CellInfo; +import android.telephony.ims.ImsReasonInfo; import android.telephony.PhoneCapability; import android.telephony.PhysicalChannelConfig; import android.telephony.ServiceState; @@ -84,4 +85,5 @@ interface ITelephonyRegistry { void notifyRadioPowerStateChanged(in int state); void notifyEmergencyNumberList(); void notifyCallQualityChanged(in CallQuality callQuality, int phoneId); + void notifyImsDisconnectCause(int subId, in ImsReasonInfo imsReasonInfo); } 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/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/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java index 964a31304db5..9080e23eb88f 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; @@ -746,8 +745,10 @@ public class SmsMessage extends SmsMessageBase { /** * 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 +759,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,11 +858,11 @@ 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; 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..015efa6a0c7d 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java @@ -384,16 +384,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 +444,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 +462,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/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/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/RcsParticipantQueryParametersTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantQueryParametersTest.java new file mode 100644 index 000000000000..b4bcb5d12e0d --- /dev/null +++ b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantQueryParametersTest.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.RcsParticipantQueryParameters; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class RcsParticipantQueryParametersTest { + + @Test + public void testCanUnparcel() { + RcsParticipantQueryParameters rcsParticipantQueryParameters = + new RcsParticipantQueryParameters.Builder() + .setAliasLike("%alias_") + .setCanonicalAddressLike("_canonical%") + .setSortProperty(RcsParticipantQueryParameters.SORT_BY_CANONICAL_ADDRESS) + .setSortDirection(true) + .setResultLimit(432) + .build(); + + + Parcel parcel = Parcel.obtain(); + rcsParticipantQueryParameters.writeToParcel(parcel, + rcsParticipantQueryParameters.describeContents()); + + parcel.setDataPosition(0); + rcsParticipantQueryParameters = RcsParticipantQueryParameters.CREATOR.createFromParcel( + parcel); + + assertThat(rcsParticipantQueryParameters.getAliasLike()).isEqualTo("%alias_"); + assertThat(rcsParticipantQueryParameters.getCanonicalAddressLike()).contains("_canonical%"); + assertThat(rcsParticipantQueryParameters.getLimit()).isEqualTo(432); + assertThat(rcsParticipantQueryParameters.getSortingProperty()).isEqualTo( + RcsParticipantQueryParameters.SORT_BY_CANONICAL_ADDRESS); + assertThat(rcsParticipantQueryParameters.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 index a890a389bdfc..0a70eeccb0fa 100644 --- a/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParametersTest.java +++ b/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParametersTest.java @@ -15,39 +15,44 @@ */ package com.android.tests.ims; +import static android.telephony.ims.RcsThreadQueryParameters.SORT_BY_TIMESTAMP; +import static android.telephony.ims.RcsThreadQueryParameters.THREAD_TYPE_GROUP; + import static com.google.common.truth.Truth.assertThat; -import android.os.Bundle; +import android.os.Parcel; 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) + public void testCanUnparcel() { + RcsParticipant rcsParticipant = new RcsParticipant(1); + RcsThreadQueryParameters rcsThreadQueryParameters = new RcsThreadQueryParameters.Builder() + .setThreadType(THREAD_TYPE_GROUP) + .setParticipant(rcsParticipant) + .setResultLimit(50) + .setSortProperty(SORT_BY_TIMESTAMP) + .setSortDirection(true) .build(); - Bundle bundle = new Bundle(); - bundle.putParcelable(key, mRcsThreadQueryParameters); - mRcsThreadQueryParameters = bundle.getParcelable(key); + Parcel parcel = Parcel.obtain(); + rcsThreadQueryParameters.writeToParcel(parcel, rcsThreadQueryParameters.describeContents()); + + parcel.setDataPosition(0); + rcsThreadQueryParameters = RcsThreadQueryParameters.CREATOR.createFromParcel(parcel); - assertThat(mRcsThreadQueryParameters.isGroupThread()).isTrue(); - assertThat(mRcsThreadQueryParameters.getRcsParticipants()).contains(mMockParticipant); - assertThat(mRcsThreadQueryParameters.getLimit()).isEqualTo(50); - assertThat(mRcsThreadQueryParameters.isAscending()).isTrue(); + assertThat(rcsThreadQueryParameters.getThreadType()).isEqualTo(THREAD_TYPE_GROUP); + assertThat(rcsThreadQueryParameters.getRcsParticipantsIds()) + .contains(rcsParticipant.getId()); + assertThat(rcsThreadQueryParameters.getLimit()).isEqualTo(50); + assertThat(rcsThreadQueryParameters.getSortingProperty()).isEqualTo(SORT_BY_TIMESTAMP); + assertThat(rcsThreadQueryParameters.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/android/net/ip/InterfaceControllerTest.java b/tests/net/java/android/net/ip/InterfaceControllerTest.java new file mode 100644 index 000000000000..d27a4f99cfd9 --- /dev/null +++ b/tests/net/java/android/net/ip/InterfaceControllerTest.java @@ -0,0 +1,88 @@ +/* + * 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.ip; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.net.INetd; +import android.net.InetAddresses; +import android.net.InterfaceConfigurationParcel; +import android.net.LinkAddress; +import android.net.util.SharedLog; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class InterfaceControllerTest { + private static final String TEST_IFACE = "testif"; + private static final String TEST_IPV4_ADDR = "192.168.123.28"; + private static final int TEST_PREFIXLENGTH = 31; + + @Mock private INetd mNetd; + @Mock private SharedLog mLog; + @Captor private ArgumentCaptor<InterfaceConfigurationParcel> mConfigCaptor; + + private InterfaceController mController; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mController = new InterfaceController(TEST_IFACE, mNetd, mLog); + + doNothing().when(mNetd).interfaceSetCfg(mConfigCaptor.capture()); + } + + @Test + public void testSetIPv4Address() throws Exception { + mController.setIPv4Address( + new LinkAddress(InetAddresses.parseNumericAddress(TEST_IPV4_ADDR), + TEST_PREFIXLENGTH)); + verify(mNetd, times(1)).interfaceSetCfg(any()); + final InterfaceConfigurationParcel parcel = mConfigCaptor.getValue(); + assertEquals(TEST_IFACE, parcel.ifName); + assertEquals(TEST_IPV4_ADDR, parcel.ipv4Addr); + assertEquals(TEST_PREFIXLENGTH, parcel.prefixLength); + assertEquals("", parcel.hwAddr); + assertArrayEquals(new String[0], parcel.flags); + } + + @Test + public void testClearIPv4Address() throws Exception { + mController.clearIPv4Address(); + verify(mNetd, times(1)).interfaceSetCfg(any()); + final InterfaceConfigurationParcel parcel = mConfigCaptor.getValue(); + assertEquals(TEST_IFACE, parcel.ifName); + assertEquals("0.0.0.0", parcel.ipv4Addr); + assertEquals(0, parcel.prefixLength); + assertEquals("", parcel.hwAddr); + assertArrayEquals(new String[0], parcel.flags); + } +} 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 923c7dd5fb94..1548a76cc443 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -57,6 +57,7 @@ import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED; import static android.net.NetworkPolicyManager.RULE_NONE; import static android.net.NetworkPolicyManager.RULE_REJECT_ALL; import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; +import static android.net.shared.NetworkParcelableUtil.fromStableParcelable; import static com.android.internal.util.TestUtils.waitForIdleHandler; import static com.android.internal.util.TestUtils.waitForIdleLooper; @@ -119,6 +120,7 @@ import android.net.NetworkFactory; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkMisc; +import android.net.NetworkParcelable; import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.NetworkStack; @@ -192,6 +194,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; @@ -482,8 +485,8 @@ public class ConnectivityServiceTest { fail(e.getMessage()); } - final ArgumentCaptor<Network> nmNetworkCaptor = - ArgumentCaptor.forClass(Network.class); + final ArgumentCaptor<NetworkParcelable> nmNetworkCaptor = + ArgumentCaptor.forClass(NetworkParcelable.class); final ArgumentCaptor<INetworkMonitorCallbacks> nmCbCaptor = ArgumentCaptor.forClass(INetworkMonitorCallbacks.class); doNothing().when(mNetworkStack).makeNetworkMonitor( @@ -498,17 +501,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 @@ -523,7 +526,8 @@ public class ConnectivityServiceTest { } }; - assertEquals(mNetworkAgent.netId, nmNetworkCaptor.getValue().netId); + assertEquals( + mNetworkAgent.netId, fromStableParcelable(nmNetworkCaptor.getValue()).netId); mNmCallbacks = nmCbCaptor.getValue(); try { @@ -903,6 +907,7 @@ public class ConnectivityServiceTest { mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities()); mConnected = true; mConfig = new VpnConfig(); + mConfig.isMetered = false; } @Override @@ -3787,10 +3792,17 @@ public class ConnectivityServiceTest { @Test public void testNattSocketKeepalives() throws Exception { + final ExecutorService executorSingleThread = Executors.newSingleThreadExecutor(); + doTestNattSocketKeepalivesWithExecutor(executorSingleThread); + executorSingleThread.shutdown(); + + 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"); @@ -3804,8 +3816,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)); @@ -3922,6 +3932,11 @@ public class ConnectivityServiceTest { ka2.stop(); callback2.expectStopped(); + + testSocket.close(); + testSocket2.close(); + + mWiFiNetworkAgent.disconnect(); } @Test diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index 5b17224e41e5..46de3d0608ff 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.updateCapabilities( + 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.updateCapabilities( + 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.updateCapabilities( + 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.updateCapabilities( + 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.updateCapabilities( + 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..ec286759354a 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,16 @@ 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 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 +35,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 +60,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 +73,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 +80,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 +135,21 @@ 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)); } @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 +168,7 @@ public final class EntitlementManagerTest { @Test public void canRequireProvisioning() { setupForRequiredProvisioning(); - mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog)); assertTrue(mEnMgr.isTetherProvisioningRequired()); } @@ -113,7 +177,7 @@ 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)); // Couldn't get the CarrierConfigManager, but still had a declared provisioning app. // Therefore provisioning still be required. assertTrue(mEnMgr.isTetherProvisioningRequired()); @@ -123,7 +187,7 @@ 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)); // We still have a provisioning app configured, so still require provisioning. assertTrue(mEnMgr.isTetherProvisioningRequired()); } @@ -133,12 +197,143 @@ 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)); 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)); 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)); + // 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/tools/apilint/apilint.py b/tools/apilint/apilint.py index 75c3eba7765b..59e89f515e82 100644 --- a/tools/apilint/apilint.py +++ b/tools/apilint/apilint.py @@ -223,6 +223,7 @@ class Package(): class V2Tokenizer(object): __slots__ = ["raw"] + SIGNATURE_PREFIX = "// Signature format: " DELIMITER = re.compile(r'\s+|[()@<>;,={}/"!?]|\[\]|\.\.\.') STRING_SPECIAL = re.compile(r'["\\]') @@ -610,8 +611,12 @@ def _parse_stream_to_generator(f): else: blame = None - if line == 1 and raw == "// Signature format: 2.0": - sig_format = 2 + if line == 1 and raw.startswith("// Signature format: "): + sig_format_string = raw[len(V2Tokenizer.SIGNATURE_PREFIX):] + if sig_format_string in ["2.0", "3.0"]: + sig_format = 2 + else: + raise ValueError("Unknown format: %s" % (sig_format_string,)) elif raw.startswith("package"): pkg = Package(line, raw, blame) elif raw.startswith(" ") and raw.endswith("{"): diff --git a/tools/apilint/apilint_test.py b/tools/apilint/apilint_test.py index 9c261d506dac..3716bf9f5d6f 100644 --- a/tools/apilint/apilint_test.py +++ b/tools/apilint/apilint_test.py @@ -164,6 +164,23 @@ package android { self.assertEquals(api['android.SomeEnum'].ctors[0].split[0], 'ctor') self.assertEquals(api['android.SomeEnum'].methods[0].split[0], 'method') +class ParseV3Stream(unittest.TestCase): + def test_field_kinds(self): + api = apilint._parse_stream(""" +// Signature format: 3.0 +package a { + + public final class ContextKt { + method public static inline <reified T> T! getSystemService(android.content.Context); + method public static inline void withStyledAttributes(android.content.Context, android.util.AttributeSet? set = null, int[] attrs, @AttrRes int defStyleAttr = 0, @StyleRes int defStyleRes = 0, kotlin.jvm.functions.Function1<? super android.content.res.TypedArray,kotlin.Unit> block); + } +} + """.strip().split('\n')) + self.assertEquals(api['a.ContextKt'].methods[0].name, 'getSystemService') + self.assertEquals(api['a.ContextKt'].methods[0].split[:4], ['method', 'public', 'static', 'inline']) + self.assertEquals(api['a.ContextKt'].methods[1].name, 'withStyledAttributes') + self.assertEquals(api['a.ContextKt'].methods[1].split[:4], ['method', 'public', 'static', 'inline']) + class V2TokenizerTests(unittest.TestCase): def _test(self, raw, expected): self.assertEquals(apilint.V2Tokenizer(raw).tokenize(), expected) |