ChildProcessService.java revision ab8f6f0bd665d3c1ff476eb06c58c42630e462d4
1// Copyright 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
5package org.chromium.content.app;
6
7import android.app.Service;
8import android.content.Context;
9import android.content.Intent;
10import android.graphics.SurfaceTexture;
11import android.os.Bundle;
12import android.os.IBinder;
13import android.os.ParcelFileDescriptor;
14import android.os.Process;
15import android.os.RemoteException;
16import android.util.Log;
17import android.view.Surface;
18
19import org.chromium.base.BaseSwitches;
20import org.chromium.base.CalledByNative;
21import org.chromium.base.CommandLine;
22import org.chromium.base.JNINamespace;
23import org.chromium.base.library_loader.LibraryLoader;
24import org.chromium.base.library_loader.Linker;
25import org.chromium.base.library_loader.ProcessInitException;
26import org.chromium.content.browser.ChildProcessConnection;
27import org.chromium.content.browser.ChildProcessLauncher;
28import org.chromium.content.common.IChildProcessCallback;
29import org.chromium.content.common.IChildProcessService;
30
31import java.util.ArrayList;
32import java.util.concurrent.atomic.AtomicReference;
33
34/**
35 * This is the base class for child services; the [Non]SandboxedProcessService0, 1.. etc
36 * subclasses provide the concrete service entry points, to enable the browser to connect
37 * to more than one distinct process (i.e. one process per service number, up to limit of N).
38 * The embedding application must declare these service instances in the application section
39 * of its AndroidManifest.xml, for example with N entries of the form:-
40 *     <service android:name="org.chromium.content.app.[Non]SandboxedProcessServiceX"
41 *              android:process=":[non]sandboxed_processX" />
42 * for X in 0...N-1 (where N is {@link ChildProcessLauncher#MAX_REGISTERED_SERVICES})
43 */
44@JNINamespace("content")
45public class ChildProcessService extends Service {
46    private static final String MAIN_THREAD_NAME = "ChildProcessMain";
47    private static final String TAG = "ChildProcessService";
48    private IChildProcessCallback mCallback;
49
50    // This is the native "Main" thread for the renderer / utility process.
51    private Thread mMainThread;
52    // Parameters received via IPC, only accessed while holding the mMainThread monitor.
53    private String[] mCommandLineParams;
54    private int mCpuCount;
55    private long mCpuFeatures;
56    // Pairs IDs and file descriptors that should be registered natively.
57    private ArrayList<Integer> mFileIds;
58    private ArrayList<ParcelFileDescriptor> mFileFds;
59    // Linker-specific parameters for this child process service.
60    private ChromiumLinkerParams mLinkerParams;
61
62    private static AtomicReference<Context> sContext = new AtomicReference<Context>(null);
63    private boolean mLibraryInitialized = false;
64    // Becomes true once the service is bound. Access must synchronize around mMainThread.
65    private boolean mIsBound = false;
66
67    // Binder object used by clients for this service.
68    private final IChildProcessService.Stub mBinder = new IChildProcessService.Stub() {
69        // NOTE: Implement any IChildProcessService methods here.
70        @Override
71        public int setupConnection(Bundle args, IChildProcessCallback callback) {
72            mCallback = callback;
73            synchronized (mMainThread) {
74                // Allow the command line to be set via bind() intent or setupConnection, but
75                // the FD can only be transferred here.
76                if (mCommandLineParams == null) {
77                    mCommandLineParams = args.getStringArray(
78                            ChildProcessConnection.EXTRA_COMMAND_LINE);
79                }
80                // We must have received the command line by now
81                assert mCommandLineParams != null;
82                mCpuCount = args.getInt(ChildProcessConnection.EXTRA_CPU_COUNT);
83                mCpuFeatures = args.getLong(ChildProcessConnection.EXTRA_CPU_FEATURES);
84                assert mCpuCount > 0;
85                mFileIds = new ArrayList<Integer>();
86                mFileFds = new ArrayList<ParcelFileDescriptor>();
87                for (int i = 0;; i++) {
88                    String fdName = ChildProcessConnection.EXTRA_FILES_PREFIX + i
89                            + ChildProcessConnection.EXTRA_FILES_FD_SUFFIX;
90                    ParcelFileDescriptor parcel = args.getParcelable(fdName);
91                    if (parcel == null) {
92                        // End of the file list.
93                        break;
94                    }
95                    mFileFds.add(parcel);
96                    String idName = ChildProcessConnection.EXTRA_FILES_PREFIX + i
97                            + ChildProcessConnection.EXTRA_FILES_ID_SUFFIX;
98                    mFileIds.add(args.getInt(idName));
99                }
100                Bundle sharedRelros = args.getBundle(Linker.EXTRA_LINKER_SHARED_RELROS);
101                if (sharedRelros != null) {
102                    Linker.useSharedRelros(sharedRelros);
103                    sharedRelros = null;
104                }
105                mMainThread.notifyAll();
106            }
107            return Process.myPid();
108        }
109
110        @Override
111        public void crashIntentionallyForTesting() {
112            Process.killProcess(Process.myPid());
113        }
114    };
115
116    /* package */ static Context getContext() {
117        return sContext.get();
118    }
119
120    @Override
121    public void onCreate() {
122        Log.i(TAG, "Creating new ChildProcessService pid=" + Process.myPid());
123        if (sContext.get() != null) {
124            Log.e(TAG, "ChildProcessService created again in process!");
125        }
126        sContext.set(this);
127        super.onCreate();
128
129        mMainThread = new Thread(new Runnable() {
130            @Override
131            public void run()  {
132                try {
133                    // CommandLine must be initialized before others, e.g., Linker.isUsed()
134                    // may check the command line options.
135                    synchronized (mMainThread) {
136                        while (mCommandLineParams == null) {
137                            mMainThread.wait();
138                        }
139                    }
140                    CommandLine.init(mCommandLineParams);
141                    boolean useLinker = Linker.isUsed();
142                    boolean requestedSharedRelro = false;
143                    if (useLinker) {
144                        synchronized (mMainThread) {
145                            while (!mIsBound) {
146                                mMainThread.wait();
147                            }
148                        }
149                        assert mLinkerParams != null;
150                        if (mLinkerParams.mWaitForSharedRelro) {
151                            requestedSharedRelro = true;
152                            Linker.initServiceProcess(mLinkerParams.mBaseLoadAddress);
153                        } else {
154                            Linker.disableSharedRelros();
155                        }
156                        Linker.setTestRunnerClassName(mLinkerParams.mTestRunnerClassName);
157                    }
158                    boolean isLoaded = false;
159                    if (CommandLine.getInstance().hasSwitch(
160                            BaseSwitches.RENDERER_WAIT_FOR_JAVA_DEBUGGER)) {
161                        android.os.Debug.waitForDebugger();
162                    }
163
164                    try {
165                        LibraryLoader.loadNow(getApplicationContext(), false);
166                        isLoaded = true;
167                    } catch (ProcessInitException e) {
168                        if (requestedSharedRelro) {
169                            Log.w(TAG, "Failed to load native library with shared RELRO, " +
170                                  "retrying without");
171                        } else {
172                            Log.e(TAG, "Failed to load native library", e);
173                        }
174                    }
175                    if (!isLoaded && requestedSharedRelro) {
176                        Linker.disableSharedRelros();
177                        try {
178                            LibraryLoader.loadNow(getApplicationContext(), false);
179                            isLoaded = true;
180                        } catch (ProcessInitException e) {
181                            Log.e(TAG, "Failed to load native library on retry", e);
182                        }
183                    }
184                    if (!isLoaded) {
185                        System.exit(-1);
186                    }
187                    LibraryLoader.initialize();
188                    synchronized (mMainThread) {
189                        mLibraryInitialized = true;
190                        mMainThread.notifyAll();
191                        while (mFileIds == null) {
192                            mMainThread.wait();
193                        }
194                    }
195                    assert mFileIds.size() == mFileFds.size();
196                    int[] fileIds = new int[mFileIds.size()];
197                    int[] fileFds = new int[mFileFds.size()];
198                    for (int i = 0; i < mFileIds.size(); ++i) {
199                        fileIds[i] = mFileIds.get(i);
200                        fileFds[i] = mFileFds.get(i).detachFd();
201                    }
202                    ContentMain.initApplicationContext(sContext.get().getApplicationContext());
203                    nativeInitChildProcess(sContext.get().getApplicationContext(),
204                            ChildProcessService.this, fileIds, fileFds,
205                            mCpuCount, mCpuFeatures);
206                    ContentMain.start();
207                    nativeExitChildProcess();
208                } catch (InterruptedException e) {
209                    Log.w(TAG, MAIN_THREAD_NAME + " startup failed: " + e);
210                } catch (ProcessInitException e) {
211                    Log.w(TAG, MAIN_THREAD_NAME + " startup failed: " + e);
212                }
213            }
214        }, MAIN_THREAD_NAME);
215        mMainThread.start();
216    }
217
218    @Override
219    public void onDestroy() {
220        Log.i(TAG, "Destroying ChildProcessService pid=" + Process.myPid());
221        super.onDestroy();
222        if (mCommandLineParams == null) {
223            // This process was destroyed before it even started. Nothing more to do.
224            return;
225        }
226        synchronized (mMainThread) {
227            try {
228                while (!mLibraryInitialized) {
229                    // Avoid a potential race in calling through to native code before the library
230                    // has loaded.
231                    mMainThread.wait();
232                }
233            } catch (InterruptedException e) {
234                // Ignore
235            }
236        }
237        // Try to shutdown the MainThread gracefully, but it might not
238        // have chance to exit normally.
239        nativeShutdownMainThread();
240    }
241
242    @Override
243    public IBinder onBind(Intent intent) {
244        // We call stopSelf() to request that this service be stopped as soon as the client
245        // unbinds. Otherwise the system may keep it around and available for a reconnect. The
246        // child processes do not currently support reconnect; they must be initialized from
247        // scratch every time.
248        stopSelf();
249
250        synchronized (mMainThread) {
251            mCommandLineParams = intent.getStringArrayExtra(
252                    ChildProcessConnection.EXTRA_COMMAND_LINE);
253            // mLinkerParams is never used if Linker.isUsed() returns false.
254            // See onCreate().
255            mLinkerParams = new ChromiumLinkerParams(intent);
256            mIsBound = true;
257            mMainThread.notifyAll();
258        }
259
260        return mBinder;
261    }
262
263    /**
264     * Called from native code to share a surface texture with another child process.
265     * Through using the callback object the browser is used as a proxy to route the
266     * call to the correct process.
267     *
268     * @param pid Process handle of the child process to share the SurfaceTexture with.
269     * @param surfaceObject The Surface or SurfaceTexture to share with the other child process.
270     * @param primaryID Used to route the call to the correct client instance.
271     * @param secondaryID Used to route the call to the correct client instance.
272     */
273    @SuppressWarnings("unused")
274    @CalledByNative
275    private void establishSurfaceTexturePeer(
276            int pid, Object surfaceObject, int primaryID, int secondaryID) {
277        if (mCallback == null) {
278            Log.e(TAG, "No callback interface has been provided.");
279            return;
280        }
281
282        Surface surface = null;
283        boolean needRelease = false;
284        if (surfaceObject instanceof Surface) {
285            surface = (Surface) surfaceObject;
286        } else if (surfaceObject instanceof SurfaceTexture) {
287            surface = new Surface((SurfaceTexture) surfaceObject);
288            needRelease = true;
289        } else {
290            Log.e(TAG, "Not a valid surfaceObject: " + surfaceObject);
291            return;
292        }
293        try {
294            mCallback.establishSurfacePeer(pid, surface, primaryID, secondaryID);
295        } catch (RemoteException e) {
296            Log.e(TAG, "Unable to call establishSurfaceTexturePeer: " + e);
297            return;
298        } finally {
299            if (needRelease) {
300                surface.release();
301            }
302        }
303    }
304
305    @SuppressWarnings("unused")
306    @CalledByNative
307    private Surface getViewSurface(int surfaceId) {
308        if (mCallback == null) {
309            Log.e(TAG, "No callback interface has been provided.");
310            return null;
311        }
312
313        try {
314            return mCallback.getViewSurface(surfaceId).getSurface();
315        } catch (RemoteException e) {
316            Log.e(TAG, "Unable to call establishSurfaceTexturePeer: " + e);
317            return null;
318        }
319    }
320
321    @SuppressWarnings("unused")
322    @CalledByNative
323    private Surface getSurfaceTextureSurface(int primaryId, int secondaryId) {
324        if (mCallback == null) {
325            Log.e(TAG, "No callback interface has been provided.");
326            return null;
327        }
328
329        try {
330            return mCallback.getSurfaceTextureSurface(primaryId, secondaryId).getSurface();
331        } catch (RemoteException e) {
332            Log.e(TAG, "Unable to call getSurfaceTextureSurface: " + e);
333            return null;
334        }
335    }
336
337    /**
338     * The main entry point for a child process. This should be called from a new thread since
339     * it will not return until the child process exits. See child_process_service.{h,cc}
340     *
341     * @param applicationContext The Application Context of the current process.
342     * @param service The current ChildProcessService object.
343     * @param fileIds A list of file IDs that should be registered for access by the renderer.
344     * @param fileFds A list of file descriptors that should be registered for access by the
345     * renderer.
346     */
347    private static native void nativeInitChildProcess(Context applicationContext,
348            ChildProcessService service, int[] extraFileIds, int[] extraFileFds,
349            int cpuCount, long cpuFeatures);
350
351    /**
352     * Force the child process to exit.
353     */
354    private static native void nativeExitChildProcess();
355
356    private native void nativeShutdownMainThread();
357}
358