• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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