Check to ensure an ImsService can handle an IMS request

Previously, if there was no ImsService available at the time of an
IMS request, we would send a not available exception, which is
documented to have the caller retry. This is inefficient because it
will cause apps to retry indefinitely on devices that support IMS
but do not have an ImsService configured for the active subscription.

This change adds a check in ImsResolver to first make sure there is
an ImsService configured for the ImsFeature type that the request
is for and if there isn't, we will instead send a
ImsException#CODE_ERROR_UNSUPPORTED_OPERATION error, which is a
permanent failure that apps can use to stop retrying.

Fixes: 184188331
Test: atest CtsTelephonyTestCases
Change-Id: I1b7a50c320e5bca59659561c5a0cfd928cd6e1c0
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index ba3d138..906d4c3 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -3845,9 +3845,9 @@
         }
         final long token = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
-                    .addRegistrationCallbackForSubscription(c, subId);
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            ImsManager.getInstance(mApp, slotId).addRegistrationCallbackForSubscription(c, subId);
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -3869,7 +3869,6 @@
         }
         final long token = Binder.clearCallingIdentity();
         try {
-            // TODO(b/159910732): Remove ImsManager dependence and query through ImsPhone directly.
             ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
                     .removeRegistrationCallbackForSubscription(c, subId);
         } catch (ImsException e) {
@@ -3965,11 +3964,11 @@
             throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
                     "IMS not available on device.");
         }
-        // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
         final long token = Binder.clearCallingIdentity();
         try {
-            ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
-                    .addCapabilitiesCallbackForSubscription(c, subId);
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            ImsManager.getInstance(mApp, slotId).addCapabilitiesCallbackForSubscription(c, subId);
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -3992,7 +3991,6 @@
 
         final long token = Binder.clearCallingIdentity();
         try {
-            // TODO(b/159910732): Remove ImsManager dependence and query through ImsPhone directly.
             ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
                         .removeCapabilitiesCallbackForSubscription(c, subId);
         } catch (ImsException e) {
@@ -4008,11 +4006,11 @@
     @Override
     public boolean isCapable(int subId, int capability, int regTech) {
         enforceReadPrivilegedPermission("isCapable");
-        // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
         final long token = Binder.clearCallingIdentity();
         try {
-            return ImsManager.getInstance(mApp,
-                    getSlotIndexOrException(subId)).queryMmTelCapability(capability, regTech);
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            return ImsManager.getInstance(mApp, slotId).queryMmTelCapability(capability, regTech);
         } catch (com.android.ims.ImsException e) {
             Log.w(LOG_TAG, "IMS isCapable - service unavailable: " + e.getMessage());
             return false;
@@ -4064,6 +4062,7 @@
                         + subId + "'");
                 throw new ServiceSpecificException(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION);
             }
+            verifyImsMmTelConfiguredOrThrow(slotId);
             ImsManager.getInstance(mApp, slotId).isSupported(capability,
                     transportType, aBoolean -> {
                         try {
@@ -4073,6 +4072,8 @@
                                     + "running. Ignore");
                         }
                     });
+        } catch (ImsException e) {
+            throw new ServiceSpecificException(e.getCode());
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -4087,11 +4088,11 @@
         TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
                 mApp, subId, "isAdvancedCallingSettingEnabled");
 
-        // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
         final long token = Binder.clearCallingIdentity();
         try {
-            return ImsManager.getInstance(mApp,
-                    getSlotIndexOrException(subId)).isEnhanced4gLteModeSettingEnabledByUser();
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            return ImsManager.getInstance(mApp, slotId).isEnhanced4gLteModeSettingEnabledByUser();
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -4105,9 +4106,9 @@
                 "setAdvancedCallingSettingEnabled");
         final long identity = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            ImsManager.getInstance(mApp,
-                    getSlotIndexOrException(subId)).setEnhanced4gLteModeSetting(isEnabled);
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            ImsManager.getInstance(mApp, slotId).setEnhanced4gLteModeSetting(isEnabled);
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -4125,8 +4126,9 @@
                 mApp, subId, "isVtSettingEnabled");
         final long identity = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            return ImsManager.getInstance(mApp, getSlotIndexOrException(subId)).isVtEnabledByUser();
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            return ImsManager.getInstance(mApp, slotId).isVtEnabledByUser();
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -4140,8 +4142,9 @@
                 "setVtSettingEnabled");
         final long identity = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            ImsManager.getInstance(mApp, getSlotIndexOrException(subId)).setVtSetting(isEnabled);
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            ImsManager.getInstance(mApp, slotId).setVtSetting(isEnabled);
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -4159,9 +4162,9 @@
                 mApp, subId, "isVoWiFiSettingEnabled");
         final long identity = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            return ImsManager.getInstance(mApp,
-                    getSlotIndexOrException(subId)).isWfcEnabledByUser();
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            return ImsManager.getInstance(mApp, slotId).isWfcEnabledByUser();
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -4175,8 +4178,9 @@
                 "setVoWiFiSettingEnabled");
         final long identity = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            ImsManager.getInstance(mApp, getSlotIndexOrException(subId)).setWfcSetting(isEnabled);
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            ImsManager.getInstance(mApp, slotId).setWfcSetting(isEnabled);
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -4195,9 +4199,9 @@
                 mApp, subId, "isCrossSimCallingEnabledByUser");
         final long identity = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            return ImsManager.getInstance(mApp,
-                    getSlotIndexOrException(subId)).isCrossSimCallingEnabledByUser();
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            return ImsManager.getInstance(mApp, slotId).isCrossSimCallingEnabledByUser();
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -4218,9 +4222,9 @@
                 "setCrossSimCallingEnabled");
         final long identity = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
-                    .setCrossSimCallingEnabled(isEnabled);
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            ImsManager.getInstance(mApp, slotId).setCrossSimCallingEnabled(isEnabled);
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -4239,9 +4243,9 @@
                 mApp, subId, "isVoWiFiRoamingSettingEnabled");
         final long identity = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            return ImsManager.getInstance(mApp,
-                    getSlotIndexOrException(subId)).isWfcRoamingEnabledByUser();
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            return ImsManager.getInstance(mApp, slotId).isWfcRoamingEnabledByUser();
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -4255,9 +4259,9 @@
                 "setVoWiFiRoamingSettingEnabled");
         final long identity = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            ImsManager.getInstance(mApp,
-                    getSlotIndexOrException(subId)).setWfcRoamingSetting(isEnabled);
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            ImsManager.getInstance(mApp, slotId).setWfcRoamingSetting(isEnabled);
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -4271,9 +4275,9 @@
                 "setVoWiFiNonPersistent");
         final long identity = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            ImsManager.getInstance(mApp,
-                    getSlotIndexOrException(subId)).setWfcNonPersistent(isCapable, mode);
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            ImsManager.getInstance(mApp, slotId).setWfcNonPersistent(isCapable, mode);
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -4291,9 +4295,9 @@
                 mApp, subId, "getVoWiFiModeSetting");
         final long identity = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            return ImsManager.getInstance(mApp,
-                    getSlotIndexOrException(subId)).getWfcMode(false /*isRoaming*/);
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            return ImsManager.getInstance(mApp, slotId).getWfcMode(false /*isRoaming*/);
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -4307,9 +4311,9 @@
                 "setVoWiFiModeSetting");
         final long identity = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            ImsManager.getInstance(mApp,
-                    getSlotIndexOrException(subId)).setWfcMode(mode, false /*isRoaming*/);
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            ImsManager.getInstance(mApp, slotId).setWfcMode(mode, false /*isRoaming*/);
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -4322,9 +4326,9 @@
         enforceReadPrivilegedPermission("getVoWiFiRoamingModeSetting");
         final long identity = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            return ImsManager.getInstance(mApp,
-                    getSlotIndexOrException(subId)).getWfcMode(true /*isRoaming*/);
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            return ImsManager.getInstance(mApp, slotId).getWfcMode(true /*isRoaming*/);
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -4338,9 +4342,9 @@
                 "setVoWiFiRoamingModeSetting");
         final long identity = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            ImsManager.getInstance(mApp,
-                    getSlotIndexOrException(subId)).setWfcMode(mode, true /*isRoaming*/);
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            ImsManager.getInstance(mApp, slotId).setWfcMode(mode, true /*isRoaming*/);
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -4354,8 +4358,9 @@
                 "setRttCapabilityEnabled");
         final long identity = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            ImsManager.getInstance(mApp, getSlotIndexOrException(subId)).setRttEnabled(isEnabled);
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            ImsManager.getInstance(mApp, slotId).setRttEnabled(isEnabled);
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -4373,9 +4378,9 @@
                 mApp, subId, "isTtyOverVolteEnabled");
         final long identity = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            return ImsManager.getInstance(mApp,
-                    getSlotIndexOrException(subId)).isTtyOnVoLteCapable();
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            return ImsManager.getInstance(mApp, slotId).isTtyOnVoLteCapable();
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -4392,8 +4397,9 @@
                 throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
                         "IMS not available on device.");
             }
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
+            int slotId = getSlotIndexOrException(subId);
+            verifyImsMmTelConfiguredOrThrow(slotId);
+            ImsManager.getInstance(mApp, slotId)
                     .addProvisioningCallbackForSubscription(callback, subId);
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
@@ -4410,7 +4416,6 @@
             throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
         }
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
             ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
                     .removeProvisioningCallbackForSubscription(callback, subId);
         } catch (ImsException e) {
@@ -4857,6 +4862,20 @@
         }
     }
 
+    /**
+     * Throw an ImsException if the IMS resolver does not have an ImsService configured for MMTEL
+     * for the given slot ID or no ImsResolver instance has been created.
+     * @param slotId The slot ID that the IMS service is created for.
+     * @throws ImsException If there is no ImsService configured for this slot.
+     */
+    private void verifyImsMmTelConfiguredOrThrow(int slotId) throws ImsException {
+        if (mImsResolver == null || !mImsResolver.isImsServiceConfiguredForFeature(slotId,
+                ImsFeature.FEATURE_MMTEL)) {
+            throw new ImsException("This subscription does not support MMTEL over IMS",
+                    ImsException.CODE_ERROR_UNSUPPORTED_OPERATION);
+        }
+    }
+
     private int getSlotIndexOrException(int subId) throws ImsException {
         int slotId = SubscriptionManager.getSlotIndex(subId);
         if (!SubscriptionManager.isValidSlotIndex(slotId)) {
@@ -5817,6 +5836,7 @@
                         + subId + "'");
                 throw new ServiceSpecificException(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION);
             }
+            verifyImsMmTelConfiguredOrThrow(slotId);
             ImsManager.getInstance(mApp, slotId).getImsServiceState(anInteger -> {
                 try {
                     callback.accept(anInteger == null ? ImsFeature.STATE_UNAVAILABLE : anInteger);
@@ -5825,6 +5845,8 @@
                             + "Ignore");
                 }
             });
+        } catch (ImsException e) {
+            throw new ServiceSpecificException(e.getCode());
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -10079,8 +10101,8 @@
         try {
             if (!RcsProvisioningMonitor.getInstance()
                     .registerRcsProvisioningCallback(subId, callback)) {
-                throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
-                        "Service not available for the subscription.");
+                throw new ServiceSpecificException(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION,
+                        "Active subscription not found.");
             }
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -10161,11 +10183,15 @@
             IImsConfig configBinder = getImsConfig(getSlotIndex(subId), ImsFeature.FEATURE_RCS);
             if (configBinder == null) {
                 Rlog.e(LOG_TAG, "null result for setRcsClientConfiguration");
+                throw new ServiceSpecificException(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION,
+                        "could not find the requested subscription");
             } else {
                 configBinder.setRcsClientConfiguration(rcc);
             }
         } catch (RemoteException e) {
             Rlog.e(LOG_TAG, "fail to setRcsClientConfiguration " + e.getMessage());
+            throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
+                    "service is temporarily unavailable.");
         } finally {
             Binder.restoreCallingIdentity(identity);
         }