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.Manifest;
20 import android.annotation.Nullable;
21 import android.content.BroadcastReceiver;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.content.pm.PackageManager;
27 import android.content.pm.ResolveInfo;
28 import android.content.pm.ServiceInfo;
29 import android.os.Handler;
30 import android.os.Looper;
31 import android.os.Message;
32 import android.os.PersistableBundle;
33 import android.os.RemoteException;
34 import android.os.UserHandle;
35 import android.telephony.CarrierConfigManager;
36 import android.telephony.SubscriptionManager;
37 import android.telephony.ims.ImsService;
38 import android.telephony.ims.aidl.IImsConfig;
39 import android.telephony.ims.aidl.IImsMmTelFeature;
40 import android.telephony.ims.aidl.IImsRcsFeature;
41 import android.telephony.ims.aidl.IImsRegistration;
42 import android.telephony.ims.feature.ImsFeature;
43 import android.telephony.ims.stub.ImsFeatureConfiguration;
44 import android.text.TextUtils;
45 import android.util.Log;
46 import android.util.SparseArray;
47 
48 import com.android.ims.internal.IImsServiceFeatureCallback;
49 import com.android.internal.annotations.VisibleForTesting;
50 import com.android.internal.os.SomeArgs;
51 
52 import java.util.ArrayList;
53 import java.util.HashMap;
54 import java.util.HashSet;
55 import java.util.List;
56 import java.util.Map;
57 import java.util.Objects;
58 import java.util.Set;
59 import java.util.stream.Collectors;
60 import java.util.stream.Stream;
61 
62 /**
63  * Creates a list of ImsServices that are available to bind to based on the Device configuration
64  * overlay value "config_ims_package" and Carrier Configuration value
65  * "config_ims_package_override_string".
66  * These ImsServices are then bound to in the following order:
67  *
68  * 1. Carrier Config defined override value per SIM.
69  * 2. Device overlay default value (including no SIM case).
70  *
71  * ImsManager can then retrieve the binding to the correct ImsService using
72  * {@link #getImsServiceControllerAndListen} on a per-slot and per feature basis.
73  */
74 
75 public class ImsResolver implements ImsServiceController.ImsServiceControllerCallbacks {
76 
77     private static final String TAG = "ImsResolver";
78 
79     public static final String METADATA_EMERGENCY_MMTEL_FEATURE =
80             "android.telephony.ims.EMERGENCY_MMTEL_FEATURE";
81     public static final String METADATA_MMTEL_FEATURE = "android.telephony.ims.MMTEL_FEATURE";
82     public static final String METADATA_RCS_FEATURE = "android.telephony.ims.RCS_FEATURE";
83     // Overrides the sanity permission check of android.permission.BIND_IMS_SERVICE for any
84     // ImsService that is connecting to the platform.
85     // This should ONLY be used for testing and should not be used in production ImsServices.
86     private static final String METADATA_OVERRIDE_PERM_CHECK = "override_bind_check";
87 
88     // Based on updates from PackageManager
89     private static final int HANDLER_ADD_PACKAGE = 0;
90     // Based on updates from PackageManager
91     private static final int HANDLER_REMOVE_PACKAGE = 1;
92     // Based on updates from CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED
93     private static final int HANDLER_CONFIG_CHANGED = 2;
94     // A query has been started for an ImsService to relay the features they support.
95     private static final int HANDLER_START_DYNAMIC_FEATURE_QUERY = 3;
96     // A query to request ImsService features has completed or the ImsService has updated features.
97     private static final int HANDLER_DYNAMIC_FEATURE_CHANGE = 4;
98     // Testing: Overrides the current configuration for ImsService binding
99     private static final int HANDLER_OVERRIDE_IMS_SERVICE_CONFIG = 5;
100 
101     // Delay between dynamic ImsService queries.
102     private static final int DELAY_DYNAMIC_QUERY_MS = 5000;
103 
104 
105     /**
106      * Stores information about an ImsService, including the package name, class name, and features
107      * that the service supports.
108      */
109     @VisibleForTesting
110     public static class ImsServiceInfo {
111         public ComponentName name;
112         // Determines if features were created from metadata in the manifest or through dynamic
113         // query.
114         public boolean featureFromMetadata = true;
115         public ImsServiceControllerFactory controllerFactory;
116 
117         // Map slotId->Feature
118         private final HashSet<ImsFeatureConfiguration.FeatureSlotPair> mSupportedFeatures;
119         private final int mNumSlots;
120 
ImsServiceInfo(int numSlots)121         public ImsServiceInfo(int numSlots) {
122             mNumSlots = numSlots;
123             mSupportedFeatures = new HashSet<>();
124         }
125 
addFeatureForAllSlots(int feature)126         void addFeatureForAllSlots(int feature) {
127             for (int i = 0; i < mNumSlots; i++) {
128                 mSupportedFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(i, feature));
129             }
130         }
131 
replaceFeatures(Set<ImsFeatureConfiguration.FeatureSlotPair> newFeatures)132         void replaceFeatures(Set<ImsFeatureConfiguration.FeatureSlotPair> newFeatures) {
133             mSupportedFeatures.clear();
134             mSupportedFeatures.addAll(newFeatures);
135         }
136 
137         @VisibleForTesting
getSupportedFeatures()138         public HashSet<ImsFeatureConfiguration.FeatureSlotPair> getSupportedFeatures() {
139             return mSupportedFeatures;
140         }
141 
142         @Override
equals(Object o)143         public boolean equals(Object o) {
144             if (this == o) return true;
145             if (o == null || getClass() != o.getClass()) return false;
146 
147             ImsServiceInfo that = (ImsServiceInfo) o;
148 
149             if (name != null ? !name.equals(that.name) : that.name != null) return false;
150             if (!mSupportedFeatures.equals(that.mSupportedFeatures)) {
151                 return false;
152             }
153             return controllerFactory != null ? controllerFactory.equals(that.controllerFactory)
154                     : that.controllerFactory == null;
155         }
156 
157         @Override
hashCode()158         public int hashCode() {
159             // We do not include mSupportedFeatures in hashcode because the internal structure
160             // changes after adding.
161             int result = name != null ? name.hashCode() : 0;
162             result = 31 * result + (controllerFactory != null ? controllerFactory.hashCode() : 0);
163             return result;
164         }
165 
166         @Override
toString()167         public String toString() {
168             StringBuilder res = new StringBuilder();
169             res.append("[ImsServiceInfo] name=");
170             res.append(name);
171             res.append(", supportedFeatures=[ ");
172             for (ImsFeatureConfiguration.FeatureSlotPair feature : mSupportedFeatures) {
173                 res.append("(");
174                 res.append(feature.slotId);
175                 res.append(",");
176                 res.append(feature.featureType);
177                 res.append(") ");
178             }
179             return res.toString();
180         }
181     }
182 
183     // Receives broadcasts from the system involving changes to the installed applications. If
184     // an ImsService that we are configured to use is installed, we must bind to it.
185     private BroadcastReceiver mAppChangedReceiver = new BroadcastReceiver() {
186         @Override
187         public void onReceive(Context context, Intent intent) {
188             final String action = intent.getAction();
189             final String packageName = intent.getData().getSchemeSpecificPart();
190             switch (action) {
191                 case Intent.ACTION_PACKAGE_ADDED:
192                     // intentional fall-through
193                 case Intent.ACTION_PACKAGE_REPLACED:
194                     // intentional fall-through
195                 case Intent.ACTION_PACKAGE_CHANGED:
196                     mHandler.obtainMessage(HANDLER_ADD_PACKAGE, packageName).sendToTarget();
197                     break;
198                 case Intent.ACTION_PACKAGE_REMOVED:
199                     mHandler.obtainMessage(HANDLER_REMOVE_PACKAGE, packageName).sendToTarget();
200                     break;
201                 default:
202                     return;
203             }
204         }
205     };
206 
207     // Receives the broadcast that a new Carrier Config has been loaded in order to possibly
208     // unbind from one service and bind to another.
209     private BroadcastReceiver mConfigChangedReceiver = new BroadcastReceiver() {
210         @Override
211         public void onReceive(Context context, Intent intent) {
212 
213             int slotId = intent.getIntExtra(CarrierConfigManager.EXTRA_SLOT_INDEX,
214                     SubscriptionManager.INVALID_SIM_SLOT_INDEX);
215 
216             if (slotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
217                 Log.i(TAG, "Received SIM change for invalid slot id.");
218                 return;
219             }
220 
221             Log.i(TAG, "Received Carrier Config Changed for SlotId: " + slotId);
222 
223             mHandler.obtainMessage(HANDLER_CONFIG_CHANGED, slotId).sendToTarget();
224         }
225     };
226 
227     /**
228      * Testing interface used to mock SubscriptionManager in testing
229      */
230     @VisibleForTesting
231     public interface SubscriptionManagerProxy {
232         /**
233          * Mock-able interface for {@link SubscriptionManager#getSubId(int)} used for testing.
234          */
getSubId(int slotId)235         int getSubId(int slotId);
236         /**
237          * Mock-able interface for {@link SubscriptionManager#getSlotIndex(int)} used for testing.
238          */
getSlotIndex(int subId)239         int getSlotIndex(int subId);
240     }
241 
242     private SubscriptionManagerProxy mSubscriptionManagerProxy = new SubscriptionManagerProxy() {
243         @Override
244         public int getSubId(int slotId) {
245             int[] subIds = SubscriptionManager.getSubId(slotId);
246             if (subIds != null) {
247                 // This is done in all other places getSubId is used.
248                 return subIds[0];
249             }
250             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
251         }
252 
253         @Override
254         public int getSlotIndex(int subId) {
255             return SubscriptionManager.getSlotIndex(subId);
256         }
257     };
258 
259     /**
260      * Testing interface for injecting mock ImsServiceControllers.
261      */
262     @VisibleForTesting
263     public interface ImsServiceControllerFactory {
264         /**
265          * @return the Service Interface String used for binding the ImsService.
266          */
getServiceInterface()267         String getServiceInterface();
268         /**
269          * @return the ImsServiceController created using the context and componentName supplied.
270          */
create(Context context, ComponentName componentName, ImsServiceController.ImsServiceControllerCallbacks callbacks)271         ImsServiceController create(Context context, ComponentName componentName,
272                 ImsServiceController.ImsServiceControllerCallbacks callbacks);
273     }
274 
275     private ImsServiceControllerFactory mImsServiceControllerFactory =
276             new ImsServiceControllerFactory() {
277 
278         @Override
279         public String getServiceInterface() {
280             return ImsService.SERVICE_INTERFACE;
281         }
282 
283         @Override
284         public ImsServiceController create(Context context, ComponentName componentName,
285                 ImsServiceController.ImsServiceControllerCallbacks callbacks) {
286             return new ImsServiceController(context, componentName, callbacks);
287         }
288     };
289 
290     /**
291      * Used for testing.
292      */
293     @VisibleForTesting
294     public interface ImsDynamicQueryManagerFactory {
create(Context context, ImsServiceFeatureQueryManager.Listener listener)295         ImsServiceFeatureQueryManager create(Context context,
296                 ImsServiceFeatureQueryManager.Listener listener);
297     }
298 
299     private ImsServiceControllerFactory mImsServiceControllerFactoryCompat =
300             new ImsServiceControllerFactory() {
301                 @Override
302                 public String getServiceInterface() {
303                     return android.telephony.ims.compat.ImsService.SERVICE_INTERFACE;
304                 }
305 
306                 @Override
307                 public ImsServiceController create(Context context, ComponentName componentName,
308                         ImsServiceController.ImsServiceControllerCallbacks callbacks) {
309                     return new ImsServiceControllerCompat(context, componentName, callbacks);
310                 }
311             };
312 
313     private ImsServiceControllerFactory mImsServiceControllerFactoryStaticBindingCompat =
314             new ImsServiceControllerFactory() {
315                 @Override
316                 public String getServiceInterface() {
317                     // The static method of binding does not use service interfaces.
318                     return null;
319                 }
320 
321                 @Override
322                 public ImsServiceController create(Context context, ComponentName componentName,
323                         ImsServiceController.ImsServiceControllerCallbacks callbacks) {
324                     return new ImsServiceControllerStaticCompat(context, componentName, callbacks);
325                 }
326             };
327 
328     private ImsDynamicQueryManagerFactory mDynamicQueryManagerFactory =
329             ImsServiceFeatureQueryManager::new;
330 
331     private final CarrierConfigManager mCarrierConfigManager;
332     private final Context mContext;
333     // Locks mBoundImsServicesByFeature only. Be careful to avoid deadlocks from
334     // ImsServiceController callbacks.
335     private final Object mBoundServicesLock = new Object();
336     private final int mNumSlots;
337     private final boolean mIsDynamicBinding;
338     // Package name of the default device service.
339     private String mDeviceService;
340 
341     // Synchronize all messages on a handler to ensure that the cache includes the most recent
342     // version of the installed ImsServices.
343     private Handler mHandler = new Handler(Looper.getMainLooper(), (msg) -> {
344         switch (msg.what) {
345             case HANDLER_ADD_PACKAGE: {
346                 String packageName = (String) msg.obj;
347                 maybeAddedImsService(packageName);
348                 break;
349             }
350             case HANDLER_REMOVE_PACKAGE: {
351                 String packageName = (String) msg.obj;
352                 maybeRemovedImsService(packageName);
353                 break;
354             }
355             case HANDLER_CONFIG_CHANGED: {
356                 int slotId = (Integer) msg.obj;
357                 carrierConfigChanged(slotId);
358                 break;
359             }
360             case HANDLER_START_DYNAMIC_FEATURE_QUERY: {
361                 ImsServiceInfo info = (ImsServiceInfo) msg.obj;
362                 startDynamicQuery(info);
363                 break;
364             }
365             case HANDLER_DYNAMIC_FEATURE_CHANGE: {
366                 SomeArgs args = (SomeArgs) msg.obj;
367                 ComponentName name = (ComponentName) args.arg1;
368                 Set<ImsFeatureConfiguration.FeatureSlotPair> features =
369                         (Set<ImsFeatureConfiguration.FeatureSlotPair>) args.arg2;
370                 args.recycle();
371                 dynamicQueryComplete(name, features);
372                 break;
373             }
374             case HANDLER_OVERRIDE_IMS_SERVICE_CONFIG: {
375                 int slotId = msg.arg1;
376                 // arg2 will be equal to 1 if it is a carrier service.
377                 boolean isCarrierImsService = (msg.arg2 == 1);
378                 String packageName = (String) msg.obj;
379                 if (isCarrierImsService) {
380                     Log.i(TAG, "overriding carrier ImsService - slot=" + slotId + " packageName="
381                             + packageName);
382                     maybeRebindService(slotId, packageName);
383                 } else {
384                     Log.i(TAG, "overriding device ImsService -  packageName=" + packageName);
385                     if (TextUtils.equals(mDeviceService, packageName)) {
386                         // No change in device service.
387                         break;
388                     }
389                     // Unbind from the previous ImsService before binding to the new one.
390                     unbindImsService(getImsServiceInfoFromCache(mDeviceService));
391                     mDeviceService = packageName;
392                     ImsServiceInfo deviceInfo = getImsServiceInfoFromCache(mDeviceService);
393                     if (deviceInfo == null) {
394                         // The package name is either "" or does not exist on the device.
395                         break;
396                     }
397                     if (deviceInfo.featureFromMetadata) {
398                         bindImsService(deviceInfo);
399                     } else {
400                         // newly added ImsServiceInfo that has not had features queried yet. Start
401                         // async bind and query features.
402                         scheduleQueryForFeatures(deviceInfo);
403                     }
404                 }
405                 break;
406             }
407             default:
408                 return false;
409         }
410         return true;
411     });
412 
413     // Results from dynamic queries to ImsService regarding the features they support.
414     private ImsServiceFeatureQueryManager.Listener mDynamicQueryListener =
415             new ImsServiceFeatureQueryManager.Listener() {
416 
417                 @Override
418                 public void onComplete(ComponentName name,
419                         Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
420                     Log.d(TAG, "onComplete called for name: " + name + "features:"
421                             + printFeatures(features));
422                     handleFeaturesChanged(name, features);
423                 }
424 
425                 @Override
426                 public void onError(ComponentName name) {
427                     Log.w(TAG, "onError: " + name + "returned with an error result");
428                     scheduleQueryForFeatures(name, DELAY_DYNAMIC_QUERY_MS);
429                 }
430             };
431 
432     // Array index corresponds to slot Id associated with the service package name.
433     private String[] mCarrierServices;
434     // List index corresponds to Slot Id, Maps ImsFeature.FEATURE->bound ImsServiceController
435     // Locked on mBoundServicesLock
436     private List<SparseArray<ImsServiceController>> mBoundImsServicesByFeature;
437     // not locked, only accessed on a handler thread.
438     private Map<ComponentName, ImsServiceInfo> mInstalledServicesCache = new HashMap<>();
439     // not locked, only accessed on a handler thread.
440     private Map<ComponentName, ImsServiceController> mActiveControllers = new HashMap<>();
441     // Only used as the Component name for legacy ImsServices that did not use dynamic binding.
442     private final ComponentName mStaticComponent;
443     private ImsServiceFeatureQueryManager mFeatureQueryManager;
444 
ImsResolver(Context context, String defaultImsPackageName, int numSlots, boolean isDynamicBinding)445     public ImsResolver(Context context, String defaultImsPackageName, int numSlots,
446             boolean isDynamicBinding) {
447         mContext = context;
448         mDeviceService = defaultImsPackageName;
449         mNumSlots = numSlots;
450         mIsDynamicBinding = isDynamicBinding;
451         mStaticComponent = new ComponentName(mContext, ImsResolver.class);
452         if (!mIsDynamicBinding) {
453             Log.i(TAG, "ImsResolver initialized with static binding.");
454             mDeviceService = mStaticComponent.getPackageName();
455         }
456         mCarrierConfigManager = (CarrierConfigManager) mContext.getSystemService(
457                 Context.CARRIER_CONFIG_SERVICE);
458         mCarrierServices = new String[numSlots];
459         mBoundImsServicesByFeature = Stream.generate(SparseArray<ImsServiceController>::new)
460                 .limit(mNumSlots).collect(Collectors.toList());
461 
462         // Only register for Package/CarrierConfig updates if dynamic binding.
463         if(mIsDynamicBinding) {
464             IntentFilter appChangedFilter = new IntentFilter();
465             appChangedFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
466             appChangedFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
467             appChangedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
468             appChangedFilter.addDataScheme("package");
469             context.registerReceiverAsUser(mAppChangedReceiver, UserHandle.ALL, appChangedFilter,
470                     null,
471                     null);
472 
473             context.registerReceiver(mConfigChangedReceiver, new IntentFilter(
474                     CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
475         }
476     }
477 
478     @VisibleForTesting
setSubscriptionManagerProxy(SubscriptionManagerProxy proxy)479     public void setSubscriptionManagerProxy(SubscriptionManagerProxy proxy) {
480         mSubscriptionManagerProxy = proxy;
481     }
482 
483     @VisibleForTesting
setImsServiceControllerFactory(ImsServiceControllerFactory factory)484     public void setImsServiceControllerFactory(ImsServiceControllerFactory factory) {
485         mImsServiceControllerFactory = factory;
486     }
487 
488     @VisibleForTesting
getHandler()489     public Handler getHandler() {
490         return mHandler;
491     }
492 
493     @VisibleForTesting
setImsDynamicQueryManagerFactory(ImsDynamicQueryManagerFactory m)494     public void setImsDynamicQueryManagerFactory(ImsDynamicQueryManagerFactory m) {
495         mDynamicQueryManagerFactory = m;
496     }
497 
498     /**
499      * Needs to be called after the constructor to first populate the cache and possibly bind to
500      * ImsServices.
501      */
initPopulateCacheAndStartBind()502     public void initPopulateCacheAndStartBind() {
503         Log.i(TAG, "Initializing cache and binding.");
504         mFeatureQueryManager = mDynamicQueryManagerFactory.create(mContext, mDynamicQueryListener);
505         // Populates the CarrierConfig override package names for each slot
506         mHandler.obtainMessage(HANDLER_CONFIG_CHANGED,
507                 SubscriptionManager.INVALID_SIM_SLOT_INDEX).sendToTarget();
508         // Starts first bind to the system.
509         mHandler.obtainMessage(HANDLER_ADD_PACKAGE, null).sendToTarget();
510     }
511 
512     /**
513      * Notify ImsService to enable IMS for the framework. This will trigger IMS registration and
514      * trigger ImsFeature status updates.
515      */
enableIms(int slotId)516     public void enableIms(int slotId) {
517         SparseArray<ImsServiceController> controllers = getImsServiceControllers(slotId);
518         if (controllers != null) {
519             for (int i = 0; i < controllers.size(); i++) {
520                 int key = controllers.keyAt(i);
521                 controllers.get(key).enableIms(slotId);
522             }
523         }
524     }
525 
526     /**
527      * Notify ImsService to disable IMS for the framework. This will trigger IMS de-registration and
528      * trigger ImsFeature capability status to become false.
529      */
disableIms(int slotId)530     public void disableIms(int slotId) {
531         SparseArray<ImsServiceController> controllers = getImsServiceControllers(slotId);
532         if (controllers != null) {
533             for (int i = 0; i < controllers.size(); i++) {
534                 int key = controllers.keyAt(i);
535                 controllers.get(key).disableIms(slotId);
536             }
537         }
538     }
539 
540     /**
541      * Returns the {@link IImsMmTelFeature} that corresponds to the given slot Id or {@link null} if
542      * the service is not available. If an IImsMMTelFeature is available, the
543      * {@link IImsServiceFeatureCallback} callback is registered as a listener for feature updates.
544      * @param slotId The SIM slot that we are requesting the {@link IImsMmTelFeature} for.
545      * @param callback Listener that will send updates to ImsManager when there are updates to
546      * the feature.
547      * @return {@link IImsMmTelFeature} interface or {@link null} if it is unavailable.
548      */
getMmTelFeatureAndListen(int slotId, IImsServiceFeatureCallback callback)549     public IImsMmTelFeature getMmTelFeatureAndListen(int slotId,
550             IImsServiceFeatureCallback callback) {
551         ImsServiceController controller = getImsServiceControllerAndListen(slotId,
552                 ImsFeature.FEATURE_MMTEL, callback);
553         return (controller != null) ? controller.getMmTelFeature(slotId) : null;
554     }
555 
556     /**
557      * Returns the {@link IImsRcsFeature} that corresponds to the given slot Id for emergency
558      * calling or {@link null} if the service is not available. If an IImsMMTelFeature is
559      * available, the {@link IImsServiceFeatureCallback} callback is registered as a listener for
560      * feature updates.
561      * @param slotId The SIM slot that we are requesting the {@link IImsRcsFeature} for.
562      * @param callback listener that will send updates to ImsManager when there are updates to
563      * the feature.
564      * @return {@link IImsRcsFeature} interface or {@link null} if it is unavailable.
565      */
getRcsFeatureAndListen(int slotId, IImsServiceFeatureCallback callback)566     public IImsRcsFeature getRcsFeatureAndListen(int slotId, IImsServiceFeatureCallback callback) {
567         ImsServiceController controller = getImsServiceControllerAndListen(slotId,
568                 ImsFeature.FEATURE_RCS, callback);
569         return (controller != null) ? controller.getRcsFeature(slotId) : null;
570     }
571 
572     /**
573      * Returns the ImsRegistration structure associated with the slotId and feature specified.
574      */
getImsRegistration(int slotId, int feature)575     public @Nullable IImsRegistration getImsRegistration(int slotId, int feature)
576             throws RemoteException {
577         ImsServiceController controller = getImsServiceController(slotId, feature);
578         if (controller != null) {
579             return controller.getRegistration(slotId);
580         }
581         return null;
582     }
583 
584     /**
585      * Returns the ImsConfig structure associated with the slotId and feature specified.
586      */
getImsConfig(int slotId, int feature)587     public @Nullable IImsConfig getImsConfig(int slotId, int feature)
588             throws RemoteException {
589         ImsServiceController controller = getImsServiceController(slotId, feature);
590         if (controller != null) {
591             return controller.getConfig(slotId);
592         }
593         return null;
594     }
595 
596     @VisibleForTesting
getImsServiceController(int slotId, int feature)597     public ImsServiceController getImsServiceController(int slotId, int feature) {
598         if (slotId < 0 || slotId >= mNumSlots) {
599             return null;
600         }
601         ImsServiceController controller;
602         synchronized (mBoundServicesLock) {
603             SparseArray<ImsServiceController> services = mBoundImsServicesByFeature.get(slotId);
604             if (services == null) {
605                 return null;
606             }
607             controller = services.get(feature);
608         }
609         return controller;
610     }
611 
getImsServiceControllers(int slotId)612     private  SparseArray<ImsServiceController> getImsServiceControllers(int slotId) {
613         if (slotId < 0 || slotId >= mNumSlots) {
614             return null;
615         }
616         synchronized (mBoundServicesLock) {
617             SparseArray<ImsServiceController> services = mBoundImsServicesByFeature.get(slotId);
618             if (services == null) {
619                 return null;
620             }
621             return services;
622         }
623     }
624 
625     @VisibleForTesting
getImsServiceControllerAndListen(int slotId, int feature, IImsServiceFeatureCallback callback)626     public ImsServiceController getImsServiceControllerAndListen(int slotId, int feature,
627             IImsServiceFeatureCallback callback) {
628         ImsServiceController controller = getImsServiceController(slotId, feature);
629 
630         if (controller != null) {
631             controller.addImsServiceFeatureCallback(callback);
632             return controller;
633         }
634         return null;
635     }
636 
637     // Used for testing only.
overrideImsServiceConfiguration(int slotId, boolean isCarrierService, String packageName)638     public boolean overrideImsServiceConfiguration(int slotId, boolean isCarrierService,
639             String packageName) {
640         if (slotId < 0 || slotId >= mNumSlots) {
641             Log.w(TAG, "overrideImsServiceConfiguration: invalid slotId!");
642             return false;
643         }
644 
645         if (packageName == null) {
646             Log.w(TAG, "overrideImsServiceConfiguration: null packageName!");
647             return false;
648         }
649 
650         // encode boolean to int for Message.
651         int carrierService = isCarrierService ? 1 : 0;
652         Message.obtain(mHandler, HANDLER_OVERRIDE_IMS_SERVICE_CONFIG, slotId, carrierService,
653                 packageName).sendToTarget();
654         return true;
655     }
656 
657     // used for testing only.
getImsServiceConfiguration(int slotId, boolean isCarrierService)658     public String getImsServiceConfiguration(int slotId, boolean isCarrierService) {
659         if (slotId < 0 || slotId >= mNumSlots) {
660             Log.w(TAG, "getImsServiceConfiguration: invalid slotId!");
661             return "";
662         }
663 
664         return isCarrierService ? mCarrierServices[slotId] : mDeviceService;
665     }
666 
putImsController(int slotId, int feature, ImsServiceController controller)667     private void putImsController(int slotId, int feature, ImsServiceController controller) {
668         if (slotId < 0 || slotId >= mNumSlots || feature <= ImsFeature.FEATURE_INVALID
669                 || feature >= ImsFeature.FEATURE_MAX) {
670             Log.w(TAG, "putImsController received invalid parameters - slot: " + slotId
671                     + ", feature: " + feature);
672             return;
673         }
674         synchronized (mBoundServicesLock) {
675             SparseArray<ImsServiceController> services = mBoundImsServicesByFeature.get(slotId);
676             if (services == null) {
677                 services = new SparseArray<>();
678                 mBoundImsServicesByFeature.add(slotId, services);
679             }
680             Log.i(TAG, "ImsServiceController added on slot: " + slotId + " with feature: "
681                     + feature + " using package: " + controller.getComponentName());
682             services.put(feature, controller);
683         }
684     }
685 
removeImsController(int slotId, int feature)686     private ImsServiceController removeImsController(int slotId, int feature) {
687         if (slotId < 0 || slotId >= mNumSlots || feature <= ImsFeature.FEATURE_INVALID
688                 || feature >= ImsFeature.FEATURE_MAX) {
689             Log.w(TAG, "removeImsController received invalid parameters - slot: " + slotId
690                     + ", feature: " + feature);
691             return null;
692         }
693         synchronized (mBoundServicesLock) {
694             SparseArray<ImsServiceController> services = mBoundImsServicesByFeature.get(slotId);
695             if (services == null) {
696                 return null;
697             }
698             ImsServiceController c = services.get(feature, null);
699             if (c != null) {
700                 Log.i(TAG, "ImsServiceController removed on slot: " + slotId + " with feature: "
701                         + feature + " using package: " + c.getComponentName());
702                 services.remove(feature);
703             }
704             return c;
705         }
706     }
707 
708     // Update the current cache with the new ImsService(s) if it has been added or update the
709     // supported IMS features if they have changed.
710     // Called from the handler ONLY
maybeAddedImsService(String packageName)711     private void maybeAddedImsService(String packageName) {
712         Log.d(TAG, "maybeAddedImsService, packageName: " + packageName);
713         List<ImsServiceInfo> infos = getImsServiceInfo(packageName);
714         List<ImsServiceInfo> newlyAddedInfos = new ArrayList<>();
715         for (ImsServiceInfo info : infos) {
716             // Checking to see if the ComponentName is the same, so we can update the supported
717             // features. Will only be one (if it exists), since it is a set.
718             ImsServiceInfo match = getInfoByComponentName(mInstalledServicesCache, info.name);
719             if (match != null) {
720                 // for dynamic query the new "info" will have no supported features yet. Don't wipe
721                 // out the cache for the existing features or update yet. Instead start a query
722                 // for features dynamically.
723                 if (info.featureFromMetadata) {
724                     // update features in the cache
725                     Log.i(TAG, "Updating features in cached ImsService: " + info.name);
726                     Log.d(TAG, "Updating features - Old features: " + match + " new features: "
727                             + info);
728                     match.replaceFeatures(info.getSupportedFeatures());
729                     updateImsServiceFeatures(info);
730                 } else {
731                     // start a query to get ImsService features
732                     scheduleQueryForFeatures(info);
733                 }
734             } else {
735                 Log.i(TAG, "Adding newly added ImsService to cache: " + info.name);
736                 mInstalledServicesCache.put(info.name, info);
737                 if (info.featureFromMetadata) {
738                     newlyAddedInfos.add(info);
739                 } else {
740                     // newly added ImsServiceInfo that has not had features queried yet. Start async
741                     // bind and query features.
742                     scheduleQueryForFeatures(info);
743                 }
744             }
745         }
746         // Loop through the newly created ServiceInfos in a separate loop to make sure the cache
747         // is fully updated.
748         for (ImsServiceInfo info : newlyAddedInfos) {
749             if (isActiveCarrierService(info)) {
750                 // New ImsService is registered to active carrier services and must be newly
751                 // bound.
752                 bindImsService(info);
753                 // Update existing device service features
754                 updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
755             } else if (isDeviceService(info)) {
756                 // New ImsService is registered as device default and must be newly bound.
757                 bindImsService(info);
758             }
759         }
760     }
761 
762     // Remove the ImsService from the cache. At this point, the ImsService will have already been
763     // killed.
764     // Called from the handler ONLY
maybeRemovedImsService(String packageName)765     private boolean maybeRemovedImsService(String packageName) {
766         ImsServiceInfo match = getInfoByPackageName(mInstalledServicesCache, packageName);
767         if (match != null) {
768             mInstalledServicesCache.remove(match.name);
769             Log.i(TAG, "Removing ImsService: " + match.name);
770             unbindImsService(match);
771             updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
772             return true;
773         }
774         return false;
775     }
776 
777     // Returns true if the CarrierConfig that has been loaded includes this ImsServiceInfo
778     // package name.
779     // Called from Handler ONLY
isActiveCarrierService(ImsServiceInfo info)780     private boolean isActiveCarrierService(ImsServiceInfo info) {
781         for (int i = 0; i < mNumSlots; i++) {
782             if (TextUtils.equals(mCarrierServices[i], info.name.getPackageName())) {
783                 return true;
784             }
785         }
786         return false;
787     }
788 
isDeviceService(ImsServiceInfo info)789     private boolean isDeviceService(ImsServiceInfo info) {
790         return TextUtils.equals(mDeviceService, info.name.getPackageName());
791     }
792 
getSlotForActiveCarrierService(ImsServiceInfo info)793     private int getSlotForActiveCarrierService(ImsServiceInfo info) {
794         for (int i = 0; i < mNumSlots; i++) {
795             if (TextUtils.equals(mCarrierServices[i], info.name.getPackageName())) {
796                 return i;
797             }
798         }
799         return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
800     }
801 
getControllerByServiceInfo( Map<ComponentName, ImsServiceController> searchMap, ImsServiceInfo matchValue)802     private ImsServiceController getControllerByServiceInfo(
803             Map<ComponentName, ImsServiceController> searchMap, ImsServiceInfo matchValue) {
804         return searchMap.values().stream()
805                 .filter(c -> Objects.equals(c.getComponentName(), matchValue.name))
806                 .findFirst().orElse(null);
807     }
808 
getInfoByPackageName(Map<ComponentName, ImsServiceInfo> searchMap, String matchValue)809     private ImsServiceInfo getInfoByPackageName(Map<ComponentName, ImsServiceInfo> searchMap,
810             String matchValue) {
811         return searchMap.values().stream()
812                 .filter((i) -> Objects.equals(i.name.getPackageName(), matchValue))
813                 .findFirst().orElse(null);
814     }
815 
getInfoByComponentName( Map<ComponentName, ImsServiceInfo> searchMap, ComponentName matchValue)816     private ImsServiceInfo getInfoByComponentName(
817             Map<ComponentName, ImsServiceInfo> searchMap, ComponentName matchValue) {
818         return searchMap.get(matchValue);
819     }
820 
821     // Creates new features in active ImsServices and removes obsolete cached features. If
822     // cachedInfo == null, then newInfo is assumed to be a new ImsService and will have all features
823     // created.
updateImsServiceFeatures(ImsServiceInfo newInfo)824     private void updateImsServiceFeatures(ImsServiceInfo newInfo) {
825         if (newInfo == null) {
826             return;
827         }
828         ImsServiceController controller = getControllerByServiceInfo(mActiveControllers, newInfo);
829         // Will return zero if these features are overridden or it should not currently have any
830         // features because it is not carrier/device.
831         HashSet<ImsFeatureConfiguration.FeatureSlotPair> features =
832                 calculateFeaturesToCreate(newInfo);
833         if (shouldFeaturesCauseBind(features)) {
834             try {
835                 if (controller != null) {
836                     Log.i(TAG, "Updating features for ImsService: "
837                             + controller.getComponentName());
838                     Log.d(TAG, "Updating Features - New Features: " + features);
839                     controller.changeImsServiceFeatures(features);
840                 } else {
841                     Log.i(TAG, "updateImsServiceFeatures: unbound with active features, rebinding");
842                     bindImsServiceWithFeatures(newInfo, features);
843                 }
844                 // If the carrier service features have changed, the device features will also
845                 // need to be recalculated.
846                 if (isActiveCarrierService(newInfo)
847                         // Prevent infinite recursion from bad behavior
848                         && !TextUtils.equals(newInfo.name.getPackageName(), mDeviceService)) {
849                     Log.i(TAG, "Updating device default");
850                     updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
851                 }
852             } catch (RemoteException e) {
853                 Log.e(TAG, "updateImsServiceFeatures: Remote Exception: " + e.getMessage());
854             }
855         // Don't stay bound if the ImsService is providing no features.
856         } else if (controller != null) {
857             Log.i(TAG, "Unbinding: features = 0 for ImsService: " + controller.getComponentName());
858             unbindImsService(newInfo);
859         }
860     }
861 
862     // Bind to an ImsService and wait for the service to be connected to create ImsFeatures.
bindImsService(ImsServiceInfo info)863     private void bindImsService(ImsServiceInfo info) {
864         if (info == null) {
865             return;
866         }
867         HashSet<ImsFeatureConfiguration.FeatureSlotPair> features = calculateFeaturesToCreate(info);
868         bindImsServiceWithFeatures(info, features);
869     }
870 
bindImsServiceWithFeatures(ImsServiceInfo info, HashSet<ImsFeatureConfiguration.FeatureSlotPair> features)871     private void bindImsServiceWithFeatures(ImsServiceInfo info,
872             HashSet<ImsFeatureConfiguration.FeatureSlotPair> features) {
873         // Only bind if there are features that will be created by the service.
874         if (shouldFeaturesCauseBind(features)) {
875             // Check to see if an active controller already exists
876             ImsServiceController controller = getControllerByServiceInfo(mActiveControllers, info);
877             if (controller != null) {
878                 Log.i(TAG, "ImsService connection exists, updating features " + features);
879                 try {
880                     controller.changeImsServiceFeatures(features);
881                     // Features have been set, there was an error adding/removing. When the
882                     // controller recovers, it will add/remove again.
883                 } catch (RemoteException e) {
884                     Log.w(TAG, "bindImsService: error=" + e.getMessage());
885                 }
886             } else {
887                 controller = info.controllerFactory.create(mContext, info.name, this);
888                 Log.i(TAG, "Binding ImsService: " + controller.getComponentName()
889                         + " with features: " + features);
890                 controller.bind(features);
891             }
892             mActiveControllers.put(info.name, controller);
893         }
894     }
895 
896     // Clean up and unbind from an ImsService
unbindImsService(ImsServiceInfo info)897     private void unbindImsService(ImsServiceInfo info) {
898         if (info == null) {
899             return;
900         }
901         ImsServiceController controller = getControllerByServiceInfo(mActiveControllers, info);
902         if (controller != null) {
903             // Calls imsServiceFeatureRemoved on all features in the controller
904             try {
905                 Log.i(TAG, "Unbinding ImsService: " + controller.getComponentName());
906                 controller.unbind();
907             } catch (RemoteException e) {
908                 Log.e(TAG, "unbindImsService: Remote Exception: " + e.getMessage());
909             }
910             mActiveControllers.remove(info.name);
911         }
912     }
913 
914     // Calculate which features an ImsServiceController will need. If it is the carrier specific
915     // ImsServiceController, it will be granted all of the features it requests on the associated
916     // slot. If it is the device ImsService, it will get all of the features not covered by the
917     // carrier implementation.
calculateFeaturesToCreate( ImsServiceInfo info)918     private HashSet<ImsFeatureConfiguration.FeatureSlotPair> calculateFeaturesToCreate(
919             ImsServiceInfo info) {
920         HashSet<ImsFeatureConfiguration.FeatureSlotPair> imsFeaturesBySlot = new HashSet<>();
921         // Check if the info is a carrier service
922         int slotId = getSlotForActiveCarrierService(info);
923         if (slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
924             imsFeaturesBySlot.addAll(info.getSupportedFeatures().stream()
925                     // Match slotId with feature slotId.
926                     .filter(feature -> slotId == feature.slotId)
927                     .collect(Collectors.toList()));
928         } else if (isDeviceService(info)) {
929             // For all slots that are not currently using a carrier ImsService, enable all features
930             // for the device default.
931             for (int i = 0; i < mNumSlots; i++) {
932                 final int currSlotId = i;
933                 ImsServiceInfo carrierImsInfo = getImsServiceInfoFromCache(mCarrierServices[i]);
934                 if (carrierImsInfo == null) {
935                     // No Carrier override, add all features for this slot
936                     imsFeaturesBySlot.addAll(info.getSupportedFeatures().stream()
937                             .filter(feature -> currSlotId == feature.slotId)
938                             .collect(Collectors.toList()));
939                 } else {
940                     // Add all features to the device service that are not currently covered by
941                     // the carrier ImsService.
942                     HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatures =
943                             new HashSet<>(info.getSupportedFeatures());
944                     deviceFeatures.removeAll(carrierImsInfo.getSupportedFeatures());
945                     // only add features for current slot
946                     imsFeaturesBySlot.addAll(deviceFeatures.stream()
947                             .filter(feature -> currSlotId == feature.slotId).collect(
948                             Collectors.toList()));
949                 }
950             }
951         }
952         return imsFeaturesBySlot;
953     }
954 
955     /**
956      * Implementation of
957      * {@link ImsServiceController.ImsServiceControllerCallbacks#imsServiceFeatureCreated}, which
958      * removes the ImsServiceController from the mBoundImsServicesByFeature structure.
959      */
imsServiceFeatureCreated(int slotId, int feature, ImsServiceController controller)960     public void imsServiceFeatureCreated(int slotId, int feature, ImsServiceController controller) {
961         putImsController(slotId, feature, controller);
962     }
963 
964     /**
965      * Implementation of
966      * {@link ImsServiceController.ImsServiceControllerCallbacks#imsServiceFeatureRemoved}, which
967      * removes the ImsServiceController from the mBoundImsServicesByFeature structure.
968      */
imsServiceFeatureRemoved(int slotId, int feature, ImsServiceController controller)969     public void imsServiceFeatureRemoved(int slotId, int feature, ImsServiceController controller) {
970         removeImsController(slotId, feature);
971     }
972 
973     /**
974      * Implementation of
975      * {@link ImsServiceController.ImsServiceControllerCallbacks#imsServiceFeaturesChanged, which
976      * notify the ImsResolver of a change to the supported ImsFeatures of a connected ImsService.
977      */
imsServiceFeaturesChanged(ImsFeatureConfiguration config, ImsServiceController controller)978     public void imsServiceFeaturesChanged(ImsFeatureConfiguration config,
979             ImsServiceController controller) {
980         if (controller == null || config == null) {
981             return;
982         }
983         Log.i(TAG, "imsServiceFeaturesChanged: config=" + config.getServiceFeatures()
984                 + ", ComponentName=" + controller.getComponentName());
985         handleFeaturesChanged(controller.getComponentName(), config.getServiceFeatures());
986     }
987 
988     /**
989      * Determines if the features specified should cause a bind or keep a binding active to an
990      * ImsService.
991      * @return true if MMTEL or RCS features are present, false if they are not or only
992      * EMERGENCY_MMTEL is specified.
993      */
shouldFeaturesCauseBind( HashSet<ImsFeatureConfiguration.FeatureSlotPair> features)994     private boolean shouldFeaturesCauseBind(
995             HashSet<ImsFeatureConfiguration.FeatureSlotPair> features) {
996         long bindableFeatures = features.stream()
997                 // remove all emergency features
998                 .filter(f -> f.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL).count();
999         return bindableFeatures > 0;
1000     }
1001 
1002     // Possibly rebind to another ImsService if currently installed ImsServices were changed or if
1003     // the SIM card has changed.
1004     // Called from the handler ONLY
maybeRebindService(int slotId, String newPackageName)1005     private void maybeRebindService(int slotId, String newPackageName) {
1006         if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
1007             // not specified, replace package on all slots.
1008             for (int i = 0; i < mNumSlots; i++) {
1009                 updateBoundCarrierServices(i, newPackageName);
1010             }
1011         } else {
1012             updateBoundCarrierServices(slotId, newPackageName);
1013         }
1014 
1015     }
1016 
carrierConfigChanged(int slotId)1017     private void carrierConfigChanged(int slotId) {
1018         int subId = mSubscriptionManagerProxy.getSubId(slotId);
1019         PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId);
1020         if (config != null) {
1021             String newPackageName = config.getString(
1022                     CarrierConfigManager.KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, null);
1023             maybeRebindService(slotId, newPackageName);
1024         } else {
1025             Log.w(TAG, "carrierConfigChanged: CarrierConfig is null!");
1026         }
1027     }
1028 
updateBoundCarrierServices(int slotId, String newPackageName)1029     private void updateBoundCarrierServices(int slotId, String newPackageName) {
1030         if (slotId > SubscriptionManager.INVALID_SIM_SLOT_INDEX && slotId < mNumSlots) {
1031             String oldPackageName = mCarrierServices[slotId];
1032             mCarrierServices[slotId] = newPackageName;
1033             if (!TextUtils.equals(newPackageName, oldPackageName)) {
1034                 Log.i(TAG, "Carrier Config updated, binding new ImsService");
1035                 // Unbind old ImsService, not needed anymore
1036                 // ImsService is retrieved from the cache. If the cache hasn't been populated yet,
1037                 // the calls to unbind/bind will fail (intended during initial start up).
1038                 unbindImsService(getImsServiceInfoFromCache(oldPackageName));
1039                 ImsServiceInfo newInfo = getImsServiceInfoFromCache(newPackageName);
1040                 // if there is no carrier ImsService, newInfo is null. This we still want to update
1041                 // bindings for device ImsService to pick up the missing features.
1042                 if (newInfo == null || newInfo.featureFromMetadata) {
1043                     bindImsService(newInfo);
1044                     // Recalculate the device ImsService features to reflect changes.
1045                     updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
1046                 } else {
1047                     // ImsServiceInfo that has not had features queried yet. Start async
1048                     // bind and query features.
1049                     scheduleQueryForFeatures(newInfo);
1050                 }
1051             }
1052         }
1053     }
1054 
1055     /**
1056      * Schedules a query for dynamic ImsService features.
1057      */
scheduleQueryForFeatures(ImsServiceInfo service, int delayMs)1058     private void scheduleQueryForFeatures(ImsServiceInfo service, int delayMs) {
1059         // if not current device/carrier service, don't perform query. If this changes, this method
1060         // will be called again.
1061         if (!isDeviceService(service) && getSlotForActiveCarrierService(service)
1062                 == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
1063             Log.i(TAG, "scheduleQueryForFeatures: skipping query for ImsService that is not"
1064                     + " set as carrier/device ImsService.");
1065             return;
1066         }
1067         Message msg = Message.obtain(mHandler, HANDLER_START_DYNAMIC_FEATURE_QUERY, service);
1068         if (mHandler.hasMessages(HANDLER_START_DYNAMIC_FEATURE_QUERY, service)) {
1069             Log.d(TAG, "scheduleQueryForFeatures: dynamic query for " + service.name
1070                     + " already scheduled");
1071             return;
1072         }
1073         Log.d(TAG, "scheduleQueryForFeatures: starting dynamic query for " + service.name
1074                 + " in " + delayMs + "ms.");
1075         mHandler.sendMessageDelayed(msg, delayMs);
1076     }
1077 
scheduleQueryForFeatures(ComponentName name, int delayMs)1078     private void scheduleQueryForFeatures(ComponentName name, int delayMs) {
1079         ImsServiceInfo service = getImsServiceInfoFromCache(name.getPackageName());
1080         if (service == null) {
1081             Log.w(TAG, "scheduleQueryForFeatures: Couldn't find cached info for name: " + name);
1082             return;
1083         }
1084         scheduleQueryForFeatures(service, delayMs);
1085     }
1086 
scheduleQueryForFeatures(ImsServiceInfo service)1087     private void scheduleQueryForFeatures(ImsServiceInfo service) {
1088         scheduleQueryForFeatures(service, 0);
1089     }
1090 
1091     /**
1092      * Schedules the processing of a completed query.
1093      */
handleFeaturesChanged(ComponentName name, Set<ImsFeatureConfiguration.FeatureSlotPair> features)1094     private void handleFeaturesChanged(ComponentName name,
1095             Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
1096         SomeArgs args = SomeArgs.obtain();
1097         args.arg1 = name;
1098         args.arg2 = features;
1099         mHandler.obtainMessage(HANDLER_DYNAMIC_FEATURE_CHANGE, args).sendToTarget();
1100     }
1101 
1102     // Starts a dynamic query. Called from handler ONLY.
startDynamicQuery(ImsServiceInfo service)1103     private void startDynamicQuery(ImsServiceInfo service) {
1104         boolean queryStarted = mFeatureQueryManager.startQuery(service.name,
1105                 service.controllerFactory.getServiceInterface());
1106         if (!queryStarted) {
1107             Log.w(TAG, "startDynamicQuery: service could not connect. Retrying after delay.");
1108             scheduleQueryForFeatures(service, DELAY_DYNAMIC_QUERY_MS);
1109         } else {
1110             Log.d(TAG, "startDynamicQuery: Service queried, waiting for response.");
1111         }
1112     }
1113 
1114     // process complete dynamic query. Called from handler ONLY.
dynamicQueryComplete(ComponentName name, Set<ImsFeatureConfiguration.FeatureSlotPair> features)1115     private void dynamicQueryComplete(ComponentName name,
1116             Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
1117         ImsServiceInfo service = getImsServiceInfoFromCache(name.getPackageName());
1118         if (service == null) {
1119             Log.w(TAG, "handleFeaturesChanged: Couldn't find cached info for name: "
1120                     + name);
1121             return;
1122         }
1123         // Add features to service
1124         service.replaceFeatures(features);
1125         if (isActiveCarrierService(service)) {
1126             // New ImsService is registered to active carrier services and must be newly
1127             // bound.
1128             bindImsService(service);
1129             // Update existing device service features
1130             updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
1131         } else if (isDeviceService(service)) {
1132             // New ImsService is registered as device default and must be newly bound.
1133             bindImsService(service);
1134         }
1135     }
1136 
printFeatures(Set<ImsFeatureConfiguration.FeatureSlotPair> features)1137     private String printFeatures(Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
1138         StringBuilder featureString = new StringBuilder();
1139         featureString.append("features: [");
1140         if (features != null) {
1141             for (ImsFeatureConfiguration.FeatureSlotPair feature : features) {
1142                 featureString.append("{");
1143                 featureString.append(feature.slotId);
1144                 featureString.append(",");
1145                 featureString.append(feature.featureType);
1146                 featureString.append("} ");
1147             }
1148             featureString.append("]");
1149         }
1150         return featureString.toString();
1151     }
1152 
1153     /**
1154      * Returns the ImsServiceInfo that matches the provided packageName. Visible for testing
1155      * the ImsService caching functionality.
1156      */
1157     @VisibleForTesting
getImsServiceInfoFromCache(String packageName)1158     public ImsServiceInfo getImsServiceInfoFromCache(String packageName) {
1159         if (TextUtils.isEmpty(packageName)) {
1160             return null;
1161         }
1162         ImsServiceInfo infoFilter = getInfoByPackageName(mInstalledServicesCache, packageName);
1163         if (infoFilter != null) {
1164             return infoFilter;
1165         } else {
1166             return null;
1167         }
1168     }
1169 
1170     // Return the ImsServiceInfo specified for the package name. If the package name is null,
1171     // get all packages that support ImsServices.
getImsServiceInfo(String packageName)1172     private List<ImsServiceInfo> getImsServiceInfo(String packageName) {
1173         List<ImsServiceInfo> infos = new ArrayList<>();
1174         if (!mIsDynamicBinding) {
1175             // always return the same ImsService info.
1176             infos.addAll(getStaticImsService());
1177         } else {
1178             // Search for Current ImsService implementations
1179             infos.addAll(searchForImsServices(packageName, mImsServiceControllerFactory));
1180             // Search for compat ImsService Implementations
1181             infos.addAll(searchForImsServices(packageName, mImsServiceControllerFactoryCompat));
1182         }
1183         return infos;
1184     }
1185 
getStaticImsService()1186     private List<ImsServiceInfo> getStaticImsService() {
1187         List<ImsServiceInfo> infos = new ArrayList<>();
1188 
1189         ImsServiceInfo info = new ImsServiceInfo(mNumSlots);
1190         info.name = mStaticComponent;
1191         info.controllerFactory = mImsServiceControllerFactoryStaticBindingCompat;
1192         info.addFeatureForAllSlots(ImsFeature.FEATURE_EMERGENCY_MMTEL);
1193         info.addFeatureForAllSlots(ImsFeature.FEATURE_MMTEL);
1194         infos.add(info);
1195         return infos;
1196     }
1197 
searchForImsServices(String packageName, ImsServiceControllerFactory controllerFactory)1198     private List<ImsServiceInfo> searchForImsServices(String packageName,
1199             ImsServiceControllerFactory controllerFactory) {
1200         List<ImsServiceInfo> infos = new ArrayList<>();
1201 
1202         Intent serviceIntent = new Intent(controllerFactory.getServiceInterface());
1203         serviceIntent.setPackage(packageName);
1204 
1205         PackageManager packageManager = mContext.getPackageManager();
1206         for (ResolveInfo entry : packageManager.queryIntentServicesAsUser(
1207                 serviceIntent,
1208                 PackageManager.GET_META_DATA,
1209                 mContext.getUserId())) {
1210             ServiceInfo serviceInfo = entry.serviceInfo;
1211 
1212             if (serviceInfo != null) {
1213                 ImsServiceInfo info = new ImsServiceInfo(mNumSlots);
1214                 info.name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
1215                 info.controllerFactory = controllerFactory;
1216 
1217                 // we will allow the manifest method of declaring manifest features in two cases:
1218                 // 1) it is the device overlay "default" ImsService, where the features do not
1219                 // change (the new method can still be used if the default does not define manifest
1220                 // entries).
1221                 // 2) using the "compat" ImsService, which only supports manifest query.
1222                 if (isDeviceService(info)
1223                         || mImsServiceControllerFactoryCompat == controllerFactory) {
1224                     if (serviceInfo.metaData != null) {
1225                         if (serviceInfo.metaData.getBoolean(METADATA_EMERGENCY_MMTEL_FEATURE,
1226                                 false)) {
1227                             info.addFeatureForAllSlots(ImsFeature.FEATURE_EMERGENCY_MMTEL);
1228                         }
1229                         if (serviceInfo.metaData.getBoolean(METADATA_MMTEL_FEATURE, false)) {
1230                             info.addFeatureForAllSlots(ImsFeature.FEATURE_MMTEL);
1231                         }
1232                         if (serviceInfo.metaData.getBoolean(METADATA_RCS_FEATURE, false)) {
1233                             info.addFeatureForAllSlots(ImsFeature.FEATURE_RCS);
1234                         }
1235                     }
1236                     // Only dynamic query if we are not a compat version of ImsService and the
1237                     // default service.
1238                     if (mImsServiceControllerFactoryCompat != controllerFactory
1239                             && info.getSupportedFeatures().isEmpty()) {
1240                         // metadata empty, try dynamic query instead
1241                         info.featureFromMetadata = false;
1242                     }
1243                 } else {
1244                     // We are a carrier service and not using the compat version of ImsService.
1245                     info.featureFromMetadata = false;
1246                 }
1247                 Log.i(TAG, "service name: " + info.name + ", manifest query: "
1248                         + info.featureFromMetadata);
1249                 // Check manifest permission to be sure that the service declares the correct
1250                 // permissions. Overridden if the METADATA_OVERRIDE_PERM_CHECK metadata is set to
1251                 // true.
1252                 // NOTE: METADATA_OVERRIDE_PERM_CHECK should only be set for testing.
1253                 if (TextUtils.equals(serviceInfo.permission, Manifest.permission.BIND_IMS_SERVICE)
1254                         || serviceInfo.metaData.getBoolean(METADATA_OVERRIDE_PERM_CHECK, false)) {
1255                     infos.add(info);
1256                 } else {
1257                     Log.w(TAG, "ImsService is not protected with BIND_IMS_SERVICE permission: "
1258                             + info.name);
1259                 }
1260             }
1261         }
1262         return infos;
1263     }
1264 }
1265