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, int userId, String token)88 public List<InstantAppResolveInfo> getInstantAppResolveInfoList(Intent sanitizedIntent, 89 int[] hashPrefix, int userId, 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, userId, 103 token); 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(Intent sanitizedIntent, int[] hashPrefix, int userId, String token, PhaseTwoCallback callback, Handler callbackHandler, final long startTime)116 public void getInstantAppIntentFilterList(Intent sanitizedIntent, int[] hashPrefix, int userId, 117 String token, 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(token) 130 .getInstantAppIntentFilterList(sanitizedIntent, hashPrefix, userId, token, 131 remoteCallback); 132 } catch (TimeoutException e) { 133 throw new ConnectionException(ConnectionException.FAILURE_BIND); 134 } catch (InterruptedException e) { 135 throw new ConnectionException(ConnectionException.FAILURE_INTERRUPTED); 136 } catch (RemoteException ignore) { 137 } 138 } 139 140 @WorkerThread getRemoteInstanceLazy(String token)141 private IInstantAppResolver getRemoteInstanceLazy(String token) 142 throws ConnectionException, TimeoutException, InterruptedException { 143 long binderToken = Binder.clearCallingIdentity(); 144 try { 145 return bind(token); 146 } finally { 147 Binder.restoreCallingIdentity(binderToken); 148 } 149 } 150 151 @GuardedBy("mLock") waitForBindLocked(String token)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 @WorkerThread bind(String token)168 private IInstantAppResolver bind(String token) 169 throws ConnectionException, TimeoutException, InterruptedException { 170 boolean doUnbind = false; 171 synchronized (mLock) { 172 if (mRemoteInstance != null) { 173 return mRemoteInstance; 174 } 175 176 if (mBindState == STATE_PENDING) { 177 // there is a pending bind, let's see if we can use it. 178 if (DEBUG_INSTANT) { 179 Slog.i(TAG, "[" + token + "] Previous bind timed out; waiting for connection"); 180 } 181 try { 182 waitForBindLocked(token); 183 if (mRemoteInstance != null) { 184 return mRemoteInstance; 185 } 186 } catch (TimeoutException e) { 187 // nope, we might have to try a rebind. 188 doUnbind = true; 189 } 190 } 191 192 if (mBindState == STATE_BINDING) { 193 // someone was binding when we called bind(), or they raced ahead while we were 194 // waiting in the PENDING case; wait for their result instead. Last chance! 195 if (DEBUG_INSTANT) { 196 Slog.i(TAG, "[" + token + "] Another thread is binding; waiting for connection"); 197 } 198 waitForBindLocked(token); 199 // if the other thread's bindService() returned false, we could still have null. 200 if (mRemoteInstance != null) { 201 return mRemoteInstance; 202 } 203 throw new ConnectionException(ConnectionException.FAILURE_BIND); 204 } 205 mBindState = STATE_BINDING; // our time to shine! :) 206 } 207 208 // only one thread can be here at a time (the one that set STATE_BINDING) 209 boolean wasBound = false; 210 IInstantAppResolver instance = null; 211 try { 212 if (doUnbind) { 213 if (DEBUG_INSTANT) { 214 Slog.i(TAG, "[" + token + "] Previous connection never established; rebinding"); 215 } 216 mContext.unbindService(mServiceConnection); 217 } 218 if (DEBUG_INSTANT) { 219 Slog.v(TAG, "[" + token + "] Binding to instant app resolver"); 220 } 221 final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE; 222 wasBound = mContext 223 .bindServiceAsUser(mIntent, mServiceConnection, flags, UserHandle.SYSTEM); 224 if (wasBound) { 225 synchronized (mLock) { 226 waitForBindLocked(token); 227 instance = mRemoteInstance; 228 return instance; 229 } 230 } else { 231 Slog.w(TAG, "[" + token + "] Failed to bind to: " + mIntent); 232 throw new ConnectionException(ConnectionException.FAILURE_BIND); 233 } 234 } finally { 235 synchronized (mLock) { 236 if (wasBound && instance == null) { 237 mBindState = STATE_PENDING; 238 } else { 239 mBindState = STATE_IDLE; 240 } 241 mLock.notifyAll(); 242 } 243 } 244 } 245 throwIfCalledOnMainThread()246 private void throwIfCalledOnMainThread() { 247 if (Thread.currentThread() == mContext.getMainLooper().getThread()) { 248 throw new RuntimeException("Cannot invoke on the main thread"); 249 } 250 } 251 252 @AnyThread optimisticBind()253 void optimisticBind() { 254 mBgHandler.post(() -> { 255 try { 256 if (bind("Optimistic Bind") != null && DEBUG_INSTANT) { 257 Slog.i(TAG, "Optimistic bind succeeded."); 258 } 259 } catch (ConnectionException | TimeoutException | InterruptedException e) { 260 Slog.e(TAG, "Optimistic bind failed.", e); 261 } 262 }); 263 } 264 265 @Override binderDied()266 public void binderDied() { 267 if (DEBUG_INSTANT) { 268 Slog.d(TAG, "Binder to instant app resolver died"); 269 } 270 synchronized (mLock) { 271 handleBinderDiedLocked(); 272 } 273 optimisticBind(); 274 } 275 276 @GuardedBy("mLock") handleBinderDiedLocked()277 private void handleBinderDiedLocked() { 278 if (mRemoteInstance != null) { 279 try { 280 mRemoteInstance.asBinder().unlinkToDeath(this, 0 /*flags*/); 281 } catch (NoSuchElementException ignore) { } 282 } 283 mRemoteInstance = null; 284 } 285 286 /** 287 * Asynchronous callback when results come back from ephemeral resolution phase two. 288 */ 289 public abstract static class PhaseTwoCallback { onPhaseTwoResolved( List<InstantAppResolveInfo> instantAppResolveInfoList, long startTime)290 abstract void onPhaseTwoResolved( 291 List<InstantAppResolveInfo> instantAppResolveInfoList, long startTime); 292 } 293 294 public static class ConnectionException extends Exception { 295 public static final int FAILURE_BIND = 1; 296 public static final int FAILURE_CALL = 2; 297 public static final int FAILURE_INTERRUPTED = 3; 298 299 public final int failure; ConnectionException(int _failure)300 public ConnectionException(int _failure) { 301 failure = _failure; 302 } 303 } 304 305 private final class MyServiceConnection implements ServiceConnection { 306 @Override onServiceConnected(ComponentName name, IBinder service)307 public void onServiceConnected(ComponentName name, IBinder service) { 308 if (DEBUG_INSTANT) { 309 Slog.d(TAG, "Connected to instant app resolver"); 310 } 311 synchronized (mLock) { 312 mRemoteInstance = IInstantAppResolver.Stub.asInterface(service); 313 if (mBindState == STATE_PENDING) { 314 mBindState = STATE_IDLE; 315 } 316 try { 317 service.linkToDeath(InstantAppResolverConnection.this, 0 /*flags*/); 318 } catch (RemoteException e) { 319 handleBinderDiedLocked(); 320 } 321 mLock.notifyAll(); 322 } 323 } 324 325 @Override onServiceDisconnected(ComponentName name)326 public void onServiceDisconnected(ComponentName name) { 327 if (DEBUG_INSTANT) { 328 Slog.d(TAG, "Disconnected from instant app resolver"); 329 } 330 synchronized (mLock) { 331 handleBinderDiedLocked(); 332 } 333 } 334 } 335 336 private static final class GetInstantAppResolveInfoCaller 337 extends TimedRemoteCaller<List<InstantAppResolveInfo>> { 338 private final IRemoteCallback mCallback; 339 GetInstantAppResolveInfoCaller()340 public GetInstantAppResolveInfoCaller() { 341 super(CALL_SERVICE_TIMEOUT_MS); 342 mCallback = new IRemoteCallback.Stub() { 343 @Override 344 public void sendResult(Bundle data) throws RemoteException { 345 final ArrayList<InstantAppResolveInfo> resolveList = 346 data.getParcelableArrayList( 347 InstantAppResolverService.EXTRA_RESOLVE_INFO); 348 int sequence = 349 data.getInt(InstantAppResolverService.EXTRA_SEQUENCE, -1); 350 onRemoteMethodResult(resolveList, sequence); 351 } 352 }; 353 } 354 getInstantAppResolveInfoList( IInstantAppResolver target, Intent sanitizedIntent, int[] hashPrefix, int userId, String token)355 public List<InstantAppResolveInfo> getInstantAppResolveInfoList( 356 IInstantAppResolver target, Intent sanitizedIntent, int[] hashPrefix, int userId, 357 String token) throws RemoteException, TimeoutException { 358 final int sequence = onBeforeRemoteCall(); 359 target.getInstantAppResolveInfoList(sanitizedIntent, hashPrefix, userId, token, 360 sequence, mCallback); 361 return getResultTimed(sequence); 362 } 363 } 364 } 365