InstantAppResolverConnection.java revision 5564f880db3292327872a07df8e230eee78be14b
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 17package com.android.server.pm; 18 19import android.app.IInstantAppResolver; 20import android.app.InstantAppResolverService; 21import android.content.ComponentName; 22import android.content.Context; 23import android.content.Intent; 24import android.content.ServiceConnection; 25import android.content.pm.InstantAppResolveInfo; 26import android.os.Binder; 27import android.os.Build; 28import android.os.Bundle; 29import android.os.Handler; 30import android.os.IBinder; 31import android.os.IBinder.DeathRecipient; 32import android.os.IRemoteCallback; 33import android.os.RemoteException; 34import android.os.SystemClock; 35import android.os.UserHandle; 36import android.util.Slog; 37import android.util.TimedRemoteCaller; 38 39import com.android.internal.annotations.GuardedBy; 40 41import java.util.ArrayList; 42import java.util.List; 43import java.util.NoSuchElementException; 44import java.util.concurrent.TimeoutException; 45 46/** 47 * Represents a remote instant app resolver. It is responsible for binding to the remote 48 * service and handling all interactions in a timely manner. 49 * @hide 50 */ 51final class InstantAppResolverConnection implements DeathRecipient { 52 private static final String TAG = "PackageManager"; 53 // This is running in a critical section and the timeout must be sufficiently low 54 private static final long BIND_SERVICE_TIMEOUT_MS = 55 Build.IS_ENG ? 500 : 300; 56 private static final long CALL_SERVICE_TIMEOUT_MS = 57 Build.IS_ENG ? 200 : 100; 58 private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE; 59 60 private final Object mLock = new Object(); 61 private final GetInstantAppResolveInfoCaller mGetInstantAppResolveInfoCaller = 62 new GetInstantAppResolveInfoCaller(); 63 private final ServiceConnection mServiceConnection = new MyServiceConnection(); 64 private final Context mContext; 65 /** Intent used to bind to the service */ 66 private final Intent mIntent; 67 68 private static final int STATE_IDLE = 0; // no bind operation is ongoing 69 private static final int STATE_BINDING = 1; // someone is binding and waiting 70 private static final int STATE_PENDING = 2; // a bind is pending, but the caller is not waiting 71 72 @GuardedBy("mLock") 73 private int mBindState = STATE_IDLE; 74 @GuardedBy("mLock") 75 private IInstantAppResolver mRemoteInstance; 76 77 public InstantAppResolverConnection( 78 Context context, ComponentName componentName, String action) { 79 mContext = context; 80 mIntent = new Intent(action).setComponent(componentName); 81 } 82 83 public final List<InstantAppResolveInfo> getInstantAppResolveInfoList(Intent sanitizedIntent, 84 int hashPrefix[], String token) throws ConnectionException { 85 throwIfCalledOnMainThread(); 86 IInstantAppResolver target = null; 87 try { 88 try { 89 target = getRemoteInstanceLazy(token); 90 } catch (TimeoutException e) { 91 throw new ConnectionException(ConnectionException.FAILURE_BIND); 92 } catch (InterruptedException e) { 93 throw new ConnectionException(ConnectionException.FAILURE_INTERRUPTED); 94 } 95 try { 96 return mGetInstantAppResolveInfoCaller 97 .getInstantAppResolveInfoList(target, sanitizedIntent, hashPrefix, token); 98 } catch (TimeoutException e) { 99 throw new ConnectionException(ConnectionException.FAILURE_CALL); 100 } catch (RemoteException ignore) { 101 } 102 } finally { 103 synchronized (mLock) { 104 mLock.notifyAll(); 105 } 106 } 107 return null; 108 } 109 110 public final void getInstantAppIntentFilterList(Intent sanitizedIntent, int hashPrefix[], 111 String token, PhaseTwoCallback callback, Handler callbackHandler, final long startTime) 112 throws ConnectionException { 113 final IRemoteCallback remoteCallback = new IRemoteCallback.Stub() { 114 @Override 115 public void sendResult(Bundle data) throws RemoteException { 116 final ArrayList<InstantAppResolveInfo> resolveList = 117 data.getParcelableArrayList( 118 InstantAppResolverService.EXTRA_RESOLVE_INFO); 119 callbackHandler.post(() -> callback.onPhaseTwoResolved(resolveList, startTime)); 120 } 121 }; 122 try { 123 getRemoteInstanceLazy(token) 124 .getInstantAppIntentFilterList(sanitizedIntent, hashPrefix, token, 125 remoteCallback); 126 } catch (TimeoutException e) { 127 throw new ConnectionException(ConnectionException.FAILURE_BIND); 128 } catch (InterruptedException e) { 129 throw new ConnectionException(ConnectionException.FAILURE_INTERRUPTED); 130 } catch (RemoteException ignore) { 131 } 132 } 133 134 private IInstantAppResolver getRemoteInstanceLazy(String token) 135 throws ConnectionException, TimeoutException, InterruptedException { 136 long binderToken = Binder.clearCallingIdentity(); 137 try { 138 return bind(token); 139 } finally { 140 Binder.restoreCallingIdentity(binderToken); 141 } 142 } 143 144 private void waitForBindLocked(String token) throws TimeoutException, InterruptedException { 145 final long startMillis = SystemClock.uptimeMillis(); 146 while (mBindState != STATE_IDLE) { 147 if (mRemoteInstance != null) { 148 break; 149 } 150 final long elapsedMillis = SystemClock.uptimeMillis() - startMillis; 151 final long remainingMillis = BIND_SERVICE_TIMEOUT_MS - elapsedMillis; 152 if (remainingMillis <= 0) { 153 throw new TimeoutException("[" + token + "] Didn't bind to resolver in time!"); 154 } 155 mLock.wait(remainingMillis); 156 } 157 } 158 159 private IInstantAppResolver bind(String token) 160 throws ConnectionException, TimeoutException, InterruptedException { 161 boolean doUnbind = false; 162 synchronized (mLock) { 163 if (mRemoteInstance != null) { 164 return mRemoteInstance; 165 } 166 167 if (mBindState == STATE_PENDING) { 168 // there is a pending bind, let's see if we can use it. 169 if (DEBUG_INSTANT) { 170 Slog.i(TAG, "[" + token + "] Previous bind timed out; waiting for connection"); 171 } 172 try { 173 waitForBindLocked(token); 174 if (mRemoteInstance != null) { 175 return mRemoteInstance; 176 } 177 } catch (TimeoutException e) { 178 // nope, we might have to try a rebind. 179 doUnbind = true; 180 } 181 } 182 183 if (mBindState == STATE_BINDING) { 184 // someone was binding when we called bind(), or they raced ahead while we were 185 // waiting in the PENDING case; wait for their result instead. Last chance! 186 if (DEBUG_INSTANT) { 187 Slog.i(TAG, "[" + token + "] Another thread is binding; waiting for connection"); 188 } 189 waitForBindLocked(token); 190 // if the other thread's bindService() returned false, we could still have null. 191 if (mRemoteInstance != null) { 192 return mRemoteInstance; 193 } 194 throw new ConnectionException(ConnectionException.FAILURE_BIND); 195 } 196 mBindState = STATE_BINDING; // our time to shine! :) 197 } 198 199 // only one thread can be here at a time (the one that set STATE_BINDING) 200 boolean wasBound = false; 201 IInstantAppResolver instance = null; 202 try { 203 if (doUnbind) { 204 if (DEBUG_INSTANT) { 205 Slog.i(TAG, "[" + token + "] Previous connection never established; rebinding"); 206 } 207 mContext.unbindService(mServiceConnection); 208 } 209 if (DEBUG_INSTANT) { 210 Slog.v(TAG, "[" + token + "] Binding to instant app resolver"); 211 } 212 final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE; 213 wasBound = mContext 214 .bindServiceAsUser(mIntent, mServiceConnection, flags, UserHandle.SYSTEM); 215 if (wasBound) { 216 synchronized (mLock) { 217 waitForBindLocked(token); 218 instance = mRemoteInstance; 219 return instance; 220 } 221 } else { 222 Slog.w(TAG, "[" + token + "] Failed to bind to: " + mIntent); 223 throw new ConnectionException(ConnectionException.FAILURE_BIND); 224 } 225 } finally { 226 synchronized (mLock) { 227 if (wasBound && instance == null) { 228 mBindState = STATE_PENDING; 229 } else { 230 mBindState = STATE_IDLE; 231 } 232 mLock.notifyAll(); 233 } 234 } 235 } 236 237 private void throwIfCalledOnMainThread() { 238 if (Thread.currentThread() == mContext.getMainLooper().getThread()) { 239 throw new RuntimeException("Cannot invoke on the main thread"); 240 } 241 } 242 243 @Override 244 public void binderDied() { 245 if (DEBUG_INSTANT) { 246 Slog.d(TAG, "Binder to instant app resolver died"); 247 } 248 synchronized (mLock) { 249 handleBinderDiedLocked(); 250 } 251 } 252 253 private void handleBinderDiedLocked() { 254 if (mRemoteInstance != null) { 255 try { 256 mRemoteInstance.asBinder().unlinkToDeath(this, 0 /*flags*/); 257 } catch (NoSuchElementException ignore) { } 258 } 259 mRemoteInstance = null; 260 } 261 262 /** 263 * Asynchronous callback when results come back from ephemeral resolution phase two. 264 */ 265 public abstract static class PhaseTwoCallback { 266 abstract void onPhaseTwoResolved( 267 List<InstantAppResolveInfo> instantAppResolveInfoList, long startTime); 268 } 269 270 public static class ConnectionException extends Exception { 271 public static final int FAILURE_BIND = 1; 272 public static final int FAILURE_CALL = 2; 273 public static final int FAILURE_INTERRUPTED = 3; 274 275 public final int failure; 276 public ConnectionException(int _failure) { 277 failure = _failure; 278 } 279 } 280 281 private final class MyServiceConnection implements ServiceConnection { 282 @Override 283 public void onServiceConnected(ComponentName name, IBinder service) { 284 if (DEBUG_INSTANT) { 285 Slog.d(TAG, "Connected to instant app resolver"); 286 } 287 synchronized (mLock) { 288 mRemoteInstance = IInstantAppResolver.Stub.asInterface(service); 289 if (mBindState == STATE_PENDING) { 290 mBindState = STATE_IDLE; 291 } 292 try { 293 service.linkToDeath(InstantAppResolverConnection.this, 0 /*flags*/); 294 } catch (RemoteException e) { 295 handleBinderDiedLocked(); 296 } 297 mLock.notifyAll(); 298 } 299 } 300 301 @Override 302 public void onServiceDisconnected(ComponentName name) { 303 if (DEBUG_INSTANT) { 304 Slog.d(TAG, "Disconnected from instant app resolver"); 305 } 306 synchronized (mLock) { 307 handleBinderDiedLocked(); 308 } 309 } 310 } 311 312 private static final class GetInstantAppResolveInfoCaller 313 extends TimedRemoteCaller<List<InstantAppResolveInfo>> { 314 private final IRemoteCallback mCallback; 315 316 public GetInstantAppResolveInfoCaller() { 317 super(CALL_SERVICE_TIMEOUT_MS); 318 mCallback = new IRemoteCallback.Stub() { 319 @Override 320 public void sendResult(Bundle data) throws RemoteException { 321 final ArrayList<InstantAppResolveInfo> resolveList = 322 data.getParcelableArrayList( 323 InstantAppResolverService.EXTRA_RESOLVE_INFO); 324 int sequence = 325 data.getInt(InstantAppResolverService.EXTRA_SEQUENCE, -1); 326 onRemoteMethodResult(resolveList, sequence); 327 } 328 }; 329 } 330 331 public List<InstantAppResolveInfo> getInstantAppResolveInfoList( 332 IInstantAppResolver target, Intent sanitizedIntent, int hashPrefix[], String token) 333 throws RemoteException, TimeoutException { 334 final int sequence = onBeforeRemoteCall(); 335 target.getInstantAppResolveInfoList(sanitizedIntent, hashPrefix, token, sequence, 336 mCallback); 337 return getResultTimed(sequence); 338 } 339 } 340} 341