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.Collections; 29 import java.util.HashMap; 30 import java.util.Map; 31 import java.util.Set; 32 33 /** 34 * Manages the querying of multiple ImsServices asynchronously in order to retrieve the ImsFeatures 35 * they support. 36 */ 37 38 public class ImsServiceFeatureQueryManager { 39 40 private final class ImsServiceFeatureQuery implements ServiceConnection { 41 42 private static final String LOG_TAG = "ImsServiceFeatureQuery"; 43 44 private final ComponentName mName; 45 private final String mIntentFilter; 46 ImsServiceFeatureQuery(ComponentName name, String intentFilter)47 ImsServiceFeatureQuery(ComponentName name, String intentFilter) { 48 mName = name; 49 mIntentFilter = intentFilter; 50 } 51 52 /** 53 * Starts the bind to the ImsService specified ComponentName. 54 * @return true if binding started, false if it failed and will not recover. 55 */ start()56 public boolean start() { 57 Log.d(LOG_TAG, "start: intent filter=" + mIntentFilter + ", name=" + mName); 58 Intent imsServiceIntent = new Intent(mIntentFilter).setComponent(mName); 59 int serviceFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE 60 | Context.BIND_IMPORTANT; 61 boolean bindStarted = mContext.bindService(imsServiceIntent, this, serviceFlags); 62 if (!bindStarted) { 63 // Docs say to unbind if this fails. 64 cleanup(); 65 } 66 return bindStarted; 67 } 68 69 @Override onServiceConnected(ComponentName name, IBinder service)70 public void onServiceConnected(ComponentName name, IBinder service) { 71 Log.i(LOG_TAG, "onServiceConnected for component: " + name); 72 if (service != null) { 73 queryImsFeatures(IImsServiceController.Stub.asInterface(service)); 74 } else { 75 Log.w(LOG_TAG, "onServiceConnected: " + name + " binder null."); 76 cleanup(); 77 mListener.onPermanentError(name); 78 } 79 } 80 81 @Override onServiceDisconnected(ComponentName name)82 public void onServiceDisconnected(ComponentName name) { 83 Log.w(LOG_TAG, "onServiceDisconnected for component: " + name); 84 } 85 86 @Override onBindingDied(ComponentName name)87 public void onBindingDied(ComponentName name) { 88 Log.w(LOG_TAG, "onBindingDied: " + name); 89 cleanup(); 90 // retry again! 91 mListener.onError(name); 92 } 93 94 @Override onNullBinding(ComponentName name)95 public void onNullBinding(ComponentName name) { 96 Log.w(LOG_TAG, "onNullBinding: " + name); 97 cleanup(); 98 mListener.onPermanentError(name); 99 } 100 queryImsFeatures(IImsServiceController controller)101 private void queryImsFeatures(IImsServiceController controller) { 102 ImsFeatureConfiguration config; 103 try { 104 config = controller.querySupportedImsFeatures(); 105 } catch (Exception e) { 106 Log.w(LOG_TAG, "queryImsFeatures - error: " + e); 107 cleanup(); 108 // Retry again! 109 mListener.onError(mName); 110 return; 111 } 112 Set<ImsFeatureConfiguration.FeatureSlotPair> servicePairs; 113 if (config == null) { 114 // ensure that if the ImsService sent a null config, we return an empty feature 115 // set to the ImsResolver. 116 servicePairs = Collections.emptySet(); 117 } else { 118 servicePairs = config.getServiceFeatures(); 119 } 120 // Complete, remove from active queries and notify. 121 cleanup(); 122 mListener.onComplete(mName, servicePairs); 123 } 124 cleanup()125 private void cleanup() { 126 mContext.unbindService(this); 127 synchronized (mLock) { 128 mActiveQueries.remove(mName); 129 } 130 } 131 } 132 133 public interface Listener { 134 /** 135 * Called when a query has completed. 136 * @param name The Package Name of the query 137 * @param features A Set of slotid->feature pairs that the ImsService supports. 138 */ onComplete(ComponentName name, Set<ImsFeatureConfiguration.FeatureSlotPair> features)139 void onComplete(ComponentName name, Set<ImsFeatureConfiguration.FeatureSlotPair> features); 140 141 /** 142 * Called when a query has failed and should be retried. 143 */ onError(ComponentName name)144 void onError(ComponentName name); 145 146 /** 147 * Called when a query has failed due to a permanent error and should not be retried. 148 */ onPermanentError(ComponentName name)149 void onPermanentError(ComponentName name); 150 } 151 152 // Maps an active ImsService query (by Package Name String) its query. 153 private final Map<ComponentName, ImsServiceFeatureQuery> mActiveQueries = new HashMap<>(); 154 private final Context mContext; 155 private final Listener mListener; 156 private final Object mLock = new Object(); 157 ImsServiceFeatureQueryManager(Context context, Listener listener)158 public ImsServiceFeatureQueryManager(Context context, Listener listener) { 159 mContext = context; 160 mListener = listener; 161 } 162 163 /** 164 * Starts an ImsService feature query for the ComponentName and Intent specified. 165 * @param name The ComponentName of the ImsService being queried. 166 * @param intentFilter The Intent filter that the ImsService specified. 167 * @return true if the query started, false if it was unable to start. 168 */ startQuery(ComponentName name, String intentFilter)169 public boolean startQuery(ComponentName name, String intentFilter) { 170 synchronized (mLock) { 171 if (mActiveQueries.containsKey(name)) { 172 // We already have an active query, wait for it to return. 173 return true; 174 } 175 ImsServiceFeatureQuery query = new ImsServiceFeatureQuery(name, intentFilter); 176 mActiveQueries.put(name, query); 177 return query.start(); 178 } 179 } 180 181 /** 182 * @return true if there are any active queries, false if the manager is idle. 183 */ isQueryInProgress()184 public boolean isQueryInProgress() { 185 synchronized (mLock) { 186 return !mActiveQueries.isEmpty(); 187 } 188 } 189 } 190