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.Nullable;
20 import android.content.Context;
21 import android.net.Uri;
22 import android.telephony.SubscriptionManager;
23 import android.telephony.ims.ImsException;
24 import android.telephony.ims.RcsContactUceCapability;
25 import android.telephony.ims.RcsUceAdapter;
26 import android.telephony.ims.RcsUceAdapter.PublishState;
27 import android.telephony.ims.aidl.IRcsUceControllerCallback;
28 import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
29 import android.util.IndentingPrintWriter;
30 import android.util.Log;
31 
32 import com.android.ims.RcsFeatureManager;
33 import com.android.ims.rcs.uce.UceController;
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.internal.telephony.flags.FeatureFlags;
36 
37 import java.io.PrintWriter;
38 import java.util.List;
39 import java.util.Set;
40 import java.util.concurrent.ExecutionException;
41 import java.util.concurrent.ExecutorService;
42 import java.util.concurrent.Executors;
43 import java.util.concurrent.Future;
44 
45 /**
46  * Responsible for managing the creation and destruction of UceController. It also received the
47  * requests from {@link com.android.phone.ImsRcsController} and pass these requests to
48  * {@link UceController}
49  */
50 public class UceControllerManager implements RcsFeatureController.Feature {
51 
52     private static final String LOG_TAG = "UceControllerManager";
53 
54     private final int mSlotId;
55     private final Context mContext;
56     private final ExecutorService mExecutorService;
57     private final FeatureFlags mFeatureFlags;
58 
59     private volatile @Nullable UceController mUceController;
60     private volatile @Nullable RcsFeatureManager mRcsFeatureManager;
61 
UceControllerManager(Context context, int slotId, int subId, FeatureFlags featureFlags)62     public UceControllerManager(Context context, int slotId, int subId, FeatureFlags featureFlags) {
63         Log.d(LOG_TAG, "create: slotId=" + slotId + ", subId=" + subId);
64         mSlotId = slotId;
65         mContext = context;
66         mExecutorService = Executors.newSingleThreadExecutor();
67         mFeatureFlags = featureFlags;
68         initUceController(subId);
69     }
70 
71     /**
72      * Constructor to inject dependencies for testing.
73      */
74     @VisibleForTesting
UceControllerManager(Context context, int slotId, ExecutorService executor, UceController uceController, FeatureFlags featureFlags)75     public UceControllerManager(Context context, int slotId, ExecutorService executor,
76             UceController uceController, FeatureFlags featureFlags) {
77         mSlotId = slotId;
78         mContext = context;
79         mExecutorService = executor;
80         mUceController = uceController;
81         mFeatureFlags = featureFlags;
82     }
83 
84     @Override
onRcsConnected(RcsFeatureManager manager)85     public void onRcsConnected(RcsFeatureManager manager) {
86         mExecutorService.submit(() -> {
87             mRcsFeatureManager = manager;
88             if (mUceController != null) {
89                 mUceController.onRcsConnected(manager);
90             } else {
91                 Log.d(LOG_TAG, "onRcsConnected: UceController is null");
92             }
93         });
94     }
95 
96     @Override
onRcsDisconnected()97     public void onRcsDisconnected() {
98         mExecutorService.submit(() -> {
99             mRcsFeatureManager = null;
100             if (mUceController != null) {
101                 mUceController.onRcsDisconnected();
102             } else {
103                 Log.d(LOG_TAG, "onRcsDisconnected: UceController is null");
104             }
105         });
106     }
107 
108     @Override
onDestroy()109     public void onDestroy() {
110         mExecutorService.submit(() -> {
111             Log.d(LOG_TAG, "onDestroy");
112             if (mUceController != null) {
113                 mUceController.onDestroy();
114             }
115         });
116         // When the shutdown is called, it will refuse any new tasks and let existing tasks finish.
117         mExecutorService.shutdown();
118     }
119 
120     /**
121      * This method will be called when the subscription ID associated with the slot has
122      * changed.
123      */
124     @Override
onAssociatedSubscriptionUpdated(int newSubId)125     public void onAssociatedSubscriptionUpdated(int newSubId) {
126         mExecutorService.submit(() -> {
127             Log.i(LOG_TAG, "onAssociatedSubscriptionUpdated: slotId=" + mSlotId
128                     + ", newSubId=" + newSubId);
129 
130             // Check and create the UceController with the new updated subscription ID.
131             initUceController(newSubId);
132 
133             // The RCS should be connected when the mRcsFeatureManager is not null. Set it to the
134             // new UceController instance.
135             if (mUceController != null && mRcsFeatureManager != null) {
136                 mUceController.onRcsConnected(mRcsFeatureManager);
137             }
138         });
139     }
140 
141     /**
142      * This method will be called when the carrier config of the subscription associated with this
143      * manager has changed.
144      */
145     @Override
onCarrierConfigChanged()146     public void onCarrierConfigChanged() {
147         mExecutorService.submit(() -> {
148             Log.i(LOG_TAG, "onCarrierConfigChanged");
149             if (mUceController != null) {
150                 mUceController.onCarrierConfigChanged();
151             } else {
152                 Log.d(LOG_TAG, "onCarrierConfigChanged: UceController is null");
153             }
154         });
155     }
156 
157     /**
158      * Request the capabilities for contacts.
159      *
160      * @param contactNumbers A list of numbers that the capabilities are being requested for.
161      * @param c A callback for when the request for capabilities completes.
162      * @throws ImsException if the ImsService connected to this controller is currently down.
163      */
requestCapabilities(List<Uri> contactNumbers, IRcsUceControllerCallback c)164     public void requestCapabilities(List<Uri> contactNumbers, IRcsUceControllerCallback c)
165             throws ImsException {
166         Future future = mExecutorService.submit(() -> {
167             checkUceControllerState();
168             mUceController.requestCapabilities(contactNumbers, c);
169             return true;
170         });
171 
172         try {
173             future.get();
174         } catch (ExecutionException | InterruptedException e) {
175             Log.w(LOG_TAG, "requestCapabilities: " + e);
176             Throwable cause = e.getCause();
177             if (cause instanceof ImsException) {
178                 throw (ImsException) cause;
179             }
180         }
181     }
182 
183     /**
184      * Request the capabilities for the given contact.
185      * @param contactNumber The contact of the capabilities are being requested for.
186      * @param c A callback for when the request for capabilities completes.
187      * @throws ImsException if the ImsService connected to this controller is currently down.
188      */
requestNetworkAvailability(Uri contactNumber, IRcsUceControllerCallback c)189     public void requestNetworkAvailability(Uri contactNumber, IRcsUceControllerCallback c)
190             throws ImsException {
191         Future future = mExecutorService.submit(() -> {
192             checkUceControllerState();
193             mUceController.requestAvailability(contactNumber, c);
194             return true;
195         });
196 
197         try {
198             future.get();
199         } catch (ExecutionException | InterruptedException e) {
200             Log.w(LOG_TAG, "requestNetworkAvailability exception: " + e);
201             Throwable cause = e.getCause();
202             if (cause instanceof ImsException) {
203                 throw (ImsException) cause;
204             }
205         }
206     }
207 
208     /**
209      * Get the UCE publish state.
210      *
211      * @throws ImsException if the ImsService connected to this controller is currently down.
212      */
getUcePublishState(boolean isSupportPublishingState)213     public @PublishState int getUcePublishState(boolean isSupportPublishingState)
214             throws ImsException {
215         Future<Integer> future = mExecutorService.submit(() -> {
216             checkUceControllerState();
217             return mUceController.getUcePublishState(isSupportPublishingState);
218         });
219 
220         try {
221             return future.get();
222         } catch (ExecutionException | InterruptedException e) {
223             Log.w(LOG_TAG, "getUcePublishState exception: " + e);
224             Throwable cause = e.getCause();
225             if (cause instanceof ImsException) {
226                 throw (ImsException) cause;
227             }
228             return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR;
229         }
230     }
231 
232     /**
233      * Add new feature tags to the Set used to calculate the capabilities in PUBLISH.
234      */
addUceRegistrationOverride( Set<String> featureTags)235     public RcsContactUceCapability addUceRegistrationOverride(
236             Set<String> featureTags) throws ImsException {
237         Future<RcsContactUceCapability> future = mExecutorService.submit(() -> {
238             checkUceControllerState();
239             return mUceController.addRegistrationOverrideCapabilities(featureTags);
240         });
241 
242         try {
243             return future.get();
244         } catch (ExecutionException | InterruptedException e) {
245             Log.w(LOG_TAG, "addUceRegistrationOverride exception: " + e);
246             Throwable cause = e.getCause();
247             if (cause instanceof ImsException) {
248                 throw (ImsException) cause;
249             }
250             return null;
251         }
252     }
253 
254     /**
255      * Remove existing feature tags to the Set used to calculate the capabilities in PUBLISH.
256      */
removeUceRegistrationOverride( Set<String> featureTags)257     public RcsContactUceCapability removeUceRegistrationOverride(
258             Set<String> featureTags) throws ImsException {
259         Future<RcsContactUceCapability> future = mExecutorService.submit(() -> {
260             checkUceControllerState();
261             return mUceController.removeRegistrationOverrideCapabilities(featureTags);
262         });
263 
264         try {
265             return future.get();
266         } catch (ExecutionException | InterruptedException e) {
267             Log.w(LOG_TAG, "removeUceRegistrationOverride exception: " + e);
268             Throwable cause = e.getCause();
269             if (cause instanceof ImsException) {
270                 throw (ImsException) cause;
271             }
272             return null;
273         }
274     }
275 
276     /**
277      * Clear all overrides in the Set used to calculate the capabilities in PUBLISH.
278      */
clearUceRegistrationOverride()279     public RcsContactUceCapability clearUceRegistrationOverride() throws ImsException {
280         Future<RcsContactUceCapability> future = mExecutorService.submit(() -> {
281             checkUceControllerState();
282             return mUceController.clearRegistrationOverrideCapabilities();
283         });
284 
285         try {
286             return future.get();
287         } catch (ExecutionException | InterruptedException e) {
288             Log.w(LOG_TAG, "clearUceRegistrationOverride exception: " + e);
289             Throwable cause = e.getCause();
290             if (cause instanceof ImsException) {
291                 throw (ImsException) cause;
292             }
293             return null;
294         }
295     }
296 
297     /**
298      * @return current RcsContactUceCapability instance that will be used for PUBLISH.
299      */
getLatestRcsContactUceCapability()300     public RcsContactUceCapability getLatestRcsContactUceCapability() throws ImsException {
301         Future<RcsContactUceCapability> future = mExecutorService.submit(() -> {
302             checkUceControllerState();
303             return mUceController.getLatestRcsContactUceCapability();
304         });
305 
306         try {
307             return future.get();
308         } catch (ExecutionException | InterruptedException e) {
309             Log.w(LOG_TAG, "getLatestRcsContactUceCapability exception: " + e);
310             Throwable cause = e.getCause();
311             if (cause instanceof ImsException) {
312                 throw (ImsException) cause;
313             }
314             return null;
315         }
316     }
317 
318     /**
319      *
320      * @return The last PIDF XML sent to the IMS stack to be published.
321      */
getLastPidfXml()322     public String getLastPidfXml() throws ImsException {
323         Future<String> future = mExecutorService.submit(() -> {
324             checkUceControllerState();
325             return mUceController.getLastPidfXml();
326         });
327 
328         try {
329             return future.get();
330         } catch (ExecutionException | InterruptedException e) {
331             Log.w(LOG_TAG, "getLastPidfXml exception: " + e);
332             Throwable cause = e.getCause();
333             if (cause instanceof ImsException) {
334                 throw (ImsException) cause;
335             }
336             return null;
337         }
338     }
339 
340     /**
341      * Remove UCE requests cannot be sent to the network status.
342      * @return true if this command is successful.
343      */
removeUceRequestDisallowedStatus()344     public boolean removeUceRequestDisallowedStatus() throws ImsException {
345         Future<Boolean> future = mExecutorService.submit(() -> {
346             if (mUceController == null) {
347                 throw new ImsException("UCE controller is null",
348                         ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
349             }
350             mUceController.removeRequestDisallowedStatus();
351             return true;
352         });
353 
354         try {
355             return future.get();
356         } catch (ExecutionException | InterruptedException e) {
357             Log.w(LOG_TAG, "removeUceRequestDisallowedStatus exception: " + e);
358             Throwable cause = e.getCause();
359             if (cause instanceof ImsException) {
360                 throw (ImsException) cause;
361             }
362             return false;
363         }
364     }
365 
366     /**
367      * Set the timeout for contact capabilities request.
368      * @param timeoutAfterMs How long when the capabilities request will time up.
369      * @return true if this command is successful.
370      */
setCapabilitiesRequestTimeout(long timeoutAfterMs)371     public boolean setCapabilitiesRequestTimeout(long timeoutAfterMs)  throws ImsException {
372         Future<Boolean> future = mExecutorService.submit(() -> {
373             if (mUceController == null) {
374                 throw new ImsException("UCE controller is null",
375                         ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
376             }
377             mUceController.setCapabilitiesRequestTimeout(timeoutAfterMs);
378             return true;
379         });
380 
381         try {
382             return future.get();
383         } catch (ExecutionException | InterruptedException e) {
384             Log.w(LOG_TAG, "setCapabilitiesRequestTimeout exception: " + e);
385             Throwable cause = e.getCause();
386             if (cause instanceof ImsException) {
387                 throw (ImsException) cause;
388             }
389             return false;
390         }
391     }
392 
393     /**
394      * Register the Publish state changed callback.
395      *
396      * @throws ImsException if the ImsService connected to this controller is currently down.
397      */
registerPublishStateCallback(IRcsUcePublishStateCallback c, boolean supportPublishingState)398     public void registerPublishStateCallback(IRcsUcePublishStateCallback c,
399             boolean supportPublishingState) throws ImsException {
400         Future future = mExecutorService.submit(() -> {
401             checkUceControllerState();
402             mUceController.registerPublishStateCallback(c, supportPublishingState);
403             return true;
404         });
405 
406         try {
407             future.get();
408         } catch (ExecutionException | InterruptedException e) {
409             Log.w(LOG_TAG, "registerPublishStateCallback exception: " + e);
410             Throwable cause = e.getCause();
411             if (cause instanceof ImsException) {
412                 throw (ImsException) cause;
413             }
414         }
415     }
416 
417     /**
418      * Unregister the existing publish state changed callback.
419      */
unregisterPublishStateCallback(IRcsUcePublishStateCallback c)420     public void unregisterPublishStateCallback(IRcsUcePublishStateCallback c) {
421         Future future = mExecutorService.submit(() -> {
422             if (checkUceControllerState()) {
423                 mUceController.unregisterPublishStateCallback(c);
424             }
425             return true;
426         });
427 
428         try {
429             future.get();
430         } catch (ExecutionException | InterruptedException e) {
431             Log.w(LOG_TAG, "unregisterPublishStateCallback exception: " + e);
432         }
433     }
434 
435     /**
436      * Initialize the UceController instance associated with the given subscription ID.
437      * The existing UceController will be destroyed if the original subscription ID is different
438      * from the new subscription ID.
439      * If the new subscription ID is invalid, the UceController instance will be null.
440      */
initUceController(int newSubId)441     private void initUceController(int newSubId) {
442         Log.d(LOG_TAG, "initUceController: newSubId=" + newSubId + ", current UceController subId="
443                 + ((mUceController == null) ? "null" : mUceController.getSubId()));
444         if (mUceController == null) {
445             // Create new UceController only when the subscription ID is valid.
446             if (SubscriptionManager.isValidSubscriptionId(newSubId)) {
447                 mUceController = new UceController(mContext, newSubId, mFeatureFlags);
448             }
449         } else if (mUceController.getSubId() != newSubId) {
450             // The subscription ID is updated. Remove the old UceController instance.
451             mUceController.onDestroy();
452             mUceController = null;
453             // Create new UceController only when the subscription ID is valid.
454             if (SubscriptionManager.isValidSubscriptionId(newSubId)) {
455                 mUceController = new UceController(mContext, newSubId, mFeatureFlags);
456             }
457         }
458     }
459 
checkUceControllerState()460     private boolean checkUceControllerState() throws ImsException {
461         if (mUceController == null || mUceController.isUnavailable()) {
462             throw new ImsException("UCE controller is unavailable",
463                     ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
464         }
465         return true;
466     }
467 
468     /**
469      * Get the UceController instance.
470      * <p>
471      * Used for testing ONLY.
472      */
473     @VisibleForTesting
getUceController()474     public UceController getUceController() {
475         return mUceController;
476     }
477 
478     @Override
dump(PrintWriter printWriter)479     public void dump(PrintWriter printWriter) {
480         IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
481         pw.println("UceControllerManager" + "[" + mSlotId + "]:");
482         pw.increaseIndent();
483         if (mUceController != null) {
484             mUceController.dump(pw);
485         } else {
486             pw.println("UceController is null.");
487         }
488         pw.decreaseIndent();
489     }
490 }
491