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