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