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