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