1 /*
2  * Copyright (C) 2017 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.internal.telephony.ims;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.ServiceConnection;
23 import android.content.pm.IPackageManager;
24 import android.os.Handler;
25 import android.os.HandlerThread;
26 import android.os.IBinder;
27 import android.os.IInterface;
28 import android.os.RemoteException;
29 import android.os.ServiceManager;
30 import android.telephony.ims.ImsService;
31 import android.telephony.ims.aidl.IImsConfig;
32 import android.telephony.ims.aidl.IImsMmTelFeature;
33 import android.telephony.ims.aidl.IImsRcsFeature;
34 import android.telephony.ims.aidl.IImsRegistration;
35 import android.telephony.ims.aidl.IImsServiceController;
36 import android.telephony.ims.feature.ImsFeature;
37 import android.telephony.ims.stub.ImsFeatureConfiguration;
38 import android.util.Log;
39 
40 import com.android.ims.internal.IImsFeatureStatusCallback;
41 import com.android.ims.internal.IImsServiceFeatureCallback;
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.internal.telephony.ExponentialBackoff;
44 
45 import java.util.HashSet;
46 import java.util.Iterator;
47 import java.util.Set;
48 import java.util.concurrent.ConcurrentHashMap;
49 
50 /**
51  * Manages the Binding lifecycle of one ImsService as well as the relevant ImsFeatures that the
52  * ImsService will support.
53  *
54  * When the ImsService is first bound, {@link ImsService#createMmTelFeature(int)} and
55  * {@link ImsService#createRcsFeature(int)} will be called
56  * on each feature that the service supports. For each ImsFeature that is created,
57  * {@link ImsServiceControllerCallbacks#imsServiceFeatureCreated} will be called to notify the
58  * listener that the ImsService now supports that feature.
59  *
60  * When {@link #changeImsServiceFeatures} is called with a set of features that is different from
61  * the original set, create*Feature and {@link IImsServiceController#removeImsFeature} will be
62  * called for each feature that is created/removed.
63  */
64 public class ImsServiceController {
65 
66     class ImsServiceConnection implements ServiceConnection {
67 
68         @Override
onServiceConnected(ComponentName name, IBinder service)69         public void onServiceConnected(ComponentName name, IBinder service) {
70             mBackoff.stop();
71             synchronized (mLock) {
72                 mIsBound = true;
73                 mIsBinding = false;
74                 Log.d(LOG_TAG, "ImsService(" + name + "): onServiceConnected with binder: "
75                         + service);
76                 if (service != null) {
77                     try {
78                         setServiceController(service);
79                         notifyImsServiceReady();
80                         // create all associated features in the ImsService
81                         for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) {
82                             addImsServiceFeature(i);
83                         }
84                     } catch (RemoteException e) {
85                         mIsBound = false;
86                         mIsBinding = false;
87                         // Remote exception means that the binder already died.
88                         cleanupConnection();
89                         startDelayedRebindToService();
90                         Log.e(LOG_TAG, "ImsService(" + name + ") RemoteException:"
91                                 + e.getMessage());
92                     }
93                 }
94             }
95         }
96 
97         @Override
onServiceDisconnected(ComponentName name)98         public void onServiceDisconnected(ComponentName name) {
99             synchronized (mLock) {
100                 mIsBinding = false;
101             }
102             cleanupConnection();
103             Log.w(LOG_TAG, "ImsService(" + name + "): onServiceDisconnected. Waiting...");
104             // Service disconnected, but we are still technically bound. Waiting for reconnect.
105         }
106 
107         @Override
onBindingDied(ComponentName name)108         public void onBindingDied(ComponentName name) {
109             synchronized (mLock) {
110                 mIsBinding = false;
111                 mIsBound = false;
112             }
113             cleanupConnection();
114             Log.w(LOG_TAG, "ImsService(" + name + "): onBindingDied. Starting rebind...");
115             startDelayedRebindToService();
116         }
117 
cleanupConnection()118         private void cleanupConnection() {
119             cleanupAllFeatures();
120             cleanUpService();
121         }
122     }
123 
124     private ImsService.Listener mFeatureChangedListener = new ImsService.Listener() {
125         @Override
126         public void onUpdateSupportedImsFeatures(ImsFeatureConfiguration c) {
127             if (mCallbacks == null) {
128                 return;
129             }
130             mCallbacks.imsServiceFeaturesChanged(c, ImsServiceController.this);
131         }
132     };
133 
134     /**
135      * Defines callbacks that are used by the ImsServiceController to notify when an ImsService
136      * has created or removed a new feature as well as the associated ImsServiceController.
137      */
138     public interface ImsServiceControllerCallbacks {
139         /**
140          * Called by ImsServiceController when a new MMTEL or RCS feature has been created.
141          */
imsServiceFeatureCreated(int slotId, int feature, ImsServiceController controller)142         void imsServiceFeatureCreated(int slotId, int feature, ImsServiceController controller);
143         /**
144          * Called by ImsServiceController when a new MMTEL or RCS feature has been removed.
145          */
imsServiceFeatureRemoved(int slotId, int feature, ImsServiceController controller)146         void imsServiceFeatureRemoved(int slotId, int feature, ImsServiceController controller);
147 
148         /**
149          * Called by the ImsServiceController when the ImsService has notified the framework that
150          * its features have changed.
151          */
imsServiceFeaturesChanged(ImsFeatureConfiguration config, ImsServiceController controller)152         void imsServiceFeaturesChanged(ImsFeatureConfiguration config,
153                 ImsServiceController controller);
154     }
155 
156     /**
157      * Returns the currently defined rebind retry timeout. Used for testing.
158      */
159     @VisibleForTesting
160     public interface RebindRetry {
161         /**
162          * Returns a long in ms indicating how long the ImsServiceController should wait before
163          * rebinding for the first time.
164          */
getStartDelay()165         long getStartDelay();
166 
167         /**
168          * Returns a long in ms indicating the maximum time the ImsServiceController should wait
169          * before rebinding.
170          */
getMaximumDelay()171         long getMaximumDelay();
172     }
173 
174     private static final String LOG_TAG = "ImsServiceController";
175     private static final int REBIND_START_DELAY_MS = 2 * 1000; // 2 seconds
176     private static final int REBIND_MAXIMUM_DELAY_MS = 60 * 1000; // 1 minute
177     private final ComponentName mComponentName;
178     private final HandlerThread mHandlerThread = new HandlerThread("ImsServiceControllerHandler");
179     private final IPackageManager mPackageManager;
180     private ImsServiceControllerCallbacks mCallbacks;
181     private ExponentialBackoff mBackoff;
182 
183     private boolean mIsBound = false;
184     private boolean mIsBinding = false;
185     // Set of a pair of slotId->feature
186     private HashSet<ImsFeatureConfiguration.FeatureSlotPair> mImsFeatures;
187     // Binder interfaces to the features set in mImsFeatures;
188     private HashSet<ImsFeatureContainer> mImsFeatureBinders = new HashSet<>();
189     private IImsServiceController mIImsServiceController;
190     private ImsServiceConnection mImsServiceConnection;
191     private Set<IImsServiceFeatureCallback> mImsStatusCallbacks = ConcurrentHashMap.newKeySet();
192     // Only added or removed, never accessed on purpose.
193     private Set<ImsFeatureStatusCallback> mFeatureStatusCallbacks = new HashSet<>();
194 
195     protected final Object mLock = new Object();
196     protected final Context mContext;
197 
198     private class ImsFeatureContainer {
199         public int slotId;
200         public int featureType;
201         private IInterface mBinder;
202 
ImsFeatureContainer(int slotId, int featureType, IInterface binder)203         ImsFeatureContainer(int slotId, int featureType, IInterface binder) {
204             this.slotId = slotId;
205             this.featureType = featureType;
206             this.mBinder = binder;
207         }
208 
209         // Casts the IInterface into the binder class we are looking for.
resolve(Class<T> className)210         public <T extends IInterface> T resolve(Class<T> className) {
211             return className.cast(mBinder);
212         }
213 
214         @Override
equals(Object o)215         public boolean equals(Object o) {
216             if (this == o) return true;
217             if (o == null || getClass() != o.getClass()) return false;
218 
219             ImsFeatureContainer that = (ImsFeatureContainer) o;
220 
221             if (slotId != that.slotId) return false;
222             if (featureType != that.featureType) return false;
223             return mBinder != null ? mBinder.equals(that.mBinder) : that.mBinder == null;
224         }
225 
226         @Override
hashCode()227         public int hashCode() {
228             int result = slotId;
229             result = 31 * result + featureType;
230             result = 31 * result + (mBinder != null ? mBinder.hashCode() : 0);
231             return result;
232         }
233     }
234 
235     /**
236      * Container class for the IImsFeatureStatusCallback callback implementation. This class is
237      * never used directly, but we need to keep track of the IImsFeatureStatusCallback
238      * implementations explicitly.
239      */
240     private class ImsFeatureStatusCallback {
241         private int mSlotId;
242         private int mFeatureType;
243 
244         private final IImsFeatureStatusCallback mCallback = new IImsFeatureStatusCallback.Stub() {
245 
246             @Override
247             public void notifyImsFeatureStatus(int featureStatus) throws RemoteException {
248                 Log.i(LOG_TAG, "notifyImsFeatureStatus: slot=" + mSlotId + ", feature="
249                         + mFeatureType + ", status=" + featureStatus);
250                 sendImsFeatureStatusChanged(mSlotId, mFeatureType, featureStatus);
251             }
252         };
253 
ImsFeatureStatusCallback(int slotId, int featureType)254         ImsFeatureStatusCallback(int slotId, int featureType) {
255             mSlotId = slotId;
256             mFeatureType = featureType;
257         }
258 
getCallback()259         public IImsFeatureStatusCallback getCallback() {
260             return mCallback;
261         }
262     }
263 
264     // Retry the bind to the ImsService that has died after mRebindRetry timeout.
265     private Runnable mRestartImsServiceRunnable = new Runnable() {
266         @Override
267         public void run() {
268             synchronized (mLock) {
269                 if (mIsBound) {
270                     return;
271                 }
272                 bind(mImsFeatures);
273             }
274         }
275     };
276 
277     private RebindRetry mRebindRetry = new RebindRetry() {
278         @Override
279         public long getStartDelay() {
280             return REBIND_START_DELAY_MS;
281         }
282 
283         @Override
284         public long getMaximumDelay() {
285             return REBIND_MAXIMUM_DELAY_MS;
286         }
287     };
288 
ImsServiceController(Context context, ComponentName componentName, ImsServiceControllerCallbacks callbacks)289     public ImsServiceController(Context context, ComponentName componentName,
290             ImsServiceControllerCallbacks callbacks) {
291         mContext = context;
292         mComponentName = componentName;
293         mCallbacks = callbacks;
294         mHandlerThread.start();
295         mBackoff = new ExponentialBackoff(
296                 mRebindRetry.getStartDelay(),
297                 mRebindRetry.getMaximumDelay(),
298                 2, /* multiplier */
299                 mHandlerThread.getLooper(),
300                 mRestartImsServiceRunnable);
301         mPackageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
302     }
303 
304     @VisibleForTesting
305     // Creating a new HandlerThread and background handler for each test causes a segfault, so for
306     // testing, use a handler supplied by the testing system.
ImsServiceController(Context context, ComponentName componentName, ImsServiceControllerCallbacks callbacks, Handler handler, RebindRetry rebindRetry)307     public ImsServiceController(Context context, ComponentName componentName,
308             ImsServiceControllerCallbacks callbacks, Handler handler, RebindRetry rebindRetry) {
309         mContext = context;
310         mComponentName = componentName;
311         mCallbacks = callbacks;
312         mBackoff = new ExponentialBackoff(
313                 rebindRetry.getStartDelay(),
314                 rebindRetry.getMaximumDelay(),
315                 2, /* multiplier */
316                 handler,
317                 mRestartImsServiceRunnable);
318         mPackageManager = null;
319     }
320 
321     /**
322      * Sends request to bind to ImsService designated by the {@link ComponentName} with the feature
323      * set imsFeatureSet.
324      *
325      * @param imsFeatureSet a Set of Pairs that designate the slotId->featureId that need to be
326      *                      created once the service is bound.
327      * @return {@link true} if the service is in the process of being bound, {@link false} if it
328      * has failed.
329      */
bind(HashSet<ImsFeatureConfiguration.FeatureSlotPair> imsFeatureSet)330     public boolean bind(HashSet<ImsFeatureConfiguration.FeatureSlotPair> imsFeatureSet) {
331         synchronized (mLock) {
332             if (!mIsBound && !mIsBinding) {
333                 mIsBinding = true;
334                 mImsFeatures = imsFeatureSet;
335                 grantPermissionsToService();
336                 Intent imsServiceIntent = new Intent(getServiceInterface()).setComponent(
337                         mComponentName);
338                 mImsServiceConnection = new ImsServiceConnection();
339                 int serviceFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
340                         | Context.BIND_IMPORTANT;
341                 Log.i(LOG_TAG, "Binding ImsService:" + mComponentName);
342                 try {
343                     boolean bindSucceeded = startBindToService(imsServiceIntent,
344                             mImsServiceConnection, serviceFlags);
345                     if (!bindSucceeded) {
346                         mIsBinding = false;
347                         mBackoff.notifyFailed();
348                     }
349                     return bindSucceeded;
350                 } catch (Exception e) {
351                     mBackoff.notifyFailed();
352                     Log.e(LOG_TAG, "Error binding (" + mComponentName + ") with exception: "
353                             + e.getMessage() + ", rebinding in " + mBackoff.getCurrentDelay()
354                             + " ms");
355                     return false;
356                 }
357             } else {
358                 return false;
359             }
360         }
361     }
362 
363     /**
364      * Starts the bind to the ImsService. Overridden by subclasses that need to access the service
365      * in a different fashion.
366      */
startBindToService(Intent intent, ImsServiceConnection connection, int flags)367     protected boolean startBindToService(Intent intent, ImsServiceConnection connection,
368             int flags) {
369         return mContext.bindService(intent, connection, flags);
370     }
371 
372     /**
373      * Calls {@link IImsServiceController#removeImsFeature} on all features that the
374      * ImsService supports and then unbinds the service.
375      */
unbind()376     public void unbind() throws RemoteException {
377         synchronized (mLock) {
378             mBackoff.stop();
379             if (mImsServiceConnection == null) {
380                 return;
381             }
382             // Clean up all features
383             changeImsServiceFeatures(new HashSet<>());
384             removeImsServiceFeatureCallbacks();
385             Log.i(LOG_TAG, "Unbinding ImsService: " + mComponentName);
386             mContext.unbindService(mImsServiceConnection);
387             cleanUpService();
388         }
389     }
390 
391     /**
392      * For every feature that is added, the service calls the associated create. For every
393      * ImsFeature that is removed, {@link IImsServiceController#removeImsFeature} is called.
394      */
changeImsServiceFeatures( HashSet<ImsFeatureConfiguration.FeatureSlotPair> newImsFeatures)395     public void changeImsServiceFeatures(
396             HashSet<ImsFeatureConfiguration.FeatureSlotPair> newImsFeatures)
397             throws RemoteException {
398         synchronized (mLock) {
399             Log.i(LOG_TAG, "Features changed (" + mImsFeatures + "->" + newImsFeatures + ") for "
400                     + "ImsService: " + mComponentName);
401             HashSet<ImsFeatureConfiguration.FeatureSlotPair> oldImsFeatures =
402                     new HashSet<>(mImsFeatures);
403             // Set features first in case we lose binding and need to rebind later.
404             mImsFeatures = newImsFeatures;
405             if (mIsBound) {
406                 // add features to service.
407                 HashSet<ImsFeatureConfiguration.FeatureSlotPair> newFeatures =
408                         new HashSet<>(mImsFeatures);
409                 newFeatures.removeAll(oldImsFeatures);
410                 for (ImsFeatureConfiguration.FeatureSlotPair i : newFeatures) {
411                     addImsServiceFeature(i);
412                 }
413                 // remove old features
414                 HashSet<ImsFeatureConfiguration.FeatureSlotPair> oldFeatures =
415                         new HashSet<>(oldImsFeatures);
416                 oldFeatures.removeAll(mImsFeatures);
417                 for (ImsFeatureConfiguration.FeatureSlotPair i : oldFeatures) {
418                     removeImsServiceFeature(i);
419                 }
420             }
421         }
422     }
423 
424     @VisibleForTesting
getImsServiceController()425     public IImsServiceController getImsServiceController() {
426         return mIImsServiceController;
427     }
428 
429     @VisibleForTesting
getRebindDelay()430     public long getRebindDelay() {
431         return mBackoff.getCurrentDelay();
432     }
433 
434     @VisibleForTesting
stopBackoffTimerForTesting()435     public void stopBackoffTimerForTesting() {
436         mBackoff.stop();
437     }
438 
getComponentName()439     public ComponentName getComponentName() {
440         return mComponentName;
441     }
442 
443     /**
444      * Add a callback to ImsManager that signals a new feature that the ImsServiceProxy can handle.
445      */
addImsServiceFeatureCallback(IImsServiceFeatureCallback callback)446     public void addImsServiceFeatureCallback(IImsServiceFeatureCallback callback) {
447         mImsStatusCallbacks.add(callback);
448         synchronized (mLock) {
449             if (mImsFeatures == null || mImsFeatures.isEmpty()) {
450                 return;
451             }
452             // notify the new status callback of the features that are available.
453             try {
454                 for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) {
455                     callback.imsFeatureCreated(i.slotId, i.featureType);
456                 }
457             } catch (RemoteException e) {
458                 Log.w(LOG_TAG, "addImsServiceFeatureCallback: exception notifying callback");
459             }
460         }
461     }
462 
enableIms(int slotId)463     public void enableIms(int slotId) {
464         try {
465             synchronized (mLock) {
466                 if (isServiceControllerAvailable()) {
467                     mIImsServiceController.enableIms(slotId);
468                 }
469             }
470         } catch (RemoteException e) {
471             Log.w(LOG_TAG, "Couldn't enable IMS: " + e.getMessage());
472         }
473     }
474 
disableIms(int slotId)475     public void disableIms(int slotId) {
476         try {
477             synchronized (mLock) {
478                 if (isServiceControllerAvailable()) {
479                     mIImsServiceController.disableIms(slotId);
480                 }
481             }
482         } catch (RemoteException e) {
483             Log.w(LOG_TAG, "Couldn't disable IMS: " + e.getMessage());
484         }
485     }
486 
487     /**
488      * Return the {@Link MMTelFeature} binder on the slot associated with the slotId.
489      * Used for normal calling.
490      */
getMmTelFeature(int slotId)491     public IImsMmTelFeature getMmTelFeature(int slotId) {
492         synchronized (mLock) {
493             ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.FEATURE_MMTEL);
494             if (f == null) {
495                 Log.w(LOG_TAG, "Requested null MMTelFeature on slot " + slotId);
496                 return null;
497             }
498             return f.resolve(IImsMmTelFeature.class);
499         }
500     }
501 
502     /**
503      * Return the {@Link RcsFeature} binder on the slot associated with the slotId.
504      */
getRcsFeature(int slotId)505     public IImsRcsFeature getRcsFeature(int slotId) {
506         synchronized (mLock) {
507             ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.FEATURE_RCS);
508             if (f == null) {
509                 Log.w(LOG_TAG, "Requested null RcsFeature on slot " + slotId);
510                 return null;
511             }
512             return f.resolve(IImsRcsFeature.class);
513         }
514     }
515 
516     /**
517      * @return the IImsRegistration that corresponds to the slot id specified.
518      */
getRegistration(int slotId)519     public IImsRegistration getRegistration(int slotId) throws RemoteException {
520         synchronized (mLock) {
521             return isServiceControllerAvailable()
522                     ? mIImsServiceController.getRegistration(slotId) : null;
523         }
524     }
525 
526     /**
527      * @return the IImsConfig that corresponds to the slot id specified.
528      */
getConfig(int slotId)529     public IImsConfig getConfig(int slotId) throws RemoteException {
530         synchronized (mLock) {
531             return isServiceControllerAvailable() ? mIImsServiceController.getConfig(slotId) : null;
532         }
533     }
534 
535     /**
536      * notify the ImsService that the ImsService is ready for feature creation.
537      */
notifyImsServiceReady()538     protected void notifyImsServiceReady() throws RemoteException {
539         synchronized (mLock) {
540             if (isServiceControllerAvailable()) {
541                 Log.d(LOG_TAG, "notifyImsServiceReady");
542                 mIImsServiceController.setListener(mFeatureChangedListener);
543                 mIImsServiceController.notifyImsServiceReadyForFeatureCreation();
544             }
545         }
546     }
547 
getServiceInterface()548     protected String getServiceInterface() {
549         return ImsService.SERVICE_INTERFACE;
550     }
551 
552     /**
553      * Sets the IImsServiceController instance. Overridden by compat layers to set compatibility
554      * versions of this service controller.
555      */
setServiceController(IBinder serviceController)556     protected void setServiceController(IBinder serviceController) {
557         mIImsServiceController = IImsServiceController.Stub.asInterface(serviceController);
558     }
559 
560     /**
561      * @return true if the controller is currently bound.
562      */
isBound()563     public boolean isBound() {
564         synchronized (mLock) {
565             return mIsBound;
566         }
567     }
568 
569     /**
570      * Check to see if the service controller is available, overridden for compat versions,
571      * @return true if available, false otherwise;
572      */
isServiceControllerAvailable()573     protected boolean isServiceControllerAvailable() {
574         return mIImsServiceController != null;
575     }
576 
577     @VisibleForTesting
removeImsServiceFeatureCallbacks()578     public void removeImsServiceFeatureCallbacks() {
579             mImsStatusCallbacks.clear();
580     }
581 
582     // Only add a new rebind if there are no pending rebinds waiting.
startDelayedRebindToService()583     private void startDelayedRebindToService() {
584         mBackoff.start();
585     }
586 
587     // Grant runtime permissions to ImsService. PackageManager ensures that the ImsService is
588     // system/signed before granting permissions.
grantPermissionsToService()589     private void grantPermissionsToService() {
590         Log.i(LOG_TAG, "Granting Runtime permissions to:" + getComponentName());
591         String[] pkgToGrant = {mComponentName.getPackageName()};
592         try {
593             if (mPackageManager != null) {
594                 mPackageManager.grantDefaultPermissionsToEnabledImsServices(pkgToGrant,
595                         mContext.getUserId());
596             }
597         } catch (RemoteException e) {
598             Log.w(LOG_TAG, "Unable to grant permissions, binder died.");
599         }
600     }
601 
sendImsFeatureCreatedCallback(int slot, int feature)602     private void sendImsFeatureCreatedCallback(int slot, int feature) {
603         for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
604                 i.hasNext(); ) {
605             IImsServiceFeatureCallback callbacks = i.next();
606             try {
607                 callbacks.imsFeatureCreated(slot, feature);
608             } catch (RemoteException e) {
609                 // binder died, remove callback.
610                 Log.w(LOG_TAG, "sendImsFeatureCreatedCallback: Binder died, removing "
611                         + "callback. Exception:" + e.getMessage());
612                 i.remove();
613             }
614         }
615     }
616 
sendImsFeatureRemovedCallback(int slot, int feature)617     private void sendImsFeatureRemovedCallback(int slot, int feature) {
618         for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
619                 i.hasNext(); ) {
620             IImsServiceFeatureCallback callbacks = i.next();
621             try {
622                 callbacks.imsFeatureRemoved(slot, feature);
623             } catch (RemoteException e) {
624                 // binder died, remove callback.
625                 Log.w(LOG_TAG, "sendImsFeatureRemovedCallback: Binder died, removing "
626                         + "callback. Exception:" + e.getMessage());
627                 i.remove();
628             }
629         }
630     }
631 
sendImsFeatureStatusChanged(int slot, int feature, int status)632     private void sendImsFeatureStatusChanged(int slot, int feature, int status) {
633         for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
634                 i.hasNext(); ) {
635             IImsServiceFeatureCallback callbacks = i.next();
636             try {
637                 callbacks.imsStatusChanged(slot, feature, status);
638             } catch (RemoteException e) {
639                 // binder died, remove callback.
640                 Log.w(LOG_TAG, "sendImsFeatureStatusChanged: Binder died, removing "
641                         + "callback. Exception:" + e.getMessage());
642                 i.remove();
643             }
644         }
645     }
646 
647     // This method should only be called when synchronized on mLock
addImsServiceFeature(ImsFeatureConfiguration.FeatureSlotPair featurePair)648     private void addImsServiceFeature(ImsFeatureConfiguration.FeatureSlotPair featurePair)
649             throws RemoteException {
650         if (!isServiceControllerAvailable() || mCallbacks == null) {
651             Log.w(LOG_TAG, "addImsServiceFeature called with null values.");
652             return;
653         }
654         if (featurePair.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) {
655             ImsFeatureStatusCallback c = new ImsFeatureStatusCallback(featurePair.slotId,
656                     featurePair.featureType);
657             mFeatureStatusCallbacks.add(c);
658             IInterface f = createImsFeature(featurePair.slotId, featurePair.featureType,
659                     c.getCallback());
660             addImsFeatureBinder(featurePair.slotId, featurePair.featureType, f);
661             // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
662             mCallbacks.imsServiceFeatureCreated(featurePair.slotId, featurePair.featureType, this);
663         } else {
664             // Don't update ImsService for emergency MMTEL feature.
665             Log.i(LOG_TAG, "supports emergency calling on slot " + featurePair.slotId);
666         }
667         // Send callback to ImsServiceProxy to change supported ImsFeatures including emergency
668         // MMTEL state.
669         sendImsFeatureCreatedCallback(featurePair.slotId, featurePair.featureType);
670     }
671 
672     // This method should only be called when synchronized on mLock
removeImsServiceFeature(ImsFeatureConfiguration.FeatureSlotPair featurePair)673     private void removeImsServiceFeature(ImsFeatureConfiguration.FeatureSlotPair featurePair) {
674         if (!isServiceControllerAvailable() || mCallbacks == null) {
675             Log.w(LOG_TAG, "removeImsServiceFeature called with null values.");
676             return;
677         }
678         if (featurePair.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) {
679             ImsFeatureStatusCallback callbackToRemove = mFeatureStatusCallbacks.stream().filter(c ->
680                     c.mSlotId == featurePair.slotId && c.mFeatureType == featurePair.featureType)
681                     .findFirst().orElse(null);
682             // Remove status callbacks from list.
683             if (callbackToRemove != null) {
684                 mFeatureStatusCallbacks.remove(callbackToRemove);
685             }
686             removeImsFeatureBinder(featurePair.slotId, featurePair.featureType);
687             // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
688             mCallbacks.imsServiceFeatureRemoved(featurePair.slotId, featurePair.featureType, this);
689             try {
690                 removeImsFeature(featurePair.slotId, featurePair.featureType,
691                         (callbackToRemove != null ? callbackToRemove.getCallback() : null));
692             } catch (RemoteException e) {
693                 // The connection to this ImsService doesn't exist. This may happen if the service
694                 // has died and we are removing features.
695                 Log.i(LOG_TAG, "Couldn't remove feature {" + featurePair.featureType
696                         + "}, connection is down: " + e.getMessage());
697             }
698         } else {
699             // Don't update ImsService for emergency MMTEL feature.
700             Log.i(LOG_TAG, "doesn't support emergency calling on slot " + featurePair.slotId);
701         }
702         // Send callback to ImsServiceProxy to change supported ImsFeatures
703         // Ensure that ImsServiceProxy callback occurs after ImsResolver callback. If an
704         // ImsManager requests the ImsService while it is being removed in ImsResolver, this
705         // callback will clean it up after.
706         sendImsFeatureRemovedCallback(featurePair.slotId, featurePair.featureType);
707     }
708 
709     // This method should only be called when already synchronized on mLock.
710     // overridden by compat layer to create features
createImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c)711     protected IInterface createImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c)
712             throws RemoteException {
713         switch (featureType) {
714             case ImsFeature.FEATURE_MMTEL: {
715                 return mIImsServiceController.createMmTelFeature(slotId, c);
716             }
717             case ImsFeature.FEATURE_RCS: {
718                 return mIImsServiceController.createRcsFeature(slotId, c);
719             }
720             default:
721                 return null;
722         }
723     }
724 
725     // overridden by compat layer to remove features
removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c)726     protected void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c)
727             throws RemoteException {
728         mIImsServiceController.removeImsFeature(slotId, featureType, c);
729     }
730 
731     // This method should only be called when synchronized on mLock
addImsFeatureBinder(int slotId, int featureType, IInterface b)732     private void addImsFeatureBinder(int slotId, int featureType, IInterface b) {
733         mImsFeatureBinders.add(new ImsFeatureContainer(slotId, featureType, b));
734     }
735 
736     // This method should only be called when synchronized on mLock
removeImsFeatureBinder(int slotId, int featureType)737     private void removeImsFeatureBinder(int slotId, int featureType) {
738         ImsFeatureContainer container = mImsFeatureBinders.stream()
739                 .filter(f-> (f.slotId == slotId && f.featureType == featureType))
740                 .findFirst().orElse(null);
741         if (container != null) {
742             mImsFeatureBinders.remove(container);
743         }
744     }
745 
getImsFeatureContainer(int slotId, int featureType)746     private ImsFeatureContainer getImsFeatureContainer(int slotId, int featureType) {
747         return mImsFeatureBinders.stream()
748                 .filter(f-> (f.slotId == slotId && f.featureType == featureType))
749                 .findFirst().orElse(null);
750     }
751 
cleanupAllFeatures()752     private void cleanupAllFeatures() {
753         synchronized (mLock) {
754             // Remove all features and clean up all associated Binders.
755             for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) {
756                 removeImsServiceFeature(i);
757             }
758             // remove all MmTelFeatureConnection callbacks, since we have already sent removed
759             // callback.
760             removeImsServiceFeatureCallbacks();
761         }
762     }
763 
cleanUpService()764     private void cleanUpService() {
765         synchronized (mLock) {
766             mImsServiceConnection = null;
767             setServiceController(null);
768         }
769     }
770 }
771