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 InstantAppResolverConnection( Context context, ComponentName componentName, String action)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 getInstantAppResolveInfoList(Intent sanitizedIntent, int hashPrefix[], String token)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 getInstantAppIntentFilterList(Intent sanitizedIntent, int hashPrefix[], String token, PhaseTwoCallback callback, Handler callbackHandler, final long startTime)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 getRemoteInstanceLazy(String token)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") 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, Intent sanitizedIntent, int hashPrefix[], String token)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