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