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