1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.telephony.ims; 18 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.ServiceConnection; 23 import android.os.IBinder; 24 import android.telephony.ims.aidl.IImsServiceController; 25 import android.telephony.ims.stub.ImsFeatureConfiguration; 26 import android.util.Log; 27 28 import java.util.HashMap; 29 import java.util.Map; 30 import java.util.Set; 31 32 /** 33 * Manages the querying of multiple ImsServices asynchronously in order to retrieve the ImsFeatures 34 * they support. 35 */ 36 37 public class ImsServiceFeatureQueryManager { 38 39 private final class ImsServiceFeatureQuery implements ServiceConnection { 40 41 private static final String LOG_TAG = "ImsServiceFeatureQuery"; 42 43 private final ComponentName mName; 44 private final String mIntentFilter; 45 ImsServiceFeatureQuery(ComponentName name, String intentFilter)46 ImsServiceFeatureQuery(ComponentName name, String intentFilter) { 47 mName = name; 48 mIntentFilter = intentFilter; 49 } 50 51 /** 52 * Starts the bind to the ImsService specified ComponentName. 53 * @return true if binding started, false if it failed and will not recover. 54 */ start()55 public boolean start() { 56 Log.d(LOG_TAG, "start: intent filter=" + mIntentFilter + ", name=" + mName); 57 Intent imsServiceIntent = new Intent(mIntentFilter).setComponent(mName); 58 int serviceFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE 59 | Context.BIND_IMPORTANT; 60 boolean bindStarted = mContext.bindService(imsServiceIntent, this, serviceFlags); 61 if (!bindStarted) { 62 // Docs say to unbind if this fails. 63 cleanup(); 64 } 65 return bindStarted; 66 } 67 68 @Override onServiceConnected(ComponentName name, IBinder service)69 public void onServiceConnected(ComponentName name, IBinder service) { 70 Log.i(LOG_TAG, "onServiceConnected for component: " + name); 71 if (service != null) { 72 queryImsFeatures(IImsServiceController.Stub.asInterface(service)); 73 } else { 74 Log.w(LOG_TAG, "onServiceConnected: " + name + " binder null, cleaning up."); 75 cleanup(); 76 } 77 } 78 79 @Override onServiceDisconnected(ComponentName name)80 public void onServiceDisconnected(ComponentName name) { 81 Log.w(LOG_TAG, "onServiceDisconnected for component: " + name); 82 } 83 queryImsFeatures(IImsServiceController controller)84 private void queryImsFeatures(IImsServiceController controller) { 85 ImsFeatureConfiguration config; 86 try { 87 config = controller.querySupportedImsFeatures(); 88 } catch (Exception e) { 89 Log.w(LOG_TAG, "queryImsFeatures - error: " + e); 90 cleanup(); 91 mListener.onError(mName); 92 return; 93 } 94 Set<ImsFeatureConfiguration.FeatureSlotPair> servicePairs = config.getServiceFeatures(); 95 // Complete, remove from active queries and notify. 96 cleanup(); 97 mListener.onComplete(mName, servicePairs); 98 } 99 cleanup()100 private void cleanup() { 101 mContext.unbindService(this); 102 synchronized (mLock) { 103 mActiveQueries.remove(mName); 104 } 105 } 106 } 107 108 public interface Listener { 109 /** 110 * Called when a query has completed. 111 * @param name The Package Name of the query 112 * @param features A Set of slotid->feature pairs that the ImsService supports. 113 */ onComplete(ComponentName name, Set<ImsFeatureConfiguration.FeatureSlotPair> features)114 void onComplete(ComponentName name, Set<ImsFeatureConfiguration.FeatureSlotPair> features); 115 116 /** 117 * Called when a query has failed and should be retried. 118 */ onError(ComponentName name)119 void onError(ComponentName name); 120 } 121 122 // Maps an active ImsService query (by Package Name String) its query. 123 private final Map<ComponentName, ImsServiceFeatureQuery> mActiveQueries = new HashMap<>(); 124 private final Context mContext; 125 private final Listener mListener; 126 private final Object mLock = new Object(); 127 ImsServiceFeatureQueryManager(Context context, Listener listener)128 public ImsServiceFeatureQueryManager(Context context, Listener listener) { 129 mContext = context; 130 mListener = listener; 131 } 132 133 /** 134 * Starts an ImsService feature query for the ComponentName and Intent specified. 135 * @param name The ComponentName of the ImsService being queried. 136 * @param intentFilter The Intent filter that the ImsService specified. 137 * @return true if the query started, false if it was unable to start. 138 */ startQuery(ComponentName name, String intentFilter)139 public boolean startQuery(ComponentName name, String intentFilter) { 140 synchronized (mLock) { 141 if (mActiveQueries.containsKey(name)) { 142 // We already have an active query, wait for it to return. 143 return true; 144 } 145 ImsServiceFeatureQuery query = new ImsServiceFeatureQuery(name, intentFilter); 146 mActiveQueries.put(name, query); 147 return query.start(); 148 } 149 } 150 151 /** 152 * @return true if there are any active queries, false if the manager is idle. 153 */ isQueryInProgress()154 public boolean isQueryInProgress() { 155 synchronized (mLock) { 156 return !mActiveQueries.isEmpty(); 157 } 158 } 159 } 160