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