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