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.Context; 21 import android.net.Uri; 22 import android.telephony.ims.ImsException; 23 import android.telephony.ims.ImsReasonInfo; 24 import android.telephony.ims.aidl.IImsCapabilityCallback; 25 import android.telephony.ims.aidl.IImsRegistrationCallback; 26 import android.telephony.ims.stub.ImsRegistrationImplBase; 27 import android.util.ArrayMap; 28 import android.util.Log; 29 30 import com.android.ims.FeatureConnector; 31 import com.android.ims.FeatureUpdates; 32 import com.android.ims.RcsFeatureManager; 33 import com.android.internal.annotations.VisibleForTesting; 34 import com.android.internal.telephony.imsphone.ImsRegistrationCallbackHelper; 35 import com.android.internal.util.IndentingPrintWriter; 36 import com.android.phone.ImsStateCallbackController; 37 38 import java.io.FileDescriptor; 39 import java.io.PrintWriter; 40 import java.util.Map; 41 import java.util.concurrent.Executor; 42 import java.util.function.Consumer; 43 44 /** 45 * Contains the RCS feature implementations that are associated with this slot's RcsFeature. 46 */ 47 @AnyThread 48 public class RcsFeatureController { 49 private static final String LOG_TAG = "RcsFeatureController"; 50 51 /** 52 * Interface used by RCS features that need to listen for when the associated service has been 53 * connected. 54 */ 55 public interface Feature { 56 /** 57 * The RcsFeature has been connected to the framework and is ready. 58 */ onRcsConnected(RcsFeatureManager manager)59 void onRcsConnected(RcsFeatureManager manager); 60 61 /** 62 * The framework has lost the binding to the RcsFeature or it is in the process of changing. 63 */ onRcsDisconnected()64 void onRcsDisconnected(); 65 66 /** 67 * The subscription associated with the slot this controller is bound to has changed. 68 */ onAssociatedSubscriptionUpdated(int subId)69 void onAssociatedSubscriptionUpdated(int subId); 70 71 /** 72 * The carrier configuration associated with the active subscription id has changed. 73 */ onCarrierConfigChanged()74 void onCarrierConfigChanged(); 75 76 /** 77 * Called when the feature should be destroyed. 78 */ onDestroy()79 void onDestroy(); 80 81 /** 82 * Called when a dumpsys is being generated for this RcsFeatureController for all Features 83 * to report their status. 84 */ dump(PrintWriter pw)85 void dump(PrintWriter pw); 86 } 87 88 /** 89 * Used to inject FeatureConnector instances for testing. 90 */ 91 @VisibleForTesting 92 public interface FeatureConnectorFactory<U extends FeatureUpdates> { 93 /** 94 * @return a {@link FeatureConnector} associated for the given {@link FeatureUpdates} 95 * and slot index. 96 */ create(Context context, int slotIndex, FeatureConnector.Listener<U> listener, Executor executor, String logPrefix)97 FeatureConnector<U> create(Context context, int slotIndex, 98 FeatureConnector.Listener<U> listener, Executor executor, String logPrefix); 99 } 100 101 /** 102 * Used to inject ImsRegistrationCallbackHelper instances for testing. 103 */ 104 @VisibleForTesting 105 public interface RegistrationHelperFactory { 106 /** 107 * @return an {@link ImsRegistrationCallbackHelper}, which helps manage IMS registration 108 * state. 109 */ create( ImsRegistrationCallbackHelper.ImsRegistrationUpdate cb, Executor executor)110 ImsRegistrationCallbackHelper create( 111 ImsRegistrationCallbackHelper.ImsRegistrationUpdate cb, Executor executor); 112 } 113 114 private FeatureConnectorFactory<RcsFeatureManager> mFeatureFactory = 115 RcsFeatureManager::getConnector; 116 private RegistrationHelperFactory mRegistrationHelperFactory = 117 ImsRegistrationCallbackHelper::new; 118 119 private final Map<Class<?>, Feature> mFeatures = new ArrayMap<>(); 120 private final Context mContext; 121 private final ImsRegistrationCallbackHelper mImsRcsRegistrationHelper; 122 private final int mSlotId; 123 private final Object mLock = new Object(); 124 private FeatureConnector<RcsFeatureManager> mFeatureConnector; 125 private RcsFeatureManager mFeatureManager; 126 private int mAssociatedSubId; 127 128 private FeatureConnector.Listener<RcsFeatureManager> mFeatureConnectorListener = 129 new FeatureConnector.Listener<RcsFeatureManager>() { 130 @Override 131 public void connectionReady(RcsFeatureManager manager, int subId) 132 throws com.android.ims.ImsException { 133 if (manager == null) { 134 logw("connectionReady returned null RcsFeatureManager"); 135 return; 136 } 137 logd("connectionReady"); 138 try { 139 // May throw ImsException if for some reason the connection to the 140 // ImsService is gone. 141 updateConnectionStatus(manager); 142 setupConnectionToService(manager); 143 ImsStateCallbackController.getInstance() 144 .notifyExternalRcsStateChanged(mSlotId, true, true); 145 } catch (ImsException e) { 146 updateConnectionStatus(null /*manager*/); 147 // Use deprecated Exception for compatibility. 148 throw new com.android.ims.ImsException(e.getMessage(), 149 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); 150 } 151 } 152 153 @Override 154 public void connectionUnavailable(int reason) { 155 if (reason == FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE) { 156 loge("unexpected - connectionUnavailable due to server unavailable"); 157 } 158 logd("connectionUnavailable"); 159 // Call before disabling connection to manager. 160 removeConnectionToService(); 161 updateConnectionStatus(null /*manager*/); 162 } 163 }; 164 165 private ImsRegistrationCallbackHelper.ImsRegistrationUpdate mRcsRegistrationUpdate = new 166 ImsRegistrationCallbackHelper.ImsRegistrationUpdate() { 167 @Override 168 public void handleImsRegistered(int imsRadioTech) { 169 } 170 171 @Override 172 public void handleImsRegistering(int imsRadioTech) { 173 } 174 175 @Override 176 public void handleImsUnregistered(ImsReasonInfo imsReasonInfo) { 177 } 178 179 @Override 180 public void handleImsSubscriberAssociatedUriChanged(Uri[] uris) { 181 } 182 }; 183 RcsFeatureController(Context context, int slotId, int associatedSubId)184 public RcsFeatureController(Context context, int slotId, int associatedSubId) { 185 mContext = context; 186 mSlotId = slotId; 187 mAssociatedSubId = associatedSubId; 188 mImsRcsRegistrationHelper = mRegistrationHelperFactory.create(mRcsRegistrationUpdate, 189 mContext.getMainExecutor()); 190 } 191 192 /** 193 * Should only be used to inject registration helpers for testing. 194 */ 195 @VisibleForTesting RcsFeatureController(Context context, int slotId, int associatedSubId, RegistrationHelperFactory f)196 public RcsFeatureController(Context context, int slotId, int associatedSubId, 197 RegistrationHelperFactory f) { 198 mContext = context; 199 mSlotId = slotId; 200 mAssociatedSubId = associatedSubId; 201 mRegistrationHelperFactory = f; 202 mImsRcsRegistrationHelper = mRegistrationHelperFactory.create(mRcsRegistrationUpdate, 203 mContext.getMainExecutor()); 204 } 205 206 /** 207 * This method should be called after constructing an instance of this class to start the 208 * connection process to the associated RcsFeature. 209 */ connect()210 public void connect() { 211 synchronized (mLock) { 212 if (mFeatureConnector != null) return; 213 mFeatureConnector = mFeatureFactory.create(mContext, mSlotId, mFeatureConnectorListener, 214 mContext.getMainExecutor(), LOG_TAG); 215 mFeatureConnector.connect(); 216 } 217 } 218 219 /** 220 * Adds a {@link Feature} to be tracked by this FeatureController. 221 */ addFeature(T connector, Class<T> clazz)222 public <T extends Feature> void addFeature(T connector, Class<T> clazz) { 223 synchronized (mLock) { 224 mFeatures.put(clazz, connector); 225 } 226 RcsFeatureManager manager = getFeatureManager(); 227 if (manager != null) { 228 connector.onRcsConnected(manager); 229 } else { 230 connector.onRcsDisconnected(); 231 } 232 } 233 234 /** 235 * @return The RCS feature implementation tracked by this controller. 236 */ 237 @SuppressWarnings("unchecked") getFeature(Class<T> clazz)238 public <T> T getFeature(Class<T> clazz) { 239 synchronized (mLock) { 240 return (T) mFeatures.get(clazz); 241 } 242 } 243 244 /** 245 * Removes the feature associated with this class. 246 */ removeFeature(Class<T> clazz)247 public <T> void removeFeature(Class<T> clazz) { 248 synchronized (mLock) { 249 RcsFeatureController.Feature feature = mFeatures.remove(clazz); 250 feature.onDestroy(); 251 } 252 } 253 254 /** 255 * @return true if this controller has features it is actively tracking. 256 */ hasActiveFeatures()257 public boolean hasActiveFeatures() { 258 synchronized (mLock) { 259 return mFeatures.size() > 0; 260 } 261 } 262 263 /** 264 * Update the Features associated with this controller due to the associated subscription 265 * changing. 266 */ updateAssociatedSubscription(int newSubId)267 public void updateAssociatedSubscription(int newSubId) { 268 mAssociatedSubId = newSubId; 269 updateCapabilities(); 270 synchronized (mLock) { 271 for (Feature c : mFeatures.values()) { 272 c.onAssociatedSubscriptionUpdated(newSubId); 273 } 274 } 275 } 276 277 /** 278 * Update the features associated with this controller due to the carrier configuration 279 * changing. 280 */ onCarrierConfigChangedForSubscription()281 public void onCarrierConfigChangedForSubscription() { 282 updateCapabilities(); 283 synchronized (mLock) { 284 for (Feature c : mFeatures.values()) { 285 c.onCarrierConfigChanged(); 286 } 287 } 288 } 289 290 /** 291 * Call before this controller is destroyed to tear down associated features. 292 */ destroy()293 public void destroy() { 294 synchronized (mLock) { 295 Log.i(LOG_TAG, "destroy: slotId=" + mSlotId); 296 if (mFeatureConnector != null) { 297 mFeatureConnector.disconnect(); 298 } 299 for (Feature c : mFeatures.values()) { 300 c.onRcsDisconnected(); 301 c.onDestroy(); 302 } 303 mFeatures.clear(); 304 } 305 } 306 307 @VisibleForTesting setFeatureConnectorFactory(FeatureConnectorFactory<RcsFeatureManager> factory)308 public void setFeatureConnectorFactory(FeatureConnectorFactory<RcsFeatureManager> factory) { 309 mFeatureFactory = factory; 310 } 311 312 /** 313 * Add a {@link RegistrationManager.RegistrationCallback} callback that gets called when IMS 314 * registration has changed for a specific subscription. 315 */ registerImsRegistrationCallback(int subId, IImsRegistrationCallback callback)316 public void registerImsRegistrationCallback(int subId, IImsRegistrationCallback callback) 317 throws ImsException { 318 RcsFeatureManager manager = getFeatureManager(); 319 if (manager == null) { 320 throw new ImsException("Service is not available", 321 ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); 322 } 323 manager.registerImsRegistrationCallback(subId, callback); 324 } 325 326 /** 327 * Removes a previously registered {@link RegistrationManager.RegistrationCallback} callback 328 * that is associated with a specific subscription. 329 */ unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback callback)330 public void unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback callback) { 331 RcsFeatureManager manager = getFeatureManager(); 332 if (manager != null) { 333 manager.unregisterImsRegistrationCallback(subId, callback); 334 } 335 } 336 337 /** 338 * Register an {@link ImsRcsManager.OnAvailabilityChangedListener} with the associated 339 * RcsFeature, which will provide availability updates. 340 */ registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback)341 public void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback) 342 throws ImsException { 343 RcsFeatureManager manager = getFeatureManager(); 344 if (manager == null) { 345 throw new ImsException("Service is not available", 346 ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); 347 } 348 manager.registerRcsAvailabilityCallback(subId, callback); 349 } 350 351 /** 352 * Remove a registered {@link ImsRcsManager.OnAvailabilityChangedListener} from the RcsFeature. 353 */ unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback)354 public void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback) { 355 RcsFeatureManager manager = getFeatureManager(); 356 if (manager != null) { 357 manager.unregisterRcsAvailabilityCallback(subId, callback); 358 } 359 } 360 361 /** 362 * Query for the specific capability. 363 */ isCapable(int capability, int radioTech)364 public boolean isCapable(int capability, int radioTech) 365 throws android.telephony.ims.ImsException { 366 RcsFeatureManager manager = getFeatureManager(); 367 if (manager == null) { 368 throw new ImsException("Service is not available", 369 ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); 370 } 371 return manager.isCapable(capability, radioTech); 372 } 373 374 /** 375 * Query the availability of an IMS RCS capability. 376 */ isAvailable(int capability, int radioTech)377 public boolean isAvailable(int capability, int radioTech) 378 throws android.telephony.ims.ImsException { 379 RcsFeatureManager manager = getFeatureManager(); 380 if (manager == null) { 381 throw new ImsException("Service is not available", 382 ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); 383 } 384 return manager.isAvailable(capability, radioTech); 385 } 386 387 /** 388 * Get the IMS RCS registration technology for this Phone. 389 */ getRegistrationTech(Consumer<Integer> callback)390 public void getRegistrationTech(Consumer<Integer> callback) { 391 RcsFeatureManager manager = getFeatureManager(); 392 if (manager != null) { 393 manager.getImsRegistrationTech(callback); 394 } 395 callback.accept(ImsRegistrationImplBase.REGISTRATION_TECH_NONE); 396 } 397 398 /** 399 * Retrieve the current RCS registration state. 400 */ getRegistrationState(Consumer<Integer> callback)401 public void getRegistrationState(Consumer<Integer> callback) { 402 callback.accept(mImsRcsRegistrationHelper.getImsRegistrationState()); 403 } 404 updateCapabilities()405 private void updateCapabilities() { 406 RcsFeatureManager manager = getFeatureManager(); 407 if (manager != null) { 408 try { 409 manager.updateCapabilities(mAssociatedSubId); 410 } catch (ImsException e) { 411 Log.w(LOG_TAG, "updateCapabilities failed:" + e); 412 } 413 } 414 } 415 setupConnectionToService(RcsFeatureManager manager)416 private void setupConnectionToService(RcsFeatureManager manager) throws ImsException { 417 logd("setupConnectionToService"); 418 // Open persistent listener connection, sends RcsFeature#onFeatureReady. 419 manager.openConnection(); 420 manager.updateCapabilities(mAssociatedSubId); 421 manager.registerImsRegistrationCallback(mImsRcsRegistrationHelper.getCallbackBinder()); 422 } 423 removeConnectionToService()424 private void removeConnectionToService() { 425 logd("removeConnectionToService"); 426 RcsFeatureManager manager = getFeatureManager(); 427 if (manager != null) { 428 manager.unregisterImsRegistrationCallback( 429 mImsRcsRegistrationHelper.getCallbackBinder()); 430 // Remove persistent listener connection. 431 manager.releaseConnection(); 432 } 433 mImsRcsRegistrationHelper.reset(); 434 } 435 updateConnectionStatus(RcsFeatureManager manager)436 private void updateConnectionStatus(RcsFeatureManager manager) { 437 synchronized (mLock) { 438 mFeatureManager = manager; 439 if (mFeatureManager != null) { 440 for (Feature c : mFeatures.values()) { 441 c.onRcsConnected(manager); 442 } 443 } else { 444 for (Feature c : mFeatures.values()) { 445 c.onRcsDisconnected(); 446 } 447 } 448 } 449 } 450 getFeatureManager()451 private RcsFeatureManager getFeatureManager() { 452 synchronized (mLock) { 453 return mFeatureManager; 454 } 455 } 456 457 /** 458 * Dump this controller's instance information for usage in dumpsys. 459 */ dump(FileDescriptor fd, PrintWriter printWriter, String[] args)460 public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { 461 IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); 462 pw.print("slotId="); 463 pw.println(mSlotId); 464 pw.print("RegistrationState="); 465 pw.println(mImsRcsRegistrationHelper.getImsRegistrationState()); 466 pw.print("connected="); 467 synchronized (mLock) { 468 pw.println(mFeatureManager != null); 469 pw.println(); 470 pw.println("RcsFeatureControllers:"); 471 pw.increaseIndent(); 472 for (Feature f : mFeatures.values()) { 473 f.dump(pw); 474 pw.println(); 475 } 476 pw.decreaseIndent(); 477 } 478 } 479 logd(String log)480 private void logd(String log) { 481 Log.d(LOG_TAG, getLogPrefix().append(log).toString()); 482 } 483 logw(String log)484 private void logw(String log) { 485 Log.w(LOG_TAG, getLogPrefix().append(log).toString()); 486 } 487 loge(String log)488 private void loge(String log) { 489 Log.e(LOG_TAG, getLogPrefix().append(log).toString()); 490 } 491 getLogPrefix()492 private StringBuilder getLogPrefix() { 493 StringBuilder sb = new StringBuilder("["); 494 sb.append(mSlotId); 495 sb.append("] "); 496 return sb; 497 } 498 } 499