1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.services.telephony.rcs; 18 19 import android.annotation.AnyThread; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.os.AsyncResult; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.os.Looper; 28 import android.telephony.CarrierConfigManager; 29 import android.telephony.SubscriptionManager; 30 import android.util.Log; 31 import android.util.SparseArray; 32 33 import com.android.internal.annotations.VisibleForTesting; 34 import com.android.internal.telephony.PhoneConfigurationManager; 35 import com.android.internal.telephony.metrics.RcsStats; 36 import com.android.internal.util.IndentingPrintWriter; 37 import com.android.phone.R; 38 39 import java.io.FileDescriptor; 40 import java.io.PrintWriter; 41 42 /** 43 * Singleton service setup to manage RCS related services that the platform provides such as User 44 * Capability Exchange. 45 */ 46 @AnyThread 47 public class TelephonyRcsService { 48 49 private static final String LOG_TAG = "TelephonyRcsService"; 50 51 /** 52 * Used to inject RcsFeatureController and UceController instances for testing. 53 */ 54 @VisibleForTesting 55 public interface FeatureFactory { 56 /** 57 * @return an {@link RcsFeatureController} associated with the slot specified. 58 */ createController(Context context, int slotId, int subId)59 RcsFeatureController createController(Context context, int slotId, int subId); 60 61 /** 62 * @return an instance of {@link UceControllerManager} associated with the slot specified. 63 */ createUceControllerManager(Context context, int slotId, int subId)64 UceControllerManager createUceControllerManager(Context context, int slotId, int subId); 65 66 /** 67 * @return an instance of {@link SipTransportController} for the slot and subscription 68 * specified. 69 */ createSipTransportController(Context context, int slotId, int subId)70 SipTransportController createSipTransportController(Context context, int slotId, int subId); 71 } 72 73 private FeatureFactory mFeatureFactory = new FeatureFactory() { 74 @Override 75 public RcsFeatureController createController(Context context, int slotId, int subId) { 76 return new RcsFeatureController(context, slotId, subId); 77 } 78 79 @Override 80 public UceControllerManager createUceControllerManager(Context context, int slotId, 81 int subId) { 82 return new UceControllerManager(context, slotId, subId); 83 } 84 85 @Override 86 public SipTransportController createSipTransportController(Context context, int slotId, 87 int subId) { 88 return new SipTransportController(context, slotId, subId); 89 } 90 }; 91 92 /** 93 * Used to inject device resource for testing. 94 */ 95 @VisibleForTesting 96 public interface ResourceProxy { 97 /** 98 * @return an whether the device supports User Capability Exchange. 99 */ getDeviceUceEnabled(Context context)100 boolean getDeviceUceEnabled(Context context); 101 } 102 103 private static ResourceProxy sResourceProxy = context -> { 104 return context.getResources().getBoolean( 105 R.bool.config_rcs_user_capability_exchange_enabled); 106 }; 107 108 // Notifies this service that there has been a change in available slots. 109 private static final int HANDLER_MSIM_CONFIGURATION_CHANGE = 1; 110 111 private final Context mContext; 112 private final Object mLock = new Object(); 113 private int mNumSlots; 114 115 // Maps slot ID -> RcsFeatureController. 116 private SparseArray<RcsFeatureController> mFeatureControllers; 117 // Maps slotId -> associatedSubIds 118 private SparseArray<Integer> mSlotToAssociatedSubIds; 119 120 // Whether the device supports User Capability Exchange 121 private boolean mRcsUceEnabled; 122 123 private BroadcastReceiver mCarrierConfigChangedReceiver = new BroadcastReceiver() { 124 @Override 125 public void onReceive(Context context, Intent intent) { 126 if (intent == null) { 127 return; 128 } 129 if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) { 130 Bundle bundle = intent.getExtras(); 131 if (bundle == null) { 132 return; 133 } 134 int slotId = bundle.getInt(CarrierConfigManager.EXTRA_SLOT_INDEX, 135 SubscriptionManager.INVALID_PHONE_INDEX); 136 int subId = bundle.getInt(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, 137 SubscriptionManager.INVALID_SUBSCRIPTION_ID); 138 onCarrierConfigChangedForSlot(slotId, subId); 139 } 140 } 141 }; 142 143 private Handler mHandler = new Handler(Looper.getMainLooper(), (msg) -> { 144 switch (msg.what) { 145 case HANDLER_MSIM_CONFIGURATION_CHANGE: { 146 AsyncResult result = (AsyncResult) msg.obj; 147 Integer numSlots = (Integer) result.result; 148 if (numSlots == null) { 149 Log.w(LOG_TAG, "msim config change with null num slots."); 150 break; 151 } 152 updateFeatureControllerSize(numSlots); 153 break; 154 } 155 default: 156 return false; 157 } 158 return true; 159 }); 160 TelephonyRcsService(Context context, int numSlots)161 public TelephonyRcsService(Context context, int numSlots) { 162 mContext = context; 163 mNumSlots = numSlots; 164 mFeatureControllers = new SparseArray<>(numSlots); 165 mSlotToAssociatedSubIds = new SparseArray<>(numSlots); 166 mRcsUceEnabled = sResourceProxy.getDeviceUceEnabled(mContext); 167 RcsStats.getInstance().registerUceCallback(); 168 } 169 170 @VisibleForTesting TelephonyRcsService(Context context, int numSlots, ResourceProxy resourceProxy)171 public TelephonyRcsService(Context context, int numSlots, ResourceProxy resourceProxy) { 172 mContext = context; 173 mNumSlots = numSlots; 174 mFeatureControllers = new SparseArray<>(numSlots); 175 mSlotToAssociatedSubIds = new SparseArray<>(numSlots); 176 sResourceProxy = resourceProxy; 177 mRcsUceEnabled = sResourceProxy.getDeviceUceEnabled(mContext); 178 RcsStats.getInstance().registerUceCallback(); 179 } 180 181 /** 182 * @return the {@link RcsFeatureController} associated with the given slot. 183 */ getFeatureController(int slotId)184 public RcsFeatureController getFeatureController(int slotId) { 185 synchronized (mLock) { 186 return mFeatureControllers.get(slotId); 187 } 188 } 189 190 /** 191 * Called after instance creation to initialize internal structures as well as register for 192 * system callbacks. 193 */ initialize()194 public void initialize() { 195 updateFeatureControllerSize(mNumSlots); 196 197 PhoneConfigurationManager.registerForMultiSimConfigChange(mHandler, 198 HANDLER_MSIM_CONFIGURATION_CHANGE, null); 199 mContext.registerReceiver(mCarrierConfigChangedReceiver, new IntentFilter( 200 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)); 201 } 202 203 @VisibleForTesting setFeatureFactory(FeatureFactory f)204 public void setFeatureFactory(FeatureFactory f) { 205 mFeatureFactory = f; 206 } 207 208 /** 209 * Update the number of {@link RcsFeatureController}s that are created based on the number of 210 * active slots on the device. 211 */ 212 @VisibleForTesting updateFeatureControllerSize(int newNumSlots)213 public void updateFeatureControllerSize(int newNumSlots) { 214 synchronized (mLock) { 215 int oldNumSlots = mFeatureControllers.size(); 216 if (oldNumSlots == newNumSlots) { 217 return; 218 } 219 Log.i(LOG_TAG, "updateFeatureControllers: oldSlots=" + oldNumSlots + ", newNumSlots=" 220 + newNumSlots); 221 mNumSlots = newNumSlots; 222 if (oldNumSlots < newNumSlots) { 223 for (int i = oldNumSlots; i < newNumSlots; i++) { 224 RcsFeatureController c = constructFeatureController(i); 225 // Do not add feature controllers for inactive subscriptions 226 if (c.hasActiveFeatures()) { 227 mFeatureControllers.put(i, c); 228 // Do not change mSlotToAssociatedSubIds, it will be updated upon carrier 229 // config change. 230 } 231 } 232 } else { 233 for (int i = (oldNumSlots - 1); i > (newNumSlots - 1); i--) { 234 RcsFeatureController c = mFeatureControllers.get(i); 235 if (c != null) { 236 mFeatureControllers.remove(i); 237 mSlotToAssociatedSubIds.remove(i); 238 c.destroy(); 239 } 240 } 241 } 242 } 243 } 244 245 /** 246 * ACTION_CARRIER_CONFIG_CHANGED was received by this service for a specific slot. 247 * @param slotId The slotId associated with the event. 248 * @param subId The subId associated with the event. May cause the subId associated with the 249 * RcsFeatureController to change if the subscription itself has changed. 250 */ onCarrierConfigChangedForSlot(int slotId, int subId)251 private void onCarrierConfigChangedForSlot(int slotId, int subId) { 252 synchronized (mLock) { 253 RcsFeatureController f = mFeatureControllers.get(slotId); 254 final int oldSubId = mSlotToAssociatedSubIds.get(slotId, 255 SubscriptionManager.INVALID_SUBSCRIPTION_ID); 256 mSlotToAssociatedSubIds.put(slotId, subId); 257 Log.i(LOG_TAG, "updateFeatureControllerSubscription: slotId=" + slotId 258 + ", oldSubId= " + oldSubId + ", subId=" + subId + ", existing feature=" 259 + (f != null)); 260 if (SubscriptionManager.isValidSubscriptionId(subId)) { 261 if (f == null) { 262 // A controller doesn't exist for this slot yet. 263 f = mFeatureFactory.createController(mContext, slotId, subId); 264 updateSupportedFeatures(f, slotId, subId); 265 if (f.hasActiveFeatures()) mFeatureControllers.put(slotId, f); 266 } else { 267 updateSupportedFeatures(f, slotId, subId); 268 // Do not keep an empty container around. 269 if (!f.hasActiveFeatures()) { 270 f.destroy(); 271 mFeatureControllers.remove(slotId); 272 } 273 } 274 } 275 if (f != null) { 276 if (oldSubId == subId) { 277 f.onCarrierConfigChangedForSubscription(); 278 } else { 279 f.updateAssociatedSubscription(subId); 280 } 281 } 282 } 283 } 284 constructFeatureController(int slotId)285 private RcsFeatureController constructFeatureController(int slotId) { 286 int subId = getSubscriptionFromSlot(slotId); 287 RcsFeatureController c = mFeatureFactory.createController(mContext, slotId, subId); 288 updateSupportedFeatures(c, slotId, subId); 289 return c; 290 } 291 updateSupportedFeatures(RcsFeatureController c, int slotId, int subId)292 private void updateSupportedFeatures(RcsFeatureController c, int slotId, int subId) { 293 if (isDeviceUceEnabled() && doesSubscriptionSupportPresence(subId)) { 294 if (c.getFeature(UceControllerManager.class) == null) { 295 c.addFeature(mFeatureFactory.createUceControllerManager(mContext, slotId, subId), 296 UceControllerManager.class); 297 } 298 } else { 299 if (c.getFeature(UceControllerManager.class) != null) { 300 c.removeFeature(UceControllerManager.class); 301 } 302 } 303 304 if (doesSubscriptionSupportSingleRegistration(subId)) { 305 if (c.getFeature(SipTransportController.class) == null) { 306 c.addFeature(mFeatureFactory.createSipTransportController(mContext, slotId, subId), 307 SipTransportController.class); 308 } 309 } else { 310 if (c.getFeature(SipTransportController.class) != null) { 311 c.removeFeature(SipTransportController.class); 312 } 313 } 314 // Only start the connection procedure if we have active features. 315 if (c.hasActiveFeatures()) c.connect(); 316 } 317 318 /** 319 * Get whether the device supports RCS User Capability Exchange or not. 320 */ isDeviceUceEnabled()321 public boolean isDeviceUceEnabled() { 322 return mRcsUceEnabled; 323 } 324 325 /** 326 * Set the device supports RCS User Capability Exchange. 327 */ setDeviceUceEnabled(boolean isEnabled)328 public void setDeviceUceEnabled(boolean isEnabled) { 329 mRcsUceEnabled = isEnabled; 330 } 331 doesSubscriptionSupportPresence(int subId)332 private boolean doesSubscriptionSupportPresence(int subId) { 333 if (!SubscriptionManager.isValidSubscriptionId(subId)) return false; 334 CarrierConfigManager carrierConfigManager = 335 mContext.getSystemService(CarrierConfigManager.class); 336 if (carrierConfigManager == null) return false; 337 boolean supportsUce = carrierConfigManager.getConfigForSubId(subId).getBoolean( 338 CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL); 339 supportsUce |= carrierConfigManager.getConfigForSubId(subId).getBoolean( 340 CarrierConfigManager.KEY_USE_RCS_SIP_OPTIONS_BOOL); 341 return supportsUce; 342 } 343 doesSubscriptionSupportSingleRegistration(int subId)344 private boolean doesSubscriptionSupportSingleRegistration(int subId) { 345 if (!SubscriptionManager.isValidSubscriptionId(subId)) return false; 346 CarrierConfigManager carrierConfigManager = 347 mContext.getSystemService(CarrierConfigManager.class); 348 if (carrierConfigManager == null) return false; 349 return carrierConfigManager.getConfigForSubId(subId).getBoolean( 350 CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL); 351 } 352 getSubscriptionFromSlot(int slotId)353 private int getSubscriptionFromSlot(int slotId) { 354 SubscriptionManager manager = mContext.getSystemService(SubscriptionManager.class); 355 if (manager == null) { 356 Log.w(LOG_TAG, "Couldn't find SubscriptionManager for slotId=" + slotId); 357 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 358 } 359 int[] subIds = manager.getSubscriptionIds(slotId); 360 if (subIds != null && subIds.length > 0) { 361 return subIds[0]; 362 } 363 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 364 } 365 366 /** 367 * Dump this instance into a readable format for dumpsys usage. 368 */ dump(FileDescriptor fd, PrintWriter printWriter, String[] args)369 public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { 370 IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); 371 pw.println("RcsFeatureControllers:"); 372 pw.increaseIndent(); 373 synchronized (mLock) { 374 for (int i = 0; i < mNumSlots; i++) { 375 RcsFeatureController f = mFeatureControllers.get(i); 376 if (f == null) continue; 377 pw.increaseIndent(); 378 f.dump(fd, printWriter, args); 379 pw.decreaseIndent(); 380 } 381 } 382 pw.decreaseIndent(); 383 } 384 } 385