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