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