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