1/**
2 * Copyright (c) 2016, 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 */
16package com.android.server.utils;
17
18import android.annotation.NonNull;
19import android.app.PendingIntent;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.ServiceConnection;
24import android.os.IBinder;
25import android.os.IBinder.DeathRecipient;
26import android.os.IInterface;
27import android.os.RemoteException;
28import android.os.UserHandle;
29import android.util.Slog;
30
31import java.util.Objects;
32
33/**
34 * Manages the lifecycle of an application-provided service bound from system server.
35 *
36 * @hide
37 */
38public class ManagedApplicationService {
39    private final String TAG = getClass().getSimpleName();
40
41    private final Context mContext;
42    private final int mUserId;
43    private final ComponentName mComponent;
44    private final int mClientLabel;
45    private final String mSettingsAction;
46    private final BinderChecker mChecker;
47
48    private final DeathRecipient mDeathRecipient = new DeathRecipient() {
49        @Override
50        public void binderDied() {
51            synchronized (mLock) {
52                mBoundInterface = null;
53            }
54        }
55    };
56
57    private final Object mLock = new Object();
58
59    // State protected by mLock
60    private ServiceConnection mPendingConnection;
61    private ServiceConnection mConnection;
62    private IInterface mBoundInterface;
63    private PendingEvent mPendingEvent;
64
65    private ManagedApplicationService(final Context context, final ComponentName component,
66            final int userId, int clientLabel, String settingsAction,
67            BinderChecker binderChecker) {
68        mContext = context;
69        mComponent = component;
70        mUserId = userId;
71        mClientLabel = clientLabel;
72        mSettingsAction = settingsAction;
73        mChecker = binderChecker;
74    }
75
76    /**
77     * Implement to validate returned IBinder instance.
78     */
79    public interface BinderChecker {
80        IInterface asInterface(IBinder binder);
81        boolean checkType(IInterface service);
82    }
83
84    /**
85     * Implement to call IInterface methods after service is connected.
86     */
87    public interface PendingEvent {
88         void runEvent(IInterface service) throws RemoteException;
89    }
90
91    /**
92     * Create a new ManagedApplicationService object but do not yet bind to the user service.
93     *
94     * @param context a Context to use for binding the application service.
95     * @param component the {@link ComponentName} of the application service to bind.
96     * @param userId the user ID of user to bind the application service as.
97     * @param clientLabel the resource ID of a label displayed to the user indicating the
98     *      binding service.
99     * @param settingsAction an action that can be used to open the Settings UI to enable/disable
100     *      binding to these services.
101     * @param binderChecker an interface used to validate the returned binder object.
102     * @return a ManagedApplicationService instance.
103     */
104    public static ManagedApplicationService build(@NonNull final Context context,
105        @NonNull final ComponentName component, final int userId, @NonNull int clientLabel,
106        @NonNull String settingsAction, @NonNull BinderChecker binderChecker) {
107        return new ManagedApplicationService(context, component, userId, clientLabel,
108            settingsAction, binderChecker);
109    }
110
111    /**
112     * @return the user ID of the user that owns the bound service.
113     */
114    public int getUserId() {
115        return mUserId;
116    }
117
118    /**
119     * @return the component of the bound service.
120     */
121    public ComponentName getComponent() {
122        return mComponent;
123    }
124
125    /**
126     * Asynchronously unbind from the application service if the bound service component and user
127     * does not match the given signature.
128     *
129     * @param componentName the component that must match.
130     * @param userId the user ID that must match.
131     * @return {@code true} if not matching.
132     */
133    public boolean disconnectIfNotMatching(final ComponentName componentName, final int userId) {
134        if (matches(componentName, userId)) {
135            return false;
136        }
137        disconnect();
138        return true;
139    }
140
141
142  /**
143   * Send an event to run as soon as the binder interface is available.
144   *
145   * @param event a {@link PendingEvent} to send.
146   */
147  public void sendEvent(@NonNull PendingEvent event) {
148        IInterface iface;
149        synchronized (mLock) {
150            iface = mBoundInterface;
151            if (iface == null) {
152                mPendingEvent = event;
153            }
154        }
155
156        if (iface != null) {
157            try {
158                event.runEvent(iface);
159            } catch (RuntimeException | RemoteException ex) {
160                Slog.e(TAG, "Received exception from user service: ", ex);
161            }
162        }
163    }
164
165    /**
166     * Asynchronously unbind from the application service if bound.
167     */
168    public void disconnect() {
169        synchronized (mLock) {
170            // Wipe out pending connections
171            mPendingConnection = null;
172
173            // Unbind existing connection, if it exists
174            if (mConnection != null) {
175                mContext.unbindService(mConnection);
176                mConnection = null;
177            }
178
179            mBoundInterface = null;
180        }
181    }
182
183    /**
184     * Asynchronously bind to the application service if not bound.
185     */
186    public void connect() {
187        synchronized (mLock) {
188            if (mConnection != null || mPendingConnection != null) {
189                // We're already connected or are trying to connect
190                return;
191            }
192
193            final PendingIntent pendingIntent = PendingIntent.getActivity(
194                    mContext, 0, new Intent(mSettingsAction), 0);
195            final Intent intent = new Intent().setComponent(mComponent).
196                    putExtra(Intent.EXTRA_CLIENT_LABEL, mClientLabel).
197                    putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
198
199            final ServiceConnection serviceConnection = new ServiceConnection() {
200                @Override
201                public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
202                    IInterface iface = null;
203                    PendingEvent pendingEvent = null;
204                    synchronized (mLock) {
205                        if (mPendingConnection == this) {
206                            // No longer pending, remove from pending connection
207                            mPendingConnection = null;
208                            mConnection = this;
209                        } else {
210                            // Service connection wasn't pending, must have been disconnected
211                            mContext.unbindService(this);
212                            return;
213                        }
214
215                        try {
216                            iBinder.linkToDeath(mDeathRecipient, 0);
217                            mBoundInterface = mChecker.asInterface(iBinder);
218                            if (!mChecker.checkType(mBoundInterface)) {
219                                // Received an invalid binder, disconnect
220                                mContext.unbindService(this);
221                                mBoundInterface = null;
222                            }
223                            iface = mBoundInterface;
224                            pendingEvent = mPendingEvent;
225                            mPendingEvent = null;
226                        } catch (RemoteException e) {
227                            // DOA
228                            Slog.w(TAG, "Unable to bind service: " + intent, e);
229                            mBoundInterface = null;
230                        }
231                    }
232                    if (iface != null && pendingEvent != null) {
233                        try {
234                            pendingEvent.runEvent(iface);
235                        } catch (RuntimeException | RemoteException ex) {
236                            Slog.e(TAG, "Received exception from user service: ", ex);
237                        }
238                    }
239                }
240
241                @Override
242                public void onServiceDisconnected(ComponentName componentName) {
243                    Slog.w(TAG, "Service disconnected: " + intent);
244                    mConnection = null;
245                    mBoundInterface = null;
246                }
247            };
248
249            mPendingConnection = serviceConnection;
250
251            try {
252                if (!mContext.bindServiceAsUser(intent, serviceConnection,
253                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
254                        new UserHandle(mUserId))) {
255                    Slog.w(TAG, "Unable to bind service: " + intent);
256                }
257            } catch (SecurityException e) {
258                Slog.w(TAG, "Unable to bind service: " + intent, e);
259            }
260        }
261    }
262
263    private boolean matches(final ComponentName component, final int userId) {
264        return Objects.equals(mComponent, component) && mUserId == userId;
265    }
266}
267