| OLD | NEW |
| (Empty) |
| 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 | |
| 5 package org.chromium.content.browser; | |
| 6 | |
| 7 import android.content.ComponentName; | |
| 8 import android.content.Context; | |
| 9 import android.content.Intent; | |
| 10 import android.content.ServiceConnection; | |
| 11 import android.os.AsyncTask; | |
| 12 import android.os.Bundle; | |
| 13 import android.os.Handler; | |
| 14 import android.os.IBinder; | |
| 15 import android.os.Looper; | |
| 16 import android.os.ParcelFileDescriptor; | |
| 17 import android.util.Log; | |
| 18 | |
| 19 import java.io.IOException; | |
| 20 import java.util.concurrent.atomic.AtomicBoolean; | |
| 21 | |
| 22 import org.chromium.base.CalledByNative; | |
| 23 import org.chromium.base.CpuFeatures; | |
| 24 import org.chromium.base.ThreadUtils; | |
| 25 import org.chromium.content.common.CommandLine; | |
| 26 import org.chromium.content.common.ISandboxedProcessCallback; | |
| 27 import org.chromium.content.common.ISandboxedProcessService; | |
| 28 import org.chromium.content.common.TraceEvent; | |
| 29 | |
| 30 public class SandboxedProcessConnection implements ServiceConnection { | |
| 31 interface DeathCallback { | |
| 32 void onSandboxedProcessDied(int pid); | |
| 33 } | |
| 34 | |
| 35 // Names of items placed in the bind intent or connection bundle. | |
| 36 public static final String EXTRA_COMMAND_LINE = | |
| 37 "com.google.android.apps.chrome.extra.sandbox_command_line"; | |
| 38 public static final String EXTRA_NATIVE_LIBRARY_NAME = | |
| 39 "com.google.android.apps.chrome.extra.sandbox_native_library_name"; | |
| 40 // Note the FDs may only be passed in the connection bundle. | |
| 41 public static final String EXTRA_FILES_PREFIX = | |
| 42 "com.google.android.apps.chrome.extra.sandbox_extraFile_"; | |
| 43 public static final String EXTRA_FILES_ID_SUFFIX = "_id"; | |
| 44 public static final String EXTRA_FILES_FD_SUFFIX = "_fd"; | |
| 45 | |
| 46 // Used to pass the CPU core count to sandboxed processes. | |
| 47 public static final String EXTRA_CPU_COUNT = | |
| 48 "com.google.android.apps.chrome.extra.cpu_count"; | |
| 49 // Used to pass the CPU features mask to sandboxed processes. | |
| 50 public static final String EXTRA_CPU_FEATURES = | |
| 51 "com.google.android.apps.chrome.extra.cpu_features"; | |
| 52 | |
| 53 private final Context mContext; | |
| 54 private final int mServiceNumber; | |
| 55 private final SandboxedProcessConnection.DeathCallback mDeathCallback; | |
| 56 | |
| 57 // Synchronization: While most internal flow occurs on the UI thread, the pu
blic API | |
| 58 // (specifically bind and unbind) may be called from any thread, hence all e
ntry point methods | |
| 59 // into the class are synchronized on the SandboxedProcessConnection instanc
e to protect access | |
| 60 // to these members. But see also the TODO where AsyncBoundServiceConnection
is created. | |
| 61 private ISandboxedProcessService mService = null; | |
| 62 private boolean mServiceConnectComplete = false; | |
| 63 private int mPID = 0; // Process ID of the corresponding sandboxed process. | |
| 64 private HighPriorityConnection mHighPriorityConnection = null; | |
| 65 private int mHighPriorityConnectionCount = 0; | |
| 66 | |
| 67 private static final String TAG = "SandboxedProcessConnection"; | |
| 68 | |
| 69 private static class ConnectionParams { | |
| 70 final String[] mCommandLine; | |
| 71 final FileDescriptorInfo[] mFilesToBeMapped; | |
| 72 final ISandboxedProcessCallback mCallback; | |
| 73 final Runnable mOnConnectionCallback; | |
| 74 | |
| 75 ConnectionParams( | |
| 76 String[] commandLine, | |
| 77 FileDescriptorInfo[] filesToBeMapped, | |
| 78 ISandboxedProcessCallback callback, | |
| 79 Runnable onConnectionCallback) { | |
| 80 mCommandLine = commandLine; | |
| 81 mFilesToBeMapped = filesToBeMapped; | |
| 82 mCallback = callback; | |
| 83 mOnConnectionCallback = onConnectionCallback; | |
| 84 } | |
| 85 } | |
| 86 | |
| 87 // This is only valid while the connection is being established. | |
| 88 private ConnectionParams mConnectionParams; | |
| 89 private boolean mIsBound; | |
| 90 | |
| 91 SandboxedProcessConnection(Context context, int number, | |
| 92 SandboxedProcessConnection.DeathCallback deathCallback) { | |
| 93 mContext = context; | |
| 94 mServiceNumber = number; | |
| 95 mDeathCallback = deathCallback; | |
| 96 } | |
| 97 | |
| 98 int getServiceNumber() { | |
| 99 return mServiceNumber; | |
| 100 } | |
| 101 | |
| 102 synchronized ISandboxedProcessService getService() { | |
| 103 return mService; | |
| 104 } | |
| 105 | |
| 106 private Intent createServiceBindIntent() { | |
| 107 Intent intent = new Intent(); | |
| 108 String n = org.chromium.content.app.SandboxedProcessService.class.getNam
e(); | |
| 109 intent.setClassName(mContext, n + mServiceNumber); | |
| 110 intent.setPackage(mContext.getPackageName()); | |
| 111 return intent; | |
| 112 } | |
| 113 | |
| 114 /** | |
| 115 * Bind to an ISandboxedProcessService. This must be followed by a call to s
etupConnection() | |
| 116 * to setup the connection parameters. (These methods are separated to allow
the client | |
| 117 * to pass whatever parameters they have available here, and complete the re
mainder | |
| 118 * later while reducing the connection setup latency). | |
| 119 * @param nativeLibraryName The name of the shared native library to be load
ed for the | |
| 120 * sandboxed process. | |
| 121 * @param commandLine (Optional) Command line for the sandboxed process. If
omitted, then | |
| 122 * the command line parameters must instead be passed to
setupConnection(). | |
| 123 */ | |
| 124 synchronized void bind(String nativeLibraryName, String[] commandLine) { | |
| 125 TraceEvent.begin(); | |
| 126 assert !ThreadUtils.runningOnUiThread(); | |
| 127 | |
| 128 final Intent intent = createServiceBindIntent(); | |
| 129 | |
| 130 intent.putExtra(EXTRA_NATIVE_LIBRARY_NAME, nativeLibraryName); | |
| 131 if (commandLine != null) { | |
| 132 intent.putExtra(EXTRA_COMMAND_LINE, commandLine); | |
| 133 } | |
| 134 | |
| 135 mIsBound = mContext.bindService(intent, this, Context.BIND_AUTO_CREATE); | |
| 136 if (!mIsBound) { | |
| 137 onBindFailed(); | |
| 138 } | |
| 139 TraceEvent.end(); | |
| 140 } | |
| 141 | |
| 142 /** Setup a connection previous bound via a call to bind(). | |
| 143 * | |
| 144 * This establishes the parameters that were not already supplied in bind. | |
| 145 * @param commandLine (Optional) will be ignored if the command line was alr
eady sent in bind() | |
| 146 * @param fileToBeMapped a list of file descriptors that should be registere
d | |
| 147 * @param callback Used for status updates regarding this process connection
. | |
| 148 * @param onConnectionCallback will be run when the connection is setup and
ready to use. | |
| 149 */ | |
| 150 synchronized void setupConnection( | |
| 151 String[] commandLine, | |
| 152 FileDescriptorInfo[] filesToBeMapped, | |
| 153 ISandboxedProcessCallback callback, | |
| 154 Runnable onConnectionCallback) { | |
| 155 TraceEvent.begin(); | |
| 156 assert mConnectionParams == null; | |
| 157 mConnectionParams = new ConnectionParams(commandLine, filesToBeMapped, c
allback, | |
| 158 onConnectionCallback); | |
| 159 if (mServiceConnectComplete) { | |
| 160 doConnectionSetup(); | |
| 161 } | |
| 162 TraceEvent.end(); | |
| 163 } | |
| 164 | |
| 165 /** | |
| 166 * Unbind the ISandboxedProcessService. It is safe to call this multiple tim
es. | |
| 167 */ | |
| 168 synchronized void unbind() { | |
| 169 if (mIsBound) { | |
| 170 mContext.unbindService(this); | |
| 171 mIsBound = false; | |
| 172 } | |
| 173 if (mService != null) { | |
| 174 if (mHighPriorityConnection != null) { | |
| 175 unbindHighPriority(true); | |
| 176 } | |
| 177 mService = null; | |
| 178 mPID = 0; | |
| 179 } | |
| 180 mConnectionParams = null; | |
| 181 mServiceConnectComplete = false; | |
| 182 } | |
| 183 | |
| 184 // Called on the main thread to notify that the service is connected. | |
| 185 @Override | |
| 186 public void onServiceConnected(ComponentName className, IBinder service) { | |
| 187 TraceEvent.begin(); | |
| 188 mServiceConnectComplete = true; | |
| 189 mService = ISandboxedProcessService.Stub.asInterface(service); | |
| 190 if (mConnectionParams != null) { | |
| 191 doConnectionSetup(); | |
| 192 } | |
| 193 TraceEvent.end(); | |
| 194 } | |
| 195 | |
| 196 // Called on the main thread to notify that the bindService() call failed (r
eturned false). | |
| 197 private void onBindFailed() { | |
| 198 mServiceConnectComplete = true; | |
| 199 if (mConnectionParams != null) { | |
| 200 doConnectionSetup(); | |
| 201 } | |
| 202 } | |
| 203 | |
| 204 /** | |
| 205 * Called when the connection parameters have been set, and a connection has
been established | |
| 206 * (as signaled by onServiceConnected), or if the connection failed (mServic
e will be false). | |
| 207 */ | |
| 208 private void doConnectionSetup() { | |
| 209 TraceEvent.begin(); | |
| 210 assert mServiceConnectComplete && mConnectionParams != null; | |
| 211 // Capture the callback before it is potentially nulled in unbind(). | |
| 212 Runnable onConnectionCallback = | |
| 213 mConnectionParams != null ? mConnectionParams.mOnConnectionCallback
: null; | |
| 214 if (onConnectionCallback == null) { | |
| 215 unbind(); | |
| 216 } else if (mService != null) { | |
| 217 Bundle bundle = new Bundle(); | |
| 218 bundle.putStringArray(EXTRA_COMMAND_LINE, mConnectionParams.mCommand
Line); | |
| 219 | |
| 220 FileDescriptorInfo[] fileInfos = mConnectionParams.mFilesToBeMapped; | |
| 221 ParcelFileDescriptor[] parcelFiles = new ParcelFileDescriptor[fileIn
fos.length]; | |
| 222 for (int i = 0; i < fileInfos.length; i++) { | |
| 223 if (fileInfos[i].mFd == -1) { | |
| 224 // If someone provided an invalid FD, they are doing somethi
ng wrong. | |
| 225 Log.e(TAG, "Invalid FD (id=" + fileInfos[i].mId + ") for pro
cess connection, " | |
| 226 + "aborting connection."); | |
| 227 return; | |
| 228 } | |
| 229 String idName = EXTRA_FILES_PREFIX + i + EXTRA_FILES_ID_SUFFIX; | |
| 230 String fdName = EXTRA_FILES_PREFIX + i + EXTRA_FILES_FD_SUFFIX; | |
| 231 if (fileInfos[i].mAutoClose) { | |
| 232 // Adopt the FD, it will be closed when we close the ParcelF
ileDescriptor. | |
| 233 parcelFiles[i] = ParcelFileDescriptor.adoptFd(fileInfos[i].m
Fd); | |
| 234 } else { | |
| 235 try { | |
| 236 parcelFiles[i] = ParcelFileDescriptor.fromFd(fileInfos[i
].mFd); | |
| 237 } catch(IOException e) { | |
| 238 Log.e(TAG, | |
| 239 "Invalid FD provided for process connection, abort
ing connection.", | |
| 240 e); | |
| 241 return; | |
| 242 } | |
| 243 | |
| 244 } | |
| 245 bundle.putParcelable(fdName, parcelFiles[i]); | |
| 246 bundle.putInt(idName, fileInfos[i].mId); | |
| 247 } | |
| 248 // Add the CPU properties now. | |
| 249 bundle.putInt(EXTRA_CPU_COUNT, CpuFeatures.getCount()); | |
| 250 bundle.putLong(EXTRA_CPU_FEATURES, CpuFeatures.getMask()); | |
| 251 | |
| 252 try { | |
| 253 mPID = mService.setupConnection(bundle, mConnectionParams.mCallb
ack); | |
| 254 } catch (android.os.RemoteException re) { | |
| 255 Log.e(TAG, "Failed to setup connection.", re); | |
| 256 } | |
| 257 // We proactivley close the FDs rather than wait for GC & finalizer. | |
| 258 try { | |
| 259 for (ParcelFileDescriptor parcelFile : parcelFiles) { | |
| 260 if (parcelFile != null) parcelFile.close(); | |
| 261 } | |
| 262 } catch (IOException ioe) { | |
| 263 Log.w(TAG, "Failed to close FD.", ioe); | |
| 264 } | |
| 265 } | |
| 266 mConnectionParams = null; | |
| 267 if (onConnectionCallback != null) { | |
| 268 onConnectionCallback.run(); | |
| 269 } | |
| 270 TraceEvent.end(); | |
| 271 } | |
| 272 | |
| 273 // Called on the main thread to notify that the sandboxed service did not di
sconnect gracefully. | |
| 274 @Override | |
| 275 public void onServiceDisconnected(ComponentName className) { | |
| 276 int pid = mPID; // Stash pid & connection callback since unbind() will
clear them. | |
| 277 Runnable onConnectionCallback = | |
| 278 mConnectionParams != null ? mConnectionParams.mOnConnectionCallback
: null; | |
| 279 Log.w(TAG, "onServiceDisconnected (crash?): pid=" + pid); | |
| 280 unbind(); // We don't want to auto-restart on crash. Let the browser do
that. | |
| 281 if (pid != 0) { | |
| 282 mDeathCallback.onSandboxedProcessDied(pid); | |
| 283 } | |
| 284 if (onConnectionCallback != null) { | |
| 285 onConnectionCallback.run(); | |
| 286 } | |
| 287 } | |
| 288 | |
| 289 /** | |
| 290 * Bind the service with a new high priority connection. This will make the
service | |
| 291 * as important as the main process. | |
| 292 */ | |
| 293 synchronized void bindHighPriority() { | |
| 294 if (mService == null) { | |
| 295 Log.w(TAG, "The connection is not bound for " + mPID); | |
| 296 return; | |
| 297 } | |
| 298 if (mHighPriorityConnection == null) { | |
| 299 mHighPriorityConnection = new HighPriorityConnection(); | |
| 300 mHighPriorityConnection.bind(); | |
| 301 } | |
| 302 mHighPriorityConnectionCount++; | |
| 303 } | |
| 304 | |
| 305 /** | |
| 306 * Unbind the service as the high priority connection. | |
| 307 */ | |
| 308 synchronized void unbindHighPriority(boolean force) { | |
| 309 if (mService == null) { | |
| 310 Log.w(TAG, "The connection is not bound for " + mPID); | |
| 311 return; | |
| 312 } | |
| 313 mHighPriorityConnectionCount--; | |
| 314 if (force || (mHighPriorityConnectionCount == 0 && mHighPriorityConnecti
on != null)) { | |
| 315 mHighPriorityConnection.unbind(); | |
| 316 mHighPriorityConnection = null; | |
| 317 } | |
| 318 } | |
| 319 | |
| 320 private class HighPriorityConnection implements ServiceConnection { | |
| 321 | |
| 322 private boolean mHBound = false; | |
| 323 | |
| 324 void bind() { | |
| 325 final Intent intent = createServiceBindIntent(); | |
| 326 | |
| 327 mHBound = mContext.bindService(intent, this, | |
| 328 Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT); | |
| 329 } | |
| 330 | |
| 331 void unbind() { | |
| 332 if (mHBound) { | |
| 333 mContext.unbindService(this); | |
| 334 mHBound = false; | |
| 335 } | |
| 336 } | |
| 337 | |
| 338 @Override | |
| 339 public void onServiceConnected(ComponentName className, IBinder service)
{ | |
| 340 } | |
| 341 | |
| 342 @Override | |
| 343 public void onServiceDisconnected(ComponentName className) { | |
| 344 } | |
| 345 } | |
| 346 | |
| 347 /** | |
| 348 * @return The connection PID, or 0 if not yet connected. | |
| 349 */ | |
| 350 synchronized public int getPid() { | |
| 351 return mPID; | |
| 352 } | |
| 353 } | |
| OLD | NEW |