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