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.annotation.NonNull;
21 import android.content.Context;
22 import android.net.Uri;
23 import android.telephony.SubscriptionManager;
24 import android.telephony.ims.ImsException;
25 import android.telephony.ims.ImsReasonInfo;
26 import android.telephony.ims.ImsRegistrationAttributes;
27 import android.telephony.ims.aidl.IImsCapabilityCallback;
28 import android.telephony.ims.aidl.IImsRegistrationCallback;
29 import android.telephony.ims.stub.ImsRegistrationImplBase;
30 import android.util.ArrayMap;
31 import android.util.Log;
32 
33 import com.android.ims.FeatureConnector;
34 import com.android.ims.FeatureUpdates;
35 import com.android.ims.RcsFeatureManager;
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.internal.telephony.imsphone.ImsRegistrationCallbackHelper;
38 import com.android.internal.util.IndentingPrintWriter;
39 import com.android.phone.ImsStateCallbackController;
40 
41 import java.io.FileDescriptor;
42 import java.io.PrintWriter;
43 import java.util.Map;
44 import java.util.concurrent.Executor;
45 import java.util.function.Consumer;
46 
47 /**
48  * Contains the RCS feature implementations that are associated with this slot's RcsFeature.
49  */
50 @AnyThread
51 public class RcsFeatureController {
52     private static final String LOG_TAG = "RcsFeatureController";
53 
54     /**
55      * Interface used by RCS features that need to listen for when the associated service has been
56      * connected.
57      */
58     public interface Feature {
59         /**
60          * The RcsFeature has been connected to the framework and is ready.
61          */
onRcsConnected(RcsFeatureManager manager)62         void onRcsConnected(RcsFeatureManager manager);
63 
64         /**
65          * The framework has lost the binding to the RcsFeature or it is in the process of changing.
66          */
onRcsDisconnected()67         void onRcsDisconnected();
68 
69         /**
70          * The subscription associated with the slot this controller is bound to has changed.
71          */
onAssociatedSubscriptionUpdated(int subId)72         void onAssociatedSubscriptionUpdated(int subId);
73 
74         /**
75          * The carrier configuration associated with the active subscription id has changed.
76          */
onCarrierConfigChanged()77         void onCarrierConfigChanged();
78 
79         /**
80          * Called when the feature should be destroyed.
81          */
onDestroy()82         void onDestroy();
83 
84         /**
85          * Called when a dumpsys is being generated for this RcsFeatureController for all Features
86          * to report their status.
87          */
dump(PrintWriter pw)88         void dump(PrintWriter pw);
89     }
90 
91     /**
92      * Used to inject FeatureConnector instances for testing.
93      */
94     @VisibleForTesting
95     public interface FeatureConnectorFactory<U extends FeatureUpdates> {
96         /**
97          * @return a {@link FeatureConnector} associated for the given {@link FeatureUpdates}
98          * and slot index.
99          */
create(Context context, int slotIndex, FeatureConnector.Listener<U> listener, Executor executor, String logPrefix)100         FeatureConnector<U> create(Context context, int slotIndex,
101                 FeatureConnector.Listener<U> listener, Executor executor, String logPrefix);
102     }
103 
104     /**
105      * Used to inject ImsRegistrationCallbackHelper instances for testing.
106      */
107     @VisibleForTesting
108     public interface RegistrationHelperFactory {
109         /**
110          * @return an {@link ImsRegistrationCallbackHelper}, which helps manage IMS registration
111          * state.
112          */
create( ImsRegistrationCallbackHelper.ImsRegistrationUpdate cb, Executor executor)113         ImsRegistrationCallbackHelper create(
114                 ImsRegistrationCallbackHelper.ImsRegistrationUpdate cb, Executor executor);
115     }
116 
117     private FeatureConnectorFactory<RcsFeatureManager> mFeatureFactory =
118             RcsFeatureManager::getConnector;
119     private RegistrationHelperFactory mRegistrationHelperFactory =
120             ImsRegistrationCallbackHelper::new;
121 
122     private final Map<Class<?>, Feature> mFeatures = new ArrayMap<>();
123     private final Context mContext;
124     private final ImsRegistrationCallbackHelper mImsRcsRegistrationHelper;
125     private final int mSlotId;
126     private final Object mLock = new Object();
127     private FeatureConnector<RcsFeatureManager> mFeatureConnector;
128     private RcsFeatureManager mFeatureManager;
129     private int mAssociatedSubId;
130 
131     private FeatureConnector.Listener<RcsFeatureManager> mFeatureConnectorListener =
132             new FeatureConnector.Listener<RcsFeatureManager>() {
133                 @Override
134                 public void connectionReady(RcsFeatureManager manager, int subId)
135                         throws com.android.ims.ImsException {
136                     if (manager == null) {
137                         logw("connectionReady returned null RcsFeatureManager");
138                         return;
139                     }
140                     logd("connectionReady");
141                     try {
142                         // May throw ImsException if for some reason the connection to the
143                         // ImsService is gone.
144                         updateConnectionStatus(manager);
145                         setupConnectionToService(manager);
146                         ImsStateCallbackController.getInstance()
147                                 .notifyExternalRcsStateChanged(mSlotId, true, true);
148                     } catch (ImsException e) {
149                         updateConnectionStatus(null /*manager*/);
150                         // Use deprecated Exception for compatibility.
151                         throw new com.android.ims.ImsException(e.getMessage(),
152                                 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
153                     }
154                 }
155 
156                 @Override
157                 public void connectionUnavailable(int reason) {
158                     if (reason == FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE) {
159                         loge("unexpected - connectionUnavailable due to server unavailable");
160                     }
161                     logd("connectionUnavailable");
162                     // Call before disabling connection to manager.
163                     removeConnectionToService();
164                     updateConnectionStatus(null /*manager*/);
165                 }
166             };
167 
168     private ImsRegistrationCallbackHelper.ImsRegistrationUpdate mRcsRegistrationUpdate = new
169             ImsRegistrationCallbackHelper.ImsRegistrationUpdate() {
170                 @Override
171                 public void handleImsRegistered(@NonNull ImsRegistrationAttributes attributes) {
172                 }
173 
174                 @Override
175                 public void handleImsRegistering(int imsRadioTech) {
176                 }
177 
178                 @Override
179                 public void handleImsUnregistered(ImsReasonInfo imsReasonInfo,
180                         int suggestedAction, int imsRadioTech) {
181                 }
182 
183                 @Override
184                 public void handleImsSubscriberAssociatedUriChanged(Uri[] uris) {
185                 }
186             };
187 
RcsFeatureController(Context context, int slotId, int associatedSubId)188     public RcsFeatureController(Context context, int slotId, int associatedSubId) {
189         mContext = context;
190         mSlotId = slotId;
191         mAssociatedSubId = associatedSubId;
192         mImsRcsRegistrationHelper = mRegistrationHelperFactory.create(mRcsRegistrationUpdate,
193                 mContext.getMainExecutor());
194     }
195 
196     /**
197      * Should only be used to inject registration helpers for testing.
198      */
199     @VisibleForTesting
RcsFeatureController(Context context, int slotId, int associatedSubId, RegistrationHelperFactory f)200     public RcsFeatureController(Context context, int slotId, int associatedSubId,
201             RegistrationHelperFactory f) {
202         mContext = context;
203         mSlotId = slotId;
204         mAssociatedSubId = associatedSubId;
205         mRegistrationHelperFactory = f;
206         mImsRcsRegistrationHelper = mRegistrationHelperFactory.create(mRcsRegistrationUpdate,
207                 mContext.getMainExecutor());
208     }
209 
210     /**
211      * This method should be called after constructing an instance of this class to start the
212      * connection process to the associated RcsFeature.
213      */
connect()214     public void connect() {
215         synchronized (mLock) {
216             if (mFeatureConnector != null) return;
217             mFeatureConnector = mFeatureFactory.create(mContext, mSlotId, mFeatureConnectorListener,
218                     mContext.getMainExecutor(), LOG_TAG);
219             mFeatureConnector.connect();
220         }
221     }
222 
223     /**
224      * Adds a {@link Feature} to be tracked by this FeatureController.
225      */
addFeature(T connector, Class<T> clazz)226     public <T extends Feature> void addFeature(T connector, Class<T> clazz) {
227         synchronized (mLock) {
228             mFeatures.put(clazz, connector);
229         }
230         RcsFeatureManager manager = getFeatureManager();
231         if (manager != null) {
232             connector.onRcsConnected(manager);
233         } else {
234             connector.onRcsDisconnected();
235         }
236     }
237 
238     /**
239      * @return The RCS feature implementation tracked by this controller.
240      */
241     @SuppressWarnings("unchecked")
getFeature(Class<T> clazz)242     public <T> T getFeature(Class<T> clazz) {
243         synchronized (mLock) {
244             return (T) mFeatures.get(clazz);
245         }
246     }
247 
248     /**
249      * Removes the feature associated with this class.
250      */
removeFeature(Class<T> clazz)251     public <T> void removeFeature(Class<T> clazz) {
252         synchronized (mLock) {
253             RcsFeatureController.Feature feature = mFeatures.remove(clazz);
254             feature.onDestroy();
255         }
256     }
257 
258     /**
259      * @return true if this controller has features it is actively tracking.
260      */
hasActiveFeatures()261     public boolean hasActiveFeatures() {
262         synchronized (mLock) {
263             return mFeatures.size() > 0;
264         }
265     }
266 
267     /**
268      * Update the Features associated with this controller due to the associated subscription
269      * changing.
270      */
updateAssociatedSubscription(int newSubId)271     public void updateAssociatedSubscription(int newSubId) {
272         mAssociatedSubId = newSubId;
273         updateCapabilities();
274         synchronized (mLock) {
275             for (Feature c : mFeatures.values()) {
276                 c.onAssociatedSubscriptionUpdated(newSubId);
277             }
278         }
279     }
280 
281     /**
282      * Update the features associated with this controller due to the carrier configuration
283      * changing.
284      */
onCarrierConfigChangedForSubscription()285     public void onCarrierConfigChangedForSubscription() {
286         updateCapabilities();
287         synchronized (mLock) {
288             for (Feature c : mFeatures.values()) {
289                 c.onCarrierConfigChanged();
290             }
291         }
292     }
293 
294     /**
295      * Call before this controller is destroyed to tear down associated features.
296      */
destroy()297     public void destroy() {
298         synchronized (mLock) {
299             Log.i(LOG_TAG, "destroy: slotId=" + mSlotId);
300             if (mFeatureConnector != null) {
301                 mFeatureConnector.disconnect();
302             }
303             for (Feature c : mFeatures.values()) {
304                 c.onRcsDisconnected();
305                 c.onDestroy();
306             }
307             mFeatures.clear();
308         }
309     }
310 
311     @VisibleForTesting
setFeatureConnectorFactory(FeatureConnectorFactory<RcsFeatureManager> factory)312     public void setFeatureConnectorFactory(FeatureConnectorFactory<RcsFeatureManager> factory) {
313         mFeatureFactory = factory;
314     }
315 
316     /**
317      * Add a {@link RegistrationManager.RegistrationCallback} callback that gets called when IMS
318      * registration has changed for a specific subscription.
319      */
registerImsRegistrationCallback(int subId, IImsRegistrationCallback callback)320     public void registerImsRegistrationCallback(int subId, IImsRegistrationCallback callback)
321             throws ImsException {
322         RcsFeatureManager manager = getFeatureManager();
323         if (manager == null) {
324             throw new ImsException("Service is not available",
325                     ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
326         }
327         manager.registerImsRegistrationCallback(subId, callback);
328     }
329 
330     /**
331      * Removes a previously registered {@link RegistrationManager.RegistrationCallback} callback
332      * that is associated with a specific subscription.
333      */
unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback callback)334     public void unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback callback) {
335         RcsFeatureManager manager = getFeatureManager();
336         if (manager != null) {
337             manager.unregisterImsRegistrationCallback(subId, callback);
338         }
339     }
340 
341     /**
342      * Register an {@link ImsRcsManager.OnAvailabilityChangedListener} with the associated
343      * RcsFeature, which will provide availability updates.
344      */
registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback)345     public void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback)
346             throws ImsException {
347         RcsFeatureManager manager = getFeatureManager();
348         if (manager == null) {
349             throw new ImsException("Service is not available",
350                     ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
351         }
352         manager.registerRcsAvailabilityCallback(subId, callback);
353     }
354 
355     /**
356      * Remove a registered {@link ImsRcsManager.OnAvailabilityChangedListener} from the RcsFeature.
357      */
unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback)358     public void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback) {
359         RcsFeatureManager manager = getFeatureManager();
360         if (manager != null) {
361             manager.unregisterRcsAvailabilityCallback(subId, callback);
362         }
363     }
364 
365     /**
366      * Query for the specific capability.
367      */
isCapable(int capability, int radioTech)368     public boolean isCapable(int capability, int radioTech)
369             throws android.telephony.ims.ImsException {
370         RcsFeatureManager manager = getFeatureManager();
371         if (manager == null) {
372             throw new ImsException("Service is not available",
373                     ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
374         }
375         return manager.isCapable(capability, radioTech);
376     }
377 
378     /**
379      * Query the availability of an IMS RCS capability.
380      */
isAvailable(int capability, int radioTech)381     public boolean isAvailable(int capability, int radioTech)
382             throws android.telephony.ims.ImsException {
383         RcsFeatureManager manager = getFeatureManager();
384         if (manager == null) {
385             throw new ImsException("Service is not available",
386                     ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
387         }
388         return manager.isAvailable(capability, radioTech);
389     }
390 
391     /**
392      * Get the IMS RCS registration technology for this Phone.
393      */
getRegistrationTech(Consumer<Integer> callback)394     public void getRegistrationTech(Consumer<Integer> callback) {
395         RcsFeatureManager manager = getFeatureManager();
396         if (manager != null) {
397             manager.getImsRegistrationTech(callback);
398         }
399         callback.accept(ImsRegistrationImplBase.REGISTRATION_TECH_NONE);
400     }
401 
402     /**
403      * Retrieve the current RCS registration state.
404      */
getRegistrationState(Consumer<Integer> callback)405     public void getRegistrationState(Consumer<Integer> callback) {
406         callback.accept(mImsRcsRegistrationHelper.getImsRegistrationState());
407     }
408 
409     /**
410      * @return the subscription ID that is currently associated with this RCS feature.
411      */
getAssociatedSubId()412     public int getAssociatedSubId() {
413         RcsFeatureManager manager = getFeatureManager();
414         if (manager != null) {
415             return manager.getSubId();
416         }
417         return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
418     }
419 
updateCapabilities()420     private void updateCapabilities() {
421         RcsFeatureManager manager = getFeatureManager();
422         if (manager != null) {
423             try {
424                 manager.updateCapabilities(mAssociatedSubId);
425             } catch (ImsException e) {
426                 Log.w(LOG_TAG, "updateCapabilities failed:" + e);
427             }
428         }
429     }
430 
setupConnectionToService(RcsFeatureManager manager)431     private void setupConnectionToService(RcsFeatureManager manager) throws ImsException {
432         logd("setupConnectionToService");
433         // Open persistent listener connection, sends RcsFeature#onFeatureReady.
434         manager.openConnection();
435         manager.updateCapabilities(mAssociatedSubId);
436         manager.registerImsRegistrationCallback(mImsRcsRegistrationHelper.getCallbackBinder());
437     }
438 
removeConnectionToService()439     private void removeConnectionToService() {
440         logd("removeConnectionToService");
441         RcsFeatureManager manager = getFeatureManager();
442         if (manager != null) {
443             manager.unregisterImsRegistrationCallback(
444                     mImsRcsRegistrationHelper.getCallbackBinder());
445             // Remove persistent listener connection.
446             manager.releaseConnection();
447         }
448         mImsRcsRegistrationHelper.reset();
449     }
450 
updateConnectionStatus(RcsFeatureManager manager)451     private void updateConnectionStatus(RcsFeatureManager manager) {
452         synchronized (mLock) {
453             mFeatureManager = manager;
454             if (mFeatureManager != null) {
455                 for (Feature c : mFeatures.values()) {
456                     c.onRcsConnected(manager);
457                 }
458             } else {
459                 for (Feature c : mFeatures.values()) {
460                     c.onRcsDisconnected();
461                 }
462             }
463         }
464     }
465 
getFeatureManager()466     private RcsFeatureManager getFeatureManager() {
467         synchronized (mLock) {
468             return mFeatureManager;
469         }
470     }
471 
472     /**
473      * Dump this controller's instance information for usage in dumpsys.
474      */
dump(FileDescriptor fd, PrintWriter printWriter, String[] args)475     public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
476         IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
477         pw.print("slotId=");
478         pw.println(mSlotId);
479         pw.print("RegistrationState=");
480         pw.println(mImsRcsRegistrationHelper.getImsRegistrationState());
481         pw.print("connected=");
482         synchronized (mLock) {
483             pw.println(mFeatureManager != null);
484             pw.println();
485             pw.println("RcsFeatureControllers:");
486             pw.increaseIndent();
487             for (Feature f : mFeatures.values()) {
488                 f.dump(pw);
489                 pw.println();
490             }
491             pw.decreaseIndent();
492         }
493     }
494 
logd(String log)495     private void logd(String log) {
496         Log.d(LOG_TAG, getLogPrefix().append(log).toString());
497     }
498 
logw(String log)499     private void logw(String log) {
500         Log.w(LOG_TAG, getLogPrefix().append(log).toString());
501     }
502 
loge(String log)503     private void loge(String log) {
504         Log.e(LOG_TAG, getLogPrefix().append(log).toString());
505     }
506 
getLogPrefix()507     private StringBuilder getLogPrefix() {
508         StringBuilder sb = new StringBuilder("[");
509         sb.append(mSlotId);
510         sb.append("] ");
511         return sb;
512     }
513 }
514