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