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

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

Issue 2845243002: Moving BindingManager and ChildProcessConnection to base/.
Patch Set: Moving BindingManager and ChildProcessConnection to base/. Created 3 years, 7 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
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698