/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.pm; import android.annotation.AnyThread; import android.annotation.WorkerThread; import android.app.IInstantAppResolver; import android.app.InstantAppResolverService; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.InstantAppResolveInfo; import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.IBinder.DeathRecipient; import android.os.IRemoteCallback; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.util.Slog; import android.util.TimedRemoteCaller; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.BackgroundThread; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.TimeoutException; /** * Represents a remote instant app resolver. It is responsible for binding to the remote * service and handling all interactions in a timely manner. * @hide */ final class InstantAppResolverConnection implements DeathRecipient { private static final String TAG = "PackageManager"; // This is running in a critical section and the timeout must be sufficiently low private static final long BIND_SERVICE_TIMEOUT_MS = Build.IS_ENG ? 500 : 300; private static final long CALL_SERVICE_TIMEOUT_MS = Build.IS_ENG ? 200 : 100; private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE; private final Object mLock = new Object(); private final GetInstantAppResolveInfoCaller mGetInstantAppResolveInfoCaller = new GetInstantAppResolveInfoCaller(); private final ServiceConnection mServiceConnection = new MyServiceConnection(); private final Context mContext; /** Intent used to bind to the service */ private final Intent mIntent; private static final int STATE_IDLE = 0; // no bind operation is ongoing private static final int STATE_BINDING = 1; // someone is binding and waiting private static final int STATE_PENDING = 2; // a bind is pending, but the caller is not waiting private final Handler mBgHandler; @GuardedBy("mLock") private int mBindState = STATE_IDLE; @GuardedBy("mLock") private IInstantAppResolver mRemoteInstance; public InstantAppResolverConnection( Context context, ComponentName componentName, String action) { mContext = context; mIntent = new Intent(action).setComponent(componentName); mBgHandler = BackgroundThread.getHandler(); } public final List<InstantAppResolveInfo> getInstantAppResolveInfoList(Intent sanitizedIntent, int hashPrefix[], String token) throws ConnectionException { throwIfCalledOnMainThread(); IInstantAppResolver target = null; try { try { target = getRemoteInstanceLazy(token); } catch (TimeoutException e) { throw new ConnectionException(ConnectionException.FAILURE_BIND); } catch (InterruptedException e) { throw new ConnectionException(ConnectionException.FAILURE_INTERRUPTED); } try { return mGetInstantAppResolveInfoCaller .getInstantAppResolveInfoList(target, sanitizedIntent, hashPrefix, token); } catch (TimeoutException e) { throw new ConnectionException(ConnectionException.FAILURE_CALL); } catch (RemoteException ignore) { } } finally { synchronized (mLock) { mLock.notifyAll(); } } return null; } public final void getInstantAppIntentFilterList(Intent sanitizedIntent, int hashPrefix[], String token, PhaseTwoCallback callback, Handler callbackHandler, final long startTime) throws ConnectionException { final IRemoteCallback remoteCallback = new IRemoteCallback.Stub() { @Override public void sendResult(Bundle data) throws RemoteException { final ArrayList<InstantAppResolveInfo> resolveList = data.getParcelableArrayList( InstantAppResolverService.EXTRA_RESOLVE_INFO); callbackHandler.post(() -> callback.onPhaseTwoResolved(resolveList, startTime)); } }; try { getRemoteInstanceLazy(token) .getInstantAppIntentFilterList(sanitizedIntent, hashPrefix, token, remoteCallback); } catch (TimeoutException e) { throw new ConnectionException(ConnectionException.FAILURE_BIND); } catch (InterruptedException e) { throw new ConnectionException(ConnectionException.FAILURE_INTERRUPTED); } catch (RemoteException ignore) { } } @WorkerThread private IInstantAppResolver getRemoteInstanceLazy(String token) throws ConnectionException, TimeoutException, InterruptedException { long binderToken = Binder.clearCallingIdentity(); try { return bind(token); } finally { Binder.restoreCallingIdentity(binderToken); } } @GuardedBy("mLock") private void waitForBindLocked(String token) throws TimeoutException, InterruptedException { final long startMillis = SystemClock.uptimeMillis(); while (mBindState != STATE_IDLE) { if (mRemoteInstance != null) { break; } final long elapsedMillis = SystemClock.uptimeMillis() - startMillis; final long remainingMillis = BIND_SERVICE_TIMEOUT_MS - elapsedMillis; if (remainingMillis <= 0) { throw new TimeoutException("[" + token + "] Didn't bind to resolver in time!"); } mLock.wait(remainingMillis); } } @WorkerThread private IInstantAppResolver bind(String token) throws ConnectionException, TimeoutException, InterruptedException { boolean doUnbind = false; synchronized (mLock) { if (mRemoteInstance != null) { return mRemoteInstance; } if (mBindState == STATE_PENDING) { // there is a pending bind, let's see if we can use it. if (DEBUG_INSTANT) { Slog.i(TAG, "[" + token + "] Previous bind timed out; waiting for connection"); } try { waitForBindLocked(token); if (mRemoteInstance != null) { return mRemoteInstance; } } catch (TimeoutException e) { // nope, we might have to try a rebind. doUnbind = true; } } if (mBindState == STATE_BINDING) { // someone was binding when we called bind(), or they raced ahead while we were // waiting in the PENDING case; wait for their result instead. Last chance! if (DEBUG_INSTANT) { Slog.i(TAG, "[" + token + "] Another thread is binding; waiting for connection"); } waitForBindLocked(token); // if the other thread's bindService() returned false, we could still have null. if (mRemoteInstance != null) { return mRemoteInstance; } throw new ConnectionException(ConnectionException.FAILURE_BIND); } mBindState = STATE_BINDING; // our time to shine! :) } // only one thread can be here at a time (the one that set STATE_BINDING) boolean wasBound = false; IInstantAppResolver instance = null; try { if (doUnbind) { if (DEBUG_INSTANT) { Slog.i(TAG, "[" + token + "] Previous connection never established; rebinding"); } mContext.unbindService(mServiceConnection); } if (DEBUG_INSTANT) { Slog.v(TAG, "[" + token + "] Binding to instant app resolver"); } final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE; wasBound = mContext .bindServiceAsUser(mIntent, mServiceConnection, flags, UserHandle.SYSTEM); if (wasBound) { synchronized (mLock) { waitForBindLocked(token); instance = mRemoteInstance; return instance; } } else { Slog.w(TAG, "[" + token + "] Failed to bind to: " + mIntent); throw new ConnectionException(ConnectionException.FAILURE_BIND); } } finally { synchronized (mLock) { if (wasBound && instance == null) { mBindState = STATE_PENDING; } else { mBindState = STATE_IDLE; } mLock.notifyAll(); } } } private void throwIfCalledOnMainThread() { if (Thread.currentThread() == mContext.getMainLooper().getThread()) { throw new RuntimeException("Cannot invoke on the main thread"); } } @AnyThread void optimisticBind() { mBgHandler.post(() -> { try { if (bind("Optimistic Bind") != null && DEBUG_INSTANT) { Slog.i(TAG, "Optimistic bind succeeded."); } } catch (ConnectionException | TimeoutException | InterruptedException e) { Slog.e(TAG, "Optimistic bind failed.", e); } }); } @Override public void binderDied() { if (DEBUG_INSTANT) { Slog.d(TAG, "Binder to instant app resolver died"); } synchronized (mLock) { handleBinderDiedLocked(); } optimisticBind(); } @GuardedBy("mLock") private void handleBinderDiedLocked() { if (mRemoteInstance != null) { try { mRemoteInstance.asBinder().unlinkToDeath(this, 0 /*flags*/); } catch (NoSuchElementException ignore) { } } mRemoteInstance = null; } /** * Asynchronous callback when results come back from ephemeral resolution phase two. */ public abstract static class PhaseTwoCallback { abstract void onPhaseTwoResolved( List<InstantAppResolveInfo> instantAppResolveInfoList, long startTime); } public static class ConnectionException extends Exception { public static final int FAILURE_BIND = 1; public static final int FAILURE_CALL = 2; public static final int FAILURE_INTERRUPTED = 3; public final int failure; public ConnectionException(int _failure) { failure = _failure; } } private final class MyServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { if (DEBUG_INSTANT) { Slog.d(TAG, "Connected to instant app resolver"); } synchronized (mLock) { mRemoteInstance = IInstantAppResolver.Stub.asInterface(service); if (mBindState == STATE_PENDING) { mBindState = STATE_IDLE; } try { service.linkToDeath(InstantAppResolverConnection.this, 0 /*flags*/); } catch (RemoteException e) { handleBinderDiedLocked(); } mLock.notifyAll(); } } @Override public void onServiceDisconnected(ComponentName name) { if (DEBUG_INSTANT) { Slog.d(TAG, "Disconnected from instant app resolver"); } synchronized (mLock) { handleBinderDiedLocked(); } } } private static final class GetInstantAppResolveInfoCaller extends TimedRemoteCaller<List<InstantAppResolveInfo>> { private final IRemoteCallback mCallback; public GetInstantAppResolveInfoCaller() { super(CALL_SERVICE_TIMEOUT_MS); mCallback = new IRemoteCallback.Stub() { @Override public void sendResult(Bundle data) throws RemoteException { final ArrayList<InstantAppResolveInfo> resolveList = data.getParcelableArrayList( InstantAppResolverService.EXTRA_RESOLVE_INFO); int sequence = data.getInt(InstantAppResolverService.EXTRA_SEQUENCE, -1); onRemoteMethodResult(resolveList, sequence); } }; } public List<InstantAppResolveInfo> getInstantAppResolveInfoList( IInstantAppResolver target, Intent sanitizedIntent, int hashPrefix[], String token) throws RemoteException, TimeoutException { final int sequence = onBeforeRemoteCall(); target.getInstantAppResolveInfoList(sanitizedIntent, hashPrefix, token, sequence, mCallback); return getResultTimed(sequence); } } }