ChildProcessService.java revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1// Copyright (c) 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.common.IChildProcessCallback;
23import org.chromium.content.common.IChildProcessService;
24import org.chromium.content.browser.ChildProcessLauncher;
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
56    private static AtomicReference<Context> sContext = new AtomicReference<Context>(null);
57    private boolean mLibraryInitialized = false;
58
59    // Binder object used by clients for this service.
60    private final IChildProcessService.Stub mBinder = new IChildProcessService.Stub() {
61        // NOTE: Implement any IChildProcessService methods here.
62        @Override
63        public int setupConnection(Bundle args, IChildProcessCallback callback) {
64            mCallback = callback;
65            synchronized (mMainThread) {
66                // Allow the command line to be set via bind() intent or setupConnection, but
67                // the FD can only be transferred here.
68                if (mCommandLineParams == null) {
69                    mCommandLineParams = args.getStringArray(
70                            ChildProcessConnection.EXTRA_COMMAND_LINE);
71                }
72                // We must have received the command line by now
73                assert mCommandLineParams != null;
74                mCpuCount = args.getInt(ChildProcessConnection.EXTRA_CPU_COUNT);
75                mCpuFeatures = args.getLong(ChildProcessConnection.EXTRA_CPU_FEATURES);
76                assert mCpuCount > 0;
77                mFileIds = new ArrayList<Integer>();
78                mFileFds = new ArrayList<ParcelFileDescriptor>();
79                for (int i = 0;; i++) {
80                    String fdName = ChildProcessConnection.EXTRA_FILES_PREFIX + i
81                            + ChildProcessConnection.EXTRA_FILES_FD_SUFFIX;
82                    ParcelFileDescriptor parcel = args.getParcelable(fdName);
83                    if (parcel == null) {
84                        // End of the file list.
85                        break;
86                    }
87                    mFileFds.add(parcel);
88                    String idName = ChildProcessConnection.EXTRA_FILES_PREFIX + i
89                            + ChildProcessConnection.EXTRA_FILES_ID_SUFFIX;
90                    mFileIds.add(args.getInt(idName));
91                }
92                mMainThread.notifyAll();
93            }
94            return Process.myPid();
95        }
96    };
97
98    /* package */ static Context getContext() {
99        return sContext.get();
100    }
101
102    @Override
103    public void onCreate() {
104        Log.i(TAG, "Creating new ChildProcessService pid=" + Process.myPid());
105        if (sContext.get() != null) {
106            Log.e(TAG, "ChildProcessService created again in process!");
107        }
108        sContext.set(this);
109        super.onCreate();
110
111        mMainThread = new Thread(new Runnable() {
112            @Override
113            public void run()  {
114                try {
115                    try {
116                        LibraryLoader.loadNow();
117                    } catch (ProcessInitException e) {
118                        Log.e(TAG, "Failed to load native library, exiting child process", e);
119                        return;
120                    }
121                    synchronized (mMainThread) {
122                        while (mCommandLineParams == null) {
123                            mMainThread.wait();
124                        }
125                    }
126                    LibraryLoader.initialize(mCommandLineParams);
127                    synchronized (mMainThread) {
128                        mLibraryInitialized = true;
129                        mMainThread.notifyAll();
130                        while (mFileIds == null) {
131                            mMainThread.wait();
132                        }
133                    }
134                    assert mFileIds.size() == mFileFds.size();
135                    int[] fileIds = new int[mFileIds.size()];
136                    int[] fileFds = new int[mFileFds.size()];
137                    for (int i = 0; i < mFileIds.size(); ++i) {
138                        fileIds[i] = mFileIds.get(i);
139                        fileFds[i] = mFileFds.get(i).detachFd();
140                    }
141                    ContentMain.initApplicationContext(sContext.get().getApplicationContext());
142                    nativeInitChildProcess(sContext.get().getApplicationContext(),
143                            ChildProcessService.this, fileIds, fileFds,
144                            mCpuCount, mCpuFeatures);
145                    ContentMain.start();
146                    nativeExitChildProcess();
147                } catch (InterruptedException e) {
148                    Log.w(TAG, MAIN_THREAD_NAME + " startup failed: " + e);
149                } catch (ProcessInitException e) {
150                    Log.w(TAG, MAIN_THREAD_NAME + " startup failed: " + e);
151                }
152            }
153        }, MAIN_THREAD_NAME);
154        mMainThread.start();
155    }
156
157    @Override
158    public void onDestroy() {
159        Log.i(TAG, "Destroying ChildProcessService pid=" + Process.myPid());
160        super.onDestroy();
161        if (mCommandLineParams == null) {
162            // This process was destroyed before it even started. Nothing more to do.
163            return;
164        }
165        synchronized (mMainThread) {
166            try {
167                while (!mLibraryInitialized) {
168                    // Avoid a potential race in calling through to native code before the library
169                    // has loaded.
170                    mMainThread.wait();
171                }
172            } catch (InterruptedException e) {
173            }
174        }
175        // Try to shutdown the MainThread gracefully, but it might not
176        // have chance to exit normally.
177        nativeShutdownMainThread();
178    }
179
180    @Override
181    public IBinder onBind(Intent intent) {
182        // We call stopSelf() to request that this service be stopped as soon as the client
183        // unbinds. Otherwise the system may keep it around and available for a reconnect. The
184        // child processes do not currently support reconnect; they must be initialized from
185        // scratch every time.
186        stopSelf();
187
188        synchronized (mMainThread) {
189            mCommandLineParams = intent.getStringArrayExtra(
190                    ChildProcessConnection.EXTRA_COMMAND_LINE);
191            mMainThread.notifyAll();
192        }
193
194        return mBinder;
195    }
196
197    /**
198     * Called from native code to share a surface texture with another child process.
199     * Through using the callback object the browser is used as a proxy to route the
200     * call to the correct process.
201     *
202     * @param pid Process handle of the child process to share the SurfaceTexture with.
203     * @param surfaceObject The Surface or SurfaceTexture to share with the other child process.
204     * @param primaryID Used to route the call to the correct client instance.
205     * @param secondaryID Used to route the call to the correct client instance.
206     */
207    @SuppressWarnings("unused")
208    @CalledByNative
209    private void establishSurfaceTexturePeer(
210            int pid, Object surfaceObject, int primaryID, int secondaryID) {
211        if (mCallback == null) {
212            Log.e(TAG, "No callback interface has been provided.");
213            return;
214        }
215
216        Surface surface = null;
217        boolean needRelease = false;
218        if (surfaceObject instanceof Surface) {
219            surface = (Surface)surfaceObject;
220        } else if (surfaceObject instanceof SurfaceTexture) {
221            surface = new Surface((SurfaceTexture)surfaceObject);
222            needRelease = true;
223        } else {
224            Log.e(TAG, "Not a valid surfaceObject: " + surfaceObject);
225            return;
226        }
227        try {
228            mCallback.establishSurfacePeer(pid, surface, primaryID, secondaryID);
229        } catch (RemoteException e) {
230            Log.e(TAG, "Unable to call establishSurfaceTexturePeer: " + e);
231            return;
232        } finally {
233            if (needRelease) {
234                surface.release();
235            }
236        }
237    }
238
239    @SuppressWarnings("unused")
240    @CalledByNative
241    private Surface getViewSurface(int surfaceId) {
242        if (mCallback == null) {
243            Log.e(TAG, "No callback interface has been provided.");
244            return null;
245        }
246
247        try {
248            return mCallback.getViewSurface(surfaceId);
249        } catch (RemoteException e) {
250            Log.e(TAG, "Unable to call establishSurfaceTexturePeer: " + e);
251            return null;
252        }
253    }
254
255    /**
256     * The main entry point for a child process. This should be called from a new thread since
257     * it will not return until the child process exits. See child_process_service.{h,cc}
258     *
259     * @param applicationContext The Application Context of the current process.
260     * @param service The current ChildProcessService object.
261     * @param fileIds A list of file IDs that should be registered for access by the renderer.
262     * @param fileFds A list of file descriptors that should be registered for access by the
263     * renderer.
264     */
265    private static native void nativeInitChildProcess(Context applicationContext,
266            ChildProcessService service, int[] extraFileIds, int[] extraFileFds,
267            int cpuCount, long cpuFeatures);
268
269    /**
270     * Force the child process to exit.
271     */
272    private static native void nativeExitChildProcess();
273
274    private native void nativeShutdownMainThread();
275}
276