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.InstantAppRequestInfo; 28 import android.content.pm.InstantAppResolveInfo; 29 import android.os.Binder; 30 import android.os.Build; 31 import android.os.Bundle; 32 import android.os.Handler; 33 import android.os.IBinder; 34 import android.os.IBinder.DeathRecipient; 35 import android.os.IRemoteCallback; 36 import android.os.RemoteException; 37 import android.os.SystemClock; 38 import android.os.UserHandle; 39 import android.util.Slog; 40 import android.util.TimedRemoteCaller; 41 42 import com.android.internal.annotations.GuardedBy; 43 import com.android.internal.os.BackgroundThread; 44 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 instant app 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 InstantAppResolverConnection 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_INSTANT = Build.IS_DEBUGGABLE; 63 64 private final Object mLock = new Object(); 65 private final GetInstantAppResolveInfoCaller mGetInstantAppResolveInfoCaller = 66 new GetInstantAppResolveInfoCaller(); 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 private final Handler mBgHandler; 76 77 @GuardedBy("mLock") 78 private int mBindState = STATE_IDLE; 79 @GuardedBy("mLock") 80 private IInstantAppResolver mRemoteInstance; 81 InstantAppResolverConnection( Context context, ComponentName componentName, String action)82 public InstantAppResolverConnection( 83 Context context, ComponentName componentName, String action) { 84 mContext = context; 85 mIntent = new Intent(action).setComponent(componentName); 86 mBgHandler = BackgroundThread.getHandler(); 87 } 88 getInstantAppResolveInfoList(InstantAppRequestInfo request)89 public List<InstantAppResolveInfo> getInstantAppResolveInfoList(InstantAppRequestInfo request) 90 throws ConnectionException { 91 throwIfCalledOnMainThread(); 92 IInstantAppResolver target = null; 93 try { 94 try { 95 target = getRemoteInstanceLazy(request.getToken()); 96 } catch (TimeoutException e) { 97 throw new ConnectionException(ConnectionException.FAILURE_BIND); 98 } catch (InterruptedException e) { 99 throw new ConnectionException(ConnectionException.FAILURE_INTERRUPTED); 100 } 101 try { 102 return mGetInstantAppResolveInfoCaller 103 .getInstantAppResolveInfoList(target, request); 104 } catch (TimeoutException e) { 105 throw new ConnectionException(ConnectionException.FAILURE_CALL); 106 } catch (RemoteException ignore) { 107 } 108 } finally { 109 synchronized (mLock) { 110 mLock.notifyAll(); 111 } 112 } 113 return null; 114 } 115 getInstantAppIntentFilterList(InstantAppRequestInfo request, PhaseTwoCallback callback, Handler callbackHandler, final long startTime)116 public void getInstantAppIntentFilterList(InstantAppRequestInfo request, 117 PhaseTwoCallback callback, Handler callbackHandler, final long startTime) 118 throws ConnectionException { 119 final IRemoteCallback remoteCallback = new IRemoteCallback.Stub() { 120 @Override 121 public void sendResult(Bundle data) throws RemoteException { 122 final ArrayList<InstantAppResolveInfo> resolveList = 123 data.getParcelableArrayList( 124 InstantAppResolverService.EXTRA_RESOLVE_INFO, android.content.pm.InstantAppResolveInfo.class); 125 callbackHandler.post(() -> callback.onPhaseTwoResolved(resolveList, startTime)); 126 } 127 }; 128 try { 129 getRemoteInstanceLazy(request.getToken()) 130 .getInstantAppIntentFilterList(request, 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 getRemoteInstanceLazy(String token)140 private IInstantAppResolver getRemoteInstanceLazy(String token) 141 throws ConnectionException, TimeoutException, InterruptedException { 142 final long binderToken = Binder.clearCallingIdentity(); 143 try { 144 return bind(token); 145 } finally { 146 Binder.restoreCallingIdentity(binderToken); 147 } 148 } 149 150 @GuardedBy("mLock") waitForBindLocked(String token)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 bind(String token)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 try { 216 mContext.unbindService(mServiceConnection); 217 } catch (Exception e) { 218 Slog.e(TAG, "[" + token + "] Service already unbound", e); 219 } 220 221 } 222 if (DEBUG_INSTANT) { 223 Slog.v(TAG, "[" + token + "] Binding to instant app resolver"); 224 } 225 final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE; 226 wasBound = mContext 227 .bindServiceAsUser(mIntent, mServiceConnection, flags, UserHandle.SYSTEM); 228 if (wasBound) { 229 synchronized (mLock) { 230 waitForBindLocked(token); 231 instance = mRemoteInstance; 232 return instance; 233 } 234 } else { 235 Slog.w(TAG, "[" + token + "] Failed to bind to: " + mIntent); 236 throw new ConnectionException(ConnectionException.FAILURE_BIND); 237 } 238 } finally { 239 synchronized (mLock) { 240 if (wasBound && instance == null) { 241 mBindState = STATE_PENDING; 242 } else { 243 mBindState = STATE_IDLE; 244 } 245 mLock.notifyAll(); 246 } 247 } 248 } 249 throwIfCalledOnMainThread()250 private void throwIfCalledOnMainThread() { 251 if (Thread.currentThread() == mContext.getMainLooper().getThread()) { 252 throw new RuntimeException("Cannot invoke on the main thread"); 253 } 254 } 255 256 @AnyThread optimisticBind()257 void optimisticBind() { 258 mBgHandler.post(() -> { 259 try { 260 if (bind("Optimistic Bind") != null && DEBUG_INSTANT) { 261 Slog.i(TAG, "Optimistic bind succeeded."); 262 } 263 } catch (ConnectionException | TimeoutException | InterruptedException e) { 264 Slog.e(TAG, "Optimistic bind failed.", e); 265 } 266 }); 267 } 268 269 @Override binderDied()270 public void binderDied() { 271 if (DEBUG_INSTANT) { 272 Slog.d(TAG, "Binder to instant app resolver died"); 273 } 274 synchronized (mLock) { 275 handleBinderDiedLocked(); 276 } 277 optimisticBind(); 278 } 279 280 @GuardedBy("mLock") handleBinderDiedLocked()281 private void handleBinderDiedLocked() { 282 if (mRemoteInstance != null) { 283 try { 284 mRemoteInstance.asBinder().unlinkToDeath(this, 0 /*flags*/); 285 } catch (NoSuchElementException ignore) { } 286 } 287 mRemoteInstance = null; 288 289 try { 290 mContext.unbindService(mServiceConnection); 291 } catch (Exception ignored) { 292 } 293 } 294 295 /** 296 * Asynchronous callback when results come back from ephemeral resolution phase two. 297 */ 298 public abstract static class PhaseTwoCallback { onPhaseTwoResolved( List<InstantAppResolveInfo> instantAppResolveInfoList, long startTime)299 abstract void onPhaseTwoResolved( 300 List<InstantAppResolveInfo> instantAppResolveInfoList, long startTime); 301 } 302 303 public static class ConnectionException extends Exception { 304 public static final int FAILURE_BIND = 1; 305 public static final int FAILURE_CALL = 2; 306 public static final int FAILURE_INTERRUPTED = 3; 307 308 public final int failure; ConnectionException(int _failure)309 public ConnectionException(int _failure) { 310 failure = _failure; 311 } 312 } 313 314 private final class MyServiceConnection implements ServiceConnection { 315 @Override onServiceConnected(ComponentName name, IBinder service)316 public void onServiceConnected(ComponentName name, IBinder service) { 317 if (DEBUG_INSTANT) { 318 Slog.d(TAG, "Connected to instant app resolver"); 319 } 320 synchronized (mLock) { 321 mRemoteInstance = IInstantAppResolver.Stub.asInterface(service); 322 if (mBindState == STATE_PENDING) { 323 mBindState = STATE_IDLE; 324 } 325 try { 326 service.linkToDeath(InstantAppResolverConnection.this, 0 /*flags*/); 327 } catch (RemoteException e) { 328 handleBinderDiedLocked(); 329 } 330 mLock.notifyAll(); 331 } 332 } 333 334 @Override onServiceDisconnected(ComponentName name)335 public void onServiceDisconnected(ComponentName name) { 336 if (DEBUG_INSTANT) { 337 Slog.d(TAG, "Disconnected from instant app resolver"); 338 } 339 synchronized (mLock) { 340 handleBinderDiedLocked(); 341 } 342 } 343 } 344 345 private static final class GetInstantAppResolveInfoCaller 346 extends TimedRemoteCaller<List<InstantAppResolveInfo>> { 347 private final IRemoteCallback mCallback; 348 GetInstantAppResolveInfoCaller()349 public GetInstantAppResolveInfoCaller() { 350 super(CALL_SERVICE_TIMEOUT_MS); 351 mCallback = new IRemoteCallback.Stub() { 352 @Override 353 public void sendResult(Bundle data) throws RemoteException { 354 final ArrayList<InstantAppResolveInfo> resolveList = 355 data.getParcelableArrayList( 356 InstantAppResolverService.EXTRA_RESOLVE_INFO, android.content.pm.InstantAppResolveInfo.class); 357 int sequence = 358 data.getInt(InstantAppResolverService.EXTRA_SEQUENCE, -1); 359 onRemoteMethodResult(resolveList, sequence); 360 } 361 }; 362 } 363 getInstantAppResolveInfoList(IInstantAppResolver target, InstantAppRequestInfo request)364 public List<InstantAppResolveInfo> getInstantAppResolveInfoList(IInstantAppResolver target, 365 InstantAppRequestInfo request) throws RemoteException, TimeoutException { 366 final int sequence = onBeforeRemoteCall(); 367 target.getInstantAppResolveInfoList(request, sequence, mCallback); 368 return getResultTimed(sequence); 369 } 370 } 371 } 372