• 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 
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