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.app.IInstantAppResolver;
     20 import android.app.InstantAppResolverService;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.ServiceConnection;
     25 import android.content.pm.InstantAppResolveInfo;
     26 import android.os.Binder;
     27 import android.os.Build;
     28 import android.os.Bundle;
     29 import android.os.Handler;
     30 import android.os.IBinder;
     31 import android.os.IBinder.DeathRecipient;
     32 import android.os.IRemoteCallback;
     33 import android.os.RemoteException;
     34 import android.os.SystemClock;
     35 import android.os.UserHandle;
     36 import android.util.Slog;
     37 import android.util.TimedRemoteCaller;
     38 
     39 import com.android.internal.annotations.GuardedBy;
     40 import com.android.internal.os.TransferPipe;
     41 
     42 import java.io.FileDescriptor;
     43 import java.io.IOException;
     44 import java.io.PrintWriter;
     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 ephemeral 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 EphemeralResolverConnection 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_EPHEMERAL = Build.IS_DEBUGGABLE;
     63 
     64     private final Object mLock = new Object();
     65     private final GetEphemeralResolveInfoCaller mGetEphemeralResolveInfoCaller =
     66             new GetEphemeralResolveInfoCaller();
     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 
     76     @GuardedBy("mLock")
     77     private int mBindState = STATE_IDLE;
     78     @GuardedBy("mLock")
     79     private IInstantAppResolver mRemoteInstance;
     80 
     81     public EphemeralResolverConnection(
     82             Context context, ComponentName componentName, String action) {
     83         mContext = context;
     84         mIntent = new Intent(action).setComponent(componentName);
     85     }
     86 
     87     public final List<InstantAppResolveInfo> getInstantAppResolveInfoList(int hashPrefix[],
     88             String token) throws ConnectionException {
     89         throwIfCalledOnMainThread();
     90         IInstantAppResolver target = null;
     91         try {
     92             try {
     93                 target = getRemoteInstanceLazy(token);
     94             } catch (TimeoutException e) {
     95                 throw new ConnectionException(ConnectionException.FAILURE_BIND);
     96             } catch (InterruptedException e) {
     97                 throw new ConnectionException(ConnectionException.FAILURE_INTERRUPTED);
     98             }
     99             try {
    100                 return mGetEphemeralResolveInfoCaller
    101                         .getEphemeralResolveInfoList(target, hashPrefix, token);
    102             } catch (TimeoutException e) {
    103                 throw new ConnectionException(ConnectionException.FAILURE_CALL);
    104             } catch (RemoteException ignore) {
    105             }
    106         } finally {
    107             synchronized (mLock) {
    108                 mLock.notifyAll();
    109             }
    110         }
    111         return null;
    112     }
    113 
    114     public final void getInstantAppIntentFilterList(int hashPrefix[], String token,
    115             String hostName, PhaseTwoCallback callback, Handler callbackHandler,
    116             final long startTime) throws ConnectionException {
    117         final IRemoteCallback remoteCallback = new IRemoteCallback.Stub() {
    118             @Override
    119             public void sendResult(Bundle data) throws RemoteException {
    120                 final ArrayList<InstantAppResolveInfo> resolveList =
    121                         data.getParcelableArrayList(
    122                                 InstantAppResolverService.EXTRA_RESOLVE_INFO);
    123                 callbackHandler.post(new Runnable() {
    124                     @Override
    125                     public void run() {
    126                         callback.onPhaseTwoResolved(resolveList, startTime);
    127                     }
    128                 });
    129             }
    130         };
    131         try {
    132             getRemoteInstanceLazy(token)
    133                     .getInstantAppIntentFilterList(hashPrefix, token, hostName, remoteCallback);
    134         } catch (TimeoutException e) {
    135             throw new ConnectionException(ConnectionException.FAILURE_BIND);
    136         } catch (InterruptedException e) {
    137             throw new ConnectionException(ConnectionException.FAILURE_INTERRUPTED);
    138         } catch (RemoteException ignore) {
    139         }
    140     }
    141 
    142     private IInstantAppResolver getRemoteInstanceLazy(String token)
    143             throws ConnectionException, TimeoutException, InterruptedException {
    144         long binderToken = Binder.clearCallingIdentity();
    145         try {
    146             return bind(token);
    147         } finally {
    148             Binder.restoreCallingIdentity(binderToken);
    149         }
    150     }
    151 
    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     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_EPHEMERAL) {
    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_EPHEMERAL) {
    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_EPHEMERAL) {
    213                     Slog.i(TAG, "[" + token + "] Previous connection never established; rebinding");
    214                 }
    215                 mContext.unbindService(mServiceConnection);
    216             }
    217             if (DEBUG_EPHEMERAL) {
    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     @Override
    252     public void binderDied() {
    253         if (DEBUG_EPHEMERAL) {
    254             Slog.d(TAG, "Binder to instant app resolver died");
    255         }
    256         synchronized (mLock) {
    257             handleBinderDiedLocked();
    258         }
    259     }
    260 
    261     private void handleBinderDiedLocked() {
    262         if (mRemoteInstance != null) {
    263             try {
    264                 mRemoteInstance.asBinder().unlinkToDeath(this, 0 /*flags*/);
    265             } catch (NoSuchElementException ignore) { }
    266         }
    267         mRemoteInstance = null;
    268     }
    269 
    270     /**
    271      * Asynchronous callback when results come back from ephemeral resolution phase two.
    272      */
    273     public abstract static class PhaseTwoCallback {
    274         abstract void onPhaseTwoResolved(
    275                 List<InstantAppResolveInfo> instantAppResolveInfoList, long startTime);
    276     }
    277 
    278     public static class ConnectionException extends Exception {
    279         public static final int FAILURE_BIND = 1;
    280         public static final int FAILURE_CALL = 2;
    281         public static final int FAILURE_INTERRUPTED = 3;
    282 
    283         public final int failure;
    284         public ConnectionException(int _failure) {
    285             failure = _failure;
    286         }
    287     }
    288 
    289     private final class MyServiceConnection implements ServiceConnection {
    290         @Override
    291         public void onServiceConnected(ComponentName name, IBinder service) {
    292             if (DEBUG_EPHEMERAL) {
    293                 Slog.d(TAG, "Connected to instant app resolver");
    294             }
    295             synchronized (mLock) {
    296                 mRemoteInstance = IInstantAppResolver.Stub.asInterface(service);
    297                 if (mBindState == STATE_PENDING) {
    298                     mBindState = STATE_IDLE;
    299                 }
    300                 try {
    301                     service.linkToDeath(EphemeralResolverConnection.this, 0 /*flags*/);
    302                 } catch (RemoteException e) {
    303                     handleBinderDiedLocked();
    304                 }
    305                 mLock.notifyAll();
    306             }
    307         }
    308 
    309         @Override
    310         public void onServiceDisconnected(ComponentName name) {
    311             if (DEBUG_EPHEMERAL) {
    312                 Slog.d(TAG, "Disconnected from instant app resolver");
    313             }
    314             synchronized (mLock) {
    315                 handleBinderDiedLocked();
    316             }
    317         }
    318     }
    319 
    320     private static final class GetEphemeralResolveInfoCaller
    321             extends TimedRemoteCaller<List<InstantAppResolveInfo>> {
    322         private final IRemoteCallback mCallback;
    323 
    324         public GetEphemeralResolveInfoCaller() {
    325             super(CALL_SERVICE_TIMEOUT_MS);
    326             mCallback = new IRemoteCallback.Stub() {
    327                     @Override
    328                     public void sendResult(Bundle data) throws RemoteException {
    329                         final ArrayList<InstantAppResolveInfo> resolveList =
    330                                 data.getParcelableArrayList(
    331                                         InstantAppResolverService.EXTRA_RESOLVE_INFO);
    332                         int sequence =
    333                                 data.getInt(InstantAppResolverService.EXTRA_SEQUENCE, -1);
    334                         onRemoteMethodResult(resolveList, sequence);
    335                     }
    336             };
    337         }
    338 
    339         public List<InstantAppResolveInfo> getEphemeralResolveInfoList(
    340                 IInstantAppResolver target, int hashPrefix[], String token)
    341                         throws RemoteException, TimeoutException {
    342             final int sequence = onBeforeRemoteCall();
    343             target.getInstantAppResolveInfoList(hashPrefix, token, sequence, mCallback);
    344             return getResultTimed(sequence);
    345         }
    346     }
    347 }
    348