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