• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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);
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                 mContext.unbindService(mServiceConnection);
216             }
217             if (DEBUG_INSTANT) {
218                 Slog.v(TAG, "[" + token + "] Binding to instant app resolver");
219             }
220             final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
221             wasBound = mContext
222                     .bindServiceAsUser(mIntent, mServiceConnection, flags, UserHandle.SYSTEM);
223             if (wasBound) {
224                 synchronized (mLock) {
225                     waitForBindLocked(token);
226                     instance = mRemoteInstance;
227                     return instance;
228                 }
229             } else {
230                 Slog.w(TAG, "[" + token + "] Failed to bind to: " + mIntent);
231                 throw new ConnectionException(ConnectionException.FAILURE_BIND);
232             }
233         } finally {
234             synchronized (mLock) {
235                 if (wasBound && instance == null) {
236                     mBindState = STATE_PENDING;
237                 } else {
238                     mBindState = STATE_IDLE;
239                 }
240                 mLock.notifyAll();
241             }
242         }
243     }
244 
throwIfCalledOnMainThread()245     private void throwIfCalledOnMainThread() {
246         if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
247             throw new RuntimeException("Cannot invoke on the main thread");
248         }
249     }
250 
251     @AnyThread
optimisticBind()252     void optimisticBind() {
253         mBgHandler.post(() -> {
254             try {
255                 if (bind("Optimistic Bind") != null && DEBUG_INSTANT) {
256                     Slog.i(TAG, "Optimistic bind succeeded.");
257                 }
258             } catch (ConnectionException | TimeoutException | InterruptedException e) {
259                 Slog.e(TAG, "Optimistic bind failed.", e);
260             }
261         });
262     }
263 
264     @Override
binderDied()265     public void binderDied() {
266         if (DEBUG_INSTANT) {
267             Slog.d(TAG, "Binder to instant app resolver died");
268         }
269         synchronized (mLock) {
270             handleBinderDiedLocked();
271         }
272         optimisticBind();
273     }
274 
275     @GuardedBy("mLock")
handleBinderDiedLocked()276     private void handleBinderDiedLocked() {
277         if (mRemoteInstance != null) {
278             try {
279                 mRemoteInstance.asBinder().unlinkToDeath(this, 0 /*flags*/);
280             } catch (NoSuchElementException ignore) { }
281         }
282         mRemoteInstance = null;
283     }
284 
285     /**
286      * Asynchronous callback when results come back from ephemeral resolution phase two.
287      */
288     public abstract static class PhaseTwoCallback {
onPhaseTwoResolved( List<InstantAppResolveInfo> instantAppResolveInfoList, long startTime)289         abstract void onPhaseTwoResolved(
290                 List<InstantAppResolveInfo> instantAppResolveInfoList, long startTime);
291     }
292 
293     public static class ConnectionException extends Exception {
294         public static final int FAILURE_BIND = 1;
295         public static final int FAILURE_CALL = 2;
296         public static final int FAILURE_INTERRUPTED = 3;
297 
298         public final int failure;
ConnectionException(int _failure)299         public ConnectionException(int _failure) {
300             failure = _failure;
301         }
302     }
303 
304     private final class MyServiceConnection implements ServiceConnection {
305         @Override
onServiceConnected(ComponentName name, IBinder service)306         public void onServiceConnected(ComponentName name, IBinder service) {
307             if (DEBUG_INSTANT) {
308                 Slog.d(TAG, "Connected to instant app resolver");
309             }
310             synchronized (mLock) {
311                 mRemoteInstance = IInstantAppResolver.Stub.asInterface(service);
312                 if (mBindState == STATE_PENDING) {
313                     mBindState = STATE_IDLE;
314                 }
315                 try {
316                     service.linkToDeath(InstantAppResolverConnection.this, 0 /*flags*/);
317                 } catch (RemoteException e) {
318                     handleBinderDiedLocked();
319                 }
320                 mLock.notifyAll();
321             }
322         }
323 
324         @Override
onServiceDisconnected(ComponentName name)325         public void onServiceDisconnected(ComponentName name) {
326             if (DEBUG_INSTANT) {
327                 Slog.d(TAG, "Disconnected from instant app resolver");
328             }
329             synchronized (mLock) {
330                 handleBinderDiedLocked();
331             }
332         }
333     }
334 
335     private static final class GetInstantAppResolveInfoCaller
336             extends TimedRemoteCaller<List<InstantAppResolveInfo>> {
337         private final IRemoteCallback mCallback;
338 
GetInstantAppResolveInfoCaller()339         public GetInstantAppResolveInfoCaller() {
340             super(CALL_SERVICE_TIMEOUT_MS);
341             mCallback = new IRemoteCallback.Stub() {
342                     @Override
343                     public void sendResult(Bundle data) throws RemoteException {
344                         final ArrayList<InstantAppResolveInfo> resolveList =
345                                 data.getParcelableArrayList(
346                                         InstantAppResolverService.EXTRA_RESOLVE_INFO);
347                         int sequence =
348                                 data.getInt(InstantAppResolverService.EXTRA_SEQUENCE, -1);
349                         onRemoteMethodResult(resolveList, sequence);
350                     }
351             };
352         }
353 
getInstantAppResolveInfoList(IInstantAppResolver target, InstantAppRequestInfo request)354         public List<InstantAppResolveInfo> getInstantAppResolveInfoList(IInstantAppResolver target,
355                 InstantAppRequestInfo request) throws RemoteException, TimeoutException {
356             final int sequence = onBeforeRemoteCall();
357             target.getInstantAppResolveInfoList(request, sequence, mCallback);
358             return getResultTimed(sequence);
359         }
360     }
361 }
362