diff options
author | Hui Wang <huiwang@google.com> | 2020-12-17 22:53:31 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2020-12-17 22:53:31 +0000 |
commit | 7c9eef5613cd6e9716dd608cbc08715c4f2ff19f (patch) | |
tree | 5aa813df5687e192255637c07ab21ac368de3987 | |
parent | b8ff8f238e20cefbe4654d64b69cb7d003ce4610 (diff) | |
parent | e92ddfef4ff3aee092f09682704fc716fef44432 (diff) |
Merge "RCS Provisioning APIs for Single Registration"
13 files changed, 1044 insertions, 2 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 6e8adff46b16..3644eb8c3fd4 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -40254,6 +40254,7 @@ package android.telephony { field public static final String KEY_TREAT_DOWNGRADED_VIDEO_CALLS_AS_VIDEO_CALLS_BOOL = "treat_downgraded_video_calls_as_video_calls_bool"; field public static final String KEY_TTY_SUPPORTED_BOOL = "tty_supported_bool"; field public static final String KEY_UNLOGGABLE_NUMBERS_STRING_ARRAY = "unloggable_numbers_string_array"; + field public static final String KEY_USE_ACS_FOR_RCS_BOOL = "use_acs_for_rcs_bool"; field public static final String KEY_USE_HFA_FOR_PROVISIONING_BOOL = "use_hfa_for_provisioning_bool"; field public static final String KEY_USE_OTASP_FOR_PROVISIONING_BOOL = "use_otasp_for_provisioning_bool"; field public static final String KEY_USE_RCS_PRESENCE_BOOL = "use_rcs_presence_bool"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 572d6e6c2cfc..cbd65d174fb0 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -11448,18 +11448,29 @@ package android.telephony.ims { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getProvisioningStatusForCapability(int, int); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public String getProvisioningStringValue(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getRcsProvisioningStatusForCapability(int); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isRcsVolteSingleRegistrationCapable() throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException; + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerRcsProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.RcsProvisioningCallback) 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) @WorkerThread public void setProvisioningStatusForCapability(int, int, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setRcsClientConfiguration(@NonNull android.telephony.ims.RcsClientConfiguration) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void triggerRcsReconfiguration(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterRcsProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.RcsProvisioningCallback); + field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final String ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE = "android.telephony.ims.action.RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE"; + field public static final String EXTRA_STATUS = "android.telephony.ims.extra.STATUS"; + field public static final String EXTRA_SUBSCRIPTION_ID = "android.telephony.ims.extra.SUBSCRIPTION_ID"; field public static final int KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID = 67; // 0x43 field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0 field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1 + field public static final int STATUS_CAPABLE = 0; // 0x0 + field public static final int STATUS_CARRIER_NOT_CAPABLE = 2; // 0x2 + field public static final int STATUS_DEVICE_NOT_CAPABLE = 1; // 0x1 field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC"; field public static final String STRING_QUERY_RESULT_ERROR_NOT_READY = "STRING_QUERY_RESULT_ERROR_NOT_READY"; } @@ -11470,6 +11481,27 @@ package android.telephony.ims { method public void onProvisioningStringChanged(int, @NonNull String); } + public static class ProvisioningManager.RcsProvisioningCallback { + ctor public ProvisioningManager.RcsProvisioningCallback(); + method public void onAutoConfigurationErrorReceived(int, @NonNull String); + method public void onConfigurationChanged(@NonNull byte[]); + method public void onConfigurationReset(); + method public void onRemoved(); + } + + public final class RcsClientConfiguration implements android.os.Parcelable { + ctor public RcsClientConfiguration(@NonNull String, @NonNull String, @NonNull String, @NonNull String); + method public int describeContents(); + method @NonNull public String getClientVendor(); + method @NonNull public String getClientVersion(); + method @NonNull public String getRcsProfile(); + method @NonNull public String getRcsVersion(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsClientConfiguration> CREATOR; + field public static final String RCS_PROFILE_1_0 = "UP_1.0"; + field public static final String RCS_PROFILE_2_3 = "UP_2.3"; + } + public class RcsUceAdapter { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUceSettingEnabled(boolean) throws android.telephony.ims.ImsException; } @@ -11758,11 +11790,15 @@ package android.telephony.ims.stub { ctor public ImsConfigImplBase(); method public int getConfigInt(int); method public String getConfigString(int); + method public final void notifyAutoConfigurationErrorReceived(int, @NonNull String); method public final void notifyProvisionedValueChanged(int, int); method public final void notifyProvisionedValueChanged(int, String); method public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean); + method public void notifyRcsAutoConfigurationRemoved(); method public int setConfig(int, int); method public int setConfig(int, String); + method public void setRcsClientConfiguration(@NonNull android.telephony.ims.RcsClientConfiguration); + method public void triggerAutoConfiguration(); field public static final int CONFIG_RESULT_FAILED = 1; // 0x1 field public static final int CONFIG_RESULT_SUCCESS = 0; // 0x0 field public static final int CONFIG_RESULT_UNKNOWN = -1; // 0xffffffff diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 99c82c507ae8..727769cb5ab8 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -5278,5 +5278,13 @@ public final class Telephony { * @hide */ public static final String COLUMN_ALLOWED_NETWORK_TYPES = "allowed_network_types"; + + /** + * TelephonyProvider column name for RCS configuration. + * <p>TYPE: BLOB + * + * @hide + */ + public static final String COLUMN_RCS_CONFIG = "rcs_config"; } } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 27ea6902146f..11bb8674d5a9 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -4018,6 +4018,12 @@ public class CarrierConfigManager { public static final String KEY_USE_LOWER_MTU_VALUE_IF_BOTH_RECEIVED = "use_lower_mtu_value_if_both_received"; + /** + * Indicates if auto-configuration server is used for the RCS config + * Reference: GSMA RCC.14 + */ + public static final String KEY_USE_ACS_FOR_RCS_BOOL = "use_acs_for_rcs_bool"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -4561,6 +4567,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false); sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, ""); sDefaults.putBoolean(KEY_USE_LOWER_MTU_VALUE_IF_BOTH_RECEIVED, false); + sDefaults.putBoolean(KEY_USE_ACS_FOR_RCS_BOOL, false); } /** diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index 0fe76a04bf9d..f3c38bcba98a 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -21,6 +21,7 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; import android.annotation.StringDef; import android.annotation.SystemApi; import android.annotation.WorkerThread; @@ -31,6 +32,7 @@ import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.TelephonyFrameworkInitializer; import android.telephony.ims.aidl.IImsConfigCallback; +import android.telephony.ims.aidl.IRcsConfigCallback; import android.telephony.ims.feature.MmTelFeature; import android.telephony.ims.feature.RcsFeature; import android.telephony.ims.stub.ImsConfigImplBase; @@ -936,6 +938,115 @@ public class ProvisioningManager { private int mSubId; /** + * The callback for RCS provisioning changes. + */ + public static class RcsProvisioningCallback { + private static class CallbackBinder extends IRcsConfigCallback.Stub { + + private final RcsProvisioningCallback mLocalCallback; + private Executor mExecutor; + + private CallbackBinder(RcsProvisioningCallback localCallback) { + mLocalCallback = localCallback; + } + + @Override + public void onConfigurationChanged(byte[] configXml) { + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mLocalCallback.onConfigurationChanged(configXml)); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void onAutoConfigurationErrorReceived(int errorCode, String errorString) { + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mLocalCallback.onAutoConfigurationErrorReceived( + errorCode, errorString)); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void onConfigurationReset() { + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mLocalCallback.onConfigurationReset()); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void onRemoved() { + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mLocalCallback.onRemoved()); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + private void setExecutor(Executor executor) { + mExecutor = executor; + } + } + + private final CallbackBinder mBinder = new CallbackBinder(this); + + /** + * RCS configuration received via OTA provisioning. Configuration may change + * due to various triggers defined in GSMA RCC.14 for ACS(auto configuration + * server) or other operator defined triggers. If RCS provisioning is already + * completed at the time of callback registration, then this method shall be + * invoked with the current configuration + * @param configXml The RCS configurationXML received OTA. + */ + public void onConfigurationChanged(@NonNull byte[] configXml) {} + + /** + * Errors during autoconfiguration connection setup are notified by the + * ACS(auto configuration server) client using this interface. + * @param errorCode HTTP error received during connection setup defined in + * GSMA RCC.14 2.4.3, like {@link java.net.HttpURLConnection#HTTP_UNAUTHORIZED}, + * {@link java.net.HttpURLConnection#HTTP_FORBIDDEN}, etc. + * @param errorString reason phrase received with the error + */ + public void onAutoConfigurationErrorReceived(int errorCode, + @NonNull String errorString) {} + + /** + * When the previously valid RCS configuration is cleaned up by telephony for + * any case like SIM removed, default messaging application changed, etc., + * this method will be invoked to notify the application regarding this change. + */ + public void onConfigurationReset() {} + + /** + * When the RCS application is no longer the Default messaging application, + * or when the subscription associated with this callback is removed (SIM + * removed, ESIM swap,etc...), callback will automatically be removed and + * the below method is invoked. There is a possibility that the method is + * invoked after the subscription has become inactive + */ + public void onRemoved() {} + + /**@hide*/ + public final IRcsConfigCallback getBinder() { + return mBinder; + } + + /**@hide*/ + public void setExecutor(Executor executor) { + mBinder.setExecutor(executor); + } + } + + /** * Create a new {@link ProvisioningManager} for the subscription specified. * * @param subId The ID of the subscription that this ProvisioningManager will use. @@ -1207,6 +1318,174 @@ public class ProvisioningManager { } + /** + * Provides the single registration capability of the device and the carrier. + * + * <p>This intent only provides the capability and not the current provisioning status of + * the RCS VoLTE single registration feature. Only default messaging application may receive + * the intent. + * + * <p>Contains {@link #EXTRA_SUBSCRIPTION_INDEX} to specify the subscription index for which + * the intent is valid. and {@link #EXTRA_STATUS} to specify RCS VoLTE single registration + * status. + */ + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE = + "android.telephony.ims.action.RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE"; + + /** + * Integer extra to specify subscription index. + */ + public static final String EXTRA_SUBSCRIPTION_ID = + "android.telephony.ims.extra.SUBSCRIPTION_ID"; + + /** + * Integer extra to specify RCS single registration status + * + * <p>The value can be {@link #STATUS_CAPABLE}, {@link #STATUS_DEVICE_NOT_CAPABLE}, + * {@link #STATUS_CARRIER_NOT_CAPABLE}, or bitwise OR of + * {@link #STATUS_DEVICE_NOT_CAPABLE} and {@link #STATUS_CARRIER_NOT_CAPABLE}. + */ + public static final String EXTRA_STATUS = "android.telephony.ims.extra.STATUS"; + + /** + * RCS VoLTE single registration is supported by the device and carrier. + */ + public static final int STATUS_CAPABLE = 0; + + /** + * RCS VoLTE single registration is not supported by the device. + */ + public static final int STATUS_DEVICE_NOT_CAPABLE = 0x01; + + /** + * RCS VoLTE single registration is not supported by the carrier + */ + public static final int STATUS_CARRIER_NOT_CAPABLE = 0x01 << 1; + + /** + * Provide the client configuration parameters of the RCS application. + * + * <p>When this application is also the default messaging application, and RCS + * provisioning is done using autoconfiguration, then these parameters shall be + * sent in the HTTP get request to fetch the RCS provisioning. RCS client + * configuration must be provided by the application before registering for the + * provisioning status events {@link #registerRcsProvisioningChangedCallback()} + * @param rcc RCS client configuration {@link RcsClientConfiguration} + */ + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void setRcsClientConfiguration( + @NonNull RcsClientConfiguration rcc) throws ImsException { + try { + getITelephony().setRcsClientConfiguration(mSubId, rcc); + } catch (ServiceSpecificException e) { + throw new ImsException(e.getMessage(), e.errorCode); + } catch (RemoteException | IllegalStateException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + } + } + + /** + * Returns a flag to indicate if the device software and the carrier + * have the capability to support RCS Volte single IMS registration. + * @return true if this single registration is capable, false otherwise + * @throws ImsException If the remote ImsService is not available for + * any reason or the subscription associated with this instance is no + * longer active. See {@link ImsException#getCode()} for more + * information. + */ + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public boolean isRcsVolteSingleRegistrationCapable() throws ImsException { + try { + return getITelephony().isRcsVolteSingleRegistrationCapable(mSubId); + } catch (RemoteException | IllegalStateException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + } + } + + /** + * Registers a new {@link RcsProvisioningCallback} to listen to changes to + * RCS provisioning xml. + * + * <p>RCS application must be the default messaging application and must + * have already registered its {@link RcsClientConfiguration} by using + * {@link #setRcsClientConfiguration} before it registers the provisioning + * callback. If ProvisioningManager has a valid RCS configuration at the + * time of callback registration and a reconfiguration is not required + * due to RCS client parameters change, then the callback shall be invoked + * immediately with the xml. + * When the subscription associated with this callback is removed (SIM removed, + * ESIM swap,etc...), this callback will automatically be removed. + * + * @param executor The {@link Executor} to call the callback methods on + * @param callback The rcs provisioning callback to be registered. + * @see #unregisterRcsProvisioningChangedCallback(RcsProvisioningCallback) + * @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 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. + * It shall also throw this exception when the RCS client parameters for the + * application are not valid. In that case application must set the client + * params (See {@link #setRcsClientConfiguration()}) and re register the + * callback. + * See {@link ImsException#getCode()} for a more detailed reason. + */ + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public void registerRcsProvisioningChangedCallback( + @NonNull @CallbackExecutor Executor executor, + @NonNull RcsProvisioningCallback callback) throws ImsException { + callback.setExecutor(executor); + try { + getITelephony().registerRcsProvisioningChangedCallback(mSubId, callback.getBinder()); + } catch (ServiceSpecificException e) { + throw new ImsException(e.getMessage(), e.errorCode); + } catch (RemoteException | IllegalStateException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + } + } + + /** + * Unregister an existing {@link RcsProvisioningCallback}. Application can + * unregister when its no longer interested in the provisioning updates + * like when a user disables RCS from the UI/settings. + * When the subscription associated with this callback is removed (SIM + * removed, ESIM swap, etc...), this callback will automatically be + * removed. If this method is called for an inactive subscription, it + * will result in a no-op. + * @param callback The existing {@link RcsProvisioningCallback} to be + * removed. + * @see #registerRcsProvisioningChangedCallback(RcsClientConfiguration, + * Executor, RcsProvisioningCallback) @throws IllegalArgumentException + * if the subscription associated with this callback is invalid. + */ + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public void unregisterRcsProvisioningChangedCallback( + @NonNull RcsProvisioningCallback callback) { + try { + getITelephony().unregisterRcsProvisioningChangedCallback( + mSubId, callback.getBinder()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Reconfiguration triggered by the RCS application. Most likely cause + * is the 403 forbidden to a HTTP request. + */ + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public void triggerRcsReconfiguration() { + try { + getITelephony().triggerRcsReconfiguration(mSubId); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + private static ITelephony getITelephony() { ITelephony binder = ITelephony.Stub.asInterface( TelephonyFrameworkInitializer diff --git a/telephony/java/android/telephony/ims/RcsClientConfiguration.aidl b/telephony/java/android/telephony/ims/RcsClientConfiguration.aidl new file mode 100644 index 000000000000..a702f0f42c54 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsClientConfiguration.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.ims; + +parcelable RcsClientConfiguration; diff --git a/telephony/java/android/telephony/ims/RcsClientConfiguration.java b/telephony/java/android/telephony/ims/RcsClientConfiguration.java new file mode 100644 index 000000000000..793c37745de6 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsClientConfiguration.java @@ -0,0 +1,162 @@ +/* + * 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.StringDef; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * The container of RCS application related configs. + * + * @hide + */ +@SystemApi +public final class RcsClientConfiguration implements Parcelable { + + /**@hide*/ + @StringDef(prefix = "RCS_PROFILE_", + value = {RCS_PROFILE_1_0, RCS_PROFILE_2_3}) + public @interface StringRcsProfile {} + + /** + * RCS profile UP 1.0 + */ + public static final String RCS_PROFILE_1_0 = "UP_1.0"; + /** + * RCS profile UP 2.3 + */ + public static final String RCS_PROFILE_2_3 = "UP_2.3"; + + private String mRcsVersion; + private String mRcsProfile; + private String mClientVendor; + private String mClientVersion; + + /** + * Create a RcsClientConfiguration object. + * Default messaging application must pass a valid configuration object + * @param rcsVersion The parameter identifies the RCS version supported + * by the client. Refer to GSMA RCC.07 "rcs_version" parameter. + * @param rcsProfile Identifies a fixed set of RCS services that are + * supported by the client. See {@link #RCS_PROFILE_1_0 } or + * {@link #RCS_PROFILE_2_3 } + * @param clientVendor Identifies the vendor providing the RCS client. + * @param clientVersion Identifies the RCS client version. Refer to GSMA + * RCC.07 "client_version" parameter. + * Example:client_version=RCSAndrd-1.0 + */ + public RcsClientConfiguration(@NonNull String rcsVersion, + @NonNull @StringRcsProfile String rcsProfile, + @NonNull String clientVendor, @NonNull String clientVersion) { + mRcsVersion = rcsVersion; + mRcsProfile = rcsProfile; + mClientVendor = clientVendor; + mClientVersion = clientVersion; + } + + /** + * Returns RCS version supported. + */ + public @NonNull String getRcsVersion() { + return mRcsVersion; + } + + /** + * Returns RCS profile supported. + */ + public @NonNull @StringRcsProfile String getRcsProfile() { + return mRcsProfile; + } + + /** + * Returns the name of the vendor providing the RCS client. + */ + public @NonNull String getClientVendor() { + return mClientVendor; + } + + /** + * Returns the RCS client version. + */ + public @NonNull String getClientVersion() { + return mClientVersion; + } + + /** + * {@link Parcelable#writeToParcel} + */ + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeString(mRcsVersion); + out.writeString(mRcsProfile); + out.writeString(mClientVendor); + out.writeString(mClientVersion); + } + + /** + * {@link Parcelable.Creator} + * + */ + public static final @android.annotation.NonNull Parcelable.Creator< + RcsClientConfiguration> CREATOR = new Creator<RcsClientConfiguration>() { + @Override + public RcsClientConfiguration createFromParcel(Parcel in) { + String rcsVersion = in.readString(); + String rcsProfile = in.readString(); + String clientVendor = in.readString(); + String clientVersion = in.readString(); + return new RcsClientConfiguration(rcsVersion, rcsProfile, + clientVendor, clientVersion); + } + + @Override + public RcsClientConfiguration[] newArray(int size) { + return new RcsClientConfiguration[size]; + } + }; + + /** + * {@link Parcelable#describeContents} + */ + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof RcsClientConfiguration)) { + return false; + } + + RcsClientConfiguration other = (RcsClientConfiguration) obj; + + return mRcsVersion.equals(other.mRcsVersion) && mRcsProfile.equals(other.mRcsProfile) + && mClientVendor.equals(other.mClientVendor) + && mClientVersion.equals(other.mClientVersion); + } + + @Override + public int hashCode() { + return Objects.hash(mRcsVersion, mRcsProfile, mClientVendor, mClientVersion); + } +} diff --git a/telephony/java/android/telephony/ims/RcsConfig.aidl b/telephony/java/android/telephony/ims/RcsConfig.aidl new file mode 100644 index 000000000000..cfd93fbe2edb --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsConfig.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.ims; + +parcelable RcsConfig; diff --git a/telephony/java/android/telephony/ims/RcsConfig.java b/telephony/java/android/telephony/ims/RcsConfig.java new file mode 100644 index 000000000000..07e95cc99290 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsConfig.java @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.ims; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.provider.Telephony.SimInfo; +import android.text.TextUtils; + +import com.android.telephony.Rlog; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +/** + * RCS config data and methods to process the config + * @hide + */ +public final class RcsConfig implements Parcelable { + private static final String LOG_TAG = "RcsConfig"; + private static final boolean DBG = Build.IS_ENG; + + private final HashMap<String, String> mValues = new HashMap<>(); + + private RcsConfig(HashMap<String, String> values) { + mValues.putAll(values); + } + + public RcsConfig(byte[] data) throws IllegalArgumentException { + if (data == null || data.length == 0) { + throw new IllegalArgumentException("Empty data"); + } + ByteArrayInputStream inputStream = new ByteArrayInputStream(data); + try { + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(true); + XmlPullParser xpp = factory.newPullParser(); + xpp.setInput(inputStream, null); + int eventType = xpp.getEventType(); + String tag = null; + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + tag = xpp.getName().trim(); + } else if (eventType == XmlPullParser.END_TAG) { + tag = null; + } else if (eventType == XmlPullParser.TEXT) { + String value = xpp.getText().trim(); + if (!TextUtils.isEmpty(tag) && !TextUtils.isEmpty(value)) { + mValues.put(tag, value); + } + } + eventType = xpp.next(); + } + } catch (IOException | XmlPullParserException e) { + throw new IllegalArgumentException(e); + } finally { + try { + inputStream.close(); + } catch (IOException e) { + loge("error to close input stream, skip."); + } + } + } + + /** + * Retrieve a String value of the config item with the tag + * + * @param tag The name of the config to retrieve. + * @param defaultVal Value to return if the config does not exist. + * + * @return Returns the config value if it exists, or defaultVal. + */ + public @Nullable String getString(@NonNull String tag, @Nullable String defaultVal) { + return mValues.containsKey(tag) ? mValues.get(tag) : defaultVal; + } + + /** + * Retrieve a int value of the config item with the tag + * + * @param tag The name of the config to retrieve. + * @param defaultVal Value to return if the config does not exist or not valid. + * + * @return Returns the config value if it exists and is a valid int, or defaultVal. + */ + public int getInteger(@NonNull String tag, int defaultVal) { + try { + return Integer.parseInt(mValues.get(tag)); + } catch (NumberFormatException e) { + logd("error to getInteger for " + tag + " due to " + e); + } + return defaultVal; + } + + /** + * Retrieve a boolean value of the config item with the tag + * + * @param tag The name of the config to retrieve. + * @param defaultVal Value to return if the config does not exist. + * + * @return Returns the config value if it exists, or defaultVal. + */ + public boolean getBoolean(@NonNull String tag, boolean defaultVal) { + if (!mValues.containsKey(tag)) { + return defaultVal; + } + return Boolean.parseBoolean(mValues.get(tag)); + } + + /** + * Check whether the config item exists + * + * @param tag The name of the config to retrieve. + * + * @return Returns true if it exists, or false. + */ + public boolean hasConfig(@NonNull String tag) { + return mValues.containsKey(tag); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("[RCS Config]"); + if (DBG) { + mValues.forEach((t, v) -> { + sb.append("\n"); + sb.append(t); + sb.append(" : "); + sb.append(v); + }); + } + return sb.toString(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof RcsConfig)) { + return false; + } + + RcsConfig other = (RcsConfig) obj; + + return mValues.equals(other.mValues); + } + + @Override + public int hashCode() { + return mValues.hashCode(); + } + + /** + * compress the gzip format data + */ + public static @Nullable byte[] compressGzip(@NonNull byte[] data) { + if (data == null || data.length == 0) { + return data; + } + byte[] out = null; + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length); + GZIPOutputStream gzipCompressingStream = + new GZIPOutputStream(outputStream); + gzipCompressingStream.write(data); + gzipCompressingStream.close(); + out = outputStream.toByteArray(); + outputStream.close(); + } catch (IOException e) { + loge("Error to compressGzip due to " + e); + } + return out; + } + + /** + * decompress the gzip format data + */ + public static @Nullable byte[] decompressGzip(@NonNull byte[] data) { + if (data == null || data.length == 0) { + return data; + } + byte[] out = null; + try { + ByteArrayInputStream inputStream = new ByteArrayInputStream(data); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + GZIPInputStream gzipDecompressingStream = + new GZIPInputStream(inputStream); + byte[] buf = new byte[1024]; + int size = gzipDecompressingStream.read(buf); + while (size >= 0) { + outputStream.write(buf, 0, size); + size = gzipDecompressingStream.read(buf); + } + gzipDecompressingStream.close(); + inputStream.close(); + out = outputStream.toByteArray(); + outputStream.close(); + } catch (IOException e) { + loge("Error to decompressGzip due to " + e); + } + return out; + } + + /** + * save the config to siminfo db. It is only used internally. + */ + public static void updateConfigForSub(@NonNull Context cxt, int subId, + @NonNull byte[] config, boolean isCompressed) { + //always store gzip compressed data + byte[] data = isCompressed ? config : compressGzip(config); + ContentValues values = new ContentValues(); + values.put(SimInfo.COLUMN_RCS_CONFIG, data); + cxt.getContentResolver().update(SimInfo.CONTENT_URI, values, + SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID + "=" + subId, null); + } + + /** + * load the config from siminfo db. It is only used internally. + */ + public static @Nullable byte[] loadRcsConfigForSub(@NonNull Context cxt, + int subId, boolean isCompressed) { + + byte[] data = null; + + Cursor cursor = cxt.getContentResolver().query(SimInfo.CONTENT_URI, null, + SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID + "=" + subId, null, null); + try { + if (cursor != null && cursor.moveToFirst()) { + data = cursor.getBlob(cursor.getColumnIndexOrThrow(SimInfo.COLUMN_RCS_CONFIG)); + } + } catch (Exception e) { + loge("error to load rcs config for sub:" + subId + " due to " + e); + } finally { + if (cursor != null) { + cursor.close(); + } + } + return isCompressed ? data : decompressGzip(data); + } + + /** + * {@link Parcelable#writeToParcel} + */ + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeMap(mValues); + } + + /** + * {@link Parcelable.Creator} + * + */ + public static final @NonNull Parcelable.Creator<RcsConfig> + CREATOR = new Creator<RcsConfig>() { + @Override + public RcsConfig createFromParcel(Parcel in) { + HashMap<String, String> values = in.readHashMap(null); + return values == null ? null : new RcsConfig(values); + } + + @Override + public RcsConfig[] newArray(int size) { + return new RcsConfig[size]; + } + }; + + /** + * {@link Parcelable#describeContents} + */ + public int describeContents() { + return 0; + } + + private static void logd(String msg) { + Rlog.d(LOG_TAG, msg); + } + + private static void loge(String msg) { + Rlog.e(LOG_TAG, msg); + } +} diff --git a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl index 57206c9f059a..5eee3890f1dc 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl @@ -18,8 +18,9 @@ package android.telephony.ims.aidl; import android.os.PersistableBundle; - import android.telephony.ims.aidl.IImsConfigCallback; +import android.telephony.ims.aidl.IRcsConfigCallback; +import android.telephony.ims.RcsClientConfiguration; import com.android.ims.ImsConfigListener; @@ -41,4 +42,9 @@ interface IImsConfig { int setConfigString(int item, String value); void updateImsCarrierConfigs(in PersistableBundle bundle); void notifyRcsAutoConfigurationReceived(in byte[] config, boolean isCompressed); + void notifyRcsAutoConfigurationRemoved(); + void addRcsConfigCallback(IRcsConfigCallback c); + void removeRcsConfigCallback(IRcsConfigCallback c); + void triggerRcsReconfiguration(); + void setRcsClientConfiguration(in RcsClientConfiguration rcc); } diff --git a/telephony/java/android/telephony/ims/aidl/IRcsConfigCallback.aidl b/telephony/java/android/telephony/ims/aidl/IRcsConfigCallback.aidl new file mode 100644 index 000000000000..5a8973e37bce --- /dev/null +++ b/telephony/java/android/telephony/ims/aidl/IRcsConfigCallback.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.ims.aidl; + +/** + * The callback for RCS provisioning changes. + * {@hide} + */ +oneway interface IRcsConfigCallback { + void onConfigurationChanged(in byte[] config); + void onAutoConfigurationErrorReceived(int errorCode, String errorString); + void onConfigurationReset(); + void onRemoved(); +} + diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java index e757d9f70ccc..cc050becfb25 100644 --- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java @@ -23,8 +23,11 @@ import android.content.Context; import android.os.PersistableBundle; import android.os.RemoteException; import android.telephony.ims.ProvisioningManager; +import android.telephony.ims.RcsClientConfiguration; +import android.telephony.ims.RcsConfig; import android.telephony.ims.aidl.IImsConfig; import android.telephony.ims.aidl.IImsConfigCallback; +import android.telephony.ims.aidl.IRcsConfigCallback; import android.util.Log; import com.android.ims.ImsConfig; @@ -202,7 +205,13 @@ public class ImsConfigImplBase { @Override public void notifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed) throws RemoteException { - getImsConfigImpl().notifyRcsAutoConfigurationReceived(config, isCompressed); + getImsConfigImpl().onNotifyRcsAutoConfigurationReceived(config, isCompressed); + } + + @Override + public void notifyRcsAutoConfigurationRemoved() + throws RemoteException { + getImsConfigImpl().onNotifyRcsAutoConfigurationRemoved(); } private void notifyImsConfigChanged(int item, int value) throws RemoteException { @@ -228,6 +237,26 @@ public class ImsConfigImplBase { notifyImsConfigChanged(item, value); } } + + @Override + public void addRcsConfigCallback(IRcsConfigCallback c) throws RemoteException { + getImsConfigImpl().addRcsConfigCallback(c); + } + + @Override + public void removeRcsConfigCallback(IRcsConfigCallback c) throws RemoteException { + getImsConfigImpl().removeRcsConfigCallback(c); + } + + @Override + public void triggerRcsReconfiguration() throws RemoteException { + getImsConfigImpl().triggerAutoConfiguration(); + } + + @Override + public void setRcsClientConfiguration(RcsClientConfiguration rcc) throws RemoteException { + getImsConfigImpl().setRcsClientConfiguration(rcc); + } } /** @@ -257,6 +286,9 @@ public class ImsConfigImplBase { private final RemoteCallbackListExt<IImsConfigCallback> mCallbacks = new RemoteCallbackListExt<>(); + private final RemoteCallbackListExt<IRcsConfigCallback> mRcsCallbacks = + new RemoteCallbackListExt<>(); + private byte[] mRcsConfigData; ImsConfigStub mImsConfigStub; /** @@ -320,6 +352,50 @@ public class ImsConfigImplBase { }); } + private void addRcsConfigCallback(IRcsConfigCallback c) { + mRcsCallbacks.register(c); + if (mRcsConfigData != null) { + try { + c.onConfigurationChanged(mRcsConfigData); + } catch (RemoteException e) { + Log.w(TAG, "dead binder to call onConfigurationChanged, skipping."); + } + } + } + + private void removeRcsConfigCallback(IRcsConfigCallback c) { + mRcsCallbacks.unregister(c); + } + + private void onNotifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed) { + mRcsConfigData = isCompressed ? RcsConfig.decompressGzip(config) : config; + // can be null in testing + if (mRcsCallbacks != null) { + mRcsCallbacks.broadcastAction(c -> { + try { + c.onConfigurationChanged(mRcsConfigData); + } catch (RemoteException e) { + Log.w(TAG, "dead binder in notifyRcsAutoConfigurationReceived, skipping."); + } + }); + } + notifyRcsAutoConfigurationReceived(config, isCompressed); + } + + private void onNotifyRcsAutoConfigurationRemoved() { + mRcsConfigData = null; + if (mRcsCallbacks != null) { + mRcsCallbacks.broadcastAction(c -> { + try { + c.onConfigurationReset(); + } catch (RemoteException e) { + Log.w(TAG, "dead binder in notifyRcsAutoConfigurationRemoved, skipping."); + } + }); + } + notifyRcsAutoConfigurationRemoved(); + } + /** * @hide */ @@ -369,6 +445,12 @@ public class ImsConfigImplBase { } /** + * The RCS autoconfiguration XML file is removed or invalid. + */ + public void notifyRcsAutoConfigurationRemoved() { + } + + /** * Sets the configuration value for this ImsService. * * @param item an integer key. @@ -421,4 +503,43 @@ public class ImsConfigImplBase { public void updateImsCarrierConfigs(PersistableBundle bundle) { // Base Implementation - Should be overridden } + + /** + * Default messaging application parameters are sent to the ACS client + * using this interface. + * @param rcc RCS client configuration {@link RcsClientConfiguration} + */ + public void setRcsClientConfiguration(@NonNull RcsClientConfiguration rcc) { + // Base Implementation - Should be overridden + } + + /** + * Reconfiguration triggered by the RCS application. Most likely cause + * is the 403 forbidden to a SIP/HTTP request + */ + public void triggerAutoConfiguration() { + // Base Implementation - Should be overridden + } + + /** + * Errors during autoconfiguration connection setup are notified by the + * ACS client using this interface. + * @param errorCode HTTP error received during connection setup. + * @param errorString reason phrase received with the error + */ + public final void notifyAutoConfigurationErrorReceived(int errorCode, + @NonNull String errorString) { + // can be null in testing + if (mRcsCallbacks == null) { + return; + } + mRcsCallbacks.broadcastAction(c -> { + try { + //TODO compressed by default? + c.onAutoConfigurationErrorReceived(errorCode, errorString); + } catch (RemoteException e) { + Log.w(TAG, "dead binder in notifyAutoConfigurationErrorReceived, skipping."); + } + }); + } } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 205a425a5161..7c5047c2deaf 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -52,6 +52,7 @@ import android.telephony.SignalStrength; import android.telephony.TelephonyHistogram; import android.telephony.VisualVoicemailSmsFilterSettings; import android.telephony.emergency.EmergencyNumber; +import android.telephony.ims.RcsClientConfiguration; import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.aidl.IImsConfig; import android.telephony.ims.aidl.IImsConfigCallback; @@ -59,6 +60,7 @@ import android.telephony.ims.aidl.IImsMmTelFeature; import android.telephony.ims.aidl.IImsRcsFeature; import android.telephony.ims.aidl.IImsRegistration; import android.telephony.ims.aidl.IImsRegistrationCallback; +import android.telephony.ims.aidl.IRcsConfigCallback; import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.telephony.CellNetworkScanResult; import com.android.internal.telephony.IBooleanConsumer; @@ -2303,4 +2305,51 @@ interface ITelephony { * Return the release time for telephony to unbind GbaService. */ int getGbaReleaseTime(int subId); + + /** + * Provide the client configuration parameters of the RCS application. + */ + void setRcsClientConfiguration(int subId, in RcsClientConfiguration rcc); + + /** + * return value to indicate whether the device and the carrier can support RCS VoLTE + * single registration. + */ + boolean isRcsVolteSingleRegistrationCapable(int subId); + + /** + * Register RCS provisioning callback. + */ + void registerRcsProvisioningChangedCallback(int subId, + IRcsConfigCallback callback); + + /** + * Unregister RCS provisioning callback. + */ + void unregisterRcsProvisioningChangedCallback(int subId, IRcsConfigCallback callback); + + /** + * trigger RCS reconfiguration. + */ + void triggerRcsReconfiguration(int subId); + + /** + * Overrides the config of RCS VoLTE single registration enabled for the device. + */ + void setDeviceSingleRegistrationEnabledOverride(String enabled); + + /** + * Gets the config of RCS VoLTE single registration enabled for the device. + */ + boolean getDeviceSingleRegistrationEnabled(); + + /** + * Overrides the config of RCS VoLTE single registration enabled for the carrier/subscription. + */ + boolean setCarrierSingleRegistrationEnabledOverride(int subId, String enabled); + + /** + * Gets the config of RCS VoLTE single registration enabled for the carrier/subscription. + */ + boolean getCarrierSingleRegistrationEnabled(int subId); } |