1 /*
2  * Copyright (C) 2014 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.server.telecom;
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.IInterface;
25 import android.os.Process;
26 import android.os.UserHandle;
27 import android.text.TextUtils;
28 import android.util.ArraySet;
29 
30 import com.android.internal.util.Preconditions;
31 
32 import java.util.Collections;
33 import java.util.Set;
34 import java.util.concurrent.ConcurrentHashMap;
35 
36 /**
37  * Abstract class to perform the work of binding and unbinding to the specified service interface.
38  * Subclasses supply the service intent and component name and this class will invoke protected
39  * methods when the class is bound, unbound, or upon failure.
40  */
41 abstract class ServiceBinder<ServiceInterface extends IInterface> {
42 
43     /**
44      * Callback to notify after a binding succeeds or fails.
45      */
46     interface BindCallback {
onSuccess()47         void onSuccess();
onFailure()48         void onFailure();
49     }
50 
51     /**
52      * Listener for bind events on ServiceBinder.
53      */
54     interface Listener<ServiceBinderClass extends ServiceBinder<?>> {
onUnbind(ServiceBinderClass serviceBinder)55         void onUnbind(ServiceBinderClass serviceBinder);
56     }
57 
58     /**
59      * Helper class to perform on-demand binding.
60      */
61     final class Binder {
62         /**
63          * Performs an asynchronous bind to the service (only if not already bound) and executes the
64          * specified callback.
65          *
66          * @param callback The callback to notify of the binding's success or failure.
67          */
bind(BindCallback callback)68         void bind(BindCallback callback) {
69             ThreadUtil.checkOnMainThread();
70             Log.d(ServiceBinder.this, "bind()");
71 
72             // Reset any abort request if we're asked to bind again.
73             clearAbort();
74 
75             if (!mCallbacks.isEmpty()) {
76                 // Binding already in progress, append to the list of callbacks and bail out.
77                 mCallbacks.add(callback);
78                 return;
79             }
80 
81             mCallbacks.add(callback);
82             if (mServiceConnection == null) {
83                 Intent serviceIntent = new Intent(mServiceAction).setComponent(mComponentName);
84                 ServiceConnection connection = new ServiceBinderConnection();
85 
86                 Log.d(ServiceBinder.this, "Binding to service with intent: %s", serviceIntent);
87                 final boolean binding;
88                 if (mUserHandle != null) {
89                     binding = mContext.bindServiceAsUser(serviceIntent, connection,
90                         Context.BIND_AUTO_CREATE, mUserHandle);
91                 } else {
92                     binding = mContext.bindService(serviceIntent, connection,
93                         Context.BIND_AUTO_CREATE);
94                 }
95                 if (!binding) {
96                     handleFailedConnection();
97                     return;
98                 }
99             } else {
100                 Log.d(ServiceBinder.this, "Service is already bound.");
101                 Preconditions.checkNotNull(mBinder);
102                 handleSuccessfulConnection();
103             }
104         }
105     }
106 
107     private final class ServiceBinderConnection implements ServiceConnection {
108         @Override
onServiceConnected(ComponentName componentName, IBinder binder)109         public void onServiceConnected(ComponentName componentName, IBinder binder) {
110             ThreadUtil.checkOnMainThread();
111             Log.i(this, "Service bound %s", componentName);
112 
113             // Unbind request was queued so unbind immediately.
114             if (mIsBindingAborted) {
115                 clearAbort();
116                 logServiceDisconnected("onServiceConnected");
117                 mContext.unbindService(this);
118                 handleFailedConnection();
119                 return;
120             }
121 
122             mServiceConnection = this;
123             setBinder(binder);
124             handleSuccessfulConnection();
125         }
126 
127         @Override
onServiceDisconnected(ComponentName componentName)128         public void onServiceDisconnected(ComponentName componentName) {
129             logServiceDisconnected("onServiceDisconnected");
130 
131             mServiceConnection = null;
132             clearAbort();
133 
134             handleServiceDisconnected();
135         }
136     }
137 
138     /** The application context. */
139     private final Context mContext;
140 
141     /** The intent action to use when binding through {@link Context#bindService}. */
142     private final String mServiceAction;
143 
144     /** The component name of the service to bind to. */
145     private final ComponentName mComponentName;
146 
147     /** The set of callbacks waiting for notification of the binding's success or failure. */
148     private final Set<BindCallback> mCallbacks = new ArraySet<>();
149 
150     /** Used to bind and unbind from the service. */
151     private ServiceConnection mServiceConnection;
152 
153     /** {@link UserHandle} to use for binding, to support work profiles and multi-user. */
154     private UserHandle mUserHandle;
155 
156     /** The binder provided by {@link ServiceConnection#onServiceConnected} */
157     private IBinder mBinder;
158 
159     private int mAssociatedCallCount = 0;
160 
161     /**
162      * Indicates that an unbind request was made when the service was not yet bound. If the service
163      * successfully connects when this is true, it should be unbound immediately.
164      */
165     private boolean mIsBindingAborted;
166 
167     /**
168      * Set of currently registered listeners.
169      * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
170      * load factor before resizing, 1 means we only expect a single thread to
171      * access the map so make only a single shard
172      */
173     private final Set<Listener> mListeners = Collections.newSetFromMap(
174             new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
175 
176     /**
177      * Persists the specified parameters and initializes the new instance.
178      *
179      * @param serviceAction The intent-action used with {@link Context#bindService}.
180      * @param componentName The component name of the service with which to bind.
181      * @param context The context.
182      * @param userHandle The {@link UserHandle} to use for binding.
183      */
ServiceBinder(String serviceAction, ComponentName componentName, Context context, UserHandle userHandle)184     protected ServiceBinder(String serviceAction, ComponentName componentName, Context context,
185             UserHandle userHandle) {
186         Preconditions.checkState(!TextUtils.isEmpty(serviceAction));
187         Preconditions.checkNotNull(componentName);
188 
189         mContext = context;
190         mServiceAction = serviceAction;
191         mComponentName = componentName;
192         mUserHandle = userHandle;
193     }
194 
incrementAssociatedCallCount()195     final void incrementAssociatedCallCount() {
196         mAssociatedCallCount++;
197         Log.v(this, "Call count increment %d, %s", mAssociatedCallCount,
198                 mComponentName.flattenToShortString());
199     }
200 
decrementAssociatedCallCount()201     final void decrementAssociatedCallCount() {
202         if (mAssociatedCallCount > 0) {
203             mAssociatedCallCount--;
204             Log.v(this, "Call count decrement %d, %s", mAssociatedCallCount,
205                     mComponentName.flattenToShortString());
206 
207             if (mAssociatedCallCount == 0) {
208                 unbind();
209             }
210         } else {
211             Log.wtf(this, "%s: ignoring a request to decrement mAssociatedCallCount below zero",
212                     mComponentName.getClassName());
213         }
214     }
215 
getAssociatedCallCount()216     final int getAssociatedCallCount() {
217         return mAssociatedCallCount;
218     }
219 
220     /**
221      * Unbinds from the service if already bound, no-op otherwise.
222      */
unbind()223     final void unbind() {
224         ThreadUtil.checkOnMainThread();
225 
226         if (mServiceConnection == null) {
227             // We're not yet bound, so queue up an abort request.
228             mIsBindingAborted = true;
229         } else {
230             logServiceDisconnected("unbind");
231             mContext.unbindService(mServiceConnection);
232             mServiceConnection = null;
233             setBinder(null);
234         }
235     }
236 
getComponentName()237     final ComponentName getComponentName() {
238         return mComponentName;
239     }
240 
isServiceValid(String actionName)241     final boolean isServiceValid(String actionName) {
242         if (mBinder == null) {
243             Log.w(this, "%s invoked while service is unbound", actionName);
244             return false;
245         }
246 
247         return true;
248     }
249 
addListener(Listener listener)250     final void addListener(Listener listener) {
251         mListeners.add(listener);
252     }
253 
removeListener(Listener listener)254     final void removeListener(Listener listener) {
255         if (listener != null) {
256             mListeners.remove(listener);
257         }
258     }
259 
260     /**
261      * Logs a standard message upon service disconnection. This method exists because there is no
262      * single method called whenever the service unbinds and we want to log the same string in all
263      * instances where that occurs.  (Context.unbindService() does not cause onServiceDisconnected
264      * to execute).
265      *
266      * @param sourceTag Tag to disambiguate
267      */
logServiceDisconnected(String sourceTag)268     private void logServiceDisconnected(String sourceTag) {
269         Log.i(this, "Service unbound %s, from %s.", mComponentName, sourceTag);
270     }
271 
272     /**
273      * Notifies all the outstanding callbacks that the service is successfully bound. The list of
274      * outstanding callbacks is cleared afterwards.
275      */
handleSuccessfulConnection()276     private void handleSuccessfulConnection() {
277         for (BindCallback callback : mCallbacks) {
278             callback.onSuccess();
279         }
280         mCallbacks.clear();
281     }
282 
283     /**
284      * Notifies all the outstanding callbacks that the service failed to bind. The list of
285      * outstanding callbacks is cleared afterwards.
286      */
handleFailedConnection()287     private void handleFailedConnection() {
288         for (BindCallback callback : mCallbacks) {
289             callback.onFailure();
290         }
291         mCallbacks.clear();
292     }
293 
294     /**
295      * Handles a service disconnection.
296      */
handleServiceDisconnected()297     private void handleServiceDisconnected() {
298         setBinder(null);
299     }
300 
clearAbort()301     private void clearAbort() {
302         mIsBindingAborted = false;
303     }
304 
305     /**
306      * Sets the (private) binder and updates the child class.
307      *
308      * @param binder The new binder value.
309      */
setBinder(IBinder binder)310     private void setBinder(IBinder binder) {
311         if (mBinder != binder) {
312             mBinder = binder;
313 
314             setServiceInterface(binder);
315 
316             if (binder == null) {
317                 for (Listener l : mListeners) {
318                     l.onUnbind(this);
319                 }
320             }
321         }
322     }
323 
324     /**
325      * Sets the service interface after the service is bound or unbound.
326      *
327      * @param binder The actual bound service implementation.
328      */
setServiceInterface(IBinder binder)329     protected abstract void setServiceInterface(IBinder binder);
330 }
331