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); 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 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 throwIfCalledOnMainThread()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 optimisticBind()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 binderDied()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") handleBinderDiedLocked()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 { onPhaseTwoResolved( List<InstantAppResolveInfo> instantAppResolveInfoList, long startTime)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; ConnectionException(int _failure)299 public ConnectionException(int _failure) { 300 failure = _failure; 301 } 302 } 303 304 private final class MyServiceConnection implements ServiceConnection { 305 @Override onServiceConnected(ComponentName name, IBinder service)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 onServiceDisconnected(ComponentName name)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 GetInstantAppResolveInfoCaller()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 getInstantAppResolveInfoList(IInstantAppResolver target, InstantAppRequestInfo request)354 public List<InstantAppResolveInfo> getInstantAppResolveInfoList(IInstantAppResolver target, 355 InstantAppRequestInfo request) throws RemoteException, TimeoutException { 356 final int sequence = onBeforeRemoteCall(); 357 target.getInstantAppResolveInfoList(request, sequence, mCallback); 358 return getResultTimed(sequence); 359 } 360 } 361 } 362