• Home
  • 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.CalledByNative;
20 import org.chromium.base.JNINamespace;
21 import org.chromium.base.library_loader.LibraryLoader;
22 import org.chromium.base.library_loader.Linker;
23 import org.chromium.base.library_loader.ProcessInitException;
24 import org.chromium.content.browser.ChildProcessConnection;
25 import org.chromium.content.browser.ChildProcessLauncher;
26 import org.chromium.content.common.IChildProcessCallback;
27 import org.chromium.content.common.IChildProcessService;
28 
29 import java.util.ArrayList;
30 import 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")
43 public 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 
getContext()114     /* package */ static Context getContext() {
115         return sContext.get();
116     }
117 
118     @Override
onCreate()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
onDestroy()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
onBind(Intent intent)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
establishSurfaceTexturePeer( int pid, Object surfaceObject, int primaryID, int secondaryID)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
getViewSurface(int surfaceId)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).getSurface();
287         } catch (RemoteException e) {
288             Log.e(TAG, "Unable to call establishSurfaceTexturePeer: " + e);
289             return null;
290         }
291     }
292 
293     @SuppressWarnings("unused")
294     @CalledByNative
getSurfaceTextureSurface(int primaryId, int secondaryId)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).getSurface();
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      */
nativeInitChildProcess(Context applicationContext, ChildProcessService service, int[] extraFileIds, int[] extraFileFds, int cpuCount, long cpuFeatures)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      */
nativeExitChildProcess()326     private static native void nativeExitChildProcess();
327 
nativeShutdownMainThread()328     private native void nativeShutdownMainThread();
329 }
330