ChildProcessService.java revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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        @Override
109        public void crashIntentionallyForTesting() {
110            Process.killProcess(Process.myPid());
111        }
112    };
113
114    /* package */ static Context getContext() {
115        return sContext.get();
116    }
117
118    @Override
119    public void onCreate() {
120        Log.i(TAG, "Creating new ChildProcessService pid=" + Process.myPid());
121        if (sContext.get() != null) {
122            Log.e(TAG, "ChildProcessService created again in process!");
123        }
124        sContext.set(this);
125        super.onCreate();
126
127        mMainThread = new Thread(new Runnable() {
128            @Override
129            public void run()  {
130                try {
131                    boolean useLinker = Linker.isUsed();
132
133                    if (useLinker) {
134                        synchronized (mMainThread) {
135                            while (!mIsBound) {
136                                mMainThread.wait();
137                            }
138                        }
139                        if (mLinkerParams != null) {
140                            if (mLinkerParams.mWaitForSharedRelro)
141                                Linker.initServiceProcess(mLinkerParams.mBaseLoadAddress);
142                            else
143                                Linker.disableSharedRelros();
144
145                            Linker.setTestRunnerClassName(mLinkerParams.mTestRunnerClassName);
146                        }
147                    }
148                    try {
149                        LibraryLoader.loadNow(getApplicationContext(), false);
150                    } catch (ProcessInitException e) {
151                        Log.e(TAG, "Failed to load native library, exiting child process", e);
152                        System.exit(-1);
153                    }
154                    synchronized (mMainThread) {
155                        while (mCommandLineParams == null) {
156                            mMainThread.wait();
157                        }
158                    }
159                    LibraryLoader.initialize(mCommandLineParams);
160                    synchronized (mMainThread) {
161                        mLibraryInitialized = true;
162                        mMainThread.notifyAll();
163                        while (mFileIds == null) {
164                            mMainThread.wait();
165                        }
166                    }
167                    assert mFileIds.size() == mFileFds.size();
168                    int[] fileIds = new int[mFileIds.size()];
169                    int[] fileFds = new int[mFileFds.size()];
170                    for (int i = 0; i < mFileIds.size(); ++i) {
171                        fileIds[i] = mFileIds.get(i);
172                        fileFds[i] = mFileFds.get(i).detachFd();
173                    }
174                    ContentMain.initApplicationContext(sContext.get().getApplicationContext());
175                    nativeInitChildProcess(sContext.get().getApplicationContext(),
176                            ChildProcessService.this, fileIds, fileFds,
177                            mCpuCount, mCpuFeatures);
178                    ContentMain.start();
179                    nativeExitChildProcess();
180                } catch (InterruptedException e) {
181                    Log.w(TAG, MAIN_THREAD_NAME + " startup failed: " + e);
182                } catch (ProcessInitException e) {
183                    Log.w(TAG, MAIN_THREAD_NAME + " startup failed: " + e);
184                }
185            }
186        }, MAIN_THREAD_NAME);
187        mMainThread.start();
188    }
189
190    @Override
191    public void onDestroy() {
192        Log.i(TAG, "Destroying ChildProcessService pid=" + Process.myPid());
193        super.onDestroy();
194        if (mCommandLineParams == null) {
195            // This process was destroyed before it even started. Nothing more to do.
196            return;
197        }
198        synchronized (mMainThread) {
199            try {
200                while (!mLibraryInitialized) {
201                    // Avoid a potential race in calling through to native code before the library
202                    // has loaded.
203                    mMainThread.wait();
204                }
205            } catch (InterruptedException e) {
206                // Ignore
207            }
208        }
209        // Try to shutdown the MainThread gracefully, but it might not
210        // have chance to exit normally.
211        nativeShutdownMainThread();
212    }
213
214    @Override
215    public IBinder onBind(Intent intent) {
216        // We call stopSelf() to request that this service be stopped as soon as the client
217        // unbinds. Otherwise the system may keep it around and available for a reconnect. The
218        // child processes do not currently support reconnect; they must be initialized from
219        // scratch every time.
220        stopSelf();
221
222        synchronized (mMainThread) {
223            mCommandLineParams = intent.getStringArrayExtra(
224                    ChildProcessConnection.EXTRA_COMMAND_LINE);
225            mLinkerParams = null;
226            if (Linker.isUsed())
227                mLinkerParams = new ChromiumLinkerParams(intent);
228            mIsBound = true;
229            mMainThread.notifyAll();
230        }
231
232        return mBinder;
233    }
234
235    /**
236     * Called from native code to share a surface texture with another child process.
237     * Through using the callback object the browser is used as a proxy to route the
238     * call to the correct process.
239     *
240     * @param pid Process handle of the child process to share the SurfaceTexture with.
241     * @param surfaceObject The Surface or SurfaceTexture to share with the other child process.
242     * @param primaryID Used to route the call to the correct client instance.
243     * @param secondaryID Used to route the call to the correct client instance.
244     */
245    @SuppressWarnings("unused")
246    @CalledByNative
247    private void establishSurfaceTexturePeer(
248            int pid, Object surfaceObject, int primaryID, int secondaryID) {
249        if (mCallback == null) {
250            Log.e(TAG, "No callback interface has been provided.");
251            return;
252        }
253
254        Surface surface = null;
255        boolean needRelease = false;
256        if (surfaceObject instanceof Surface) {
257            surface = (Surface) surfaceObject;
258        } else if (surfaceObject instanceof SurfaceTexture) {
259            surface = new Surface((SurfaceTexture) surfaceObject);
260            needRelease = true;
261        } else {
262            Log.e(TAG, "Not a valid surfaceObject: " + surfaceObject);
263            return;
264        }
265        try {
266            mCallback.establishSurfacePeer(pid, surface, primaryID, secondaryID);
267        } catch (RemoteException e) {
268            Log.e(TAG, "Unable to call establishSurfaceTexturePeer: " + e);
269            return;
270        } finally {
271            if (needRelease) {
272                surface.release();
273            }
274        }
275    }
276
277    @SuppressWarnings("unused")
278    @CalledByNative
279    private Surface getViewSurface(int surfaceId) {
280        if (mCallback == null) {
281            Log.e(TAG, "No callback interface has been provided.");
282            return null;
283        }
284
285        try {
286            return mCallback.getViewSurface(surfaceId);
287        } catch (RemoteException e) {
288            Log.e(TAG, "Unable to call establishSurfaceTexturePeer: " + e);
289            return null;
290        }
291    }
292
293    @SuppressWarnings("unused")
294    @CalledByNative
295    private Surface getSurfaceTextureSurface(int primaryId, int secondaryId) {
296        if (mCallback == null) {
297            Log.e(TAG, "No callback interface has been provided.");
298            return null;
299        }
300
301        try {
302            return mCallback.getSurfaceTextureSurface(primaryId, secondaryId);
303        } catch (RemoteException e) {
304            Log.e(TAG, "Unable to call getSurfaceTextureSurface: " + e);
305            return null;
306        }
307    }
308
309    /**
310     * The main entry point for a child process. This should be called from a new thread since
311     * it will not return until the child process exits. See child_process_service.{h,cc}
312     *
313     * @param applicationContext The Application Context of the current process.
314     * @param service The current ChildProcessService object.
315     * @param fileIds A list of file IDs that should be registered for access by the renderer.
316     * @param fileFds A list of file descriptors that should be registered for access by the
317     * renderer.
318     */
319    private static native void nativeInitChildProcess(Context applicationContext,
320            ChildProcessService service, int[] extraFileIds, int[] extraFileFds,
321            int cpuCount, long cpuFeatures);
322
323    /**
324     * Force the child process to exit.
325     */
326    private static native void nativeExitChildProcess();
327
328    private native void nativeShutdownMainThread();
329}
330