Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(539)

Side by Side Diff: content/public/android/java/org/chromium/content/browser/SandboxedProcessConnection.java

Issue 10546079: Added sandboxed process service. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Sync Created 8 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698