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