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