Merge "[Settings] Implement APIs in PhoneInterfaceManager"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 98d1919..a1949dd 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -129,7 +129,7 @@
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.SEND_RESPOND_VIA_MESSAGE" />
<uses-permission android:name="android.permission.SET_TIME_ZONE" />
- <uses-permission android:name="android.permission.SUGGEST_PHONE_TIME_AND_ZONE" />
+ <uses-permission android:name="android.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 8d84baf..fca8acf 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -15,21 +15,6 @@
-->
<resources>
- <!-- Base attributes available to CheckBoxPreference. Copied from frameworks/base/core/res. -->
- <declare-styleable name="CheckBoxPreference">
- <!-- The summary for the Preference in a PreferenceActivity screen when the
- CheckBoxPreference is checked. If separate on/off summaries are not
- needed, the summary attribute can be used instead. -->
- <attr name="android:summaryOn" />
- <!-- The summary for the Preference in a PreferenceActivity screen when the
- CheckBoxPreference is unchecked. If separate on/off summaries are not
- needed, the summary attribute can be used instead. -->
- <attr name="android:summaryOff" />
- <!-- The state (true for on, or false for off) that causes dependents to be disabled. By default,
- dependents will be disabled when this is unchecked, so the value of this preference is false. -->
- <attr name="android:disableDependentsState" />
- </declare-styleable>
-
<declare-styleable name="EditPhoneNumberPreference">
<!-- The enable button text. -->
<attr name="enableButtonText" format="string" />
diff --git a/sip/src/com/android/services/telephony/sip/SipSettings.java b/sip/src/com/android/services/telephony/sip/SipSettings.java
index 700fe81..813ba51 100644
--- a/sip/src/com/android/services/telephony/sip/SipSettings.java
+++ b/sip/src/com/android/services/telephony/sip/SipSettings.java
@@ -241,7 +241,7 @@
private void processActiveProfilesFromSipService() {
List<SipProfile> activeList = new ArrayList<>();
try {
- activeList = mSipManager.getListOfProfiles();
+ activeList = mSipManager.getProfiles();
} catch (SipException e) {
log("SipManager could not retrieve SIP profiles: " + e);
}
diff --git a/src/com/android/phone/EditPhoneNumberPreference.java b/src/com/android/phone/EditPhoneNumberPreference.java
index 35af20d..74b8a45 100644
--- a/src/com/android/phone/EditPhoneNumberPreference.java
+++ b/src/com/android/phone/EditPhoneNumberPreference.java
@@ -136,9 +136,9 @@
a.recycle();
//get the summary settings, use CheckBoxPreference as the standard.
- a = context.obtainStyledAttributes(attrs, R.styleable.CheckBoxPreference, 0, 0);
- mSummaryOn = a.getString(R.styleable.CheckBoxPreference_summaryOn);
- mSummaryOff = a.getString(R.styleable.CheckBoxPreference_summaryOff);
+ a = context.obtainStyledAttributes(attrs, android.R.styleable.CheckBoxPreference, 0, 0);
+ mSummaryOn = a.getString(android.R.styleable.CheckBoxPreference_summaryOn);
+ mSummaryOff = a.getString(android.R.styleable.CheckBoxPreference_summaryOff);
a.recycle();
}
diff --git a/src/com/android/phone/ImsRcsController.java b/src/com/android/phone/ImsRcsController.java
index e09c6af..6b1b5e3 100644
--- a/src/com/android/phone/ImsRcsController.java
+++ b/src/com/android/phone/ImsRcsController.java
@@ -34,11 +34,12 @@
import android.util.Log;
import com.android.ims.ImsManager;
-import com.android.ims.RcsFeatureManager;
import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.imsphone.ImsPhone;
+import com.android.services.telephony.rcs.RcsFeatureController;
import com.android.services.telephony.rcs.TelephonyRcsService;
+import com.android.services.telephony.rcs.UserCapabilityExchangeImpl;
import java.util.List;
@@ -77,16 +78,16 @@
}
/**
- * Register a IImsRegistrationCallback to receive IMS network registration state.
+ * Register a {@link RegistrationManager.RegistrationCallback} to receive IMS network
+ * registration state.
*/
@Override
- public void registerImsRegistrationCallback(int subId, IImsRegistrationCallback callback)
- throws RemoteException {
+ public void registerImsRegistrationCallback(int subId, IImsRegistrationCallback callback) {
enforceReadPrivilegedPermission("registerImsRegistrationCallback");
final long token = Binder.clearCallingIdentity();
try {
- getRcsFeatureManager(subId).registerImsRegistrationCallback(callback);
- } catch (com.android.ims.ImsException e) {
+ getRcsFeatureController(subId).registerImsRegistrationCallback(subId, callback);
+ } catch (ImsException e) {
Log.e(TAG, "registerImsRegistrationCallback: sudId=" + subId + ", " + e.getMessage());
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -95,14 +96,14 @@
}
/**
- * Removes an existing {@link RegistrationCallback}.
+ * Removes an existing {@link RegistrationManager.RegistrationCallback}.
*/
@Override
public void unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback callback) {
enforceReadPrivilegedPermission("unregisterImsRegistrationCallback");
final long token = Binder.clearCallingIdentity();
try {
- getRcsFeatureManager(subId).unregisterImsRegistrationCallback(callback);
+ getRcsFeatureController(subId).unregisterImsRegistrationCallback(subId, callback);
} catch (ServiceSpecificException e) {
Log.e(TAG, "unregisterImsRegistrationCallback: error=" + e.errorCode);
} finally {
@@ -118,7 +119,7 @@
enforceReadPrivilegedPermission("getImsRcsRegistrationState");
final long token = Binder.clearCallingIdentity();
try {
- getImsPhone(subId).getImsRcsRegistrationState(regState -> {
+ getRcsFeatureController(subId).getRegistrationState(regState -> {
try {
consumer.accept((regState == null)
? RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED : regState);
@@ -139,7 +140,7 @@
enforceReadPrivilegedPermission("getImsRcsRegistrationTransportType");
final long token = Binder.clearCallingIdentity();
try {
- getImsPhone(subId).getImsRcsRegistrationTech(regTech -> {
+ getRcsFeatureController(subId).getRegistrationTech(regTech -> {
// Convert registration tech from ImsRegistrationImplBase -> RegistrationManager
int regTechConverted = (regTech == null)
? ImsRegistrationImplBase.REGISTRATION_TECH_NONE : regTech;
@@ -164,13 +165,12 @@
* @param callback The ImsCapabilityCallback to be registered.
*/
@Override
- public void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback)
- throws RemoteException {
+ public void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback) {
enforceReadPrivilegedPermission("registerRcsAvailabilityCallback");
final long token = Binder.clearCallingIdentity();
try {
- getRcsFeatureManager(subId).registerRcsAvailabilityCallback(callback);
- } catch (com.android.ims.ImsException e) {
+ getRcsFeatureController(subId).registerRcsAvailabilityCallback(subId, callback);
+ } catch (ImsException e) {
Log.e(TAG, "registerRcsAvailabilityCallback: sudId=" + subId + ", " + e.getMessage());
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -189,9 +189,7 @@
enforceReadPrivilegedPermission("unregisterRcsAvailabilityCallback");
final long token = Binder.clearCallingIdentity();
try {
- getRcsFeatureManager(subId).unregisterRcsAvailabilityCallback(callback);
- } catch (com.android.ims.ImsException e) {
- Log.e(TAG, "unregisterRcsAvailabilityCallback: sudId=" + subId + "," + e.getMessage());
+ getRcsFeatureController(subId).unregisterRcsAvailabilityCallback(subId, callback);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -212,8 +210,8 @@
enforceReadPrivilegedPermission("isCapable");
final long token = Binder.clearCallingIdentity();
try {
- return getRcsFeatureManager(subId).isCapable(capability, radioTech);
- } catch (com.android.ims.ImsException e) {
+ return getRcsFeatureController(subId).isCapable(capability, radioTech);
+ } catch (ImsException e) {
Log.e(TAG, "isCapable: sudId=" + subId
+ ", capability=" + capability + ", " + e.getMessage());
return false;
@@ -236,8 +234,8 @@
enforceReadPrivilegedPermission("isAvailable");
final long token = Binder.clearCallingIdentity();
try {
- return getRcsFeatureManager(subId).isAvailable(capability);
- } catch (com.android.ims.ImsException e) {
+ return getRcsFeatureController(subId).isAvailable(capability);
+ } catch (ImsException e) {
Log.e(TAG, "isAvailable: sudId=" + subId
+ ", capability=" + capability + ", " + e.getMessage());
return false;
@@ -250,21 +248,35 @@
public void requestCapabilities(int subId, List<Uri> contactNumbers,
IRcsUceControllerCallback c) {
enforceReadPrivilegedPermission("requestCapabilities");
- if (mRcsService == null) {
- throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
- "IMS is not available on device.");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UserCapabilityExchangeImpl uce = getRcsFeatureController(subId).getFeature(
+ UserCapabilityExchangeImpl.class);
+ if (uce == null) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "This subscription does not support UCE.");
+ }
+ uce.requestCapabilities(contactNumbers, c);
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
- mRcsService.requestCapabilities(getImsPhone(subId).getPhoneId(), contactNumbers, c);
}
@Override
public int getUcePublishState(int subId) {
enforceReadPrivilegedPermission("getUcePublishState");
- if (mRcsService == null) {
- throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
- "IMS is not available on device.");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UserCapabilityExchangeImpl uce = getRcsFeatureController(subId).getFeature(
+ UserCapabilityExchangeImpl.class);
+ if (uce == null) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "This subscription does not support UCE.");
+ }
+ return uce.getUcePublishState();
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
- return mRcsService.getUcePublishState(getImsPhone(subId).getPhoneId());
}
@Override
@@ -332,27 +344,27 @@
* @return The RcsFeatureManager instance
* @throws ServiceSpecificException if getting RcsFeatureManager instance failed.
*/
- private RcsFeatureManager getRcsFeatureManager(int subId) {
+ private RcsFeatureController getRcsFeatureController(int subId) {
if (!ImsManager.isImsSupportedOnDevice(mApp)) {
throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
"IMS is not available on device.");
}
+ if (mRcsService == null) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "IMS is not available on device.");
+ }
Phone phone = PhoneGlobals.getPhone(subId);
if (phone == null) {
throw new ServiceSpecificException(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION,
"Invalid subscription Id: " + subId);
}
- ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
- if (imsPhone == null) {
+ int slotId = phone.getPhoneId();
+ RcsFeatureController c = mRcsService.getFeatureController(slotId);
+ if (c == null) {
throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
- "Cannot find ImsPhone instance: " + subId);
+ "Cannot find RcsFeatureController instance for sub: " + subId);
}
- RcsFeatureManager rcsFeatureManager = imsPhone.getRcsManager();
- if (rcsFeatureManager == null) {
- throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
- "Cannot find RcsFeatureManager instance: " + subId);
- }
- return rcsFeatureManager;
+ return c;
}
void setRcsService(TelephonyRcsService rcsService) {
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index b0e0105..d361bb1 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -375,7 +375,9 @@
imsRcsController = ImsRcsController.init(this);
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS)) {
- mTelephonyRcsService = new TelephonyRcsService(this);
+ mTelephonyRcsService = new TelephonyRcsService(this,
+ PhoneFactory.getPhones().length);
+ mTelephonyRcsService.initialize();
imsRcsController.setRcsService(mTelephonyRcsService);
}
@@ -912,6 +914,12 @@
e.printStackTrace();
}
pw.decreaseIndent();
+ pw.println("RcsService:");
+ try {
+ if (mTelephonyRcsService != null) mTelephonyRcsService.dump(fd, pw, args);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
pw.decreaseIndent();
pw.println("------- End PhoneGlobals -------");
}
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 2191bfc..4eb1788 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -5645,24 +5645,15 @@
}
/**
- * Get whether mobile data is enabled.
+ * Checks if the device is capable of mobile data by considering whether whether the
+ * user has enabled mobile data, whether the carrier has enabled mobile data, and
+ * whether the network policy allows data connections.
*
- * Comparable to {@link #isUserDataEnabled(int)}, this considers all factors deciding
- * whether mobile data is actually enabled.
- *
- * Accepts either ACCESS_NETWORK_STATE, MODIFY_PHONE_STATE or carrier privileges.
- *
- * @return {@code true} if data is enabled else {@code false}
+ * @return {@code true} if the overall data connection is capable; {@code false} if not.
*/
@Override
public boolean isDataEnabled(int subId) {
- try {
- mApp.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NETWORK_STATE,
- null);
- } catch (Exception e) {
- TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
- mApp, subId, "isDataEnabled");
- }
+ enforceReadPrivilegedPermission("isDataEnabled");
final long identity = Binder.clearCallingIdentity();
try {
diff --git a/src/com/android/services/telephony/rcs/PresenceHelper.java b/src/com/android/services/telephony/rcs/PresenceHelper.java
deleted file mode 100644
index 5f7e35f..0000000
--- a/src/com/android/services/telephony/rcs/PresenceHelper.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.services.telephony.rcs;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.telephony.SubscriptionManager;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.ims.RcsFeatureConnection;
-import com.android.ims.RcsFeatureManager;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneFactory;
-import com.android.internal.telephony.imsphone.ImsPhone;
-import com.android.internal.telephony.imsphone.ImsRcsStatusListener;
-import com.android.phone.R;
-import com.android.service.ims.presence.PresencePublication;
-import com.android.service.ims.presence.PresenceSubscriber;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-
-class PresenceHelper {
-
- private static final String LOG_TAG = "PresenceHelper";
-
- private final Context mContext;
- private final List<Phone> mPhones;
-
- private final SparseArray<PresencePublication> mPresencePublications = new SparseArray<>();
- private final SparseArray<PresenceSubscriber> mPresenceSubscribers = new SparseArray<>();
-
- PresenceHelper(Context context) {
- mContext = context;
-
- // Get phones
- Phone[] phoneAry = PhoneFactory.getPhones();
- mPhones = (phoneAry != null) ? Arrays.asList(phoneAry) : new ArrayList<>();
-
- initRcsPresencesInstance();
- registerRcsConnectionStatus();
-
- Log.i(LOG_TAG, "initialized: phone size=" + mPhones.size());
- }
-
- private void initRcsPresencesInstance() {
- String[] volteError = mContext.getResources().getStringArray(
- R.array.config_volte_provision_error_on_publish_response);
- String[] rcsError = mContext.getResources().getStringArray(
- R.array.config_rcs_provision_error_on_publish_response);
-
- mPhones.forEach((phone) -> {
- RcsFeatureConnection rcsConnection = getRcsFeatureConnection(phone);
- // Initialize PresencePublication
- mPresencePublications.put(
- phone.getPhoneId(),
- new PresencePublication(rcsConnection, mContext, volteError, rcsError));
- // Initialize PresenceSubscriber
- mPresenceSubscribers.put(
- phone.getPhoneId(),
- new PresenceSubscriber(rcsConnection, mContext, volteError, rcsError));
- });
- }
-
- private @Nullable RcsFeatureConnection getRcsFeatureConnection(Phone phone) {
- ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
- if (imsPhone != null) {
- RcsFeatureManager rcsFeatureManager = imsPhone.getRcsManager();
- if (rcsFeatureManager != null) {
- return rcsFeatureManager.getRcsFeatureConnection();
- }
- }
- return null;
- }
-
- /*
- * RcsFeatureManager in ImsPhone is not null only when RCS is connected. Register a callback to
- * receive the RCS connection status.
- */
- private void registerRcsConnectionStatus() {
- mPhones.forEach((phone) -> {
- ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
- if (imsPhone != null) {
- imsPhone.setRcsStatusListener(mStatusListener);
- }
- });
- }
-
- /**
- * The IMS RCS status listener to listen the status changed
- */
- private ImsRcsStatusListener mStatusListener = new ImsRcsStatusListener() {
- @Override
- public void onRcsConnected(int phoneId, RcsFeatureManager rcsFeatureManager) {
- int subId = getSubscriptionId(phoneId);
- if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
- Log.e(LOG_TAG, "onRcsConnected: invalid subId, phoneId=" + phoneId);
- return;
- }
-
- Log.i(LOG_TAG, "onRcsConnected: phoneId=" + phoneId + ", subId=" + subId);
- RcsFeatureConnection connection = rcsFeatureManager.getRcsFeatureConnection();
- PresencePublication presencePublication = getPresencePublication(phoneId);
- if (presencePublication != null) {
- Log.i(LOG_TAG, "Update PresencePublisher because RCS is connected");
- presencePublication.updatePresencePublisher(subId, connection);
- }
- PresenceSubscriber presenceSubscriber = getPresenceSubscriber(phoneId);
- if (presenceSubscriber != null) {
- Log.i(LOG_TAG, "Update PresenceSubscriber because RCS is connected");
- presenceSubscriber.updatePresenceSubscriber(subId, connection);
- }
- }
-
- @Override
- public void onRcsDisconnected(int phoneId) {
- int subId = getSubscriptionId(phoneId);
- Log.i(LOG_TAG, "onRcsDisconnected: phoneId=" + phoneId + ", subId=" + subId);
- PresencePublication publication = getPresencePublication(phoneId);
- if (publication != null) {
- Log.i(LOG_TAG, "Remove PresencePublisher because RCS is disconnected");
- publication.removePresencePublisher(subId);
- }
-
- PresenceSubscriber subscriber = getPresenceSubscriber(phoneId);
- if (subscriber != null) {
- Log.i(LOG_TAG, "Remove PresencePublisher because RCS is disconnected");
- subscriber.removePresenceSubscriber(subId);
- }
- }
- };
-
- private int getSubscriptionId(int phoneId) {
- Optional<Phone> phone = mPhones.stream()
- .filter(p -> p.getPhoneId() == phoneId).findFirst();
- if (phone.isPresent()) {
- return phone.get().getSubId();
- }
- return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- }
-
- public @Nullable PresencePublication getPresencePublication(int phoneId) {
- return mPresencePublications.get(phoneId);
- }
-
- public @Nullable PresenceSubscriber getPresenceSubscriber(int phoneId) {
- return mPresenceSubscribers.get(phoneId);
- }
-}
diff --git a/src/com/android/services/telephony/rcs/RcsFeatureController.java b/src/com/android/services/telephony/rcs/RcsFeatureController.java
new file mode 100644
index 0000000..f451e9b
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/RcsFeatureController.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.services.telephony.rcs;
+
+import android.annotation.AnyThread;
+import android.content.Context;
+import android.net.Uri;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsRcsManager;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.RegistrationManager;
+import android.telephony.ims.aidl.IImsCapabilityCallback;
+import android.telephony.ims.aidl.IImsRegistrationCallback;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.ims.FeatureConnector;
+import com.android.ims.IFeatureConnector;
+import com.android.ims.RcsFeatureManager;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.imsphone.ImsRegistrationCallbackHelper;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Contains the RCS feature implementations that are associated with this slot's RcsFeature.
+ */
+@AnyThread
+public class RcsFeatureController {
+ private static final String LOG_TAG = "RcsFeatureController";
+
+ /**
+ * Interface used by RCS features that need to listen for when the associated service has been
+ * connected.
+ */
+ public interface Feature {
+ /**
+ * The RcsFeature has been connected to the framework and is ready.
+ */
+ void onRcsConnected(RcsFeatureManager manager);
+
+ /**
+ * The framework has lost the binding to the RcsFeature or it is in the process of changing.
+ */
+ void onRcsDisconnected();
+
+ /**
+ * The subscription associated with the slot this controller is bound to has changed or its
+ * carrier configuration has changed.
+ */
+ void onAssociatedSubscriptionUpdated(int subId);
+
+ /**
+ * Called when the feature should be destroyed.
+ */
+ void onDestroy();
+ }
+
+ /**
+ * Used to inject FeatureConnector instances for testing.
+ */
+ @VisibleForTesting
+ public interface FeatureConnectorFactory<T extends IFeatureConnector> {
+ /**
+ * @return a {@link FeatureConnector} associated for the given {@link IFeatureConnector}
+ * and slot id.
+ */
+ FeatureConnector<T> create(Context context, int slotId,
+ FeatureConnector.Listener<T> listener, Executor executor, String tag);
+ }
+
+ /**
+ * Used to inject ImsRegistrationCallbackHelper instances for testing.
+ */
+ @VisibleForTesting
+ public interface RegistrationHelperFactory {
+ /**
+ * @return an {@link ImsRegistrationCallbackHelper}, which helps manage IMS registration
+ * state.
+ */
+ ImsRegistrationCallbackHelper create(
+ ImsRegistrationCallbackHelper.ImsRegistrationUpdate cb, Executor executor);
+ }
+
+ private FeatureConnectorFactory<RcsFeatureManager> mFeatureFactory = FeatureConnector::new;
+ private RegistrationHelperFactory mRegistrationHelperFactory =
+ ImsRegistrationCallbackHelper::new;
+
+ private final Map<Class<?>, Feature> mFeatures = new ArrayMap<>();
+ private final Context mContext;
+ private final ImsRegistrationCallbackHelper mImsRcsRegistrationHelper;
+ private final int mSlotId;
+ private final Object mLock = new Object();
+ private FeatureConnector<RcsFeatureManager> mFeatureConnector;
+ private RcsFeatureManager mFeatureManager;
+
+ private FeatureConnector.Listener<RcsFeatureManager> mFeatureConnectorListener =
+ new FeatureConnector.Listener<RcsFeatureManager>() {
+ @Override
+ public RcsFeatureManager getFeatureManager() {
+ return new RcsFeatureManager(mContext, mSlotId);
+ }
+
+ @Override
+ public void connectionReady(RcsFeatureManager manager)
+ throws com.android.ims.ImsException {
+ if (manager == null) {
+ Log.w(LOG_TAG, "connectionReady returned null RcsFeatureManager");
+ return;
+ }
+ try {
+ // May throw ImsException if for some reason the connection to the
+ // ImsService is gone.
+ setupConnectionToService(manager);
+ } catch (ImsException e) {
+ // Use deprecated Exception for compatibility.
+ throw new com.android.ims.ImsException(e.getMessage(),
+ ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+ }
+ updateConnectionStatus(manager);
+ }
+
+ @Override
+ public void connectionUnavailable() {
+ // Call before disabling connection to manager.
+ removeConnectionToService();
+ updateConnectionStatus(null /*manager*/);
+ }
+ };
+
+ private ImsRegistrationCallbackHelper.ImsRegistrationUpdate mRcsRegistrationUpdate = new
+ ImsRegistrationCallbackHelper.ImsRegistrationUpdate() {
+ @Override
+ public void handleImsRegistered(int imsRadioTech) {
+ }
+
+ @Override
+ public void handleImsRegistering(int imsRadioTech) {
+ }
+
+ @Override
+ public void handleImsUnregistered(ImsReasonInfo imsReasonInfo) {
+ }
+
+ @Override
+ public void handleImsSubscriberAssociatedUriChanged(Uri[] uris) {
+ }
+ };
+
+ public RcsFeatureController(Context context, int slotId) {
+ mContext = context;
+ mSlotId = slotId;
+ mImsRcsRegistrationHelper = mRegistrationHelperFactory.create(mRcsRegistrationUpdate,
+ mContext.getMainExecutor());
+ }
+
+ /**
+ * Should only be used to inject registration helpers for testing.
+ */
+ @VisibleForTesting
+ public RcsFeatureController(Context context, int slotId, RegistrationHelperFactory f) {
+ mContext = context;
+ mSlotId = slotId;
+ mRegistrationHelperFactory = f;
+ mImsRcsRegistrationHelper = mRegistrationHelperFactory.create(mRcsRegistrationUpdate,
+ mContext.getMainExecutor());
+ }
+
+ /**
+ * This method should be called after constructing an instance of this class to start the
+ * connection process to the associated RcsFeature.
+ */
+ public void connect() {
+ synchronized (mLock) {
+ mFeatureConnector = mFeatureFactory.create(mContext, mSlotId, mFeatureConnectorListener,
+ mContext.getMainExecutor(), LOG_TAG);
+ mFeatureConnector.connect();
+ }
+ }
+
+ /**
+ * Adds a {@link Feature} to be tracked by this FeatureController.
+ */
+ public <T extends Feature> void addFeature(T connector, Class<T> clazz) {
+ synchronized (mLock) {
+ mFeatures.put(clazz, connector);
+ }
+ RcsFeatureManager manager = getFeatureManager();
+ if (manager != null) {
+ connector.onRcsConnected(manager);
+ } else {
+ connector.onRcsDisconnected();
+ }
+ }
+
+ /**
+ * @return The RCS feature implementation tracked by this controller.
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T getFeature(Class<T> clazz) {
+ synchronized (mLock) {
+ return (T) mFeatures.get(clazz);
+ }
+ }
+
+ /**
+ * Update the subscription associated with this controller.
+ */
+ public void updateAssociatedSubscription(int newSubId) {
+ RcsFeatureManager manager = getFeatureManager();
+ if (manager != null) {
+ try {
+ manager.updateCapabilities();
+ } catch (ImsException e) {
+ Log.w(LOG_TAG, "associatedSubscriptionChanged failed:" + e);
+ }
+ }
+ synchronized (mLock) {
+ for (Feature c : mFeatures.values()) {
+ c.onAssociatedSubscriptionUpdated(newSubId);
+ }
+ }
+ }
+
+ /**
+ * Call before this controller is destroyed to tear down associated features.
+ */
+ public void destroy() {
+ synchronized (mLock) {
+ mFeatureConnector.disconnect();
+ for (Feature c : mFeatures.values()) {
+ c.onRcsDisconnected();
+ c.onDestroy();
+ }
+ mFeatures.clear();
+ }
+ }
+
+ @VisibleForTesting
+ public void setFeatureConnectorFactory(FeatureConnectorFactory factory) {
+ mFeatureFactory = factory;
+ }
+
+ /**
+ * Add a {@link RegistrationManager.RegistrationCallback} callback that gets called when IMS
+ * registration has changed for a specific subscription.
+ */
+ public void registerImsRegistrationCallback(int subId, IImsRegistrationCallback callback)
+ throws ImsException {
+ RcsFeatureManager manager = getFeatureManager();
+ if (manager == null) {
+ throw new ImsException("Service is not available",
+ ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+ }
+ manager.registerImsRegistrationCallback(subId, callback);
+ }
+
+ /**
+ * Removes a previously registered {@link RegistrationManager.RegistrationCallback} callback
+ * that is associated with a specific subscription.
+ */
+ public void unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback callback) {
+ RcsFeatureManager manager = getFeatureManager();
+ if (manager != null) {
+ manager.unregisterImsRegistrationCallback(subId, callback);
+ }
+ }
+
+ /**
+ * Register an {@link ImsRcsManager.AvailabilityCallback} with the associated RcsFeature,
+ * which will provide availability updates.
+ */
+ public void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback)
+ throws ImsException {
+ RcsFeatureManager manager = getFeatureManager();
+ if (manager == null) {
+ throw new ImsException("Service is not available",
+ ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+ }
+ manager.registerRcsAvailabilityCallback(subId, callback);
+ }
+
+ /**
+ * Remove a registered {@link ImsRcsManager.AvailabilityCallback} from the RcsFeature.
+ */
+ public void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback) {
+ RcsFeatureManager manager = getFeatureManager();
+ if (manager != null) {
+ manager.unregisterRcsAvailabilityCallback(subId, callback);
+ }
+ }
+
+ /**
+ * Query for the specific capability.
+ */
+ public boolean isCapable(int capability, int radioTech)
+ throws android.telephony.ims.ImsException {
+ RcsFeatureManager manager = getFeatureManager();
+ if (manager == null) {
+ throw new ImsException("Service is not available",
+ ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+ }
+ return manager.isCapable(capability, radioTech);
+ }
+
+ /**
+ * Query the availability of an IMS RCS capability.
+ */
+ public boolean isAvailable(int capability) throws android.telephony.ims.ImsException {
+ RcsFeatureManager manager = getFeatureManager();
+ if (manager == null) {
+ throw new ImsException("Service is not available",
+ ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+ }
+ return manager.isAvailable(capability);
+ }
+
+ /**
+ * Get the IMS RCS registration technology for this Phone.
+ */
+ public void getRegistrationTech(Consumer<Integer> callback) {
+ RcsFeatureManager manager = getFeatureManager();
+ if (manager != null) {
+ manager.getImsRegistrationTech(callback);
+ }
+ callback.accept(ImsRegistrationImplBase.REGISTRATION_TECH_NONE);
+ }
+
+ /**
+ * Retrieve the current RCS registration state.
+ */
+ public void getRegistrationState(Consumer<Integer> callback) {
+ callback.accept(mImsRcsRegistrationHelper.getImsRegistrationState());
+ }
+
+ private void setupConnectionToService(RcsFeatureManager manager) throws ImsException {
+ // Open persistent listener connection, sends RcsFeature#onFeatureReady.
+ manager.openConnection();
+ manager.updateCapabilities();
+ manager.registerImsRegistrationCallback(mImsRcsRegistrationHelper.getCallbackBinder());
+ }
+
+ private void removeConnectionToService() {
+ RcsFeatureManager manager = getFeatureManager();
+ if (manager != null) {
+ manager.unregisterImsRegistrationCallback(
+ mImsRcsRegistrationHelper.getCallbackBinder());
+ // Remove persistent listener connection.
+ manager.releaseConnection();
+ }
+ mImsRcsRegistrationHelper.reset();
+ }
+
+ private void updateConnectionStatus(RcsFeatureManager manager) {
+ synchronized (mLock) {
+ mFeatureManager = manager;
+ if (mFeatureManager != null) {
+ for (Feature c : mFeatures.values()) {
+ c.onRcsConnected(manager);
+ }
+ } else {
+ for (Feature c : mFeatures.values()) {
+ c.onRcsDisconnected();
+ }
+ }
+ }
+ }
+
+ private RcsFeatureManager getFeatureManager() {
+ synchronized (mLock) {
+ return mFeatureManager;
+ }
+ }
+
+ /**
+ * Dump this controller's instance information for usage in dumpsys.
+ */
+ public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+ IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
+ pw.print("slotId=");
+ pw.println(mSlotId);
+ pw.print("RegistrationState=");
+ pw.println(mImsRcsRegistrationHelper.getImsRegistrationState());
+ pw.print("connected=");
+ synchronized (mLock) {
+ pw.println(mFeatureManager != null);
+ }
+ }
+}
diff --git a/src/com/android/services/telephony/rcs/TelephonyRcsService.java b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
index 0e8f8de..b4223d3 100644
--- a/src/com/android/services/telephony/rcs/TelephonyRcsService.java
+++ b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
@@ -16,195 +16,225 @@
package com.android.services.telephony.rcs;
+import android.annotation.AnyThread;
+import android.content.BroadcastReceiver;
import android.content.Context;
-import android.net.Uri;
-import android.os.RemoteException;
-import android.os.ServiceSpecificException;
-import android.telephony.ims.ImsException;
-import android.telephony.ims.RcsContactUceCapability;
-import android.telephony.ims.RcsUceAdapter;
-import android.telephony.ims.aidl.IRcsUceControllerCallback;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
import android.util.Log;
-import com.android.ims.ResultCode;
-import com.android.service.ims.presence.ContactCapabilityResponse;
-import com.android.service.ims.presence.PresenceBase;
-import com.android.service.ims.presence.PresencePublication;
-import com.android.service.ims.presence.PresenceSubscriber;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.PhoneConfigurationManager;
+import com.android.internal.util.IndentingPrintWriter;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Collectors;
/**
- * Telephony RCS Service integrates PresencePublication and PresenceSubscriber into the service.
+ * Singleton service setup to manage RCS related services that the platform provides such as User
+ * Capability Exchange.
*/
+@AnyThread
public class TelephonyRcsService {
private static final String LOG_TAG = "TelephonyRcsService";
+ /**
+ * Used to inject RcsFeatureController and UserCapabilityExchangeImpl instances for testing.
+ */
+ @VisibleForTesting
+ public interface FeatureFactory {
+ /**
+ * @return an {@link RcsFeatureController} assoicated with the slot specified.
+ */
+ RcsFeatureController createController(Context context, int slotId);
+
+ /**
+ * @return an instance of {@link UserCapabilityExchangeImpl} associated with the slot
+ * specified.
+ */
+ UserCapabilityExchangeImpl createUserCapabilityExchange(Context context, int slotId,
+ int subId);
+ }
+
+ private FeatureFactory mFeatureFactory = new FeatureFactory() {
+ @Override
+ public RcsFeatureController createController(Context context, int slotId) {
+ return new RcsFeatureController(context, slotId);
+ }
+
+ @Override
+ public UserCapabilityExchangeImpl createUserCapabilityExchange(Context context, int slotId,
+ int subId) {
+ return new UserCapabilityExchangeImpl(context, slotId, subId);
+ }
+ };
+
+ // Notifies this service that there has been a change in available slots.
+ private static final int HANDLER_MSIM_CONFIGURATION_CHANGE = 1;
+
private final Context mContext;
+ private final Object mLock = new Object();
+ private int mNumSlots;
- // A helper class to manage the RCS Presences instances.
- private final PresenceHelper mPresenceHelper;
+ // Index corresponds to the slot ID.
+ private List<RcsFeatureController> mFeatureControllers;
- private ConcurrentHashMap<Integer, IRcsUceControllerCallback> mPendingRequests;
+ private BroadcastReceiver mCarrierConfigChangedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent == null) {
+ return;
+ }
+ if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+ Bundle bundle = intent.getExtras();
+ if (bundle == null) {
+ return;
+ }
+ int slotId = bundle.getInt(CarrierConfigManager.EXTRA_SLOT_INDEX);
+ int subId = bundle.getInt(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX);
+ updateFeatureControllerSubscription(slotId, subId);
+ }
+ }
+ };
- public TelephonyRcsService(Context context) {
+ private Handler mHandler = new Handler(Looper.getMainLooper(), (msg) -> {
+ switch (msg.what) {
+ case HANDLER_MSIM_CONFIGURATION_CHANGE: {
+ AsyncResult result = (AsyncResult) msg.obj;
+ Integer numSlots = (Integer) result.result;
+ if (numSlots == null) {
+ Log.w(LOG_TAG, "msim config change with null num slots.");
+ break;
+ }
+ updateFeatureControllerSize(numSlots);
+ break;
+ }
+ default:
+ return false;
+ }
+ return true;
+ });
+
+ public TelephonyRcsService(Context context, int numSlots) {
Log.i(LOG_TAG, "initialize");
mContext = context;
- mPresenceHelper = new PresenceHelper(mContext);
- mPendingRequests = new ConcurrentHashMap<>();
+ mNumSlots = numSlots;
+ mFeatureControllers = new ArrayList<>(numSlots);
}
/**
- * @return the UCE Publish state for the phone ID specified.
+ * @return the {@link RcsFeatureController} associated with the given slot.
*/
- public int getUcePublishState(int phoneId) {
- PresencePublication publisher = getPresencePublication(phoneId);
- if (publisher == null) {
- throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
- "UCE service is not currently running.");
+ public RcsFeatureController getFeatureController(int slotId) {
+ synchronized (mLock) {
+ return mFeatureControllers.get(slotId);
}
- int publishState = publisher.getPublishState();
- return toUcePublishState(publishState);
}
/**
- * Perform a capabilities request and call {@link IRcsUceControllerCallback} with the result.
+ * Called after instance creation to initialize internal structures as well as register for
+ * system callbacks.
*/
- public void requestCapabilities(int phoneId, List<Uri> contactNumbers,
- IRcsUceControllerCallback c) {
- PresenceSubscriber subscriber = getPresenceSubscriber(phoneId);
- if (subscriber == null) {
- throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
- "UCE service is not currently running.");
+ public void initialize() {
+ synchronized (mLock) {
+ for (int i = 0; i < mNumSlots; i++) {
+ mFeatureControllers.add(constructFeatureController(i));
+ }
}
- List<String> numbers = contactNumbers.stream().map(TelephonyRcsService::getNumberFromUri)
- .collect(Collectors.toList());
- int taskId = subscriber.requestCapability(numbers, new ContactCapabilityResponse() {
- @Override
- public void onSuccess(int reqId) {
- Log.i(LOG_TAG, "onSuccess called for reqId:" + reqId);
- }
- @Override
- public void onError(int reqId, int resultCode) {
- IRcsUceControllerCallback c = mPendingRequests.remove(reqId);
- try {
- if (c != null) {
- c.onError(toUceError(resultCode));
- } else {
- Log.w(LOG_TAG, "onError called for unknown reqId:" + reqId);
- }
- } catch (RemoteException e) {
- Log.i(LOG_TAG, "Calling back to dead service");
- }
- }
+ PhoneConfigurationManager.registerForMultiSimConfigChange(mHandler,
+ HANDLER_MSIM_CONFIGURATION_CHANGE, null);
+ mContext.registerReceiver(mCarrierConfigChangedReceiver, new IntentFilter(
+ CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+ }
- @Override
- public void onFinish(int reqId) {
- Log.i(LOG_TAG, "onFinish called for reqId:" + reqId);
- }
+ @VisibleForTesting
+ public void setFeatureFactory(FeatureFactory f) {
+ mFeatureFactory = f;
+ }
- @Override
- public void onTimeout(int reqId) {
- IRcsUceControllerCallback c = mPendingRequests.remove(reqId);
- try {
- if (c != null) {
- c.onError(RcsUceAdapter.ERROR_REQUEST_TIMEOUT);
- } else {
- Log.w(LOG_TAG, "onTimeout called for unknown reqId:" + reqId);
- }
- } catch (RemoteException e) {
- Log.i(LOG_TAG, "Calling back to dead service");
- }
- }
-
- @Override
- public void onCapabilitiesUpdated(int reqId,
- List<RcsContactUceCapability> contactCapabilities,
- boolean updateLastTimestamp) {
- IRcsUceControllerCallback c = mPendingRequests.remove(reqId);
- try {
- if (c != null) {
- c.onCapabilitiesReceived(contactCapabilities);
- } else {
- Log.w(LOG_TAG, "onCapabilitiesUpdated, unknown reqId:" + reqId);
- }
- } catch (RemoteException e) {
- Log.w(LOG_TAG, "onCapabilitiesUpdated on dead service");
- }
- }
- });
- if (taskId < 0) {
- try {
- c.onError(toUceError(taskId));
+ /**
+ * Update the number of {@link RcsFeatureController}s that are created based on the number of
+ * active slots on the device.
+ */
+ @VisibleForTesting
+ public void updateFeatureControllerSize(int newNumSlots) {
+ synchronized (mLock) {
+ int oldNumSlots = mFeatureControllers.size();
+ if (oldNumSlots == newNumSlots) {
return;
- } catch (RemoteException e) {
- Log.i(LOG_TAG, "Calling back to dead service");
+ }
+ mNumSlots = newNumSlots;
+ if (oldNumSlots < newNumSlots) {
+ for (int i = oldNumSlots; i < newNumSlots; i++) {
+ mFeatureControllers.add(constructFeatureController(i));
+ }
+ } else {
+ for (int i = (oldNumSlots - 1); i > (newNumSlots - 1); i--) {
+ RcsFeatureController controller = mFeatureControllers.remove(i);
+ controller.destroy();
+ }
}
}
- mPendingRequests.put(taskId, c);
}
- private PresencePublication getPresencePublication(int phoneId) {
- return mPresenceHelper.getPresencePublication(phoneId);
- }
-
- private PresenceSubscriber getPresenceSubscriber(int phoneId) {
- return mPresenceHelper.getPresenceSubscriber(phoneId);
- }
-
- private static String getNumberFromUri(Uri uri) {
- String number = uri.getSchemeSpecificPart();
- String[] numberParts = number.split("[@;:]");
-
- if (numberParts.length == 0) {
- return null;
- }
- return numberParts[0];
- }
-
- private static int toUcePublishState(int publishState) {
- switch (publishState) {
- case PresenceBase.PUBLISH_STATE_200_OK:
- return RcsUceAdapter.PUBLISH_STATE_200_OK;
- case PresenceBase.PUBLISH_STATE_NOT_PUBLISHED:
- return RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED;
- case PresenceBase.PUBLISH_STATE_VOLTE_PROVISION_ERROR:
- return RcsUceAdapter.PUBLISH_STATE_VOLTE_PROVISION_ERROR;
- case PresenceBase.PUBLISH_STATE_RCS_PROVISION_ERROR:
- return RcsUceAdapter.PUBLISH_STATE_RCS_PROVISION_ERROR;
- case PresenceBase.PUBLISH_STATE_REQUEST_TIMEOUT:
- return RcsUceAdapter.PUBLISH_STATE_REQUEST_TIMEOUT;
- case PresenceBase.PUBLISH_STATE_OTHER_ERROR:
- return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR;
- default:
- return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR;
+ private void updateFeatureControllerSubscription(int slotId, int newSubId) {
+ synchronized (mLock) {
+ RcsFeatureController f = mFeatureControllers.get(slotId);
+ if (f == null) {
+ Log.w(LOG_TAG, "unexpected null FeatureContainer for slot " + slotId);
+ return;
+ }
+ f.updateAssociatedSubscription(newSubId);
}
}
- private static int toUceError(int resultCode) {
- switch(resultCode) {
- case ResultCode.SUBSCRIBE_NOT_REGISTERED:
- return RcsUceAdapter.ERROR_NOT_REGISTERED;
- case ResultCode.SUBSCRIBE_REQUEST_TIMEOUT:
- return RcsUceAdapter.ERROR_REQUEST_TIMEOUT;
- case ResultCode.SUBSCRIBE_FORBIDDEN:
- return RcsUceAdapter.ERROR_FORBIDDEN;
- case ResultCode.SUBSCRIBE_NOT_FOUND:
- return RcsUceAdapter.ERROR_NOT_FOUND;
- case ResultCode.SUBSCRIBE_TOO_LARGE:
- return RcsUceAdapter.ERROR_REQUEST_TOO_LARGE;
- case ResultCode.SUBSCRIBE_INSUFFICIENT_MEMORY:
- return RcsUceAdapter.ERROR_INSUFFICIENT_MEMORY;
- case ResultCode.SUBSCRIBE_LOST_NETWORK:
- return RcsUceAdapter.ERROR_LOST_NETWORK;
- case ResultCode.SUBSCRIBE_ALREADY_IN_QUEUE:
- return RcsUceAdapter.ERROR_ALREADY_IN_QUEUE;
- default:
- return RcsUceAdapter.ERROR_GENERIC_FAILURE;
+ private RcsFeatureController constructFeatureController(int slotId) {
+ RcsFeatureController c = mFeatureFactory.createController(mContext, slotId);
+ // TODO: integrate user setting into whether or not this feature is added as well as logic
+ // to listen for changes in user setting.
+ c.addFeature(mFeatureFactory.createUserCapabilityExchange(mContext, slotId,
+ getSubscriptionFromSlot(slotId)), UserCapabilityExchangeImpl.class);
+ c.connect();
+ return c;
+ }
+
+ private int getSubscriptionFromSlot(int slotId) {
+ SubscriptionManager manager = mContext.getSystemService(SubscriptionManager.class);
+ if (manager == null) {
+ Log.w(LOG_TAG, "Couldn't find SubscriptionManager for slotId=" + slotId);
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
+ int[] subIds = manager.getSubscriptionIds(slotId);
+ if (subIds != null && subIds.length > 0) {
+ return subIds[0];
+ }
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
+
+ /**
+ * Dump this instance into a readable format for dumpsys usage.
+ */
+ public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+ IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
+ pw.println("RcsFeatureControllers:");
+ pw.increaseIndent();
+ synchronized (mLock) {
+ for (RcsFeatureController f : mFeatureControllers) {
+ pw.increaseIndent();
+ f.dump(fd, printWriter, args);
+ pw.decreaseIndent();
+ }
+ }
+ pw.decreaseIndent();
}
}
diff --git a/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java b/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
new file mode 100644
index 0000000..7521205
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.services.telephony.rcs;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.telephony.ims.RcsContactUceCapability;
+import android.telephony.ims.RcsUceAdapter;
+import android.telephony.ims.aidl.IRcsUceControllerCallback;
+import android.util.Log;
+
+import com.android.ims.RcsFeatureManager;
+import com.android.ims.ResultCode;
+import com.android.phone.R;
+import com.android.service.ims.presence.ContactCapabilityResponse;
+import com.android.service.ims.presence.PresenceBase;
+import com.android.service.ims.presence.PresencePublication;
+import com.android.service.ims.presence.PresencePublisher;
+import com.android.service.ims.presence.PresenceSubscriber;
+import com.android.service.ims.presence.SubscribePublisher;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * Implements User Capability Exchange using Presence.
+ */
+public class UserCapabilityExchangeImpl implements RcsFeatureController.Feature, SubscribePublisher,
+ PresencePublisher {
+
+ private static final String LOG_TAG = "UserCapabilityExchangeImpl";
+
+ private int mSlotId;
+ private int mSubId;
+
+ private final PresencePublication mPresencePublication;
+ private final PresenceSubscriber mPresenceSubscriber;
+
+ private final ConcurrentHashMap<Integer, IRcsUceControllerCallback> mPendingCapabilityRequests =
+ new ConcurrentHashMap<>();
+
+ UserCapabilityExchangeImpl(Context context, int slotId, int subId) {
+ mSlotId = slotId;
+ mSubId = subId;
+
+ String[] volteError = context.getResources().getStringArray(
+ R.array.config_volte_provision_error_on_publish_response);
+ String[] rcsError = context.getResources().getStringArray(
+ R.array.config_rcs_provision_error_on_publish_response);
+
+ // Initialize PresencePublication
+ mPresencePublication = new PresencePublication(null /*PresencePublisher*/, context,
+ volteError, rcsError);
+ // Initialize PresenceSubscriber
+ mPresenceSubscriber = new PresenceSubscriber(null /*SubscribePublisher*/, context,
+ volteError, rcsError);
+
+ onAssociatedSubscriptionUpdated(mSubId);
+ }
+
+
+ // Runs on main thread.
+ @Override
+ public void onRcsConnected(RcsFeatureManager rcsFeatureManager) {
+ Log.i(LOG_TAG, "onRcsConnected: slotId=" + mSlotId + ", subId=" + mSubId);
+ mPresencePublication.updatePresencePublisher(this);
+ mPresenceSubscriber.updatePresenceSubscriber(this);
+ }
+
+ // Runs on main thread.
+ @Override
+ public void onRcsDisconnected() {
+ Log.i(LOG_TAG, "onRcsDisconnected: phoneId=" + mSlotId + ", subId=" + mSubId);
+ mPresencePublication.removePresencePublisher();
+ mPresenceSubscriber.removePresenceSubscriber();
+ }
+
+ // Runs on main thread.
+ @Override
+ public void onAssociatedSubscriptionUpdated(int subId) {
+ mPresencePublication.handleAssociatedSubscriptionChanged(subId);
+ mPresenceSubscriber.handleAssociatedSubscriptionChanged(subId);
+ }
+
+ /**
+ * Should be called before destroying this instance.
+ * This instance is not usable after this method is called.
+ */
+ // Called on main thread.
+ public void onDestroy() {
+ onRcsDisconnected();
+ }
+
+ /**
+ * @return the UCE Publish state.
+ */
+ // May happen on a Binder thread, PresencePublication locks to get result.
+ public int getUcePublishState() {
+ int publishState = mPresencePublication.getPublishState();
+ return toUcePublishState(publishState);
+ }
+
+ /**
+ * Perform a capabilities request and call {@link IRcsUceControllerCallback} with the result.
+ */
+ // May happen on a Binder thread, PresenceSubscriber locks when requesting Capabilities.
+ public void requestCapabilities(List<Uri> contactNumbers, IRcsUceControllerCallback c) {
+ List<String> numbers = contactNumbers.stream()
+ .map(UserCapabilityExchangeImpl::getNumberFromUri).collect(Collectors.toList());
+ int taskId = mPresenceSubscriber.requestCapability(numbers,
+ new ContactCapabilityResponse() {
+ @Override
+ public void onSuccess(int reqId) {
+ Log.i(LOG_TAG, "onSuccess called for reqId:" + reqId);
+ }
+
+ @Override
+ public void onError(int reqId, int resultCode) {
+ IRcsUceControllerCallback c = mPendingCapabilityRequests.remove(reqId);
+ try {
+ if (c != null) {
+ c.onError(toUceError(resultCode));
+ } else {
+ Log.w(LOG_TAG, "onError called for unknown reqId:" + reqId);
+ }
+ } catch (RemoteException e) {
+ Log.i(LOG_TAG, "Calling back to dead service");
+ }
+ }
+
+ @Override
+ public void onFinish(int reqId) {
+ Log.i(LOG_TAG, "onFinish called for reqId:" + reqId);
+ }
+
+ @Override
+ public void onTimeout(int reqId) {
+ IRcsUceControllerCallback c = mPendingCapabilityRequests.remove(reqId);
+ try {
+ if (c != null) {
+ c.onError(RcsUceAdapter.ERROR_REQUEST_TIMEOUT);
+ } else {
+ Log.w(LOG_TAG, "onTimeout called for unknown reqId:" + reqId);
+ }
+ } catch (RemoteException e) {
+ Log.i(LOG_TAG, "Calling back to dead service");
+ }
+ }
+
+ @Override
+ public void onCapabilitiesUpdated(int reqId,
+ List<RcsContactUceCapability> contactCapabilities,
+ boolean updateLastTimestamp) {
+ IRcsUceControllerCallback c = mPendingCapabilityRequests.remove(reqId);
+ try {
+ if (c != null) {
+ c.onCapabilitiesReceived(contactCapabilities);
+ } else {
+ Log.w(LOG_TAG, "onCapabilitiesUpdated, unknown reqId:" + reqId);
+ }
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "onCapabilitiesUpdated on dead service");
+ }
+ }
+ });
+ if (taskId < 0) {
+ try {
+ c.onError(toUceError(taskId));
+ return;
+ } catch (RemoteException e) {
+ Log.i(LOG_TAG, "Calling back to dead service");
+ }
+ }
+ mPendingCapabilityRequests.put(taskId, c);
+ }
+
+ @Override
+ public int getPublisherState() {
+ return 0;
+ }
+
+ @Override
+ public int requestPublication(RcsContactUceCapability capabilities, String contactUri,
+ int taskId) {
+ return 0;
+ }
+
+ @Override
+ public int requestCapability(String[] formatedContacts, int taskId) {
+ return 0;
+ }
+
+ @Override
+ public int requestAvailability(String formattedContact, int taskId) {
+ return 0;
+ }
+
+ @Override
+ public int getStackStatusForCapabilityRequest() {
+ return 0;
+ }
+
+ @Override
+ public void updatePublisherState(int publishState) {
+
+ }
+
+ private static String getNumberFromUri(Uri uri) {
+ String number = uri.getSchemeSpecificPart();
+ String[] numberParts = number.split("[@;:]");
+
+ if (numberParts.length == 0) {
+ return null;
+ }
+ return numberParts[0];
+ }
+
+ private static int toUcePublishState(int publishState) {
+ switch (publishState) {
+ case PresenceBase.PUBLISH_STATE_200_OK:
+ return RcsUceAdapter.PUBLISH_STATE_200_OK;
+ case PresenceBase.PUBLISH_STATE_NOT_PUBLISHED:
+ return RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED;
+ case PresenceBase.PUBLISH_STATE_VOLTE_PROVISION_ERROR:
+ return RcsUceAdapter.PUBLISH_STATE_VOLTE_PROVISION_ERROR;
+ case PresenceBase.PUBLISH_STATE_RCS_PROVISION_ERROR:
+ return RcsUceAdapter.PUBLISH_STATE_RCS_PROVISION_ERROR;
+ case PresenceBase.PUBLISH_STATE_REQUEST_TIMEOUT:
+ return RcsUceAdapter.PUBLISH_STATE_REQUEST_TIMEOUT;
+ case PresenceBase.PUBLISH_STATE_OTHER_ERROR:
+ return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR;
+ default:
+ return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR;
+ }
+ }
+
+ private static int toUceError(int resultCode) {
+ switch (resultCode) {
+ case ResultCode.SUBSCRIBE_NOT_REGISTERED:
+ return RcsUceAdapter.ERROR_NOT_REGISTERED;
+ case ResultCode.SUBSCRIBE_REQUEST_TIMEOUT:
+ return RcsUceAdapter.ERROR_REQUEST_TIMEOUT;
+ case ResultCode.SUBSCRIBE_FORBIDDEN:
+ return RcsUceAdapter.ERROR_FORBIDDEN;
+ case ResultCode.SUBSCRIBE_NOT_FOUND:
+ return RcsUceAdapter.ERROR_NOT_FOUND;
+ case ResultCode.SUBSCRIBE_TOO_LARGE:
+ return RcsUceAdapter.ERROR_REQUEST_TOO_LARGE;
+ case ResultCode.SUBSCRIBE_INSUFFICIENT_MEMORY:
+ return RcsUceAdapter.ERROR_INSUFFICIENT_MEMORY;
+ case ResultCode.SUBSCRIBE_LOST_NETWORK:
+ return RcsUceAdapter.ERROR_LOST_NETWORK;
+ case ResultCode.SUBSCRIBE_ALREADY_IN_QUEUE:
+ return RcsUceAdapter.ERROR_ALREADY_IN_QUEUE;
+ default:
+ return RcsUceAdapter.ERROR_GENERIC_FAILURE;
+ }
+ }
+}
diff --git a/testapps/Android.mk b/testapps/Android.mk
deleted file mode 100644
index 5053e7d..0000000
--- a/testapps/Android.mk
+++ /dev/null
@@ -1 +0,0 @@
-include $(call all-subdir-makefiles)
diff --git a/testapps/EmbmsServiceTestApp/Android.bp b/testapps/EmbmsServiceTestApp/Android.bp
new file mode 100644
index 0000000..e4a54cb
--- /dev/null
+++ b/testapps/EmbmsServiceTestApp/Android.bp
@@ -0,0 +1,11 @@
+// Build the Sample Embms Services
+android_app {
+ name: "EmbmsTestService",
+ srcs: ["src/**/*.java"],
+ platform_apis: true,
+ certificate: "platform",
+ privileged: true,
+ // Uncomment the following line to build the EmbmsTestService
+ // into the userdebug build:
+ // LOCAL_MODULE_TAGS := debug
+}
diff --git a/testapps/EmbmsServiceTestApp/Android.mk b/testapps/EmbmsServiceTestApp/Android.mk
deleted file mode 100644
index 29b8112..0000000
--- a/testapps/EmbmsServiceTestApp/Android.mk
+++ /dev/null
@@ -1,20 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-# Build the Sample Embms Services
-include $(CLEAR_VARS)
-
-src_dirs := src
-res_dirs := res
-
-LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs))
-LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs))
-
-LOCAL_PACKAGE_NAME := EmbmsTestService
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_CERTIFICATE := platform
-LOCAL_PRIVILEGED_MODULE := true
-# Uncomment the following line to build the EmbmsTestService into the userdebug build.
-# LOCAL_MODULE_TAGS := debug
-
-include $(BUILD_PACKAGE)
diff --git a/testapps/EmbmsTestDownloadApp/Android.bp b/testapps/EmbmsTestDownloadApp/Android.bp
new file mode 100644
index 0000000..63f4e83
--- /dev/null
+++ b/testapps/EmbmsTestDownloadApp/Android.bp
@@ -0,0 +1,12 @@
+src_dirs = ["src"]
+res_dirs = ["res"]
+android_test {
+ name: "EmbmsTestDownloadApp",
+ static_libs: [
+ "androidx.recyclerview_recyclerview",
+ "androidx.legacy_legacy-support-v4",
+ ],
+ srcs: ["src/**/*.java"],
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/testapps/EmbmsTestDownloadApp/Android.mk b/testapps/EmbmsTestDownloadApp/Android.mk
deleted file mode 100644
index bd53d79..0000000
--- a/testapps/EmbmsTestDownloadApp/Android.mk
+++ /dev/null
@@ -1,22 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-# Build the Sample Embms Download frontend
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- androidx.recyclerview_recyclerview \
- androidx.legacy_legacy-support-v4
-
-src_dirs := src
-res_dirs := res
-
-LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs))
-LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs))
-
-LOCAL_PACKAGE_NAME := EmbmsTestDownloadApp
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_CERTIFICATE := platform
-LOCAL_MODULE_TAGS := tests
-
-include $(BUILD_PACKAGE)
diff --git a/testapps/EmbmsTestStreamingApp/Android.bp b/testapps/EmbmsTestStreamingApp/Android.bp
new file mode 100644
index 0000000..814c5ca
--- /dev/null
+++ b/testapps/EmbmsTestStreamingApp/Android.bp
@@ -0,0 +1,7 @@
+android_test {
+ name: "EmbmsTestStreamingApp",
+ srcs: ["src/**/*.java"],
+ platform_apis: true,
+ certificate: "platform",
+ //LOCAL_MODULE_TAGS := debug
+}
diff --git a/testapps/EmbmsTestStreamingApp/Android.mk b/testapps/EmbmsTestStreamingApp/Android.mk
deleted file mode 100644
index f574990..0000000
--- a/testapps/EmbmsTestStreamingApp/Android.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-# Build the Sample Embms Streaming frontend
-include $(CLEAR_VARS)
-
-src_dirs := src
-res_dirs := res
-
-LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs))
-LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs))
-
-LOCAL_PACKAGE_NAME := EmbmsTestStreamingApp
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_CERTIFICATE := platform
-LOCAL_MODULE_TAGS := tests
-#LOCAL_MODULE_TAGS := debug
-
-include $(BUILD_PACKAGE)
diff --git a/testapps/ImsTestService/Android.bp b/testapps/ImsTestService/Android.bp
new file mode 100644
index 0000000..a0b4edb
--- /dev/null
+++ b/testapps/ImsTestService/Android.bp
@@ -0,0 +1,13 @@
+android_app {
+ name: "ImsTestApp",
+ static_libs: [
+ "androidx.legacy_legacy-support-v4",
+ "androidx.appcompat_appcompat",
+ "androidx.recyclerview_recyclerview",
+ "androidx.cardview_cardview",
+ ],
+ srcs: ["src/**/*.java"],
+ platform_apis: true,
+ certificate: "platform",
+ privileged: true,
+}
diff --git a/testapps/ImsTestService/Android.mk b/testapps/ImsTestService/Android.mk
deleted file mode 100644
index 2869c86..0000000
--- a/testapps/ImsTestService/Android.mk
+++ /dev/null
@@ -1,27 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- androidx.legacy_legacy-support-v4 \
- androidx.appcompat_appcompat \
- androidx.recyclerview_recyclerview \
- androidx.cardview_cardview
-
-LOCAL_USE_AAPT2 := true
-
-src_dirs := src
-res_dirs := res
-
-LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs))
-LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs))
-
-LOCAL_PACKAGE_NAME := ImsTestApp
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_CERTIFICATE := platform
-LOCAL_PRIVILEGED_MODULE := true
-
-include $(BUILD_PACKAGE)
diff --git a/testapps/SmsManagerTestApp/Android.bp b/testapps/SmsManagerTestApp/Android.bp
new file mode 100644
index 0000000..5333eab
--- /dev/null
+++ b/testapps/SmsManagerTestApp/Android.bp
@@ -0,0 +1,5 @@
+android_app {
+ name: "SmsManagerTestApp",
+ srcs: ["src/**/*.java"],
+ sdk_version: "current",
+}
diff --git a/testapps/SmsManagerTestApp/Android.mk b/testapps/SmsManagerTestApp/Android.mk
deleted file mode 100644
index 307366b..0000000
--- a/testapps/SmsManagerTestApp/Android.mk
+++ /dev/null
@@ -1,17 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-
-src_dirs := src
-res_dirs := res
-
-LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs))
-LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs))
-
-LOCAL_PACKAGE_NAME := SmsManagerTestApp
-
-LOCAL_SDK_VERSION := current
-
-include $(BUILD_PACKAGE)
diff --git a/testapps/TelephonyManagerTestApp/Android.bp b/testapps/TelephonyManagerTestApp/Android.bp
new file mode 100644
index 0000000..8a37c99
--- /dev/null
+++ b/testapps/TelephonyManagerTestApp/Android.bp
@@ -0,0 +1,7 @@
+android_test {
+ name: "TelephonyManagerTestApp",
+ srcs: ["src/**/*.java"],
+ javacflags: ["-parameters"],
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/testapps/TelephonyManagerTestApp/Android.mk b/testapps/TelephonyManagerTestApp/Android.mk
deleted file mode 100644
index 290b261..0000000
--- a/testapps/TelephonyManagerTestApp/Android.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-src_dirs := src
-res_dirs := res
-
-LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs))
-LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs))
-
-LOCAL_JAVACFLAGS := -parameters
-
-LOCAL_PACKAGE_NAME := TelephonyManagerTestApp
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_CERTIFICATE := platform
-LOCAL_MODULE_TAGS := tests
-
-include $(BUILD_PACKAGE)
diff --git a/testapps/TelephonyRegistryTestApp/Android.bp b/testapps/TelephonyRegistryTestApp/Android.bp
new file mode 100644
index 0000000..fec5286
--- /dev/null
+++ b/testapps/TelephonyRegistryTestApp/Android.bp
@@ -0,0 +1,7 @@
+android_test {
+ name: "TelephonyRegistryTestApp",
+ srcs: ["src/**/*.java"],
+ platform_apis: true,
+ certificate: "platform",
+ //LOCAL_MODULE_TAGS := debug
+}
diff --git a/testapps/TelephonyRegistryTestApp/Android.mk b/testapps/TelephonyRegistryTestApp/Android.mk
deleted file mode 100644
index 8c0d286..0000000
--- a/testapps/TelephonyRegistryTestApp/Android.mk
+++ /dev/null
@@ -1,18 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-src_dirs := src
-res_dirs := res
-
-LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs))
-LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs))
-
-LOCAL_PACKAGE_NAME := TelephonyRegistryTestApp
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_CERTIFICATE := platform
-LOCAL_MODULE_TAGS := tests
-#LOCAL_MODULE_TAGS := debug
-
-include $(BUILD_PACKAGE)
diff --git a/tests/src/com/android/TelephonyTestBase.java b/tests/src/com/android/TelephonyTestBase.java
index 01267d8..86c5402 100644
--- a/tests/src/com/android/TelephonyTestBase.java
+++ b/tests/src/com/android/TelephonyTestBase.java
@@ -16,6 +16,8 @@
package com.android;
+import static org.mockito.Mockito.spy;
+
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
@@ -34,7 +36,7 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mContext = new TestContext();
+ mContext = spy(new TestContext());
// Set up the looper if it does not exist on the test thread.
if (Looper.myLooper() == null) {
Looper.prepare();
diff --git a/tests/src/com/android/TestContext.java b/tests/src/com/android/TestContext.java
index 776ec6a..c190be9 100644
--- a/tests/src/com/android/TestContext.java
+++ b/tests/src/com/android/TestContext.java
@@ -34,6 +34,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.Executor;
+
public class TestContext extends MockContext {
@Mock CarrierConfigManager mMockCarrierConfigManager;
@@ -49,6 +51,12 @@
}
@Override
+ public Executor getMainExecutor() {
+ // Just run on current thread
+ return Runnable::run;
+ }
+
+ @Override
public Context getApplicationContext() {
return this;
}
diff --git a/tests/src/com/android/services/telephony/rcs/RcsFeatureControllerTest.java b/tests/src/com/android/services/telephony/rcs/RcsFeatureControllerTest.java
new file mode 100644
index 0000000..cfede94
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/RcsFeatureControllerTest.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.services.telephony.rcs;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.telephony.AccessNetworkConstants;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.RegistrationManager;
+import android.telephony.ims.aidl.IImsCapabilityCallback;
+import android.telephony.ims.aidl.IImsRegistrationCallback;
+import android.telephony.ims.feature.RcsFeature;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+import com.android.ims.FeatureConnector;
+import com.android.ims.RcsFeatureManager;
+import com.android.internal.telephony.imsphone.ImsRegistrationCallbackHelper;
+
+import org.junit.After;
+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 java.util.concurrent.Executor;
+
+@RunWith(AndroidJUnit4.class)
+public class RcsFeatureControllerTest extends TelephonyTestBase {
+
+ private static final ImsReasonInfo REASON_DISCONNECTED = new ImsReasonInfo(
+ ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN, 0, "test");
+
+ @Mock RcsFeatureManager mFeatureManager;
+ @Mock RcsFeatureController.FeatureConnectorFactory<RcsFeatureManager> mFeatureFactory;
+ @Mock ImsRegistrationCallbackHelper.ImsRegistrationUpdate mRegistrationCallback;
+ @Mock FeatureConnector<RcsFeatureManager> mFeatureConnector;
+ @Mock RcsFeatureController.Feature mMockFeature;
+ @Captor ArgumentCaptor<FeatureConnector.Listener<RcsFeatureManager>> mConnectorListener;
+
+ private RcsFeatureController.RegistrationHelperFactory mRegistrationFactory =
+ new RcsFeatureController.RegistrationHelperFactory() {
+ @Override
+ public ImsRegistrationCallbackHelper create(
+ ImsRegistrationCallbackHelper.ImsRegistrationUpdate cb, Executor executor) {
+ // Run on current thread for testing.
+ return new ImsRegistrationCallbackHelper(mRegistrationCallback, Runnable::run);
+ }
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ public void testRcsFeatureManagerConnectDisconnect() throws Exception {
+ RcsFeatureController controller = createFeatureController();
+ controller.addFeature(mMockFeature, RcsFeatureController.Feature.class);
+ verify(mMockFeature).onRcsDisconnected();
+ // Connect the RcsFeatureManager
+ mConnectorListener.getValue().connectionReady(mFeatureManager);
+
+ verify(mFeatureManager).updateCapabilities();
+ verify(mFeatureManager).registerImsRegistrationCallback(any());
+ verify(mMockFeature).onRcsConnected(mFeatureManager);
+
+ // Disconnect
+ mConnectorListener.getValue().connectionUnavailable();
+
+ verify(mFeatureManager).unregisterImsRegistrationCallback(any());
+ verify(mMockFeature, times(2)).onRcsDisconnected();
+ }
+
+ @Test
+ public void testFeatureManagerConnectedAddFeature() throws Exception {
+ RcsFeatureController controller = createFeatureController();
+ // Connect the RcsFeatureManager
+ mConnectorListener.getValue().connectionReady(mFeatureManager);
+ controller.addFeature(mMockFeature, RcsFeatureController.Feature.class);
+
+ verify(mMockFeature).onRcsConnected(mFeatureManager);
+ assertEquals(mMockFeature, controller.getFeature(RcsFeatureController.Feature.class));
+ }
+
+ @Test
+ public void testFeatureManagerConnectedRegister() throws Exception {
+ RcsFeatureController controller = createFeatureController();
+ IImsRegistrationCallback regCb = mock(IImsRegistrationCallback.class);
+ IImsCapabilityCallback capCb = mock(IImsCapabilityCallback.class);
+ // Connect the RcsFeatureManager
+ mConnectorListener.getValue().connectionReady(mFeatureManager);
+
+ try {
+ controller.registerImsRegistrationCallback(0 /*subId*/, regCb);
+ controller.registerRcsAvailabilityCallback(0 /*subId*/, capCb);
+ controller.isCapable(RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE,
+ ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+ controller.isAvailable(RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE);
+ controller.getRegistrationTech(integer -> {
+ });
+ verify(mFeatureManager).registerImsRegistrationCallback(0, regCb);
+ verify(mFeatureManager).registerRcsAvailabilityCallback(0, capCb);
+ verify(mFeatureManager).isCapable(
+ RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE,
+ ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+ verify(mFeatureManager).isAvailable(
+ RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE);
+ verify(mFeatureManager).getImsRegistrationTech(any());
+ } catch (ImsException e) {
+ fail("ImsException not expected.");
+ }
+
+ controller.unregisterImsRegistrationCallback(0, regCb);
+ controller.unregisterRcsAvailabilityCallback(0, capCb);
+ verify(mFeatureManager).unregisterImsRegistrationCallback(0, regCb);
+ verify(mFeatureManager).unregisterRcsAvailabilityCallback(0, capCb);
+ }
+
+ @Test
+ public void testFeatureManagerConnectedHelper() throws Exception {
+ RcsFeatureController controller = createFeatureController();
+ // Connect the RcsFeatureManager
+ mConnectorListener.getValue().connectionReady(mFeatureManager);
+ ArgumentCaptor<IImsRegistrationCallback> captor =
+ ArgumentCaptor.forClass(IImsRegistrationCallback.class);
+ verify(mFeatureManager).registerImsRegistrationCallback(captor.capture());
+ assertNotNull(captor.getValue());
+
+ captor.getValue().onDeregistered(REASON_DISCONNECTED);
+ controller.getRegistrationState(result -> {
+ assertNotNull(result);
+ assertEquals(RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED, result.intValue());
+ });
+ verify(mRegistrationCallback).handleImsUnregistered(REASON_DISCONNECTED);
+
+ captor.getValue().onRegistering(ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+ controller.getRegistrationState(result -> {
+ assertNotNull(result);
+ assertEquals(RegistrationManager.REGISTRATION_STATE_REGISTERING, result.intValue());
+ });
+ verify(mRegistrationCallback).handleImsRegistering(
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+ captor.getValue().onRegistered(ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+ controller.getRegistrationState(result -> {
+ assertNotNull(result);
+ assertEquals(RegistrationManager.REGISTRATION_STATE_REGISTERED, result.intValue());
+ });
+ verify(mRegistrationCallback).handleImsRegistered(
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+ }
+
+ @Test
+ public void testFeatureManagerDisconnectedAddFeature() {
+ RcsFeatureController controller = createFeatureController();
+ // Disconnect the RcsFeatureManager
+ mConnectorListener.getValue().connectionUnavailable();
+ controller.addFeature(mMockFeature, RcsFeatureController.Feature.class);
+
+ verify(mMockFeature).onRcsDisconnected();
+ }
+
+ @Test
+ public void testFeatureManagerDisconnectedException() {
+ RcsFeatureController controller = createFeatureController();
+ IImsRegistrationCallback regCb = mock(IImsRegistrationCallback.class);
+ IImsCapabilityCallback capCb = mock(IImsCapabilityCallback.class);
+ // Disconnect the RcsFeatureManager
+ mConnectorListener.getValue().connectionUnavailable();
+
+ try {
+ controller.registerImsRegistrationCallback(0 /*subId*/, null /*callback*/);
+ fail("ImsException expected for IMS registration.");
+ } catch (ImsException e) {
+ //expected
+ }
+ try {
+ controller.registerRcsAvailabilityCallback(0 /*subId*/, null /*callback*/);
+ fail("ImsException expected for availability");
+ } catch (ImsException e) {
+ //expected
+ }
+ try {
+ controller.isCapable(RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE,
+ ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+ fail("ImsException expected for capability check");
+ } catch (ImsException e) {
+ //expected
+ }
+ try {
+ controller.isAvailable(RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE);
+ fail("ImsException expected for availability check");
+ } catch (ImsException e) {
+ //expected
+ }
+ controller.getRegistrationTech(integer -> {
+ assertNotNull(integer);
+ assertEquals(ImsRegistrationImplBase.REGISTRATION_TECH_NONE, integer.intValue());
+ });
+ controller.unregisterImsRegistrationCallback(0, regCb);
+ controller.unregisterRcsAvailabilityCallback(0, capCb);
+ verify(mFeatureManager, never()).unregisterImsRegistrationCallback(0, regCb);
+ verify(mFeatureManager, never()).unregisterRcsAvailabilityCallback(0, capCb);
+ }
+
+ @Test
+ public void testChangeSubId() throws Exception {
+ RcsFeatureController controller = createFeatureController();
+ // Connect the RcsFeatureManager
+ mConnectorListener.getValue().connectionReady(mFeatureManager);
+ verify(mFeatureManager).updateCapabilities();
+ controller.addFeature(mMockFeature, RcsFeatureController.Feature.class);
+
+ controller.updateAssociatedSubscription(1 /*new sub id*/);
+
+ verify(mFeatureManager, times(2)).updateCapabilities();
+ verify(mMockFeature).onAssociatedSubscriptionUpdated(1 /*new sub id*/);
+ }
+
+ @Test
+ public void testDestroy() throws Exception {
+ RcsFeatureController controller = createFeatureController();
+ // Connect the RcsFeatureManager
+ mConnectorListener.getValue().connectionReady(mFeatureManager);
+ controller.addFeature(mMockFeature, RcsFeatureController.Feature.class);
+ controller.destroy();
+
+ verify(mFeatureConnector).disconnect();
+ verify(mMockFeature).onRcsDisconnected();
+ verify(mMockFeature).onDestroy();
+ assertNull(controller.getFeature(RcsFeatureController.Feature.class));
+ }
+
+ private RcsFeatureController createFeatureController() {
+ RcsFeatureController controller = new RcsFeatureController(mContext, 0 /*slotId*/,
+ mRegistrationFactory);
+ controller.setFeatureConnectorFactory(mFeatureFactory);
+ doReturn(mFeatureConnector).when(mFeatureFactory).create(any(), anyInt(),
+ mConnectorListener.capture(), any(), any());
+ controller.connect();
+ assertNotNull(mConnectorListener.getValue());
+ return controller;
+ }
+}
diff --git a/tests/src/com/android/services/telephony/rcs/TelephonyRcsServiceTest.java b/tests/src/com/android/services/telephony/rcs/TelephonyRcsServiceTest.java
new file mode 100644
index 0000000..68b08a7
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/TelephonyRcsServiceTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.services.telephony.rcs;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.telephony.CarrierConfigManager;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+
+import org.junit.After;
+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;
+
+@RunWith(AndroidJUnit4.class)
+public class TelephonyRcsServiceTest extends TelephonyTestBase {
+
+ @Captor ArgumentCaptor<BroadcastReceiver> mReceiverCaptor;
+ @Mock TelephonyRcsService.FeatureFactory mFeatureFactory;
+ @Mock RcsFeatureController mFeatureControllerSlot0;
+ @Mock RcsFeatureController mFeatureControllerSlot1;
+ @Mock UserCapabilityExchangeImpl mMockUceSlot0;
+ @Mock UserCapabilityExchangeImpl mMockUceSlot1;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ doReturn(mFeatureControllerSlot0).when(mFeatureFactory).createController(any(), eq(0));
+ doReturn(mFeatureControllerSlot1).when(mFeatureFactory).createController(any(), eq(1));
+ doReturn(mMockUceSlot0).when(mFeatureFactory).createUserCapabilityExchange(any(), eq(0),
+ anyInt());
+ doReturn(mMockUceSlot1).when(mFeatureFactory).createUserCapabilityExchange(any(), eq(1),
+ anyInt());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ public void testUserCapabilityExchangeConnected() {
+ createRcsService(1 /*numSlots*/);
+ verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UserCapabilityExchangeImpl.class);
+ verify(mFeatureControllerSlot0).connect();
+ }
+
+ @Test
+ public void testSlotUpdates() {
+ TelephonyRcsService service = createRcsService(1 /*numSlots*/);
+ verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UserCapabilityExchangeImpl.class);
+ verify(mFeatureControllerSlot0).connect();
+
+ // there should be no changes if the new num slots = old num
+ service.updateFeatureControllerSize(1 /*newNumSlots*/);
+ verify(mFeatureControllerSlot0, times(1)).addFeature(mMockUceSlot0,
+ UserCapabilityExchangeImpl.class);
+ verify(mFeatureControllerSlot0, times(1)).connect();
+
+ // Add a new slot.
+ verify(mFeatureControllerSlot1, never()).addFeature(mMockUceSlot1,
+ UserCapabilityExchangeImpl.class);
+ verify(mFeatureControllerSlot1, never()).connect();
+ service.updateFeatureControllerSize(2 /*newNumSlots*/);
+ // This shouldn't have changed for slot 0.
+ verify(mFeatureControllerSlot0, times(1)).addFeature(mMockUceSlot0,
+ UserCapabilityExchangeImpl.class);
+ verify(mFeatureControllerSlot0, times(1)).connect();
+ verify(mFeatureControllerSlot1, times(1)).addFeature(mMockUceSlot1,
+ UserCapabilityExchangeImpl.class);
+ verify(mFeatureControllerSlot1, times(1)).connect();
+
+ // Remove a slot.
+ verify(mFeatureControllerSlot0, never()).destroy();
+ verify(mFeatureControllerSlot1, never()).destroy();
+ service.updateFeatureControllerSize(1 /*newNumSlots*/);
+ // addFeature/connect shouldn't have been called again
+ verify(mFeatureControllerSlot0, times(1)).addFeature(mMockUceSlot0,
+ UserCapabilityExchangeImpl.class);
+ verify(mFeatureControllerSlot0, times(1)).connect();
+ verify(mFeatureControllerSlot1, times(1)).addFeature(mMockUceSlot1,
+ UserCapabilityExchangeImpl.class);
+ verify(mFeatureControllerSlot1, times(1)).connect();
+ // Verify destroy is only called for slot 1.
+ verify(mFeatureControllerSlot0, never()).destroy();
+ verify(mFeatureControllerSlot1, times(1)).destroy();
+ }
+
+ @Test
+ public void testCarrierConfigUpdate() {
+ createRcsService(2 /*numSlots*/);
+ verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UserCapabilityExchangeImpl.class);
+ verify(mFeatureControllerSlot1).addFeature(mMockUceSlot1, UserCapabilityExchangeImpl.class);
+ verify(mFeatureControllerSlot0).connect();
+ verify(mFeatureControllerSlot1).connect();
+
+
+ // Send carrier config update for each slot.
+ sendCarrierConfigChanged(0 /*slotId*/, 1 /*subId*/);
+ verify(mFeatureControllerSlot0).updateAssociatedSubscription(1);
+ verify(mFeatureControllerSlot1, never()).updateAssociatedSubscription(1);
+ sendCarrierConfigChanged(1 /*slotId*/, 2 /*subId*/);
+ verify(mFeatureControllerSlot0, never()).updateAssociatedSubscription(2);
+ verify(mFeatureControllerSlot1, times(1)).updateAssociatedSubscription(2);
+ }
+
+ private void sendCarrierConfigChanged(int slotId, int subId) {
+ Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+ intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, slotId);
+ intent.putExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, subId);
+ mReceiverCaptor.getValue().onReceive(mContext, intent);
+ }
+
+ private TelephonyRcsService createRcsService(int numSlots) {
+ TelephonyRcsService service = new TelephonyRcsService(mContext, numSlots);
+ service.setFeatureFactory(mFeatureFactory);
+ service.initialize();
+ verify(mContext).registerReceiver(mReceiverCaptor.capture(), any());
+ return service;
+ }
+}