Home | History | Annotate | Download | only in pm
      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 
     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 
     88     public final List<InstantAppResolveInfo> getInstantAppResolveInfoList(Intent sanitizedIntent,
     89             int hashPrefix[], 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, token);
    103             } catch (TimeoutException e) {
    104                 throw new ConnectionException(ConnectionException.FAILURE_CALL);
    105             } catch (RemoteException ignore) {
    106             }
    107         } finally {
    108             synchronized (mLock) {
    109                 mLock.notifyAll();
    110             }
    111         }
    112         return null;
    113     }
    114 
    115     public final void getInstantAppIntentFilterList(Intent sanitizedIntent, int hashPrefix[],
    116             String token, PhaseTwoCallback callback, Handler callbackHandler, final long startTime)
    117             throws ConnectionException {
    118         final IRemoteCallback remoteCallback = new IRemoteCallback.Stub() {
    119             @Override
    120             public void sendResult(Bundle data) throws RemoteException {
    121                 final ArrayList<InstantAppResolveInfo> resolveList =
    122                         data.getParcelableArrayList(
    123                                 InstantAppResolverService.EXTRA_RESOLVE_INFO);
    124                 callbackHandler.post(() -> callback.onPhaseTwoResolved(resolveList, startTime));
    125             }
    126         };
    127         try {
    128             getRemoteInstanceLazy(token)
    129                     .getInstantAppIntentFilterList(sanitizedIntent, hashPrefix, token,
    130                             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
    140     private IInstantAppResolver getRemoteInstanceLazy(String token)
    141             throws ConnectionException, TimeoutException, InterruptedException {
    142         long binderToken = Binder.clearCallingIdentity();
    143         try {
    144             return bind(token);
    145         } finally {
    146             Binder.restoreCallingIdentity(binderToken);
    147         }
    148     }
    149 
    150     @GuardedBy("mLock")
    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
    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 
    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
    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
    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")
    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 {
    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;
    299         public ConnectionException(int _failure) {
    300             failure = _failure;
    301         }
    302     }
    303 
    304     private final class MyServiceConnection implements ServiceConnection {
    305         @Override
    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
    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 
    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 
    354         public List<InstantAppResolveInfo> getInstantAppResolveInfoList(
    355                 IInstantAppResolver target, Intent sanitizedIntent,  int hashPrefix[], String token)
    356                         throws RemoteException, TimeoutException {
    357             final int sequence = onBeforeRemoteCall();
    358             target.getInstantAppResolveInfoList(sanitizedIntent, hashPrefix, token, sequence,
    359                     mCallback);
    360             return getResultTimed(sequence);
    361         }
    362     }
    363 }
    364