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.io.IOException; | |
20 import java.util.concurrent.atomic.AtomicBoolean; | |
21 | |
22 import org.chromium.base.CalledByNative; | |
23 import org.chromium.base.CpuFeatures; | |
24 import org.chromium.base.ThreadUtils; | |
25 import org.chromium.content.common.CommandLine; | |
26 import org.chromium.content.common.ISandboxedProcessCallback; | |
27 import org.chromium.content.common.ISandboxedProcessService; | |
28 import org.chromium.content.common.TraceEvent; | |
29 | |
30 public class SandboxedProcessConnection implements ServiceConnection { | |
31 interface DeathCallback { | |
32 void onSandboxedProcessDied(int pid); | |
33 } | |
34 | |
35 // Names of items placed in the bind intent or connection bundle. | |
36 public static final String EXTRA_COMMAND_LINE = | |
37 "com.google.android.apps.chrome.extra.sandbox_command_line"; | |
38 public static final String EXTRA_NATIVE_LIBRARY_NAME = | |
39 "com.google.android.apps.chrome.extra.sandbox_native_library_name"; | |
40 // Note the FDs may only be passed in the connection bundle. | |
41 public static final String EXTRA_FILES_PREFIX = | |
42 "com.google.android.apps.chrome.extra.sandbox_extraFile_"; | |
43 public static final String EXTRA_FILES_ID_SUFFIX = "_id"; | |
44 public static final String EXTRA_FILES_FD_SUFFIX = "_fd"; | |
45 | |
46 // Used to pass the CPU core count to sandboxed processes. | |
47 public static final String EXTRA_CPU_COUNT = | |
48 "com.google.android.apps.chrome.extra.cpu_count"; | |
49 // Used to pass the CPU features mask to sandboxed processes. | |
50 public static final String EXTRA_CPU_FEATURES = | |
51 "com.google.android.apps.chrome.extra.cpu_features"; | |
52 | |
53 private final Context mContext; | |
54 private final int mServiceNumber; | |
55 private final SandboxedProcessConnection.DeathCallback mDeathCallback; | |
56 | |
57 // Synchronization: While most internal flow occurs on the UI thread, the pu
blic API | |
58 // (specifically bind and unbind) may be called from any thread, hence all e
ntry point methods | |
59 // into the class are synchronized on the SandboxedProcessConnection instanc
e to protect access | |
60 // to these members. But see also the TODO where AsyncBoundServiceConnection
is created. | |
61 private ISandboxedProcessService mService = null; | |
62 private boolean mServiceConnectComplete = false; | |
63 private int mPID = 0; // Process ID of the corresponding sandboxed process. | |
64 private HighPriorityConnection mHighPriorityConnection = null; | |
65 private int mHighPriorityConnectionCount = 0; | |
66 | |
67 private static final String TAG = "SandboxedProcessConnection"; | |
68 | |
69 private static class ConnectionParams { | |
70 final String[] mCommandLine; | |
71 final FileDescriptorInfo[] mFilesToBeMapped; | |
72 final ISandboxedProcessCallback mCallback; | |
73 final Runnable mOnConnectionCallback; | |
74 | |
75 ConnectionParams( | |
76 String[] commandLine, | |
77 FileDescriptorInfo[] filesToBeMapped, | |
78 ISandboxedProcessCallback callback, | |
79 Runnable onConnectionCallback) { | |
80 mCommandLine = commandLine; | |
81 mFilesToBeMapped = filesToBeMapped; | |
82 mCallback = callback; | |
83 mOnConnectionCallback = onConnectionCallback; | |
84 } | |
85 } | |
86 | |
87 // This is only valid while the connection is being established. | |
88 private ConnectionParams mConnectionParams; | |
89 private boolean mIsBound; | |
90 | |
91 SandboxedProcessConnection(Context context, int number, | |
92 SandboxedProcessConnection.DeathCallback deathCallback) { | |
93 mContext = context; | |
94 mServiceNumber = number; | |
95 mDeathCallback = deathCallback; | |
96 } | |
97 | |
98 int getServiceNumber() { | |
99 return mServiceNumber; | |
100 } | |
101 | |
102 synchronized ISandboxedProcessService getService() { | |
103 return mService; | |
104 } | |
105 | |
106 private Intent createServiceBindIntent() { | |
107 Intent intent = new Intent(); | |
108 String n = org.chromium.content.app.SandboxedProcessService.class.getNam
e(); | |
109 intent.setClassName(mContext, n + mServiceNumber); | |
110 intent.setPackage(mContext.getPackageName()); | |
111 return intent; | |
112 } | |
113 | |
114 /** | |
115 * Bind to an ISandboxedProcessService. This must be followed by a call to s
etupConnection() | |
116 * to setup the connection parameters. (These methods are separated to allow
the client | |
117 * to pass whatever parameters they have available here, and complete the re
mainder | |
118 * later while reducing the connection setup latency). | |
119 * @param nativeLibraryName The name of the shared native library to be load
ed for the | |
120 * sandboxed process. | |
121 * @param commandLine (Optional) Command line for the sandboxed process. If
omitted, then | |
122 * the command line parameters must instead be passed to
setupConnection(). | |
123 */ | |
124 synchronized void bind(String nativeLibraryName, String[] commandLine) { | |
125 TraceEvent.begin(); | |
126 assert !ThreadUtils.runningOnUiThread(); | |
127 | |
128 final Intent intent = createServiceBindIntent(); | |
129 | |
130 intent.putExtra(EXTRA_NATIVE_LIBRARY_NAME, nativeLibraryName); | |
131 if (commandLine != null) { | |
132 intent.putExtra(EXTRA_COMMAND_LINE, commandLine); | |
133 } | |
134 | |
135 mIsBound = mContext.bindService(intent, this, Context.BIND_AUTO_CREATE); | |
136 if (!mIsBound) { | |
137 onBindFailed(); | |
138 } | |
139 TraceEvent.end(); | |
140 } | |
141 | |
142 /** Setup a connection previous bound via a call to bind(). | |
143 * | |
144 * This establishes the parameters that were not already supplied in bind. | |
145 * @param commandLine (Optional) will be ignored if the command line was alr
eady sent in bind() | |
146 * @param fileToBeMapped a list of file descriptors that should be registere
d | |
147 * @param callback Used for status updates regarding this process connection
. | |
148 * @param onConnectionCallback will be run when the connection is setup and
ready to use. | |
149 */ | |
150 synchronized void setupConnection( | |
151 String[] commandLine, | |
152 FileDescriptorInfo[] filesToBeMapped, | |
153 ISandboxedProcessCallback callback, | |
154 Runnable onConnectionCallback) { | |
155 TraceEvent.begin(); | |
156 assert mConnectionParams == null; | |
157 mConnectionParams = new ConnectionParams(commandLine, filesToBeMapped, c
allback, | |
158 onConnectionCallback); | |
159 if (mServiceConnectComplete) { | |
160 doConnectionSetup(); | |
161 } | |
162 TraceEvent.end(); | |
163 } | |
164 | |
165 /** | |
166 * Unbind the ISandboxedProcessService. It is safe to call this multiple tim
es. | |
167 */ | |
168 synchronized void unbind() { | |
169 if (mIsBound) { | |
170 mContext.unbindService(this); | |
171 mIsBound = false; | |
172 } | |
173 if (mService != null) { | |
174 if (mHighPriorityConnection != null) { | |
175 unbindHighPriority(true); | |
176 } | |
177 mService = null; | |
178 mPID = 0; | |
179 } | |
180 mConnectionParams = null; | |
181 mServiceConnectComplete = false; | |
182 } | |
183 | |
184 // Called on the main thread to notify that the service is connected. | |
185 @Override | |
186 public void onServiceConnected(ComponentName className, IBinder service) { | |
187 TraceEvent.begin(); | |
188 mServiceConnectComplete = true; | |
189 mService = ISandboxedProcessService.Stub.asInterface(service); | |
190 if (mConnectionParams != null) { | |
191 doConnectionSetup(); | |
192 } | |
193 TraceEvent.end(); | |
194 } | |
195 | |
196 // Called on the main thread to notify that the bindService() call failed (r
eturned false). | |
197 private void onBindFailed() { | |
198 mServiceConnectComplete = true; | |
199 if (mConnectionParams != null) { | |
200 doConnectionSetup(); | |
201 } | |
202 } | |
203 | |
204 /** | |
205 * Called when the connection parameters have been set, and a connection has
been established | |
206 * (as signaled by onServiceConnected), or if the connection failed (mServic
e will be false). | |
207 */ | |
208 private void doConnectionSetup() { | |
209 TraceEvent.begin(); | |
210 assert mServiceConnectComplete && mConnectionParams != null; | |
211 // Capture the callback before it is potentially nulled in unbind(). | |
212 Runnable onConnectionCallback = | |
213 mConnectionParams != null ? mConnectionParams.mOnConnectionCallback
: null; | |
214 if (onConnectionCallback == null) { | |
215 unbind(); | |
216 } else if (mService != null) { | |
217 Bundle bundle = new Bundle(); | |
218 bundle.putStringArray(EXTRA_COMMAND_LINE, mConnectionParams.mCommand
Line); | |
219 | |
220 FileDescriptorInfo[] fileInfos = mConnectionParams.mFilesToBeMapped; | |
221 ParcelFileDescriptor[] parcelFiles = new ParcelFileDescriptor[fileIn
fos.length]; | |
222 for (int i = 0; i < fileInfos.length; i++) { | |
223 if (fileInfos[i].mFd == -1) { | |
224 // If someone provided an invalid FD, they are doing somethi
ng wrong. | |
225 Log.e(TAG, "Invalid FD (id=" + fileInfos[i].mId + ") for pro
cess connection, " | |
226 + "aborting connection."); | |
227 return; | |
228 } | |
229 String idName = EXTRA_FILES_PREFIX + i + EXTRA_FILES_ID_SUFFIX; | |
230 String fdName = EXTRA_FILES_PREFIX + i + EXTRA_FILES_FD_SUFFIX; | |
231 if (fileInfos[i].mAutoClose) { | |
232 // Adopt the FD, it will be closed when we close the ParcelF
ileDescriptor. | |
233 parcelFiles[i] = ParcelFileDescriptor.adoptFd(fileInfos[i].m
Fd); | |
234 } else { | |
235 try { | |
236 parcelFiles[i] = ParcelFileDescriptor.fromFd(fileInfos[i
].mFd); | |
237 } catch(IOException e) { | |
238 Log.e(TAG, | |
239 "Invalid FD provided for process connection, abort
ing connection.", | |
240 e); | |
241 return; | |
242 } | |
243 | |
244 } | |
245 bundle.putParcelable(fdName, parcelFiles[i]); | |
246 bundle.putInt(idName, fileInfos[i].mId); | |
247 } | |
248 // Add the CPU properties now. | |
249 bundle.putInt(EXTRA_CPU_COUNT, CpuFeatures.getCount()); | |
250 bundle.putLong(EXTRA_CPU_FEATURES, CpuFeatures.getMask()); | |
251 | |
252 try { | |
253 mPID = mService.setupConnection(bundle, mConnectionParams.mCallb
ack); | |
254 } catch (android.os.RemoteException re) { | |
255 Log.e(TAG, "Failed to setup connection.", re); | |
256 } | |
257 // We proactivley close the FDs rather than wait for GC & finalizer. | |
258 try { | |
259 for (ParcelFileDescriptor parcelFile : parcelFiles) { | |
260 if (parcelFile != null) parcelFile.close(); | |
261 } | |
262 } catch (IOException ioe) { | |
263 Log.w(TAG, "Failed to close FD.", ioe); | |
264 } | |
265 } | |
266 mConnectionParams = null; | |
267 if (onConnectionCallback != null) { | |
268 onConnectionCallback.run(); | |
269 } | |
270 TraceEvent.end(); | |
271 } | |
272 | |
273 // Called on the main thread to notify that the sandboxed service did not di
sconnect gracefully. | |
274 @Override | |
275 public void onServiceDisconnected(ComponentName className) { | |
276 int pid = mPID; // Stash pid & connection callback since unbind() will
clear them. | |
277 Runnable onConnectionCallback = | |
278 mConnectionParams != null ? mConnectionParams.mOnConnectionCallback
: null; | |
279 Log.w(TAG, "onServiceDisconnected (crash?): pid=" + pid); | |
280 unbind(); // We don't want to auto-restart on crash. Let the browser do
that. | |
281 if (pid != 0) { | |
282 mDeathCallback.onSandboxedProcessDied(pid); | |
283 } | |
284 if (onConnectionCallback != null) { | |
285 onConnectionCallback.run(); | |
286 } | |
287 } | |
288 | |
289 /** | |
290 * Bind the service with a new high priority connection. This will make the
service | |
291 * as important as the main process. | |
292 */ | |
293 synchronized void bindHighPriority() { | |
294 if (mService == null) { | |
295 Log.w(TAG, "The connection is not bound for " + mPID); | |
296 return; | |
297 } | |
298 if (mHighPriorityConnection == null) { | |
299 mHighPriorityConnection = new HighPriorityConnection(); | |
300 mHighPriorityConnection.bind(); | |
301 } | |
302 mHighPriorityConnectionCount++; | |
303 } | |
304 | |
305 /** | |
306 * Unbind the service as the high priority connection. | |
307 */ | |
308 synchronized void unbindHighPriority(boolean force) { | |
309 if (mService == null) { | |
310 Log.w(TAG, "The connection is not bound for " + mPID); | |
311 return; | |
312 } | |
313 mHighPriorityConnectionCount--; | |
314 if (force || (mHighPriorityConnectionCount == 0 && mHighPriorityConnecti
on != null)) { | |
315 mHighPriorityConnection.unbind(); | |
316 mHighPriorityConnection = null; | |
317 } | |
318 } | |
319 | |
320 private class HighPriorityConnection implements ServiceConnection { | |
321 | |
322 private boolean mHBound = false; | |
323 | |
324 void bind() { | |
325 final Intent intent = createServiceBindIntent(); | |
326 | |
327 mHBound = mContext.bindService(intent, this, | |
328 Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT); | |
329 } | |
330 | |
331 void unbind() { | |
332 if (mHBound) { | |
333 mContext.unbindService(this); | |
334 mHBound = false; | |
335 } | |
336 } | |
337 | |
338 @Override | |
339 public void onServiceConnected(ComponentName className, IBinder service)
{ | |
340 } | |
341 | |
342 @Override | |
343 public void onServiceDisconnected(ComponentName className) { | |
344 } | |
345 } | |
346 | |
347 /** | |
348 * @return The connection PID, or 0 if not yet connected. | |
349 */ | |
350 synchronized public int getPid() { | |
351 return mPID; | |
352 } | |
353 } | |
OLD | NEW |