OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2012 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.os.AsyncTask; |
| 12 import android.os.Bundle; |
| 13 import android.os.Handler; |
| 14 import android.os.IBinder; |
| 15 import android.os.Looper; |
| 16 import android.os.ParcelFileDescriptor; |
| 17 import android.util.Log; |
| 18 |
| 19 import java.util.concurrent.atomic.AtomicBoolean; |
| 20 |
| 21 import org.chromium.base.CalledByNative; |
| 22 import org.chromium.content.app.SandboxedProcessService; |
| 23 import org.chromium.content.browser.CommandLine; |
| 24 import org.chromium.content.common.ISandboxedProcessCallback; |
| 25 import org.chromium.content.common.ISandboxedProcessService; |
| 26 // TODO(michaelbai): Move to org.chromium.content.commnon. |
| 27 import org.chromium.content.browser.TraceEvent; |
| 28 |
| 29 public class SandboxedProcessConnection { |
| 30 interface DeathCallback { |
| 31 void onSandboxedProcessDied(int pid); |
| 32 } |
| 33 |
| 34 // Names of items placed in the bind intent or connection bundle. |
| 35 public static final String EXTRA_COMMAND_LINE = |
| 36 "com.google.android.apps.chrome.extra.sandbox_command_line"; |
| 37 // Note the FD may only be passed in the connection bundle. |
| 38 public static final String EXTRA_IPC_FD = "com.google.android.apps.chrome.ex
tra.sandbox_ipcFd"; |
| 39 public static final String EXTRA_CRASH_FD = |
| 40 "com.google.android.apps.chrome.extra.sandbox_crashFd"; |
| 41 public static final String EXTRA_CHROME_PAK_FD = |
| 42 "com.google.android.apps.chrome.extra.sandbox_chromePakFd"; |
| 43 public static final String EXTRA_LOCALE_PAK_FD = |
| 44 "com.google.android.apps.chrome.extra.sandbox_localePakFd"; |
| 45 |
| 46 private final Context mContext; |
| 47 private final int mServiceNumber; |
| 48 private final SandboxedProcessConnection.DeathCallback mDeathCallback; |
| 49 |
| 50 // Synchronization: While most internal flow occurs on the UI thread, the pu
blic API |
| 51 // (specifically bind and unbind) may be called from any thread, hence all e
ntry point methods |
| 52 // into the class are synchronized on the SandboxedProcessConnection instanc
e to protect access |
| 53 // to these members. But see also the TODO where AsyncBoundServiceConnection
is created. |
| 54 private ISandboxedProcessService mService = null; |
| 55 private boolean mServiceConnectComplete = false; |
| 56 private int mPID = 0; // Process ID of the corresponding sandboxed process. |
| 57 private HighPriorityConnection mHighPriorityConnection = null; |
| 58 private int mHighPriorityConnectionCount = 0; |
| 59 |
| 60 private static final String TAG = "SandboxedProcessConnection"; |
| 61 |
| 62 private static class ConnectionParams { |
| 63 final String[] mCommandLine; |
| 64 final int mIpcFd; |
| 65 final int mCrashFd; |
| 66 final int mChromePakFd; |
| 67 final int mLocalePakFd; |
| 68 final ISandboxedProcessCallback mCallback; |
| 69 final Runnable mOnConnectionCallback; |
| 70 |
| 71 ConnectionParams( |
| 72 String[] commandLine, |
| 73 int ipcFd, |
| 74 int crashFd, |
| 75 int chromePakFd, |
| 76 int localePakFd, |
| 77 ISandboxedProcessCallback callback, |
| 78 Runnable onConnectionCallback) { |
| 79 mCommandLine = commandLine; |
| 80 mIpcFd = ipcFd; |
| 81 mCrashFd = crashFd; |
| 82 mChromePakFd = chromePakFd; |
| 83 mLocalePakFd = localePakFd; |
| 84 mCallback = callback; |
| 85 mOnConnectionCallback = onConnectionCallback; |
| 86 } |
| 87 }; |
| 88 |
| 89 // Implement the ServiceConnection as an inner class, so it can stem the ser
vice |
| 90 // callbacks when cancelled or unbound. |
| 91 class AsyncBoundServiceConnection extends AsyncTask<Intent, Void, Boolean> |
| 92 implements ServiceConnection { |
| 93 private boolean mIsDestroyed = false; |
| 94 private AtomicBoolean mIsBound = new AtomicBoolean(false); |
| 95 |
| 96 // AsyncTask |
| 97 @Override |
| 98 protected Boolean doInBackground(Intent... intents) { |
| 99 boolean isBound = mContext.bindService(intents[0], this, Context.BIN
D_AUTO_CREATE); |
| 100 mIsBound.set(isBound); |
| 101 return isBound; |
| 102 } |
| 103 |
| 104 @Override |
| 105 protected void onPostExecute(Boolean boundOK) { |
| 106 synchronized (SandboxedProcessConnection.this) { |
| 107 if (!boundOK && !mIsDestroyed) { |
| 108 SandboxedProcessConnection.this.onBindFailed(); |
| 109 } |
| 110 // else: bind will complete asynchronously with a callback to on
ServiceConnected(). |
| 111 } |
| 112 } |
| 113 |
| 114 @Override |
| 115 protected void onCancelled(Boolean boundOK) { |
| 116 // According to {@link AsyncTask#onCancelled(Object)}, the Object ca
n be null. |
| 117 if (boundOK != null && boundOK) { |
| 118 unBindIfAble(); |
| 119 } |
| 120 } |
| 121 |
| 122 /** |
| 123 * Unbinds this connection if it hasn't already been unbound. There's a
guard to check that |
| 124 * we haven't already been unbound because the Browser process cancellin
g a connection can |
| 125 * race with something else (Android?) cancelling the connection. |
| 126 */ |
| 127 private void unBindIfAble() { |
| 128 if (mIsBound.getAndSet(false)) { |
| 129 mContext.unbindService(this); |
| 130 } |
| 131 } |
| 132 |
| 133 // ServiceConnection |
| 134 @Override |
| 135 public void onServiceConnected(ComponentName name, IBinder service) { |
| 136 synchronized (SandboxedProcessConnection.this) { |
| 137 if (!mIsDestroyed) { |
| 138 SandboxedProcessConnection.this.onServiceConnected(name, ser
vice); |
| 139 } |
| 140 } |
| 141 } |
| 142 |
| 143 @Override |
| 144 public void onServiceDisconnected(ComponentName name) { |
| 145 synchronized (SandboxedProcessConnection.this) { |
| 146 if (!mIsDestroyed) { |
| 147 SandboxedProcessConnection.this.onServiceDisconnected(name); |
| 148 } |
| 149 } |
| 150 } |
| 151 |
| 152 public void destroy() { |
| 153 assert Thread.holdsLock(SandboxedProcessConnection.this); |
| 154 if (!cancel(false)) { |
| 155 unBindIfAble(); |
| 156 } |
| 157 mIsDestroyed = true; |
| 158 } |
| 159 } |
| 160 |
| 161 // This is only valid while the connection is being established. |
| 162 private ConnectionParams mConnectionParams; |
| 163 private AsyncBoundServiceConnection mServiceConnection; |
| 164 |
| 165 SandboxedProcessConnection(Context context, int number, |
| 166 SandboxedProcessConnection.DeathCallback deathCallback) { |
| 167 mContext = context; |
| 168 mServiceNumber = number; |
| 169 mDeathCallback = deathCallback; |
| 170 } |
| 171 |
| 172 int getServiceNumber() { |
| 173 return mServiceNumber; |
| 174 } |
| 175 |
| 176 synchronized ISandboxedProcessService getService() { |
| 177 return mService; |
| 178 } |
| 179 |
| 180 private Intent createServiceBindIntent() { |
| 181 Intent intent = new Intent(); |
| 182 String n = SandboxedProcessService.class.getName(); |
| 183 intent.setClassName(mContext, n + mServiceNumber); |
| 184 intent.setPackage(mContext.getPackageName()); |
| 185 return intent; |
| 186 } |
| 187 |
| 188 /** |
| 189 * Bind to an ISandboxedProcessService. This must be followed by a call to s
etupConnection() |
| 190 * to setup the connection parameters. (These methods are separated to allow
the client |
| 191 * to pass whatever parameters they have available here, and complete the re
mainder |
| 192 * later while reducing the connection setup latency). |
| 193 * |
| 194 * @param commandLine (Optional) Command line for the sandboxed process. If
omitted, then |
| 195 * the command line parameters must instead be passed to setupConnection(). |
| 196 */ |
| 197 synchronized void bind(String[] commandLine) { |
| 198 TraceEvent.begin(); |
| 199 final Intent intent = createServiceBindIntent(); |
| 200 |
| 201 if (commandLine != null) { |
| 202 intent.putExtra(EXTRA_COMMAND_LINE, commandLine); |
| 203 } |
| 204 // TODO(joth): By the docs, AsyncTasks should only be created on the UI
thread, but |
| 205 // bind() currently 'may' be called on any thread. In practice it's only
ever called |
| 206 // from UI, but it's not guaranteed. See http://b/5694925. |
| 207 Looper mainLooper = Looper.getMainLooper(); |
| 208 if (Looper.myLooper() == mainLooper) { |
| 209 mServiceConnection = new AsyncBoundServiceConnection(); |
| 210 // On completion this will call back to onServiceConnected(). |
| 211 mServiceConnection.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
intent); |
| 212 } else { |
| 213 // TODO(jcivelli): http://b/5694925 we only have to post to the UI t
hread because we use |
| 214 // an AsyncTask and it requires it. Replace that AsyncTask by runnin
g our own thread and |
| 215 // change this so we run directly from the current thread. |
| 216 new Handler(mainLooper).postAtFrontOfQueue(new Runnable() { |
| 217 public void run() { |
| 218 mServiceConnection = new AsyncBoundServiceConnection
(); |
| 219 // On completion this will call back to onServiceCon
nected(). |
| 220 mServiceConnection.executeOnExecutor(AsyncTask.THREA
D_POOL_EXECUTOR, |
| 221 intent); |
| 222 } |
| 223 }); |
| 224 } |
| 225 TraceEvent.end(); |
| 226 } |
| 227 |
| 228 /** Setup a connection previous bound via a call to bind(). |
| 229 * |
| 230 * This establishes the parameters that were not already supplied in bind. |
| 231 * @param commandLine (Optional) will be ignored if the command line was alr
eady sent in bind() |
| 232 * @param ipcFd The file descriptor that will be used by the sandbox process
for IPC. |
| 233 * @param crashFd (Optional) file descriptor that will be used for crash dum
ps. |
| 234 * @param callback Used for status updates regarding this process connection
. |
| 235 * @param onConnectionCallback will be run when the connection is setup and
ready to use. |
| 236 */ |
| 237 synchronized void setupConnection( |
| 238 String[] commandLine, |
| 239 int ipcFd, |
| 240 int crashFd, |
| 241 int chromePakFd, |
| 242 int localePakFd, |
| 243 ISandboxedProcessCallback callback, |
| 244 Runnable onConnectionCallback) { |
| 245 TraceEvent.begin(); |
| 246 assert mConnectionParams == null; |
| 247 mConnectionParams = new ConnectionParams(commandLine, ipcFd, crashFd, ch
romePakFd, |
| 248 localePakFd, callback, onConnectionCallback); |
| 249 if (mServiceConnectComplete) { |
| 250 doConnectionSetup(); |
| 251 } |
| 252 TraceEvent.end(); |
| 253 } |
| 254 |
| 255 /** |
| 256 * Unbind the ISandboxedProcessService. It is safe to call this multiple tim
es. |
| 257 */ |
| 258 synchronized void unbind() { |
| 259 if (mServiceConnection != null) { |
| 260 mServiceConnection.destroy(); |
| 261 mServiceConnection = null; |
| 262 } |
| 263 if (mService != null) { |
| 264 if (mHighPriorityConnection != null) { |
| 265 unbindHighPriority(true); |
| 266 } |
| 267 mService = null; |
| 268 mPID = 0; |
| 269 } |
| 270 mConnectionParams = null; |
| 271 mServiceConnectComplete = false; |
| 272 } |
| 273 |
| 274 // Called on the main thread to notify that the service is connected. |
| 275 private void onServiceConnected(ComponentName className, IBinder service) { |
| 276 assert Thread.holdsLock(this); |
| 277 TraceEvent.begin(); |
| 278 mServiceConnectComplete = true; |
| 279 mService = ISandboxedProcessService.Stub.asInterface(service); |
| 280 if (mConnectionParams != null) { |
| 281 doConnectionSetup(); |
| 282 } |
| 283 TraceEvent.end(); |
| 284 } |
| 285 |
| 286 // Called on the main thread to notify that the bindService() call failed (r
eturned false). |
| 287 private void onBindFailed() { |
| 288 assert Thread.holdsLock(this); |
| 289 mServiceConnectComplete = true; |
| 290 if (mConnectionParams != null) { |
| 291 doConnectionSetup(); |
| 292 } |
| 293 } |
| 294 |
| 295 /** |
| 296 * Called when the connection parameters have been set, and a connection has
been established |
| 297 * (as signaled by onServiceConnected), or if the connection failed (mServic
e will be false). |
| 298 */ |
| 299 private void doConnectionSetup() { |
| 300 TraceEvent.begin(); |
| 301 assert mServiceConnectComplete && mConnectionParams != null; |
| 302 // Capture the callback before it is potentially nulled in unbind(). |
| 303 Runnable onConnectionCallback = |
| 304 mConnectionParams != null ? mConnectionParams.mOnConnectionCallback
: null; |
| 305 if (onConnectionCallback == null) { |
| 306 unbind(); |
| 307 } else if (mService != null) { |
| 308 try { |
| 309 ParcelFileDescriptor ipcFdParcel = |
| 310 ParcelFileDescriptor.fromFd(mConnectionParams.mIpcFd); |
| 311 Bundle bundle = new Bundle(); |
| 312 bundle.putStringArray(EXTRA_COMMAND_LINE, mConnectionParams.mCom
mandLine); |
| 313 bundle.putParcelable(EXTRA_IPC_FD, ipcFdParcel); |
| 314 |
| 315 ParcelFileDescriptor chromePakFdParcel = |
| 316 ParcelFileDescriptor.fromFd(mConnectionParams.mChromePak
Fd); |
| 317 bundle.putParcelable(EXTRA_CHROME_PAK_FD, chromePakFdParcel); |
| 318 |
| 319 ParcelFileDescriptor localePakFdParcel = |
| 320 ParcelFileDescriptor.fromFd(mConnectionParams.mLocalePak
Fd); |
| 321 bundle.putParcelable(EXTRA_LOCALE_PAK_FD, localePakFdParcel); |
| 322 |
| 323 try { |
| 324 ParcelFileDescriptor crashFdParcel = |
| 325 ParcelFileDescriptor.fromFd(mConnectionParams.mCrashFd); |
| 326 bundle.putParcelable(EXTRA_CRASH_FD, crashFdParcel); |
| 327 // We will let the GC close the crash ParcelFileDescriptor. |
| 328 } catch (java.io.IOException e) { |
| 329 Log.w(TAG, "Invalid crash Fd. Native crash reporting will be
disabled."); |
| 330 } |
| 331 |
| 332 mPID = mService.setupConnection(bundle, mConnectionParams.mCallb
ack); |
| 333 ipcFdParcel.close(); // We proactivley close now rather than wa
it for GC & |
| 334 // finalizer. |
| 335 } catch(java.io.IOException e) { |
| 336 Log.w(TAG, "Invalid ipc FD."); |
| 337 } catch(android.os.RemoteException e) { |
| 338 Log.w(TAG, "Exception when trying to call service method: " |
| 339 + e); |
| 340 } |
| 341 } |
| 342 mConnectionParams = null; |
| 343 if (onConnectionCallback != null) { |
| 344 onConnectionCallback.run(); |
| 345 } |
| 346 TraceEvent.end(); |
| 347 } |
| 348 |
| 349 // Called on the main thread to notify that the sandboxed service did not di
sconnect gracefully. |
| 350 private void onServiceDisconnected(ComponentName className) { |
| 351 assert Thread.holdsLock(this); |
| 352 int pid = mPID; // Stash pid & connection callback since unbind() will
clear them. |
| 353 Runnable onConnectionCallback = |
| 354 mConnectionParams != null ? mConnectionParams.mOnConnectionCallback
: null; |
| 355 Log.w(TAG, "onServiceDisconnected (crash?): pid=" + pid); |
| 356 unbind(); // We don't want to auto-restart on crash. Let the browser do
that. |
| 357 if (pid != 0) { |
| 358 mDeathCallback.onSandboxedProcessDied(pid); |
| 359 } |
| 360 if (onConnectionCallback != null) { |
| 361 onConnectionCallback.run(); |
| 362 } |
| 363 } |
| 364 |
| 365 /** |
| 366 * Bind the service with a new high priority connection. This will make the
service |
| 367 * as important as the main process. |
| 368 */ |
| 369 synchronized void bindHighPriority() { |
| 370 if (mService == null) { |
| 371 Log.w(TAG, "The connection is not bound for " + mPID); |
| 372 return; |
| 373 } |
| 374 if (mHighPriorityConnection == null) { |
| 375 mHighPriorityConnection = new HighPriorityConnection(); |
| 376 mHighPriorityConnection.bind(); |
| 377 } |
| 378 mHighPriorityConnectionCount++; |
| 379 } |
| 380 |
| 381 /** |
| 382 * Unbind the service as the high priority connection. |
| 383 */ |
| 384 synchronized void unbindHighPriority(boolean force) { |
| 385 if (mService == null) { |
| 386 Log.w(TAG, "The connection is not bound for " + mPID); |
| 387 return; |
| 388 } |
| 389 mHighPriorityConnectionCount--; |
| 390 if (force || (mHighPriorityConnectionCount == 0 && mHighPriorityConnecti
on != null)) { |
| 391 mHighPriorityConnection.unbind(); |
| 392 mHighPriorityConnection = null; |
| 393 } |
| 394 } |
| 395 |
| 396 private class HighPriorityConnection implements ServiceConnection { |
| 397 |
| 398 private boolean mHBound = false; |
| 399 |
| 400 void bind() { |
| 401 final Intent intent = createServiceBindIntent(); |
| 402 |
| 403 mHBound = mContext.bindService(intent, this, |
| 404 Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT); |
| 405 } |
| 406 |
| 407 void unbind() { |
| 408 if (mHBound) { |
| 409 mContext.unbindService(this); |
| 410 mHBound = false; |
| 411 } |
| 412 } |
| 413 |
| 414 @Override |
| 415 public void onServiceConnected(ComponentName className, IBinder service)
{ |
| 416 } |
| 417 |
| 418 @Override |
| 419 public void onServiceDisconnected(ComponentName className) { |
| 420 } |
| 421 } |
| 422 |
| 423 /** |
| 424 * @return The connection PID, or 0 if not yet connected. |
| 425 */ |
| 426 synchronized public int getPid() { |
| 427 return mPID; |
| 428 } |
| 429 } |
OLD | NEW |