SandboxedProcessService.java revision 5821806d5e7f356e8fa4b058a389a808ea183019
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 java.util.ArrayList;
20
21import org.chromium.base.CalledByNative;
22import org.chromium.base.JNINamespace;
23import org.chromium.content.app.ContentMain;
24import org.chromium.content.browser.SandboxedProcessConnection;
25import org.chromium.content.common.ISandboxedProcessCallback;
26import org.chromium.content.common.ISandboxedProcessService;
27import org.chromium.content.common.SurfaceCallback;
28
29/**
30 * This is the base class for sandboxed services; the SandboxedProcessService0, 1.. etc
31 * subclasses provide the concrete service entry points, to enable the browser to connect
32 * to more than one distinct process (i.e. one process per service number, up to limit of N).
33 * The embedding application must declare these service instances in the application section
34 * of its AndroidManifest.xml, for example with N entries of the form:-
35 *     <service android:name="org.chromium.content.app.SandboxedProcessServiceX"
36 *              android:process=":sandboxed_processX" />
37 * for X in 0...N-1 (where N is {@link SandboxedProcessLauncher#MAX_REGISTERED_SERVICES})
38 */
39@JNINamespace("content")
40public class SandboxedProcessService extends Service {
41    private static final String MAIN_THREAD_NAME = "SandboxedProcessMain";
42    private static final String TAG = "SandboxedProcessService";
43    private ISandboxedProcessCallback mCallback;
44
45    // This is the native "Main" thread for the renderer / utility process.
46    private Thread mSandboxMainThread;
47    // Parameters received via IPC, only accessed while holding the mSandboxMainThread monitor.
48    private String mNativeLibraryName;  // Must be passed in via the bind command.
49    private String[] mCommandLineParams;
50    // Pairs IDs and file descriptors that should be registered natively.
51    private ArrayList<Integer> mFileIds;
52    private ArrayList<ParcelFileDescriptor> mFileFds;
53
54    private static Context sContext = null;
55    private boolean mLibraryInitialized = false;
56
57    // Binder object used by clients for this service.
58    private final ISandboxedProcessService.Stub mBinder = new ISandboxedProcessService.Stub() {
59        // NOTE: Implement any ISandboxedProcessService methods here.
60        @Override
61        public int setupConnection(Bundle args, ISandboxedProcessCallback callback) {
62            mCallback = callback;
63            synchronized (mSandboxMainThread) {
64                // Allow the command line to be set via bind() intent or setupConnection, but
65                // the FD can only be transferred here.
66                if (mCommandLineParams == null) {
67                    mCommandLineParams = args.getStringArray(
68                            SandboxedProcessConnection.EXTRA_COMMAND_LINE);
69                }
70                // We must have received the command line by now
71                assert mCommandLineParams != null;
72                mFileIds = new ArrayList<Integer>();
73                mFileFds = new ArrayList<ParcelFileDescriptor>();
74                for (int i = 0;; i++) {
75                    String fdName = SandboxedProcessConnection.EXTRA_FILES_PREFIX + i
76                            + SandboxedProcessConnection.EXTRA_FILES_FD_SUFFIX;
77                    ParcelFileDescriptor parcel = args.getParcelable(fdName);
78                    if (parcel == null) {
79                        // End of the file list.
80                        break;
81                    }
82                    mFileFds.add(parcel);
83                    String idName = SandboxedProcessConnection.EXTRA_FILES_PREFIX + i
84                            + SandboxedProcessConnection.EXTRA_FILES_ID_SUFFIX;
85                    mFileIds.add(args.getInt(idName));
86                }
87                mSandboxMainThread.notifyAll();
88            }
89            return Process.myPid();
90        }
91
92        @Override
93        public void setSurface(int type, Surface surface, int primaryID, int secondaryID) {
94            // This gives up ownership of the Surface.
95            SurfaceCallback.setSurface(type, surface, primaryID, secondaryID);
96        }
97    };
98
99    /* package */ static Context getContext() {
100        return sContext;
101    }
102
103    @Override
104    public void onCreate() {
105        sContext = this;
106        super.onCreate();
107
108        mSandboxMainThread = new Thread(new Runnable() {
109            @Override
110            public void run()  {
111                try {
112                    synchronized (mSandboxMainThread) {
113                        while (mNativeLibraryName == null) {
114                            mSandboxMainThread.wait();
115                        }
116                    }
117                    LibraryLoader.setLibraryToLoad(mNativeLibraryName);
118                    LibraryLoader.loadNow();
119                    synchronized (mSandboxMainThread) {
120                        while (mCommandLineParams == null) {
121                            mSandboxMainThread.wait();
122                        }
123                    }
124                    LibraryLoader.initializeOnMainThread(mCommandLineParams);
125                    synchronized (mSandboxMainThread) {
126                        mLibraryInitialized = true;
127                        mSandboxMainThread.notifyAll();
128                        while (mFileIds == null) {
129                            mSandboxMainThread.wait();
130                        }
131                    }
132                    assert mFileIds.size() == mFileFds.size();
133                    int[] fileIds = new int[mFileIds.size()];
134                    int[] fileFds = new int[mFileFds.size()];
135                    for (int i = 0; i < mFileIds.size(); ++i) {
136                        fileIds[i] = mFileIds.get(i);
137                        fileFds[i] = mFileFds.get(i).detachFd();
138                    }
139                    ContentMain.initApplicationContext(sContext.getApplicationContext());
140                    nativeInitSandboxedProcess(sContext.getApplicationContext(),
141                            SandboxedProcessService.this, fileIds, fileFds);
142                    ContentMain.start();
143                    nativeExitSandboxedProcess();
144                } catch (InterruptedException e) {
145                    Log.w(TAG, MAIN_THREAD_NAME + " startup failed: " + e);
146                }
147            }
148        }, MAIN_THREAD_NAME);
149        mSandboxMainThread.start();
150    }
151
152    @Override
153    public void onDestroy() {
154        super.onDestroy();
155        if (mCommandLineParams == null) {
156            // This process was destroyed before it even started. Nothing more to do.
157            return;
158        }
159        synchronized (mSandboxMainThread) {
160            try {
161                while (!mLibraryInitialized) {
162                    // Avoid a potential race in calling through to native code before the library
163                    // has loaded.
164                    mSandboxMainThread.wait();
165                }
166            } catch (InterruptedException e) {
167            }
168        }
169
170        // This is not synchronized with the main thread in any way, but this is analogous
171        // to how desktop chrome terminates processes using SIGTERM. The mSandboxMainThread
172        // may run briefly before this is executed, but will eventually get a channel error
173        // and similarly commit suicide via SuicideOnChannelErrorFilter().
174        // TODO(tedbo): Why doesn't the activity manager SIGTERM/SIGKILL this service process?
175        nativeExitSandboxedProcess();
176    }
177
178    @Override
179    public IBinder onBind(Intent intent) {
180        // We call stopSelf() to request that this service be stopped as soon as the client
181        // unbinds. Otherwise the system may keep it around and available for a reconnect. The
182        // sandboxed processes do not currently support reconnect; they must be initialized from
183        // scratch every time.
184        stopSelf();
185
186        synchronized (mSandboxMainThread) {
187            mNativeLibraryName = intent.getStringExtra(
188                    SandboxedProcessConnection.EXTRA_NATIVE_LIBRARY_NAME);
189            mCommandLineParams = intent.getStringArrayExtra(
190                    SandboxedProcessConnection.EXTRA_COMMAND_LINE);
191            mSandboxMainThread.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 sandboxed process to share the SurfaceTexture with.
203     * @param type The type of process that the SurfaceTexture is for.
204     * @param surfaceObject The Surface or SurfaceTexture to share with the other sandboxed process.
205     * @param primaryID Used to route the call to the correct client instance.
206     * @param secondaryID Used to route the call to the correct client instance.
207     */
208    @SuppressWarnings("unused")
209    @CalledByNative
210    private void establishSurfaceTexturePeer(
211            int pid, int type, Object surfaceObject, int primaryID, int secondaryID) {
212        if (mCallback == null) {
213            Log.e(TAG, "No callback interface has been provided.");
214            return;
215        }
216
217        Surface surface = null;
218        boolean needRelease = false;
219        if (surfaceObject instanceof Surface) {
220            surface = (Surface)surfaceObject;
221        } else if (surfaceObject instanceof SurfaceTexture) {
222            surface = new Surface((SurfaceTexture)surfaceObject);
223            needRelease = true;
224        } else {
225            Log.e(TAG, "Not a valid surfaceObject: " + surfaceObject);
226            return;
227        }
228        try {
229            mCallback.establishSurfacePeer(pid, type, surface, primaryID, secondaryID);
230        } catch (RemoteException e) {
231            Log.e(TAG, "Unable to call establishSurfaceTexturePeer: " + e);
232            return;
233        } finally {
234            if (needRelease) {
235                surface.release();
236            }
237        }
238    }
239
240    /**
241     * The main entry point for a sandboxed process. This should be called from a new thread since
242     * it will not return until the sandboxed process exits. See sandboxed_process_service.{h,cc}
243     *
244     * @param applicationContext The Application Context of the current process.
245     * @param service The current SandboxedProcessService object.
246     * @param fileIds A list of file IDs that should be registered for access by the renderer.
247     * @param fileFds A list of file descriptors that should be registered for access by the
248     * renderer.
249     */
250    private static native void nativeInitSandboxedProcess(Context applicationContext,
251            SandboxedProcessService service, int[] extraFileIds, int[] extraFileFds);
252
253    /**
254     * Force the sandboxed process to exit.
255     */
256    private static native void nativeExitSandboxedProcess();
257}
258