• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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