1 /*
2  * Copyright (C) 2015 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.pm;
18 
19 import android.annotation.AnyThread;
20 import android.annotation.WorkerThread;
21 import android.app.IInstantAppResolver;
22 import android.app.InstantAppResolverService;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.ServiceConnection;
27 import android.content.pm.InstantAppRequestInfo;
28 import android.content.pm.InstantAppResolveInfo;
29 import android.os.Binder;
30 import android.os.Build;
31 import android.os.Bundle;
32 import android.os.Handler;
33 import android.os.IBinder;
34 import android.os.IBinder.DeathRecipient;
35 import android.os.IRemoteCallback;
36 import android.os.RemoteException;
37 import android.os.SystemClock;
38 import android.os.UserHandle;
39 import android.util.Slog;
40 import android.util.TimedRemoteCaller;
41 
42 import com.android.internal.annotations.GuardedBy;
43 import com.android.internal.os.BackgroundThread;
44 
45 import java.util.ArrayList;
46 import java.util.List;
47 import java.util.NoSuchElementException;
48 import java.util.concurrent.TimeoutException;
49 
50 /**
51  * Represents a remote instant app resolver. It is responsible for binding to the remote
52  * service and handling all interactions in a timely manner.
53  * @hide
54  */
55 final class InstantAppResolverConnection implements DeathRecipient {
56     private static final String TAG = "PackageManager";
57     // This is running in a critical section and the timeout must be sufficiently low
58     private static final long BIND_SERVICE_TIMEOUT_MS =
59             Build.IS_ENG ? 500 : 300;
60     private static final long CALL_SERVICE_TIMEOUT_MS =
61             Build.IS_ENG ? 200 : 100;
62     private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE;
63 
64     private final Object mLock = new Object();
65     private final GetInstantAppResolveInfoCaller mGetInstantAppResolveInfoCaller =
66             new GetInstantAppResolveInfoCaller();
67     private final ServiceConnection mServiceConnection = new MyServiceConnection();
68     private final Context mContext;
69     /** Intent used to bind to the service */
70     private final Intent mIntent;
71 
72     private static final int STATE_IDLE    = 0; // no bind operation is ongoing
73     private static final int STATE_BINDING = 1; // someone is binding and waiting
74     private static final int STATE_PENDING = 2; // a bind is pending, but the caller is not waiting
75     private final Handler mBgHandler;
76 
77     @GuardedBy("mLock")
78     private int mBindState = STATE_IDLE;
79     @GuardedBy("mLock")
80     private IInstantAppResolver mRemoteInstance;
81 
InstantAppResolverConnection( Context context, ComponentName componentName, String action)82     public InstantAppResolverConnection(
83             Context context, ComponentName componentName, String action) {
84         mContext = context;
85         mIntent = new Intent(action).setComponent(componentName);
86         mBgHandler = BackgroundThread.getHandler();
87     }
88 
getInstantAppResolveInfoList(InstantAppRequestInfo request)89     public List<InstantAppResolveInfo> getInstantAppResolveInfoList(InstantAppRequestInfo request)
90             throws ConnectionException {
91         throwIfCalledOnMainThread();
92         IInstantAppResolver target = null;
93         try {
94             try {
95                 target = getRemoteInstanceLazy(request.getToken());
96             } catch (TimeoutException e) {
97                 throw new ConnectionException(ConnectionException.FAILURE_BIND);
98             } catch (InterruptedException e) {
99                 throw new ConnectionException(ConnectionException.FAILURE_INTERRUPTED);
100             }
101             try {
102                 return mGetInstantAppResolveInfoCaller
103                         .getInstantAppResolveInfoList(target, request);
104             } catch (TimeoutException e) {
105                 throw new ConnectionException(ConnectionException.FAILURE_CALL);
106             } catch (RemoteException ignore) {
107             }
108         } finally {
109             synchronized (mLock) {
110                 mLock.notifyAll();
111             }
112         }
113         return null;
114     }
115 
getInstantAppIntentFilterList(InstantAppRequestInfo request, PhaseTwoCallback callback, Handler callbackHandler, final long startTime)116     public void getInstantAppIntentFilterList(InstantAppRequestInfo request,
117             PhaseTwoCallback callback, Handler callbackHandler, final long startTime)
118             throws ConnectionException {
119         final IRemoteCallback remoteCallback = new IRemoteCallback.Stub() {
120             @Override
121             public void sendResult(Bundle data) throws RemoteException {
122                 final ArrayList<InstantAppResolveInfo> resolveList =
123                         data.getParcelableArrayList(
124                                 InstantAppResolverService.EXTRA_RESOLVE_INFO, android.content.pm.InstantAppResolveInfo.class);
125                 callbackHandler.post(() -> callback.onPhaseTwoResolved(resolveList, startTime));
126             }
127         };
128         try {
129             getRemoteInstanceLazy(request.getToken())
130                     .getInstantAppIntentFilterList(request, remoteCallback);
131         } catch (TimeoutException e) {
132             throw new ConnectionException(ConnectionException.FAILURE_BIND);
133         } catch (InterruptedException e) {
134             throw new ConnectionException(ConnectionException.FAILURE_INTERRUPTED);
135         } catch (RemoteException ignore) {
136         }
137     }
138 
139     @WorkerThread
getRemoteInstanceLazy(String token)140     private IInstantAppResolver getRemoteInstanceLazy(String token)
141             throws ConnectionException, TimeoutException, InterruptedException {
142         final long binderToken = Binder.clearCallingIdentity();
143         try {
144             return bind(token);
145         } finally {
146             Binder.restoreCallingIdentity(binderToken);
147         }
148     }
149 
150     @GuardedBy("mLock")
waitForBindLocked(String token)151     private void waitForBindLocked(String token) throws TimeoutException, InterruptedException {
152         final long startMillis = SystemClock.uptimeMillis();
153         while (mBindState != STATE_IDLE) {
154             if (mRemoteInstance != null) {
155                 break;
156             }
157             final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
158             final long remainingMillis = BIND_SERVICE_TIMEOUT_MS - elapsedMillis;
159             if (remainingMillis <= 0) {
160                 throw new TimeoutException("[" + token + "] Didn't bind to resolver in time!");
161             }
162             mLock.wait(remainingMillis);
163         }
164     }
165 
166     @WorkerThread
bind(String token)167     private IInstantAppResolver bind(String token)
168             throws ConnectionException, TimeoutException, InterruptedException {
169         boolean doUnbind = false;
170         synchronized (mLock) {
171             if (mRemoteInstance != null) {
172                 return mRemoteInstance;
173             }
174 
175             if (mBindState == STATE_PENDING) {
176                 // there is a pending bind, let's see if we can use it.
177                 if (DEBUG_INSTANT) {
178                     Slog.i(TAG, "[" + token + "] Previous bind timed out; waiting for connection");
179                 }
180                 try {
181                     waitForBindLocked(token);
182                     if (mRemoteInstance != null) {
183                         return mRemoteInstance;
184                     }
185                 } catch (TimeoutException e) {
186                     // nope, we might have to try a rebind.
187                     doUnbind = true;
188                 }
189             }
190 
191             if (mBindState == STATE_BINDING) {
192                 // someone was binding when we called bind(), or they raced ahead while we were
193                 // waiting in the PENDING case; wait for their result instead. Last chance!
194                 if (DEBUG_INSTANT) {
195                     Slog.i(TAG, "[" + token + "] Another thread is binding; waiting for connection");
196                 }
197                 waitForBindLocked(token);
198                 // if the other thread's bindService() returned false, we could still have null.
199                 if (mRemoteInstance != null) {
200                     return mRemoteInstance;
201                 }
202                 throw new ConnectionException(ConnectionException.FAILURE_BIND);
203             }
204             mBindState = STATE_BINDING; // our time to shine! :)
205         }
206 
207         // only one thread can be here at a time (the one that set STATE_BINDING)
208         boolean wasBound = false;
209         IInstantAppResolver instance = null;
210         try {
211             if (doUnbind) {
212                 if (DEBUG_INSTANT) {
213                     Slog.i(TAG, "[" + token + "] Previous connection never established; rebinding");
214                 }
215                 try {
216                     mContext.unbindService(mServiceConnection);
217                 } catch (Exception e) {
218                     Slog.e(TAG, "[" + token + "] Service already unbound", e);
219                 }
220 
221             }
222             if (DEBUG_INSTANT) {
223                 Slog.v(TAG, "[" + token + "] Binding to instant app resolver");
224             }
225             final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
226             wasBound = mContext
227                     .bindServiceAsUser(mIntent, mServiceConnection, flags, UserHandle.SYSTEM);
228             if (wasBound) {
229                 synchronized (mLock) {
230                     waitForBindLocked(token);
231                     instance = mRemoteInstance;
232                     return instance;
233                 }
234             } else {
235                 Slog.w(TAG, "[" + token + "] Failed to bind to: " + mIntent);
236                 throw new ConnectionException(ConnectionException.FAILURE_BIND);
237             }
238         } finally {
239             synchronized (mLock) {
240                 if (wasBound && instance == null) {
241                     mBindState = STATE_PENDING;
242                 } else {
243                     mBindState = STATE_IDLE;
244                 }
245                 mLock.notifyAll();
246             }
247         }
248     }
249 
throwIfCalledOnMainThread()250     private void throwIfCalledOnMainThread() {
251         if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
252             throw new RuntimeException("Cannot invoke on the main thread");
253         }
254     }
255 
256     @AnyThread
optimisticBind()257     void optimisticBind() {
258         mBgHandler.post(() -> {
259             try {
260                 if (bind("Optimistic Bind") != null && DEBUG_INSTANT) {
261                     Slog.i(TAG, "Optimistic bind succeeded.");
262                 }
263             } catch (ConnectionException | TimeoutException | InterruptedException e) {
264                 Slog.e(TAG, "Optimistic bind failed.", e);
265             }
266         });
267     }
268 
269     @Override
binderDied()270     public void binderDied() {
271         if (DEBUG_INSTANT) {
272             Slog.d(TAG, "Binder to instant app resolver died");
273         }
274         synchronized (mLock) {
275             handleBinderDiedLocked();
276         }
277         optimisticBind();
278     }
279 
280     @GuardedBy("mLock")
handleBinderDiedLocked()281     private void handleBinderDiedLocked() {
282         if (mRemoteInstance != null) {
283             try {
284                 mRemoteInstance.asBinder().unlinkToDeath(this, 0 /*flags*/);
285             } catch (NoSuchElementException ignore) { }
286         }
287         mRemoteInstance = null;
288 
289         try {
290             mContext.unbindService(mServiceConnection);
291         } catch (Exception ignored) {
292         }
293     }
294 
295     /**
296      * Asynchronous callback when results come back from ephemeral resolution phase two.
297      */
298     public abstract static class PhaseTwoCallback {
onPhaseTwoResolved( List<InstantAppResolveInfo> instantAppResolveInfoList, long startTime)299         abstract void onPhaseTwoResolved(
300                 List<InstantAppResolveInfo> instantAppResolveInfoList, long startTime);
301     }
302 
303     public static class ConnectionException extends Exception {
304         public static final int FAILURE_BIND = 1;
305         public static final int FAILURE_CALL = 2;
306         public static final int FAILURE_INTERRUPTED = 3;
307 
308         public final int failure;
ConnectionException(int _failure)309         public ConnectionException(int _failure) {
310             failure = _failure;
311         }
312     }
313 
314     private final class MyServiceConnection implements ServiceConnection {
315         @Override
onServiceConnected(ComponentName name, IBinder service)316         public void onServiceConnected(ComponentName name, IBinder service) {
317             if (DEBUG_INSTANT) {
318                 Slog.d(TAG, "Connected to instant app resolver");
319             }
320             synchronized (mLock) {
321                 mRemoteInstance = IInstantAppResolver.Stub.asInterface(service);
322                 if (mBindState == STATE_PENDING) {
323                     mBindState = STATE_IDLE;
324                 }
325                 try {
326                     service.linkToDeath(InstantAppResolverConnection.this, 0 /*flags*/);
327                 } catch (RemoteException e) {
328                     handleBinderDiedLocked();
329                 }
330                 mLock.notifyAll();
331             }
332         }
333 
334         @Override
onServiceDisconnected(ComponentName name)335         public void onServiceDisconnected(ComponentName name) {
336             if (DEBUG_INSTANT) {
337                 Slog.d(TAG, "Disconnected from instant app resolver");
338             }
339             synchronized (mLock) {
340                 handleBinderDiedLocked();
341             }
342         }
343     }
344 
345     private static final class GetInstantAppResolveInfoCaller
346             extends TimedRemoteCaller<List<InstantAppResolveInfo>> {
347         private final IRemoteCallback mCallback;
348 
GetInstantAppResolveInfoCaller()349         public GetInstantAppResolveInfoCaller() {
350             super(CALL_SERVICE_TIMEOUT_MS);
351             mCallback = new IRemoteCallback.Stub() {
352                     @Override
353                     public void sendResult(Bundle data) throws RemoteException {
354                         final ArrayList<InstantAppResolveInfo> resolveList =
355                                 data.getParcelableArrayList(
356                                         InstantAppResolverService.EXTRA_RESOLVE_INFO, android.content.pm.InstantAppResolveInfo.class);
357                         int sequence =
358                                 data.getInt(InstantAppResolverService.EXTRA_SEQUENCE, -1);
359                         onRemoteMethodResult(resolveList, sequence);
360                     }
361             };
362         }
363 
getInstantAppResolveInfoList(IInstantAppResolver target, InstantAppRequestInfo request)364         public List<InstantAppResolveInfo> getInstantAppResolveInfoList(IInstantAppResolver target,
365                 InstantAppRequestInfo request) throws RemoteException, TimeoutException {
366             final int sequence = onBeforeRemoteCall();
367             target.getInstantAppResolveInfoList(request, sequence, mCallback);
368             return getResultTimed(sequence);
369         }
370     }
371 }
372