| OLD | NEW |
| (Empty) |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 package org.chromium.content.browser; | |
| 6 | |
| 7 import android.content.ComponentName; | |
| 8 import android.content.Context; | |
| 9 import android.content.Intent; | |
| 10 import android.content.ServiceConnection; | |
| 11 import android.content.pm.PackageManager; | |
| 12 import android.content.pm.ServiceInfo; | |
| 13 import android.os.Build; | |
| 14 import android.os.Bundle; | |
| 15 import android.os.IBinder; | |
| 16 import android.os.RemoteException; | |
| 17 | |
| 18 import org.chromium.base.Log; | |
| 19 import org.chromium.base.TraceEvent; | |
| 20 import org.chromium.base.VisibleForTesting; | |
| 21 import org.chromium.base.process_launcher.ChildProcessCreationParams; | |
| 22 import org.chromium.base.process_launcher.FileDescriptorInfo; | |
| 23 import org.chromium.base.process_launcher.ICallbackInt; | |
| 24 import org.chromium.base.process_launcher.IChildProcessService; | |
| 25 | |
| 26 import java.io.IOException; | |
| 27 | |
| 28 import javax.annotation.Nullable; | |
| 29 | |
| 30 /** | |
| 31 * Manages a connection between the browser activity and a child service. | |
| 32 */ | |
| 33 public abstract class BaseChildProcessConnection { | |
| 34 private static final String TAG = "BaseChildProcessConn"; | |
| 35 | |
| 36 /** | |
| 37 * Used to notify the consumer about disconnection of the service. This call
back is provided | |
| 38 * earlier than ConnectionCallbacks below, as a child process might die befo
re the connection is | |
| 39 * fully set up. | |
| 40 */ | |
| 41 interface DeathCallback { | |
| 42 // Called on Launcher thread. | |
| 43 void onChildProcessDied(BaseChildProcessConnection connection); | |
| 44 } | |
| 45 | |
| 46 /** | |
| 47 * Used to notify the consumer about the process start. These callbacks will
be invoked before | |
| 48 * the ConnectionCallbacks. | |
| 49 */ | |
| 50 interface StartCallback { | |
| 51 /** | |
| 52 * Called when the child process has successfully started and is ready f
or connection | |
| 53 * setup. | |
| 54 */ | |
| 55 void onChildStarted(); | |
| 56 | |
| 57 /** | |
| 58 * Called when the child process failed to start. This can happen if the
process is already | |
| 59 * in use by another client. | |
| 60 */ | |
| 61 void onChildStartFailed(); | |
| 62 } | |
| 63 | |
| 64 /** | |
| 65 * Used to notify the consumer about the connection being established. | |
| 66 */ | |
| 67 interface ConnectionCallback { | |
| 68 /** | |
| 69 * Called when the connection to the service is established. | |
| 70 * @param connecion the connection object to the child process | |
| 71 */ | |
| 72 void onConnected(BaseChildProcessConnection connection); | |
| 73 } | |
| 74 | |
| 75 /** Used to create specialization connection instances. */ | |
| 76 interface Factory { | |
| 77 BaseChildProcessConnection create(Context context, int number, boolean s
andboxed, | |
| 78 DeathCallback deathCallback, String serviceClassName, | |
| 79 Bundle childProcessCommonParameters, ChildProcessCreationParams
creationParams); | |
| 80 } | |
| 81 | |
| 82 /** Interface representing a connection to the Android service. Can be mocke
d in unit-tests. */ | |
| 83 protected interface ChildServiceConnection { | |
| 84 boolean bind(); | |
| 85 void unbind(); | |
| 86 boolean isBound(); | |
| 87 } | |
| 88 | |
| 89 /** Implementation of ChildServiceConnection that does connect to a service.
*/ | |
| 90 protected class ChildServiceConnectionImpl | |
| 91 implements ChildServiceConnection, ServiceConnection { | |
| 92 private final int mBindFlags; | |
| 93 private boolean mBound; | |
| 94 | |
| 95 private Intent createServiceBindIntent() { | |
| 96 Intent intent = new Intent(); | |
| 97 if (mCreationParams != null) { | |
| 98 mCreationParams.addIntentExtras(intent); | |
| 99 } | |
| 100 intent.setComponent(mServiceName); | |
| 101 return intent; | |
| 102 } | |
| 103 | |
| 104 private ChildServiceConnectionImpl(int bindFlags) { | |
| 105 mBindFlags = bindFlags; | |
| 106 } | |
| 107 | |
| 108 @Override | |
| 109 public boolean bind() { | |
| 110 if (!mBound) { | |
| 111 try { | |
| 112 TraceEvent.begin("BaseChildProcessConnection.ChildServiceCon
nection.bind"); | |
| 113 Intent intent = createServiceBindIntent(); | |
| 114 if (mChildProcessCommonParameters != null) { | |
| 115 intent.putExtras(mChildProcessCommonParameters); | |
| 116 } | |
| 117 mBound = mContext.bindService(intent, this, mBindFlags); | |
| 118 } finally { | |
| 119 TraceEvent.end("BaseChildProcessConnection.ChildServiceConne
ction.bind"); | |
| 120 } | |
| 121 } | |
| 122 return mBound; | |
| 123 } | |
| 124 | |
| 125 @Override | |
| 126 public void unbind() { | |
| 127 if (mBound) { | |
| 128 mContext.unbindService(this); | |
| 129 mBound = false; | |
| 130 } | |
| 131 } | |
| 132 | |
| 133 @Override | |
| 134 public boolean isBound() { | |
| 135 return mBound; | |
| 136 } | |
| 137 | |
| 138 @Override | |
| 139 public void onServiceConnected(ComponentName className, final IBinder se
rvice) { | |
| 140 LauncherThread.post(new Runnable() { | |
| 141 @Override | |
| 142 public void run() { | |
| 143 BaseChildProcessConnection.this.onServiceConnectedOnLauncher
Thread(service); | |
| 144 } | |
| 145 }); | |
| 146 } | |
| 147 | |
| 148 // Called on the main thread to notify that the child service did not di
sconnect gracefully. | |
| 149 @Override | |
| 150 public void onServiceDisconnected(ComponentName className) { | |
| 151 LauncherThread.post(new Runnable() { | |
| 152 @Override | |
| 153 public void run() { | |
| 154 BaseChildProcessConnection.this.onServiceDisconnectedOnLaunc
herThread(); | |
| 155 } | |
| 156 }); | |
| 157 } | |
| 158 } | |
| 159 | |
| 160 // Caches whether non-sandboxed and sandboxed services require an extra | |
| 161 // binding flag provided via ChildProcessCreationParams. | |
| 162 // TODO(mnaganov): Get rid of it after the release of the next Android SDK. | |
| 163 private static Boolean sNeedsExtrabindFlags[] = new Boolean[2]; | |
| 164 private final Context mContext; | |
| 165 private final int mServiceNumber; | |
| 166 private final boolean mSandboxed; | |
| 167 private final BaseChildProcessConnection.DeathCallback mDeathCallback; | |
| 168 private final ComponentName mServiceName; | |
| 169 | |
| 170 // Parameters passed to the child process through the service binding intent
. | |
| 171 // If the service gets recreated by the framework the intent will be reused,
so these parameters | |
| 172 // should be common to all processes of that type. | |
| 173 private final Bundle mChildProcessCommonParameters; | |
| 174 | |
| 175 private final ChildProcessCreationParams mCreationParams; | |
| 176 | |
| 177 private static class ConnectionParams { | |
| 178 final String[] mCommandLine; | |
| 179 final FileDescriptorInfo[] mFilesToBeMapped; | |
| 180 final IBinder mCallback; | |
| 181 | |
| 182 ConnectionParams( | |
| 183 String[] commandLine, FileDescriptorInfo[] filesToBeMapped, IBin
der callback) { | |
| 184 mCommandLine = commandLine; | |
| 185 mFilesToBeMapped = filesToBeMapped; | |
| 186 mCallback = callback; | |
| 187 } | |
| 188 } | |
| 189 | |
| 190 // This is set in start() and is used in onServiceConnected(). | |
| 191 private StartCallback mStartCallback; | |
| 192 | |
| 193 // This is set in setupConnection() and is later used in doConnectionSetupLo
cked(), after which | |
| 194 // the variable is cleared. Therefore this is only valid while the connectio
n is being set up. | |
| 195 private ConnectionParams mConnectionParams; | |
| 196 | |
| 197 // Callback provided in setupConnection() that will communicate the result t
o the caller. This | |
| 198 // has to be called exactly once after setupConnection(), even if setup fail
s, so that the | |
| 199 // caller can free up resources associated with the setup attempt. This is s
et to null after the | |
| 200 // call. | |
| 201 private ConnectionCallback mConnectionCallback; | |
| 202 | |
| 203 private IChildProcessService mService; | |
| 204 | |
| 205 // Set to true when the service connection callback runs. This differs from | |
| 206 // mServiceConnectComplete, which tracks that the connection completed succe
ssfully. | |
| 207 private boolean mDidOnServiceConnected; | |
| 208 | |
| 209 // Set to true when the service connected successfully. | |
| 210 private boolean mServiceConnectComplete; | |
| 211 | |
| 212 // Set to true when the service disconnects, as opposed to being properly cl
osed. This happens | |
| 213 // when the process crashes or gets killed by the system out-of-memory kille
r. | |
| 214 private boolean mServiceDisconnected; | |
| 215 | |
| 216 // Process ID of the corresponding child process. | |
| 217 private int mPid; | |
| 218 | |
| 219 protected BaseChildProcessConnection(Context context, int number, boolean sa
ndboxed, | |
| 220 DeathCallback deathCallback, String serviceClassName, | |
| 221 Bundle childProcessCommonParameters, ChildProcessCreationParams crea
tionParams) { | |
| 222 assert LauncherThread.runningOnLauncherThread(); | |
| 223 mContext = context; | |
| 224 mServiceNumber = number; | |
| 225 mSandboxed = sandboxed; | |
| 226 mDeathCallback = deathCallback; | |
| 227 String packageName = | |
| 228 creationParams != null ? creationParams.getPackageName() : conte
xt.getPackageName(); | |
| 229 mServiceName = new ComponentName(packageName, serviceClassName + mServic
eNumber); | |
| 230 mChildProcessCommonParameters = childProcessCommonParameters; | |
| 231 mCreationParams = creationParams; | |
| 232 } | |
| 233 | |
| 234 public final Context getContext() { | |
| 235 assert LauncherThread.runningOnLauncherThread(); | |
| 236 return mContext; | |
| 237 } | |
| 238 | |
| 239 public final int getServiceNumber() { | |
| 240 assert LauncherThread.runningOnLauncherThread(); | |
| 241 return mServiceNumber; | |
| 242 } | |
| 243 | |
| 244 public final boolean isSandboxed() { | |
| 245 assert LauncherThread.runningOnLauncherThread(); | |
| 246 return mSandboxed; | |
| 247 } | |
| 248 | |
| 249 public final String getPackageName() { | |
| 250 assert LauncherThread.runningOnLauncherThread(); | |
| 251 return mCreationParams != null ? mCreationParams.getPackageName() | |
| 252 : mContext.getPackageName(); | |
| 253 } | |
| 254 | |
| 255 public final ChildProcessCreationParams getCreationParams() { | |
| 256 assert LauncherThread.runningOnLauncherThread(); | |
| 257 return mCreationParams; | |
| 258 } | |
| 259 | |
| 260 public final IChildProcessService getService() { | |
| 261 assert LauncherThread.runningOnLauncherThread(); | |
| 262 return mService; | |
| 263 } | |
| 264 | |
| 265 public final ComponentName getServiceName() { | |
| 266 assert LauncherThread.runningOnLauncherThread(); | |
| 267 return mServiceName; | |
| 268 } | |
| 269 | |
| 270 /** | |
| 271 * @return the connection pid, or 0 if not yet connected | |
| 272 */ | |
| 273 public int getPid() { | |
| 274 assert LauncherThread.runningOnLauncherThread(); | |
| 275 return mPid; | |
| 276 } | |
| 277 | |
| 278 /** | |
| 279 * Starts a connection to an IChildProcessService. This must be followed by
a call to | |
| 280 * setupConnection() to setup the connection parameters. start() and setupCo
nnection() are | |
| 281 * separate to allow to pass whatever parameters are available in start(), a
nd complete the | |
| 282 * remainder later while reducing the connection setup latency. | |
| 283 * @param startCallback (optional) callback when the child process starts or
fails to start. | |
| 284 */ | |
| 285 public void start(StartCallback startCallback) { | |
| 286 assert LauncherThread.runningOnLauncherThread(); | |
| 287 try { | |
| 288 TraceEvent.begin("BaseChildProcessConnection.start"); | |
| 289 assert LauncherThread.runningOnLauncherThread(); | |
| 290 assert mConnectionParams | |
| 291 == null | |
| 292 : "setupConnection() called before start() in BaseChildProce
ssConnection."; | |
| 293 | |
| 294 mStartCallback = startCallback; | |
| 295 | |
| 296 if (!bind()) { | |
| 297 Log.e(TAG, "Failed to establish the service connection."); | |
| 298 // We have to notify the caller so that they can free-up associa
ted resources. | |
| 299 // TODO(ppi): Can we hard-fail here? | |
| 300 mDeathCallback.onChildProcessDied(BaseChildProcessConnection.thi
s); | |
| 301 } | |
| 302 } finally { | |
| 303 TraceEvent.end("BaseChildProcessConnection.start"); | |
| 304 } | |
| 305 } | |
| 306 | |
| 307 /** | |
| 308 * Setups the connection after it was started with start(). | |
| 309 * @param commandLine (optional) will be ignored if the command line was alr
eady sent in start() | |
| 310 * @param filesToBeMapped a list of file descriptors that should be register
ed | |
| 311 * @param callback optional client specified callbacks that the child can us
e to communicate | |
| 312 * with the parent process | |
| 313 * @param connectionCallback will be called exactly once after the connectio
n is set up or the | |
| 314 * setup fails | |
| 315 */ | |
| 316 public void setupConnection(String[] commandLine, FileDescriptorInfo[] files
ToBeMapped, | |
| 317 @Nullable IBinder callback, ConnectionCallback connectionCallback) { | |
| 318 assert LauncherThread.runningOnLauncherThread(); | |
| 319 assert mConnectionParams == null; | |
| 320 if (mServiceDisconnected) { | |
| 321 Log.w(TAG, "Tried to setup a connection that already disconnected.")
; | |
| 322 connectionCallback.onConnected(null); | |
| 323 return; | |
| 324 } | |
| 325 try { | |
| 326 TraceEvent.begin("BaseChildProcessConnection.setupConnection"); | |
| 327 mConnectionCallback = connectionCallback; | |
| 328 mConnectionParams = new ConnectionParams(commandLine, filesToBeMappe
d, callback); | |
| 329 // Run the setup if the service is already connected. If not, | |
| 330 // doConnectionSetupLocked() will be called from onServiceConnected(
). | |
| 331 if (mServiceConnectComplete) { | |
| 332 doConnectionSetupLocked(); | |
| 333 } | |
| 334 } finally { | |
| 335 TraceEvent.end("BaseChildProcessConnection.setupConnection"); | |
| 336 } | |
| 337 } | |
| 338 | |
| 339 /** | |
| 340 * Terminates the connection to IChildProcessService, closing all bindings.
It is safe to call | |
| 341 * this multiple times. | |
| 342 */ | |
| 343 public void stop() { | |
| 344 assert LauncherThread.runningOnLauncherThread(); | |
| 345 unbind(); | |
| 346 mService = null; | |
| 347 mConnectionParams = null; | |
| 348 } | |
| 349 | |
| 350 private void onServiceConnectedOnLauncherThread(IBinder service) { | |
| 351 assert LauncherThread.runningOnLauncherThread(); | |
| 352 // A flag from the parent class ensures we run the post-connection logic
only once | |
| 353 // (instead of once per each ChildServiceConnection). | |
| 354 if (mDidOnServiceConnected) { | |
| 355 return; | |
| 356 } | |
| 357 try { | |
| 358 TraceEvent.begin( | |
| 359 "BaseChildProcessConnection.ChildServiceConnection.onService
Connected"); | |
| 360 mDidOnServiceConnected = true; | |
| 361 mService = IChildProcessService.Stub.asInterface(service); | |
| 362 | |
| 363 StartCallback startCallback = mStartCallback; | |
| 364 mStartCallback = null; | |
| 365 | |
| 366 final boolean bindCheck = | |
| 367 mCreationParams != null && mCreationParams.getBindToCallerCh
eck(); | |
| 368 boolean boundToUs = false; | |
| 369 try { | |
| 370 boundToUs = bindCheck ? mService.bindToCaller() : true; | |
| 371 } catch (RemoteException ex) { | |
| 372 // Do not trigger the StartCallback here, since the service is a
lready | |
| 373 // dead and the DeathCallback will run from onServiceDisconnecte
d(). | |
| 374 Log.e(TAG, "Failed to bind service to connection.", ex); | |
| 375 return; | |
| 376 } | |
| 377 | |
| 378 if (startCallback != null) { | |
| 379 if (boundToUs) { | |
| 380 startCallback.onChildStarted(); | |
| 381 } else { | |
| 382 startCallback.onChildStartFailed(); | |
| 383 } | |
| 384 } | |
| 385 | |
| 386 if (!boundToUs) { | |
| 387 return; | |
| 388 } | |
| 389 | |
| 390 mServiceConnectComplete = true; | |
| 391 | |
| 392 // Run the setup if the connection parameters have already been prov
ided. If | |
| 393 // not, doConnectionSetupLocked() will be called from setupConnectio
n(). | |
| 394 if (mConnectionParams != null) { | |
| 395 doConnectionSetupLocked(); | |
| 396 } | |
| 397 } finally { | |
| 398 TraceEvent.end("BaseChildProcessConnection.ChildServiceConnection.on
ServiceConnected"); | |
| 399 } | |
| 400 } | |
| 401 | |
| 402 private void onServiceDisconnectedOnLauncherThread() { | |
| 403 assert LauncherThread.runningOnLauncherThread(); | |
| 404 // Ensure that the disconnection logic runs only once (instead of once p
er each | |
| 405 // ChildServiceConnection). | |
| 406 if (mServiceDisconnected) { | |
| 407 return; | |
| 408 } | |
| 409 mServiceDisconnected = true; | |
| 410 Log.w(TAG, "onServiceDisconnected (crash or killed by oom): pid=%d", mPi
d); | |
| 411 stop(); // We don't want to auto-restart on crash. Let the browser do th
at. | |
| 412 mDeathCallback.onChildProcessDied(BaseChildProcessConnection.this); | |
| 413 // If we have a pending connection callback, we need to communicate the
failure to | |
| 414 // the caller. | |
| 415 if (mConnectionCallback != null) { | |
| 416 mConnectionCallback.onConnected(null); | |
| 417 } | |
| 418 mConnectionCallback = null; | |
| 419 } | |
| 420 | |
| 421 private void onSetupConnectionResult(int pid) { | |
| 422 mPid = pid; | |
| 423 assert mPid != 0 : "Child service claims to be run by a process of pid=0
."; | |
| 424 | |
| 425 if (mConnectionCallback != null) { | |
| 426 mConnectionCallback.onConnected(this); | |
| 427 } | |
| 428 mConnectionCallback = null; | |
| 429 } | |
| 430 | |
| 431 /** | |
| 432 * Called after the connection parameters have been set (in setupConnection(
)) *and* a | |
| 433 * connection has been established (as signaled by onServiceConnected()). Th
ese two events can | |
| 434 * happen in any order. Has to be called with mLock. | |
| 435 */ | |
| 436 private void doConnectionSetupLocked() { | |
| 437 try { | |
| 438 TraceEvent.begin("BaseChildProcessConnection.doConnectionSetupLocked
"); | |
| 439 assert mServiceConnectComplete && mService != null; | |
| 440 assert mConnectionParams != null; | |
| 441 | |
| 442 Bundle bundle = ChildProcessLauncher.createsServiceBundle( | |
| 443 mConnectionParams.mCommandLine, mConnectionParams.mFilesToBe
Mapped); | |
| 444 ICallbackInt pidCallback = new ICallbackInt.Stub() { | |
| 445 @Override | |
| 446 public void call(final int pid) { | |
| 447 LauncherThread.post(new Runnable() { | |
| 448 @Override | |
| 449 public void run() { | |
| 450 onSetupConnectionResult(pid); | |
| 451 } | |
| 452 }); | |
| 453 } | |
| 454 }; | |
| 455 try { | |
| 456 mService.setupConnection(bundle, pidCallback, mConnectionParams.
mCallback); | |
| 457 } catch (RemoteException re) { | |
| 458 Log.e(TAG, "Failed to setup connection.", re); | |
| 459 } | |
| 460 // We proactively close the FDs rather than wait for GC & finalizer. | |
| 461 try { | |
| 462 for (FileDescriptorInfo fileInfo : mConnectionParams.mFilesToBeM
apped) { | |
| 463 fileInfo.fd.close(); | |
| 464 } | |
| 465 } catch (IOException ioe) { | |
| 466 Log.w(TAG, "Failed to close FD.", ioe); | |
| 467 } | |
| 468 mConnectionParams = null; | |
| 469 } finally { | |
| 470 TraceEvent.end("BaseChildProcessConnection.doConnectionSetupLocked")
; | |
| 471 } | |
| 472 } | |
| 473 | |
| 474 /** Subclasses should implement this method to bind/unbind to the actual ser
vice. */ | |
| 475 protected abstract boolean bind(); | |
| 476 protected abstract void unbind(); | |
| 477 | |
| 478 protected ChildServiceConnection createServiceConnection(int bindFlags) { | |
| 479 assert LauncherThread.runningOnLauncherThread(); | |
| 480 return new ChildServiceConnectionImpl(bindFlags); | |
| 481 } | |
| 482 | |
| 483 protected boolean shouldBindAsExportedService() { | |
| 484 assert LauncherThread.runningOnLauncherThread(); | |
| 485 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && getCreationPara
ms() != null | |
| 486 && getCreationParams().getIsExternalService() | |
| 487 && isExportedService(isSandboxed(), getContext(), getServiceName
()); | |
| 488 } | |
| 489 | |
| 490 private static boolean isExportedService( | |
| 491 boolean inSandbox, Context context, ComponentName serviceName) { | |
| 492 // Check for the cached value first. It is assumed that all pooled child
services | |
| 493 // have identical attributes in the manifest. | |
| 494 final int arrayIndex = inSandbox ? 1 : 0; | |
| 495 if (sNeedsExtrabindFlags[arrayIndex] != null) { | |
| 496 return sNeedsExtrabindFlags[arrayIndex].booleanValue(); | |
| 497 } | |
| 498 boolean result = false; | |
| 499 try { | |
| 500 PackageManager packageManager = context.getPackageManager(); | |
| 501 ServiceInfo serviceInfo = packageManager.getServiceInfo(serviceName,
0); | |
| 502 result = serviceInfo.exported; | |
| 503 } catch (PackageManager.NameNotFoundException e) { | |
| 504 Log.e(TAG, "Could not retrieve info about service %s", serviceName,
e); | |
| 505 } | |
| 506 sNeedsExtrabindFlags[arrayIndex] = Boolean.valueOf(result); | |
| 507 return result; | |
| 508 } | |
| 509 | |
| 510 @VisibleForTesting | |
| 511 public void crashServiceForTesting() throws RemoteException { | |
| 512 mService.crashIntentionallyForTesting(); | |
| 513 } | |
| 514 | |
| 515 @VisibleForTesting | |
| 516 boolean isConnected() { | |
| 517 return mService != null; | |
| 518 } | |
| 519 } | |
| OLD | NEW |