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 (packageName == null || packageName.isEmpty()) {
386                         unbindImsService(getImsServiceInfoFromCache(mDeviceService));
387                     }
388                     mDeviceService = packageName;
389                     ImsServiceInfo deviceInfo = getImsServiceInfoFromCache(mDeviceService);
390                     if (deviceInfo == null) {
391                         // The package name is either "" or does not exist on the device.
392                         break;
393                     }
394                     if (deviceInfo.featureFromMetadata) {
395                         bindImsService(deviceInfo);
396                     } else {
397                         // newly added ImsServiceInfo that has not had features queried yet. Start
398                         // async bind and query features.
399                         scheduleQueryForFeatures(deviceInfo);
400                     }
401                 }
402                 break;
403             }
404             default:
405                 return false;
406         }
407         return true;
408     });
409 
410     // Results from dynamic queries to ImsService regarding the features they support.
411     private ImsServiceFeatureQueryManager.Listener mDynamicQueryListener =
412             new ImsServiceFeatureQueryManager.Listener() {
413 
414                 @Override
415                 public void onComplete(ComponentName name,
416                         Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
417                     Log.d(TAG, "onComplete called for name: " + name + "features:"
418                             + printFeatures(features));
419                     handleFeaturesChanged(name, features);
420                 }
421 
422                 @Override
423                 public void onError(ComponentName name) {
424                     Log.w(TAG, "onError: " + name + "returned with an error result");
425                     scheduleQueryForFeatures(name, DELAY_DYNAMIC_QUERY_MS);
426                 }
427             };
428 
429     // Array index corresponds to slot Id associated with the service package name.
430     private String[] mCarrierServices;
431     // List index corresponds to Slot Id, Maps ImsFeature.FEATURE->bound ImsServiceController
432     // Locked on mBoundServicesLock
433     private List<SparseArray<ImsServiceController>> mBoundImsServicesByFeature;
434     // not locked, only accessed on a handler thread.
435     private Map<ComponentName, ImsServiceInfo> mInstalledServicesCache = new HashMap<>();
436     // not locked, only accessed on a handler thread.
437     private Map<ComponentName, ImsServiceController> mActiveControllers = new HashMap<>();
438     // Only used as the Component name for legacy ImsServices that did not use dynamic binding.
439     private final ComponentName mStaticComponent;
440     private ImsServiceFeatureQueryManager mFeatureQueryManager;
441 
ImsResolver(Context context, String defaultImsPackageName, int numSlots, boolean isDynamicBinding)442     public ImsResolver(Context context, String defaultImsPackageName, int numSlots,
443             boolean isDynamicBinding) {
444         mContext = context;
445         mDeviceService = defaultImsPackageName;
446         mNumSlots = numSlots;
447         mIsDynamicBinding = isDynamicBinding;
448         mStaticComponent = new ComponentName(mContext, ImsResolver.class);
449         if (!mIsDynamicBinding) {
450             Log.i(TAG, "ImsResolver initialized with static binding.");
451             mDeviceService = mStaticComponent.getPackageName();
452         }
453         mCarrierConfigManager = (CarrierConfigManager) mContext.getSystemService(
454                 Context.CARRIER_CONFIG_SERVICE);
455         mCarrierServices = new String[numSlots];
456         mBoundImsServicesByFeature = Stream.generate(SparseArray<ImsServiceController>::new)
457                 .limit(mNumSlots).collect(Collectors.toList());
458 
459         // Only register for Package/CarrierConfig updates if dynamic binding.
460         if(mIsDynamicBinding) {
461             IntentFilter appChangedFilter = new IntentFilter();
462             appChangedFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
463             appChangedFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
464             appChangedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
465             appChangedFilter.addDataScheme("package");
466             context.registerReceiverAsUser(mAppChangedReceiver, UserHandle.ALL, appChangedFilter,
467                     null,
468                     null);
469 
470             context.registerReceiver(mConfigChangedReceiver, new IntentFilter(
471                     CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
472         }
473     }
474 
475     @VisibleForTesting
setSubscriptionManagerProxy(SubscriptionManagerProxy proxy)476     public void setSubscriptionManagerProxy(SubscriptionManagerProxy proxy) {
477         mSubscriptionManagerProxy = proxy;
478     }
479 
480     @VisibleForTesting
setImsServiceControllerFactory(ImsServiceControllerFactory factory)481     public void setImsServiceControllerFactory(ImsServiceControllerFactory factory) {
482         mImsServiceControllerFactory = factory;
483     }
484 
485     @VisibleForTesting
getHandler()486     public Handler getHandler() {
487         return mHandler;
488     }
489 
490     @VisibleForTesting
setImsDynamicQueryManagerFactory(ImsDynamicQueryManagerFactory m)491     public void setImsDynamicQueryManagerFactory(ImsDynamicQueryManagerFactory m) {
492         mDynamicQueryManagerFactory = m;
493     }
494 
495     /**
496      * Needs to be called after the constructor to first populate the cache and possibly bind to
497      * ImsServices.
498      */
initPopulateCacheAndStartBind()499     public void initPopulateCacheAndStartBind() {
500         Log.i(TAG, "Initializing cache and binding.");
501         mFeatureQueryManager = mDynamicQueryManagerFactory.create(mContext, mDynamicQueryListener);
502         // Populates the CarrierConfig override package names for each slot
503         mHandler.obtainMessage(HANDLER_CONFIG_CHANGED,
504                 SubscriptionManager.INVALID_SIM_SLOT_INDEX).sendToTarget();
505         // Starts first bind to the system.
506         mHandler.obtainMessage(HANDLER_ADD_PACKAGE, null).sendToTarget();
507     }
508 
509     /**
510      * Notify ImsService to enable IMS for the framework. This will trigger IMS registration and
511      * trigger ImsFeature status updates.
512      */
enableIms(int slotId)513     public void enableIms(int slotId) {
514         SparseArray<ImsServiceController> controllers = getImsServiceControllers(slotId);
515         if (controllers != null) {
516             for (int i = 0; i < controllers.size(); i++) {
517                 int key = controllers.keyAt(i);
518                 controllers.get(key).enableIms(slotId);
519             }
520         }
521     }
522 
523     /**
524      * Notify ImsService to disable IMS for the framework. This will trigger IMS de-registration and
525      * trigger ImsFeature capability status to become false.
526      */
disableIms(int slotId)527     public void disableIms(int slotId) {
528         SparseArray<ImsServiceController> controllers = getImsServiceControllers(slotId);
529         if (controllers != null) {
530             for (int i = 0; i < controllers.size(); i++) {
531                 int key = controllers.keyAt(i);
532                 controllers.get(key).disableIms(slotId);
533             }
534         }
535     }
536 
537     /**
538      * Returns the {@link IImsMmTelFeature} that corresponds to the given slot Id or {@link null} if
539      * the service is not available. If an IImsMMTelFeature is available, the
540      * {@link IImsServiceFeatureCallback} callback is registered as a listener for feature updates.
541      * @param slotId The SIM slot that we are requesting the {@link IImsMmTelFeature} for.
542      * @param callback Listener that will send updates to ImsManager when there are updates to
543      * the feature.
544      * @return {@link IImsMmTelFeature} interface or {@link null} if it is unavailable.
545      */
getMmTelFeatureAndListen(int slotId, IImsServiceFeatureCallback callback)546     public IImsMmTelFeature getMmTelFeatureAndListen(int slotId,
547             IImsServiceFeatureCallback callback) {
548         ImsServiceController controller = getImsServiceControllerAndListen(slotId,
549                 ImsFeature.FEATURE_MMTEL, callback);
550         return (controller != null) ? controller.getMmTelFeature(slotId) : null;
551     }
552 
553     /**
554      * Returns the {@link IImsRcsFeature} that corresponds to the given slot Id for emergency
555      * calling or {@link null} if the service is not available. If an IImsMMTelFeature is
556      * available, the {@link IImsServiceFeatureCallback} callback is registered as a listener for
557      * feature updates.
558      * @param slotId The SIM slot that we are requesting the {@link IImsRcsFeature} for.
559      * @param callback listener that will send updates to ImsManager when there are updates to
560      * the feature.
561      * @return {@link IImsRcsFeature} interface or {@link null} if it is unavailable.
562      */
getRcsFeatureAndListen(int slotId, IImsServiceFeatureCallback callback)563     public IImsRcsFeature getRcsFeatureAndListen(int slotId, IImsServiceFeatureCallback callback) {
564         ImsServiceController controller = getImsServiceControllerAndListen(slotId,
565                 ImsFeature.FEATURE_RCS, callback);
566         return (controller != null) ? controller.getRcsFeature(slotId) : null;
567     }
568 
569     /**
570      * Returns the ImsRegistration structure associated with the slotId and feature specified.
571      */
getImsRegistration(int slotId, int feature)572     public @Nullable IImsRegistration getImsRegistration(int slotId, int feature)
573             throws RemoteException {
574         ImsServiceController controller = getImsServiceController(slotId, feature);
575         if (controller != null) {
576             return controller.getRegistration(slotId);
577         }
578         return null;
579     }
580 
581     /**
582      * Returns the ImsConfig structure associated with the slotId and feature specified.
583      */
getImsConfig(int slotId, int feature)584     public @Nullable IImsConfig getImsConfig(int slotId, int feature)
585             throws RemoteException {
586         ImsServiceController controller = getImsServiceController(slotId, feature);
587         if (controller != null) {
588             return controller.getConfig(slotId);
589         }
590         return null;
591     }
592 
593     @VisibleForTesting
getImsServiceController(int slotId, int feature)594     public ImsServiceController getImsServiceController(int slotId, int feature) {
595         if (slotId < 0 || slotId >= mNumSlots) {
596             return null;
597         }
598         ImsServiceController controller;
599         synchronized (mBoundServicesLock) {
600             SparseArray<ImsServiceController> services = mBoundImsServicesByFeature.get(slotId);
601             if (services == null) {
602                 return null;
603             }
604             controller = services.get(feature);
605         }
606         return controller;
607     }
608 
getImsServiceControllers(int slotId)609     private  SparseArray<ImsServiceController> getImsServiceControllers(int slotId) {
610         if (slotId < 0 || slotId >= mNumSlots) {
611             return null;
612         }
613         synchronized (mBoundServicesLock) {
614             SparseArray<ImsServiceController> services = mBoundImsServicesByFeature.get(slotId);
615             if (services == null) {
616                 return null;
617             }
618             return services;
619         }
620     }
621 
622     @VisibleForTesting
getImsServiceControllerAndListen(int slotId, int feature, IImsServiceFeatureCallback callback)623     public ImsServiceController getImsServiceControllerAndListen(int slotId, int feature,
624             IImsServiceFeatureCallback callback) {
625         ImsServiceController controller = getImsServiceController(slotId, feature);
626 
627         if (controller != null) {
628             controller.addImsServiceFeatureCallback(callback);
629             return controller;
630         }
631         return null;
632     }
633 
634     // Used for testing only.
overrideImsServiceConfiguration(int slotId, boolean isCarrierService, String packageName)635     public boolean overrideImsServiceConfiguration(int slotId, boolean isCarrierService,
636             String packageName) {
637         if (slotId < 0 || slotId >= mNumSlots) {
638             Log.w(TAG, "overrideImsServiceConfiguration: invalid slotId!");
639             return false;
640         }
641 
642         if (packageName == null) {
643             Log.w(TAG, "overrideImsServiceConfiguration: null packageName!");
644             return false;
645         }
646 
647         // encode boolean to int for Message.
648         int carrierService = isCarrierService ? 1 : 0;
649         Message.obtain(mHandler, HANDLER_OVERRIDE_IMS_SERVICE_CONFIG, slotId, carrierService,
650                 packageName).sendToTarget();
651         return true;
652     }
653 
654     // used for testing only.
getImsServiceConfiguration(int slotId, boolean isCarrierService)655     public String getImsServiceConfiguration(int slotId, boolean isCarrierService) {
656         if (slotId < 0 || slotId >= mNumSlots) {
657             Log.w(TAG, "getImsServiceConfiguration: invalid slotId!");
658             return "";
659         }
660 
661         return isCarrierService ? mCarrierServices[slotId] : mDeviceService;
662     }
663 
putImsController(int slotId, int feature, ImsServiceController controller)664     private void putImsController(int slotId, int feature, ImsServiceController controller) {
665         if (slotId < 0 || slotId >= mNumSlots || feature <= ImsFeature.FEATURE_INVALID
666                 || feature >= ImsFeature.FEATURE_MAX) {
667             Log.w(TAG, "putImsController received invalid parameters - slot: " + slotId
668                     + ", feature: " + feature);
669             return;
670         }
671         synchronized (mBoundServicesLock) {
672             SparseArray<ImsServiceController> services = mBoundImsServicesByFeature.get(slotId);
673             if (services == null) {
674                 services = new SparseArray<>();
675                 mBoundImsServicesByFeature.add(slotId, services);
676             }
677             Log.i(TAG, "ImsServiceController added on slot: " + slotId + " with feature: "
678                     + feature + " using package: " + controller.getComponentName());
679             services.put(feature, controller);
680         }
681     }
682 
removeImsController(int slotId, int feature)683     private ImsServiceController removeImsController(int slotId, int feature) {
684         if (slotId < 0 || slotId >= mNumSlots || feature <= ImsFeature.FEATURE_INVALID
685                 || feature >= ImsFeature.FEATURE_MAX) {
686             Log.w(TAG, "removeImsController received invalid parameters - slot: " + slotId
687                     + ", feature: " + feature);
688             return null;
689         }
690         synchronized (mBoundServicesLock) {
691             SparseArray<ImsServiceController> services = mBoundImsServicesByFeature.get(slotId);
692             if (services == null) {
693                 return null;
694             }
695             ImsServiceController c = services.get(feature, null);
696             if (c != null) {
697                 Log.i(TAG, "ImsServiceController removed on slot: " + slotId + " with feature: "
698                         + feature + " using package: " + c.getComponentName());
699                 services.remove(feature);
700             }
701             return c;
702         }
703     }
704 
705     // Update the current cache with the new ImsService(s) if it has been added or update the
706     // supported IMS features if they have changed.
707     // Called from the handler ONLY
maybeAddedImsService(String packageName)708     private void maybeAddedImsService(String packageName) {
709         Log.d(TAG, "maybeAddedImsService, packageName: " + packageName);
710         List<ImsServiceInfo> infos = getImsServiceInfo(packageName);
711         List<ImsServiceInfo> newlyAddedInfos = new ArrayList<>();
712         for (ImsServiceInfo info : infos) {
713             // Checking to see if the ComponentName is the same, so we can update the supported
714             // features. Will only be one (if it exists), since it is a set.
715             ImsServiceInfo match = getInfoByComponentName(mInstalledServicesCache, info.name);
716             if (match != null) {
717                 // for dynamic query the new "info" will have no supported features yet. Don't wipe
718                 // out the cache for the existing features or update yet. Instead start a query
719                 // for features dynamically.
720                 if (info.featureFromMetadata) {
721                     // update features in the cache
722                     Log.i(TAG, "Updating features in cached ImsService: " + info.name);
723                     Log.d(TAG, "Updating features - Old features: " + match + " new features: "
724                             + info);
725                     match.replaceFeatures(info.getSupportedFeatures());
726                     updateImsServiceFeatures(info);
727                 } else {
728                     // start a query to get ImsService features
729                     scheduleQueryForFeatures(info);
730                 }
731             } else {
732                 Log.i(TAG, "Adding newly added ImsService to cache: " + info.name);
733                 mInstalledServicesCache.put(info.name, info);
734                 if (info.featureFromMetadata) {
735                     newlyAddedInfos.add(info);
736                 } else {
737                     // newly added ImsServiceInfo that has not had features queried yet. Start async
738                     // bind and query features.
739                     scheduleQueryForFeatures(info);
740                 }
741             }
742         }
743         // Loop through the newly created ServiceInfos in a separate loop to make sure the cache
744         // is fully updated.
745         for (ImsServiceInfo info : newlyAddedInfos) {
746             if (isActiveCarrierService(info)) {
747                 // New ImsService is registered to active carrier services and must be newly
748                 // bound.
749                 bindImsService(info);
750                 // Update existing device service features
751                 updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
752             } else if (isDeviceService(info)) {
753                 // New ImsService is registered as device default and must be newly bound.
754                 bindImsService(info);
755             }
756         }
757     }
758 
759     // Remove the ImsService from the cache. At this point, the ImsService will have already been
760     // killed.
761     // Called from the handler ONLY
maybeRemovedImsService(String packageName)762     private boolean maybeRemovedImsService(String packageName) {
763         ImsServiceInfo match = getInfoByPackageName(mInstalledServicesCache, packageName);
764         if (match != null) {
765             mInstalledServicesCache.remove(match.name);
766             Log.i(TAG, "Removing ImsService: " + match.name);
767             unbindImsService(match);
768             updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
769             return true;
770         }
771         return false;
772     }
773 
774     // Returns true if the CarrierConfig that has been loaded includes this ImsServiceInfo
775     // package name.
776     // Called from Handler ONLY
isActiveCarrierService(ImsServiceInfo info)777     private boolean isActiveCarrierService(ImsServiceInfo info) {
778         for (int i = 0; i < mNumSlots; i++) {
779             if (TextUtils.equals(mCarrierServices[i], info.name.getPackageName())) {
780                 return true;
781             }
782         }
783         return false;
784     }
785 
isDeviceService(ImsServiceInfo info)786     private boolean isDeviceService(ImsServiceInfo info) {
787         return TextUtils.equals(mDeviceService, info.name.getPackageName());
788     }
789 
getSlotForActiveCarrierService(ImsServiceInfo info)790     private int getSlotForActiveCarrierService(ImsServiceInfo info) {
791         for (int i = 0; i < mNumSlots; i++) {
792             if (TextUtils.equals(mCarrierServices[i], info.name.getPackageName())) {
793                 return i;
794             }
795         }
796         return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
797     }
798 
getControllerByServiceInfo( Map<ComponentName, ImsServiceController> searchMap, ImsServiceInfo matchValue)799     private ImsServiceController getControllerByServiceInfo(
800             Map<ComponentName, ImsServiceController> searchMap, ImsServiceInfo matchValue) {
801         return searchMap.values().stream()
802                 .filter(c -> Objects.equals(c.getComponentName(), matchValue.name))
803                 .findFirst().orElse(null);
804     }
805 
getInfoByPackageName(Map<ComponentName, ImsServiceInfo> searchMap, String matchValue)806     private ImsServiceInfo getInfoByPackageName(Map<ComponentName, ImsServiceInfo> searchMap,
807             String matchValue) {
808         return searchMap.values().stream()
809                 .filter((i) -> Objects.equals(i.name.getPackageName(), matchValue))
810                 .findFirst().orElse(null);
811     }
812 
getInfoByComponentName( Map<ComponentName, ImsServiceInfo> searchMap, ComponentName matchValue)813     private ImsServiceInfo getInfoByComponentName(
814             Map<ComponentName, ImsServiceInfo> searchMap, ComponentName matchValue) {
815         return searchMap.get(matchValue);
816     }
817 
818     // Creates new features in active ImsServices and removes obsolete cached features. If
819     // cachedInfo == null, then newInfo is assumed to be a new ImsService and will have all features
820     // created.
updateImsServiceFeatures(ImsServiceInfo newInfo)821     private void updateImsServiceFeatures(ImsServiceInfo newInfo) {
822         if (newInfo == null) {
823             return;
824         }
825         ImsServiceController controller = getControllerByServiceInfo(mActiveControllers, newInfo);
826         // Will return zero if these features are overridden or it should not currently have any
827         // features because it is not carrier/device.
828         HashSet<ImsFeatureConfiguration.FeatureSlotPair> features =
829                 calculateFeaturesToCreate(newInfo);
830         if (shouldFeaturesCauseBind(features)) {
831             try {
832                 if (controller != null) {
833                     Log.i(TAG, "Updating features for ImsService: "
834                             + controller.getComponentName());
835                     Log.d(TAG, "Updating Features - New Features: " + features);
836                     controller.changeImsServiceFeatures(features);
837                 } else {
838                     Log.i(TAG, "updateImsServiceFeatures: unbound with active features, rebinding");
839                     bindImsServiceWithFeatures(newInfo, features);
840                 }
841                 // If the carrier service features have changed, the device features will also
842                 // need to be recalculated.
843                 if (isActiveCarrierService(newInfo)
844                         // Prevent infinite recursion from bad behavior
845                         && !TextUtils.equals(newInfo.name.getPackageName(), mDeviceService)) {
846                     Log.i(TAG, "Updating device default");
847                     updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
848                 }
849             } catch (RemoteException e) {
850                 Log.e(TAG, "updateImsServiceFeatures: Remote Exception: " + e.getMessage());
851             }
852         // Don't stay bound if the ImsService is providing no features.
853         } else if (controller != null) {
854             Log.i(TAG, "Unbinding: features = 0 for ImsService: " + controller.getComponentName());
855             unbindImsService(newInfo);
856         }
857     }
858 
859     // Bind to an ImsService and wait for the service to be connected to create ImsFeatures.
bindImsService(ImsServiceInfo info)860     private void bindImsService(ImsServiceInfo info) {
861         if (info == null) {
862             return;
863         }
864         HashSet<ImsFeatureConfiguration.FeatureSlotPair> features = calculateFeaturesToCreate(info);
865         bindImsServiceWithFeatures(info, features);
866     }
867 
bindImsServiceWithFeatures(ImsServiceInfo info, HashSet<ImsFeatureConfiguration.FeatureSlotPair> features)868     private void bindImsServiceWithFeatures(ImsServiceInfo info,
869             HashSet<ImsFeatureConfiguration.FeatureSlotPair> features) {
870         // Only bind if there are features that will be created by the service.
871         if (shouldFeaturesCauseBind(features)) {
872             // Check to see if an active controller already exists
873             ImsServiceController controller = getControllerByServiceInfo(mActiveControllers, info);
874             if (controller != null) {
875                 Log.i(TAG, "ImsService connection exists, updating features " + features);
876                 try {
877                     controller.changeImsServiceFeatures(features);
878                     // Features have been set, there was an error adding/removing. When the
879                     // controller recovers, it will add/remove again.
880                 } catch (RemoteException e) {
881                     Log.w(TAG, "bindImsService: error=" + e.getMessage());
882                 }
883             } else {
884                 controller = info.controllerFactory.create(mContext, info.name, this);
885                 Log.i(TAG, "Binding ImsService: " + controller.getComponentName()
886                         + " with features: " + features);
887                 controller.bind(features);
888             }
889             mActiveControllers.put(info.name, controller);
890         }
891     }
892 
893     // Clean up and unbind from an ImsService
unbindImsService(ImsServiceInfo info)894     private void unbindImsService(ImsServiceInfo info) {
895         if (info == null) {
896             return;
897         }
898         ImsServiceController controller = getControllerByServiceInfo(mActiveControllers, info);
899         if (controller != null) {
900             // Calls imsServiceFeatureRemoved on all features in the controller
901             try {
902                 Log.i(TAG, "Unbinding ImsService: " + controller.getComponentName());
903                 controller.unbind();
904             } catch (RemoteException e) {
905                 Log.e(TAG, "unbindImsService: Remote Exception: " + e.getMessage());
906             }
907             mActiveControllers.remove(info.name);
908         }
909     }
910 
911     // Calculate which features an ImsServiceController will need. If it is the carrier specific
912     // ImsServiceController, it will be granted all of the features it requests on the associated
913     // slot. If it is the device ImsService, it will get all of the features not covered by the
914     // carrier implementation.
calculateFeaturesToCreate( ImsServiceInfo info)915     private HashSet<ImsFeatureConfiguration.FeatureSlotPair> calculateFeaturesToCreate(
916             ImsServiceInfo info) {
917         HashSet<ImsFeatureConfiguration.FeatureSlotPair> imsFeaturesBySlot = new HashSet<>();
918         // Check if the info is a carrier service
919         int slotId = getSlotForActiveCarrierService(info);
920         if (slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
921             imsFeaturesBySlot.addAll(info.getSupportedFeatures().stream()
922                     // Match slotId with feature slotId.
923                     .filter(feature -> slotId == feature.slotId)
924                     .collect(Collectors.toList()));
925         } else if (isDeviceService(info)) {
926             // For all slots that are not currently using a carrier ImsService, enable all features
927             // for the device default.
928             for (int i = 0; i < mNumSlots; i++) {
929                 final int currSlotId = i;
930                 ImsServiceInfo carrierImsInfo = getImsServiceInfoFromCache(mCarrierServices[i]);
931                 if (carrierImsInfo == null) {
932                     // No Carrier override, add all features for this slot
933                     imsFeaturesBySlot.addAll(info.getSupportedFeatures().stream()
934                             .filter(feature -> currSlotId == feature.slotId)
935                             .collect(Collectors.toList()));
936                 } else {
937                     // Add all features to the device service that are not currently covered by
938                     // the carrier ImsService.
939                     HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatures =
940                             new HashSet<>(info.getSupportedFeatures());
941                     deviceFeatures.removeAll(carrierImsInfo.getSupportedFeatures());
942                     // only add features for current slot
943                     imsFeaturesBySlot.addAll(deviceFeatures.stream()
944                             .filter(feature -> currSlotId == feature.slotId).collect(
945                             Collectors.toList()));
946                 }
947             }
948         }
949         return imsFeaturesBySlot;
950     }
951 
952     /**
953      * Implementation of
954      * {@link ImsServiceController.ImsServiceControllerCallbacks#imsServiceFeatureCreated}, which
955      * removes the ImsServiceController from the mBoundImsServicesByFeature structure.
956      */
imsServiceFeatureCreated(int slotId, int feature, ImsServiceController controller)957     public void imsServiceFeatureCreated(int slotId, int feature, ImsServiceController controller) {
958         putImsController(slotId, feature, controller);
959     }
960 
961     /**
962      * Implementation of
963      * {@link ImsServiceController.ImsServiceControllerCallbacks#imsServiceFeatureRemoved}, which
964      * removes the ImsServiceController from the mBoundImsServicesByFeature structure.
965      */
imsServiceFeatureRemoved(int slotId, int feature, ImsServiceController controller)966     public void imsServiceFeatureRemoved(int slotId, int feature, ImsServiceController controller) {
967         removeImsController(slotId, feature);
968     }
969 
970     /**
971      * Implementation of
972      * {@link ImsServiceController.ImsServiceControllerCallbacks#imsServiceFeaturesChanged, which
973      * notify the ImsResolver of a change to the supported ImsFeatures of a connected ImsService.
974      */
imsServiceFeaturesChanged(ImsFeatureConfiguration config, ImsServiceController controller)975     public void imsServiceFeaturesChanged(ImsFeatureConfiguration config,
976             ImsServiceController controller) {
977         if (controller == null || config == null) {
978             return;
979         }
980         Log.i(TAG, "imsServiceFeaturesChanged: config=" + config.getServiceFeatures()
981                 + ", ComponentName=" + controller.getComponentName());
982         handleFeaturesChanged(controller.getComponentName(), config.getServiceFeatures());
983     }
984 
985     /**
986      * Determines if the features specified should cause a bind or keep a binding active to an
987      * ImsService.
988      * @return true if MMTEL or RCS features are present, false if they are not or only
989      * EMERGENCY_MMTEL is specified.
990      */
shouldFeaturesCauseBind( HashSet<ImsFeatureConfiguration.FeatureSlotPair> features)991     private boolean shouldFeaturesCauseBind(
992             HashSet<ImsFeatureConfiguration.FeatureSlotPair> features) {
993         long bindableFeatures = features.stream()
994                 // remove all emergency features
995                 .filter(f -> f.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL).count();
996         return bindableFeatures > 0;
997     }
998 
999     // Possibly rebind to another ImsService if currently installed ImsServices were changed or if
1000     // the SIM card has changed.
1001     // Called from the handler ONLY
maybeRebindService(int slotId, String newPackageName)1002     private void maybeRebindService(int slotId, String newPackageName) {
1003         if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
1004             // not specified, replace package on all slots.
1005             for (int i = 0; i < mNumSlots; i++) {
1006                 updateBoundCarrierServices(i, newPackageName);
1007             }
1008         } else {
1009             updateBoundCarrierServices(slotId, newPackageName);
1010         }
1011 
1012     }
1013 
carrierConfigChanged(int slotId)1014     private void carrierConfigChanged(int slotId) {
1015         int subId = mSubscriptionManagerProxy.getSubId(slotId);
1016         PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId);
1017         if (config != null) {
1018             String newPackageName = config.getString(
1019                     CarrierConfigManager.KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, null);
1020             maybeRebindService(slotId, newPackageName);
1021         } else {
1022             Log.w(TAG, "carrierConfigChanged: CarrierConfig is null!");
1023         }
1024     }
1025 
updateBoundCarrierServices(int slotId, String newPackageName)1026     private void updateBoundCarrierServices(int slotId, String newPackageName) {
1027         if (slotId > SubscriptionManager.INVALID_SIM_SLOT_INDEX && slotId < mNumSlots) {
1028             String oldPackageName = mCarrierServices[slotId];
1029             mCarrierServices[slotId] = newPackageName;
1030             if (!TextUtils.equals(newPackageName, oldPackageName)) {
1031                 Log.i(TAG, "Carrier Config updated, binding new ImsService");
1032                 // Unbind old ImsService, not needed anymore
1033                 // ImsService is retrieved from the cache. If the cache hasn't been populated yet,
1034                 // the calls to unbind/bind will fail (intended during initial start up).
1035                 unbindImsService(getImsServiceInfoFromCache(oldPackageName));
1036                 ImsServiceInfo newInfo = getImsServiceInfoFromCache(newPackageName);
1037                 // if there is no carrier ImsService, newInfo is null. This we still want to update
1038                 // bindings for device ImsService to pick up the missing features.
1039                 if (newInfo == null || newInfo.featureFromMetadata) {
1040                     bindImsService(newInfo);
1041                     // Recalculate the device ImsService features to reflect changes.
1042                     updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
1043                 } else {
1044                     // ImsServiceInfo that has not had features queried yet. Start async
1045                     // bind and query features.
1046                     scheduleQueryForFeatures(newInfo);
1047                 }
1048             }
1049         }
1050     }
1051 
1052     /**
1053      * Schedules a query for dynamic ImsService features.
1054      */
scheduleQueryForFeatures(ImsServiceInfo service, int delayMs)1055     private void scheduleQueryForFeatures(ImsServiceInfo service, int delayMs) {
1056         // if not current device/carrier service, don't perform query. If this changes, this method
1057         // will be called again.
1058         if (!isDeviceService(service) && getSlotForActiveCarrierService(service)
1059                 == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
1060             Log.i(TAG, "scheduleQueryForFeatures: skipping query for ImsService that is not"
1061                     + " set as carrier/device ImsService.");
1062             return;
1063         }
1064         Message msg = Message.obtain(mHandler, HANDLER_START_DYNAMIC_FEATURE_QUERY, service);
1065         if (mHandler.hasMessages(HANDLER_START_DYNAMIC_FEATURE_QUERY, service)) {
1066             Log.d(TAG, "scheduleQueryForFeatures: dynamic query for " + service.name
1067                     + " already scheduled");
1068             return;
1069         }
1070         Log.d(TAG, "scheduleQueryForFeatures: starting dynamic query for " + service.name
1071                 + " in " + delayMs + "ms.");
1072         mHandler.sendMessageDelayed(msg, delayMs);
1073     }
1074 
scheduleQueryForFeatures(ComponentName name, int delayMs)1075     private void scheduleQueryForFeatures(ComponentName name, int delayMs) {
1076         ImsServiceInfo service = getImsServiceInfoFromCache(name.getPackageName());
1077         if (service == null) {
1078             Log.w(TAG, "scheduleQueryForFeatures: Couldn't find cached info for name: " + name);
1079             return;
1080         }
1081         scheduleQueryForFeatures(service, delayMs);
1082     }
1083 
scheduleQueryForFeatures(ImsServiceInfo service)1084     private void scheduleQueryForFeatures(ImsServiceInfo service) {
1085         scheduleQueryForFeatures(service, 0);
1086     }
1087 
1088     /**
1089      * Schedules the processing of a completed query.
1090      */
handleFeaturesChanged(ComponentName name, Set<ImsFeatureConfiguration.FeatureSlotPair> features)1091     private void handleFeaturesChanged(ComponentName name,
1092             Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
1093         SomeArgs args = SomeArgs.obtain();
1094         args.arg1 = name;
1095         args.arg2 = features;
1096         mHandler.obtainMessage(HANDLER_DYNAMIC_FEATURE_CHANGE, args).sendToTarget();
1097     }
1098 
1099     // Starts a dynamic query. Called from handler ONLY.
startDynamicQuery(ImsServiceInfo service)1100     private void startDynamicQuery(ImsServiceInfo service) {
1101         boolean queryStarted = mFeatureQueryManager.startQuery(service.name,
1102                 service.controllerFactory.getServiceInterface());
1103         if (!queryStarted) {
1104             Log.w(TAG, "startDynamicQuery: service could not connect. Retrying after delay.");
1105             scheduleQueryForFeatures(service, DELAY_DYNAMIC_QUERY_MS);
1106         } else {
1107             Log.d(TAG, "startDynamicQuery: Service queried, waiting for response.");
1108         }
1109     }
1110 
1111     // process complete dynamic query. Called from handler ONLY.
dynamicQueryComplete(ComponentName name, Set<ImsFeatureConfiguration.FeatureSlotPair> features)1112     private void dynamicQueryComplete(ComponentName name,
1113             Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
1114         ImsServiceInfo service = getImsServiceInfoFromCache(name.getPackageName());
1115         if (service == null) {
1116             Log.w(TAG, "handleFeaturesChanged: Couldn't find cached info for name: "
1117                     + name);
1118             return;
1119         }
1120         // Add features to service
1121         service.replaceFeatures(features);
1122         if (isActiveCarrierService(service)) {
1123             // New ImsService is registered to active carrier services and must be newly
1124             // bound.
1125             bindImsService(service);
1126             // Update existing device service features
1127             updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
1128         } else if (isDeviceService(service)) {
1129             // New ImsService is registered as device default and must be newly bound.
1130             bindImsService(service);
1131         }
1132     }
1133 
1134     /**
1135      * @return true if the ImsResolver is in the process of resolving a dynamic query and should not
1136      * be considered available, false if the ImsResolver is idle.
1137      */
isResolvingBinding()1138     public boolean isResolvingBinding() {
1139         return mHandler.hasMessages(HANDLER_START_DYNAMIC_FEATURE_QUERY)
1140                 // We haven't processed this message yet, so it is still resolving.
1141                 || mHandler.hasMessages(HANDLER_DYNAMIC_FEATURE_CHANGE)
1142                 || mFeatureQueryManager.isQueryInProgress();
1143     }
1144 
printFeatures(Set<ImsFeatureConfiguration.FeatureSlotPair> features)1145     private String printFeatures(Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
1146         StringBuilder featureString = new StringBuilder();
1147         featureString.append("features: [");
1148         if (features != null) {
1149             for (ImsFeatureConfiguration.FeatureSlotPair feature : features) {
1150                 featureString.append("{");
1151                 featureString.append(feature.slotId);
1152                 featureString.append(",");
1153                 featureString.append(feature.featureType);
1154                 featureString.append("} ");
1155             }
1156             featureString.append("]");
1157         }
1158         return featureString.toString();
1159     }
1160 
1161     /**
1162      * Returns the ImsServiceInfo that matches the provided packageName. Visible for testing
1163      * the ImsService caching functionality.
1164      */
1165     @VisibleForTesting
getImsServiceInfoFromCache(String packageName)1166     public ImsServiceInfo getImsServiceInfoFromCache(String packageName) {
1167         if (TextUtils.isEmpty(packageName)) {
1168             return null;
1169         }
1170         ImsServiceInfo infoFilter = getInfoByPackageName(mInstalledServicesCache, packageName);
1171         if (infoFilter != null) {
1172             return infoFilter;
1173         } else {
1174             return null;
1175         }
1176     }
1177 
1178     // Return the ImsServiceInfo specified for the package name. If the package name is null,
1179     // get all packages that support ImsServices.
getImsServiceInfo(String packageName)1180     private List<ImsServiceInfo> getImsServiceInfo(String packageName) {
1181         List<ImsServiceInfo> infos = new ArrayList<>();
1182         if (!mIsDynamicBinding) {
1183             // always return the same ImsService info.
1184             infos.addAll(getStaticImsService());
1185         } else {
1186             // Search for Current ImsService implementations
1187             infos.addAll(searchForImsServices(packageName, mImsServiceControllerFactory));
1188             // Search for compat ImsService Implementations
1189             infos.addAll(searchForImsServices(packageName, mImsServiceControllerFactoryCompat));
1190         }
1191         return infos;
1192     }
1193 
getStaticImsService()1194     private List<ImsServiceInfo> getStaticImsService() {
1195         List<ImsServiceInfo> infos = new ArrayList<>();
1196 
1197         ImsServiceInfo info = new ImsServiceInfo(mNumSlots);
1198         info.name = mStaticComponent;
1199         info.controllerFactory = mImsServiceControllerFactoryStaticBindingCompat;
1200         info.addFeatureForAllSlots(ImsFeature.FEATURE_EMERGENCY_MMTEL);
1201         info.addFeatureForAllSlots(ImsFeature.FEATURE_MMTEL);
1202         infos.add(info);
1203         return infos;
1204     }
1205 
searchForImsServices(String packageName, ImsServiceControllerFactory controllerFactory)1206     private List<ImsServiceInfo> searchForImsServices(String packageName,
1207             ImsServiceControllerFactory controllerFactory) {
1208         List<ImsServiceInfo> infos = new ArrayList<>();
1209 
1210         Intent serviceIntent = new Intent(controllerFactory.getServiceInterface());
1211         serviceIntent.setPackage(packageName);
1212 
1213         PackageManager packageManager = mContext.getPackageManager();
1214         for (ResolveInfo entry : packageManager.queryIntentServicesAsUser(
1215                 serviceIntent,
1216                 PackageManager.GET_META_DATA,
1217                 mContext.getUserId())) {
1218             ServiceInfo serviceInfo = entry.serviceInfo;
1219 
1220             if (serviceInfo != null) {
1221                 ImsServiceInfo info = new ImsServiceInfo(mNumSlots);
1222                 info.name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
1223                 info.controllerFactory = controllerFactory;
1224 
1225                 // we will allow the manifest method of declaring manifest features in two cases:
1226                 // 1) it is the device overlay "default" ImsService, where the features do not
1227                 // change (the new method can still be used if the default does not define manifest
1228                 // entries).
1229                 // 2) using the "compat" ImsService, which only supports manifest query.
1230                 if (isDeviceService(info)
1231                         || mImsServiceControllerFactoryCompat == controllerFactory) {
1232                     if (serviceInfo.metaData != null) {
1233                         if (serviceInfo.metaData.getBoolean(METADATA_EMERGENCY_MMTEL_FEATURE,
1234                                 false)) {
1235                             info.addFeatureForAllSlots(ImsFeature.FEATURE_EMERGENCY_MMTEL);
1236                         }
1237                         if (serviceInfo.metaData.getBoolean(METADATA_MMTEL_FEATURE, false)) {
1238                             info.addFeatureForAllSlots(ImsFeature.FEATURE_MMTEL);
1239                         }
1240                         if (serviceInfo.metaData.getBoolean(METADATA_RCS_FEATURE, false)) {
1241                             info.addFeatureForAllSlots(ImsFeature.FEATURE_RCS);
1242                         }
1243                     }
1244                     // Only dynamic query if we are not a compat version of ImsService and the
1245                     // default service.
1246                     if (mImsServiceControllerFactoryCompat != controllerFactory
1247                             && info.getSupportedFeatures().isEmpty()) {
1248                         // metadata empty, try dynamic query instead
1249                         info.featureFromMetadata = false;
1250                     }
1251                 } else {
1252                     // We are a carrier service and not using the compat version of ImsService.
1253                     info.featureFromMetadata = false;
1254                 }
1255                 Log.i(TAG, "service name: " + info.name + ", manifest query: "
1256                         + info.featureFromMetadata);
1257                 // Check manifest permission to be sure that the service declares the correct
1258                 // permissions. Overridden if the METADATA_OVERRIDE_PERM_CHECK metadata is set to
1259                 // true.
1260                 // NOTE: METADATA_OVERRIDE_PERM_CHECK should only be set for testing.
1261                 if (TextUtils.equals(serviceInfo.permission, Manifest.permission.BIND_IMS_SERVICE)
1262                         || serviceInfo.metaData.getBoolean(METADATA_OVERRIDE_PERM_CHECK, false)) {
1263                     infos.add(info);
1264                 } else {
1265                     Log.w(TAG, "ImsService is not protected with BIND_IMS_SERVICE permission: "
1266                             + info.name);
1267                 }
1268             }
1269         }
1270         return infos;
1271     }
1272 }
1273