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 |