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