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