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