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