| Index: content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherTest.java
|
| diff --git a/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherTest.java b/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherTest.java
|
| index 6e2ae99623e64fd134fef6469d2d645af8598373..d6ae7c6bc69dc586f747a9256f5155da0310a9cd 100644
|
| --- a/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherTest.java
|
| +++ b/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherTest.java
|
| @@ -4,7 +4,15 @@
|
|
|
| package org.chromium.content.browser;
|
|
|
| +import android.content.ComponentName;
|
| import android.content.Context;
|
| +import android.content.Intent;
|
| +import android.content.ServiceConnection;
|
| +import android.os.Handler;
|
| +import android.os.IBinder;
|
| +import android.os.Looper;
|
| +import android.os.Message;
|
| +import android.os.Messenger;
|
| import android.os.RemoteException;
|
| import android.support.test.filters.MediumTest;
|
| import android.test.InstrumentationTestCase;
|
| @@ -17,7 +25,9 @@ import org.chromium.base.test.util.Feature;
|
| import org.chromium.content.browser.test.util.Criteria;
|
| import org.chromium.content.browser.test.util.CriteriaHelper;
|
| import org.chromium.content.common.FileDescriptorInfo;
|
| +import org.chromium.content_shell_apk.ChildProcessLauncherTestHelperService;
|
|
|
| +import java.util.Map;
|
| import java.util.concurrent.Callable;
|
|
|
| /**
|
| @@ -296,6 +306,143 @@ public class ChildProcessLauncherTest extends InstrumentationTestCase {
|
| assertNotNull(tabConnection);
|
| }
|
|
|
| + /**
|
| + * Tests binding to the same sandboxed service process from multiple processes in the
|
| + * same package. This uses the ChildProcessLauncherTestHelperService declared in
|
| + * ContentShell.apk as a separate android:process to bind the first (slot 0) service. The
|
| + * instrumentation test then tries to bind the same slot, which fails, so the
|
| + * ChildProcessLauncher retries on a new connection.
|
| + */
|
| + @MediumTest
|
| + @Feature({"ProcessManagement"})
|
| + public void testBindServiceFromMultipleProcesses() throws RemoteException {
|
| + final Context context = getInstrumentation().getTargetContext();
|
| +
|
| + // Start the Helper service.
|
| + class HelperConnection implements ServiceConnection {
|
| + Messenger mMessenger = null;
|
| +
|
| + @Override
|
| + public void onServiceConnected(ComponentName name, IBinder service) {
|
| + mMessenger = new Messenger(service);
|
| + }
|
| +
|
| + @Override
|
| + public void onServiceDisconnected(ComponentName name) {}
|
| + }
|
| + final HelperConnection serviceConn = new HelperConnection();
|
| +
|
| + Intent intent = new Intent();
|
| + intent.setComponent(new ComponentName(context.getPackageName(),
|
| + context.getPackageName() + ".ChildProcessLauncherTestHelperService"));
|
| + assertTrue(context.bindService(intent, serviceConn, Context.BIND_AUTO_CREATE));
|
| +
|
| + // Wait for the Helper service to connect.
|
| + CriteriaHelper.pollInstrumentationThread(
|
| + new Criteria("Failed to get helper service Messenger") {
|
| + @Override
|
| + public boolean isSatisfied() {
|
| + return serviceConn.mMessenger != null;
|
| + }
|
| + });
|
| +
|
| + assertNotNull(serviceConn.mMessenger);
|
| +
|
| + class ReplyHandler implements Handler.Callback {
|
| + Message mMessage;
|
| +
|
| + @Override
|
| + public boolean handleMessage(Message msg) {
|
| + // Copy the message so its contents outlive this Binder transaction.
|
| + mMessage = Message.obtain();
|
| + mMessage.copyFrom(msg);
|
| + return true;
|
| + }
|
| + }
|
| + final ReplyHandler replyHandler = new ReplyHandler();
|
| +
|
| + // Send a message to the Helper and wait for the reply. This will cause the slot 0
|
| + // sandboxed service connection to be bound by a different PID (i.e., not this
|
| + // process).
|
| + Message msg = Message.obtain(null, ChildProcessLauncherTestHelperService.MSG_BIND_SERVICE);
|
| + msg.replyTo = new Messenger(new Handler(Looper.getMainLooper(), replyHandler));
|
| + serviceConn.mMessenger.send(msg);
|
| +
|
| + CriteriaHelper.pollInstrumentationThread(
|
| + new Criteria("Failed waiting for helper service reply") {
|
| + @Override
|
| + public boolean isSatisfied() {
|
| + return replyHandler.mMessage != null;
|
| + }
|
| + });
|
| +
|
| + // Verify that the Helper was able to launch the sandboxed service.
|
| + assertNotNull(replyHandler.mMessage);
|
| + assertEquals(ChildProcessLauncherTestHelperService.MSG_BIND_SERVICE_REPLY,
|
| + replyHandler.mMessage.what);
|
| + assertEquals("Connection slot from helper service is not 0", 0, replyHandler.mMessage.arg2);
|
| +
|
| + final int helperConnPid = replyHandler.mMessage.arg1;
|
| + assertTrue(helperConnPid > 0);
|
| +
|
| + // Launch a service from this process. Since slot 0 is already bound by the Helper, it
|
| + // will fail to start and the ChildProcessLauncher will retry.
|
| + final ChildProcessConnection conn = ChildProcessLauncher.startForTesting(context,
|
| + sProcessWaitArguments, new FileDescriptorInfo[0],
|
| + getDefaultChildProcessCreationParams(context.getPackageName()));
|
| +
|
| + CriteriaHelper.pollInstrumentationThread(
|
| + new Criteria("Failed waiting for instrumentation-bound service") {
|
| + @Override
|
| + public boolean isSatisfied() {
|
| + return conn.getService() != null;
|
| + }
|
| + });
|
| +
|
| + assertEquals(0, conn.getServiceNumber());
|
| + assertEquals(-1, conn.getPid()); // PID gets set to -1 if service is already bound.
|
| +
|
| + final Map<Integer, ChildProcessConnection> serviceMap =
|
| + ChildProcessLauncher.getServiceMapForTesting();
|
| +
|
| + // Wait for the retry to succeed.
|
| + CriteriaHelper.pollInstrumentationThread(
|
| + new Criteria("Failed waiting for both child process services") {
|
| + @Override
|
| + public boolean isSatisfied() {
|
| + boolean allChildrenConnected = serviceMap.size() == 2;
|
| + for (ChildProcessConnection conn : serviceMap.values()) {
|
| + allChildrenConnected &= conn.getService() != null;
|
| + }
|
| + return allChildrenConnected;
|
| + }
|
| + });
|
| +
|
| + assertEquals(2, serviceMap.size());
|
| +
|
| + boolean testedSlot0 = false, testedSlot1 = false;
|
| +
|
| + for (ChildProcessConnection childProcess : serviceMap.values()) {
|
| + if (childProcess == conn) {
|
| + assertFalse(testedSlot0);
|
| + assertEquals(0, childProcess.getServiceNumber());
|
| + assertEquals(-1, childProcess.getPid());
|
| + assertFalse(childProcess.getService().bindToCaller());
|
| + testedSlot0 = true;
|
| + } else {
|
| + assertFalse(testedSlot1);
|
| + assertEquals(1, childProcess.getServiceNumber());
|
| + assertTrue(childProcess.getPid() > 0);
|
| + assertTrue(childProcess.getPid() != helperConnPid);
|
| + assertTrue(childProcess.getService().bindToCaller());
|
| + testedSlot1 = true;
|
| + }
|
| + }
|
| +
|
| + assertTrue(testedSlot0);
|
| + assertTrue(testedSlot1);
|
| + }
|
| +
|
| private ChildProcessConnectionImpl startConnection() {
|
| // Allocate a new connection.
|
| Context context = getInstrumentation().getTargetContext();
|
|
|