OLD | NEW |
---|---|
1 // Copyright 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package org.chromium.content.browser; | 5 package org.chromium.content.browser; |
6 | 6 |
7 import android.annotation.SuppressLint; | 7 import android.annotation.SuppressLint; |
8 import android.content.Context; | 8 import android.content.Context; |
9 import android.content.Intent; | 9 import android.content.Intent; |
10 import android.content.pm.ApplicationInfo; | 10 import android.content.pm.ApplicationInfo; |
11 import android.content.pm.PackageManager; | 11 import android.content.pm.PackageManager; |
12 import android.graphics.SurfaceTexture; | 12 import android.graphics.SurfaceTexture; |
13 import android.os.Build; | 13 import android.os.Build; |
14 import android.os.Bundle; | 14 import android.os.Bundle; |
15 import android.os.ParcelFileDescriptor; | 15 import android.os.ParcelFileDescriptor; |
16 import android.os.RemoteException; | 16 import android.os.RemoteException; |
17 import android.text.TextUtils; | 17 import android.text.TextUtils; |
18 import android.util.Pair; | 18 import android.util.Pair; |
19 import android.view.Surface; | 19 import android.view.Surface; |
20 | 20 |
21 import org.chromium.base.CommandLine; | 21 import org.chromium.base.CommandLine; |
22 import org.chromium.base.CpuFeatures; | 22 import org.chromium.base.CpuFeatures; |
23 import org.chromium.base.Log; | 23 import org.chromium.base.Log; |
24 import org.chromium.base.ThreadUtils; | 24 import org.chromium.base.ThreadUtils; |
25 import org.chromium.base.TraceEvent; | 25 import org.chromium.base.TraceEvent; |
26 import org.chromium.base.VisibleForTesting; | 26 import org.chromium.base.VisibleForTesting; |
27 import org.chromium.base.annotations.CalledByNative; | 27 import org.chromium.base.annotations.CalledByNative; |
28 import org.chromium.base.annotations.JNINamespace; | 28 import org.chromium.base.annotations.JNINamespace; |
29 import org.chromium.base.library_loader.LibraryProcessType; | |
29 import org.chromium.base.library_loader.Linker; | 30 import org.chromium.base.library_loader.Linker; |
30 import org.chromium.content.app.ChildProcessService; | 31 import org.chromium.content.app.ChildProcessService; |
31 import org.chromium.content.app.ChromiumLinkerParams; | 32 import org.chromium.content.app.ChromiumLinkerParams; |
32 import org.chromium.content.app.DownloadProcessService; | 33 import org.chromium.content.app.DownloadProcessService; |
33 import org.chromium.content.app.PrivilegedProcessService; | 34 import org.chromium.content.app.PrivilegedProcessService; |
34 import org.chromium.content.app.SandboxedProcessService; | 35 import org.chromium.content.app.SandboxedProcessService; |
35 import org.chromium.content.common.ContentSwitches; | 36 import org.chromium.content.common.ContentSwitches; |
36 import org.chromium.content.common.IChildProcessCallback; | 37 import org.chromium.content.common.IChildProcessCallback; |
37 import org.chromium.content.common.SurfaceWrapper; | 38 import org.chromium.content.common.SurfaceWrapper; |
38 | 39 |
39 import java.io.IOException; | 40 import java.io.IOException; |
40 import java.util.ArrayList; | 41 import java.util.ArrayList; |
42 import java.util.HashMap; | |
41 import java.util.LinkedList; | 43 import java.util.LinkedList; |
42 import java.util.Map; | 44 import java.util.Map; |
43 import java.util.Queue; | 45 import java.util.Queue; |
44 import java.util.concurrent.ConcurrentHashMap; | 46 import java.util.concurrent.ConcurrentHashMap; |
45 | 47 |
46 /** | 48 /** |
47 * This class provides the method to start/stop ChildProcess called by native. | 49 * This class provides the method to start/stop ChildProcess called by native. |
48 */ | 50 */ |
49 @JNINamespace("content") | 51 @JNINamespace("content") |
50 public class ChildProcessLauncher { | 52 public class ChildProcessLauncher { |
51 private static final String TAG = "ChildProcLauncher"; | 53 private static final String TAG = "ChildProcLauncher"; |
52 | 54 |
53 static final int CALLBACK_FOR_UNKNOWN_PROCESS = 0; | 55 static final int CALLBACK_FOR_UNKNOWN_PROCESS = 0; |
54 static final int CALLBACK_FOR_GPU_PROCESS = 1; | 56 static final int CALLBACK_FOR_GPU_PROCESS = 1; |
55 static final int CALLBACK_FOR_RENDERER_PROCESS = 2; | 57 static final int CALLBACK_FOR_RENDERER_PROCESS = 2; |
56 static final int CALLBACK_FOR_UTILITY_PROCESS = 3; | 58 static final int CALLBACK_FOR_UTILITY_PROCESS = 3; |
57 static final int CALLBACK_FOR_DOWNLOAD_PROCESS = 4; | 59 static final int CALLBACK_FOR_DOWNLOAD_PROCESS = 4; |
58 | 60 |
59 private static class ChildConnectionAllocator { | 61 private static class ChildConnectionAllocator { |
60 // Connections to services. Indices of the array correspond to the servi ce numbers. | 62 // Connections to services. Indices of the array correspond to the servi ce numbers. |
61 private final ChildProcessConnection[] mChildProcessConnections; | 63 private final ChildProcessConnection[] mChildProcessConnections; |
62 | 64 |
63 // The list of free (not bound) service indices. | 65 // The list of free (not bound) service indices. |
64 // SHOULD BE ACCESSED WITH mConnectionLock. | 66 // SHOULD BE ACCESSED WITH mConnectionLock. |
65 private final ArrayList<Integer> mFreeConnectionIndices; | 67 private final ArrayList<Integer> mFreeConnectionIndices; |
66 private final Object mConnectionLock = new Object(); | 68 private final Object mConnectionLock = new Object(); |
67 | 69 |
68 private Class<? extends ChildProcessService> mChildClass; | 70 private Class<? extends ChildProcessService> mChildClass; |
69 private final boolean mInSandbox; | 71 private final boolean mInSandbox; |
72 // Each Allocator keeps an queue for the pending spawn data. Once a conn ection is free, we | |
73 // dequeue the pending spawn data from the same allocator as the connect ion. | |
74 private PendingSpawnQueue mPendingSpawnQueue = new PendingSpawnQueue(); | |
70 | 75 |
71 public ChildConnectionAllocator(boolean inSandbox, int numChildServices) { | 76 public ChildConnectionAllocator(boolean inSandbox, int numChildServices) { |
72 mChildProcessConnections = new ChildProcessConnectionImpl[numChildSe rvices]; | 77 mChildProcessConnections = new ChildProcessConnectionImpl[numChildSe rvices]; |
73 mFreeConnectionIndices = new ArrayList<Integer>(numChildServices); | 78 mFreeConnectionIndices = new ArrayList<Integer>(numChildServices); |
74 for (int i = 0; i < numChildServices; i++) { | 79 for (int i = 0; i < numChildServices; i++) { |
75 mFreeConnectionIndices.add(i); | 80 mFreeConnectionIndices.add(i); |
76 } | 81 } |
77 mChildClass = | 82 mChildClass = |
78 inSandbox ? SandboxedProcessService.class : PrivilegedProces sService.class; | 83 inSandbox ? SandboxedProcessService.class : PrivilegedProces sService.class; |
79 mInSandbox = inSandbox; | 84 mInSandbox = inSandbox; |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
118 } | 123 } |
119 } | 124 } |
120 } | 125 } |
121 | 126 |
122 public boolean isFreeConnectionAvailable() { | 127 public boolean isFreeConnectionAvailable() { |
123 synchronized (mConnectionLock) { | 128 synchronized (mConnectionLock) { |
124 return !mFreeConnectionIndices.isEmpty(); | 129 return !mFreeConnectionIndices.isEmpty(); |
125 } | 130 } |
126 } | 131 } |
127 | 132 |
133 public PendingSpawnQueue getPendingSpawnQueue() { | |
134 return mPendingSpawnQueue; | |
135 } | |
136 | |
128 /** @return the count of connections managed by the allocator */ | 137 /** @return the count of connections managed by the allocator */ |
129 @VisibleForTesting | 138 @VisibleForTesting |
130 int allocatedConnectionsCountForTesting() { | 139 int allocatedConnectionsCountForTesting() { |
131 return mChildProcessConnections.length - mFreeConnectionIndices.size (); | 140 return mChildProcessConnections.length - mFreeConnectionIndices.size (); |
132 } | 141 } |
133 } | 142 } |
134 | 143 |
135 private static class PendingSpawnData { | 144 private static class PendingSpawnData { |
136 private final Context mContext; | 145 private final Context mContext; |
137 private final String[] mCommandLine; | 146 private final String[] mCommandLine; |
138 private final int mChildProcessId; | 147 private final int mChildProcessId; |
139 private final FileDescriptorInfo[] mFilesToBeMapped; | 148 private final FileDescriptorInfo[] mFilesToBeMapped; |
140 private final long mClientContext; | 149 private final long mClientContext; |
141 private final int mCallbackType; | 150 private final int mCallbackType; |
142 private final boolean mInSandbox; | 151 private final boolean mInSandbox; |
152 private final ChildProcessCreationParams mCreationParams; | |
143 | 153 |
144 private PendingSpawnData( | 154 private PendingSpawnData( |
145 Context context, | 155 Context context, |
146 String[] commandLine, | 156 String[] commandLine, |
147 int childProcessId, | 157 int childProcessId, |
148 FileDescriptorInfo[] filesToBeMapped, | 158 FileDescriptorInfo[] filesToBeMapped, |
149 long clientContext, | 159 long clientContext, |
150 int callbackType, | 160 int callbackType, |
151 boolean inSandbox) { | 161 boolean inSandbox, |
162 ChildProcessCreationParams creationParams) { | |
152 mContext = context; | 163 mContext = context; |
153 mCommandLine = commandLine; | 164 mCommandLine = commandLine; |
154 mChildProcessId = childProcessId; | 165 mChildProcessId = childProcessId; |
155 mFilesToBeMapped = filesToBeMapped; | 166 mFilesToBeMapped = filesToBeMapped; |
156 mClientContext = clientContext; | 167 mClientContext = clientContext; |
157 mCallbackType = callbackType; | 168 mCallbackType = callbackType; |
158 mInSandbox = inSandbox; | 169 mInSandbox = inSandbox; |
170 mCreationParams = creationParams; | |
159 } | 171 } |
160 | 172 |
161 private Context context() { | 173 private Context context() { |
162 return mContext; | 174 return mContext; |
163 } | 175 } |
164 private String[] commandLine() { | 176 private String[] commandLine() { |
165 return mCommandLine; | 177 return mCommandLine; |
166 } | 178 } |
167 private int childProcessId() { | 179 private int childProcessId() { |
168 return mChildProcessId; | 180 return mChildProcessId; |
169 } | 181 } |
170 private FileDescriptorInfo[] filesToBeMapped() { | 182 private FileDescriptorInfo[] filesToBeMapped() { |
171 return mFilesToBeMapped; | 183 return mFilesToBeMapped; |
172 } | 184 } |
173 private long clientContext() { | 185 private long clientContext() { |
174 return mClientContext; | 186 return mClientContext; |
175 } | 187 } |
176 private int callbackType() { | 188 private int callbackType() { |
177 return mCallbackType; | 189 return mCallbackType; |
178 } | 190 } |
179 private boolean inSandbox() { | 191 private boolean inSandbox() { |
180 return mInSandbox; | 192 return mInSandbox; |
181 } | 193 } |
194 private ChildProcessCreationParams getCreationParams() { | |
195 return mCreationParams; | |
196 } | |
182 } | 197 } |
183 | 198 |
184 private static class PendingSpawnQueue { | 199 private static class PendingSpawnQueue { |
185 // The list of pending process spawn requests and its lock. | 200 // The list of pending process spawn requests and its lock. |
186 // Operations on this queue must be atomical w.r.t. free connections upd ates. | 201 // Operations on this queue must be atomical w.r.t. free connections upd ates. |
187 private static Queue<PendingSpawnData> sPendingSpawns = | 202 private Queue<PendingSpawnData> mPendingSpawns = |
188 new LinkedList<PendingSpawnData>(); | 203 new LinkedList<PendingSpawnData>(); |
189 static final Object sPendingSpawnsLock = new Object(); | 204 final Object mPendingSpawnsLock = new Object(); |
190 | 205 |
191 /** | 206 /** |
192 * Queue up a spawn requests to be processed once a free service is avai lable. | 207 * Queue up a spawn requests to be processed once a free service is avai lable. |
193 * Called when a spawn is requested while we are at the capacity. | 208 * Called when a spawn is requested while we are at the capacity. |
194 */ | 209 */ |
195 public void enqueueLocked(final PendingSpawnData pendingSpawn) { | 210 public void enqueueLocked(final PendingSpawnData pendingSpawn) { |
196 assert Thread.holdsLock(sPendingSpawnsLock); | 211 assert Thread.holdsLock(mPendingSpawnsLock); |
197 sPendingSpawns.add(pendingSpawn); | 212 mPendingSpawns.add(pendingSpawn); |
198 } | 213 } |
199 | 214 |
200 /** | 215 /** |
201 * Pop the next request from the queue. Called when a free service is av ailable. | 216 * Pop the next request from the queue. Called when a free service is av ailable. |
202 * @return the next spawn request waiting in the queue. | 217 * @return the next spawn request waiting in the queue. |
203 */ | 218 */ |
204 public PendingSpawnData dequeueLocked() { | 219 public PendingSpawnData dequeueLocked() { |
205 assert Thread.holdsLock(sPendingSpawnsLock); | 220 assert Thread.holdsLock(mPendingSpawnsLock); |
206 return sPendingSpawns.poll(); | 221 return mPendingSpawns.poll(); |
207 } | 222 } |
208 | 223 |
209 /** @return the count of pending spawns in the queue */ | 224 /** @return the count of pending spawns in the queue */ |
210 public int sizeLocked() { | 225 public int sizeLocked() { |
211 assert Thread.holdsLock(sPendingSpawnsLock); | 226 assert Thread.holdsLock(mPendingSpawnsLock); |
212 return sPendingSpawns.size(); | 227 return mPendingSpawns.size(); |
213 } | 228 } |
214 } | 229 } |
215 | 230 |
216 private static final PendingSpawnQueue sPendingSpawnQueue = new PendingSpawn Queue(); | 231 // Service class for child process. |
217 | 232 // Map from package name to ChildConnectionAllocator; |
218 // Service class for child process. As the default value it uses SandboxedPr ocessService0 and | 233 private static Map<String, ChildConnectionAllocator> sSandboxedChildConnecti onAllocatorMap; |
219 // PrivilegedProcessService0. | 234 // As the default value it uses PrivilegedProcessService0. |
220 private static ChildConnectionAllocator sSandboxedChildConnectionAllocator; | |
221 private static ChildConnectionAllocator sPrivilegedChildConnectionAllocator; | 235 private static ChildConnectionAllocator sPrivilegedChildConnectionAllocator; |
222 | 236 |
223 private static final String NUM_SANDBOXED_SERVICES_KEY = | 237 private static final String NUM_SANDBOXED_SERVICES_KEY = |
224 "org.chromium.content.browser.NUM_SANDBOXED_SERVICES"; | 238 "org.chromium.content.browser.NUM_SANDBOXED_SERVICES"; |
225 private static final String NUM_PRIVILEGED_SERVICES_KEY = | 239 private static final String NUM_PRIVILEGED_SERVICES_KEY = |
226 "org.chromium.content.browser.NUM_PRIVILEGED_SERVICES"; | 240 "org.chromium.content.browser.NUM_PRIVILEGED_SERVICES"; |
227 // Overrides the number of available sandboxed services. | 241 // Overrides the number of available sandboxed services. |
228 @VisibleForTesting | 242 @VisibleForTesting |
229 public static final String SWITCH_NUM_SANDBOXED_SERVICES_FOR_TESTING = "num- sandboxed-services"; | 243 public static final String SWITCH_NUM_SANDBOXED_SERVICES_FOR_TESTING = "num- sandboxed-services"; |
230 | 244 |
231 private static int getNumberOfServices(Context context, boolean inSandbox) { | 245 private static int getNumberOfServices(Context context, boolean inSandbox, S tring packageName) { |
232 try { | 246 try { |
233 PackageManager packageManager = context.getPackageManager(); | 247 PackageManager packageManager = context.getPackageManager(); |
234 ChildProcessCreationParams childProcessCreationParams = | |
235 ChildProcessCreationParams.get(); | |
236 final String packageName = childProcessCreationParams != null | |
237 ? childProcessCreationParams.getPackageName() : context.getP ackageName(); | |
238 ApplicationInfo appInfo = packageManager.getApplicationInfo(packageN ame, | 248 ApplicationInfo appInfo = packageManager.getApplicationInfo(packageN ame, |
239 PackageManager.GET_META_DATA); | 249 PackageManager.GET_META_DATA); |
240 int numServices = appInfo.metaData.getInt(inSandbox ? NUM_SANDBOXED_ SERVICES_KEY | 250 int numServices = -1; |
241 : NUM_PRIVILEGED_SERVICES_KEY, -1); | 251 if (appInfo.metaData != null) { |
252 numServices = appInfo.metaData.getInt( | |
253 inSandbox ? NUM_SANDBOXED_SERVICES_KEY : NUM_PRIVILEGED_ SERVICES_KEY, -1); | |
254 } | |
242 if (inSandbox | 255 if (inSandbox |
243 && CommandLine.getInstance().hasSwitch( | 256 && CommandLine.getInstance().hasSwitch( |
244 SWITCH_NUM_SANDBOXED_SERVICES_FOR_TESTING)) { | 257 SWITCH_NUM_SANDBOXED_SERVICES_FOR_TESTING)) { |
245 String value = CommandLine.getInstance().getSwitchValue( | 258 String value = CommandLine.getInstance().getSwitchValue( |
246 SWITCH_NUM_SANDBOXED_SERVICES_FOR_TESTING); | 259 SWITCH_NUM_SANDBOXED_SERVICES_FOR_TESTING); |
247 if (!TextUtils.isEmpty(value)) { | 260 if (!TextUtils.isEmpty(value)) { |
248 try { | 261 try { |
249 numServices = Integer.parseInt(value); | 262 numServices = Integer.parseInt(value); |
250 } catch (NumberFormatException e) { | 263 } catch (NumberFormatException e) { |
251 Log.w(TAG, "The value of --num-sandboxed-services is for matted wrongly: " | 264 Log.w(TAG, "The value of --num-sandboxed-services is for matted wrongly: " |
252 + value); | 265 + value); |
253 } | 266 } |
254 } | 267 } |
255 } | 268 } |
256 if (numServices < 0) { | 269 if (numServices < 0) { |
257 throw new RuntimeException("Illegal meta data value for number o f child services"); | 270 throw new RuntimeException("Illegal meta data value for number o f child services"); |
258 } | 271 } |
259 return numServices; | 272 return numServices; |
260 } catch (PackageManager.NameNotFoundException e) { | 273 } catch (PackageManager.NameNotFoundException e) { |
261 throw new RuntimeException("Could not get application info"); | 274 throw new RuntimeException("Could not get application info"); |
262 } | 275 } |
263 } | 276 } |
264 | 277 |
265 private static void initConnectionAllocatorsIfNecessary(Context context) { | 278 private static void initConnectionAllocatorsIfNecessary( |
279 Context context, boolean inSandbox, String packageName) { | |
266 synchronized (ChildProcessLauncher.class) { | 280 synchronized (ChildProcessLauncher.class) { |
267 if (sSandboxedChildConnectionAllocator == null) { | 281 if (inSandbox) { |
268 sSandboxedChildConnectionAllocator = | 282 if (sSandboxedChildConnectionAllocatorMap == null) { |
269 new ChildConnectionAllocator(true, getNumberOfServices(c ontext, true)); | 283 sSandboxedChildConnectionAllocatorMap = |
284 new HashMap<String, ChildConnectionAllocator>(); | |
285 } | |
286 if (!sSandboxedChildConnectionAllocatorMap.containsKey(packageNa me)) { | |
287 Log.w(TAG, "Create a new ChildConnectionAllocator with packa ge name = %s," | |
288 + " inSandbox = true", | |
289 packageName); | |
290 sSandboxedChildConnectionAllocatorMap.put(packageName, | |
291 new ChildConnectionAllocator(true, getNumberOfServic es(context, | |
292 inSandbox , packageName))); | |
pkotwicz
2016/05/31 00:38:58
Nit: inSandbox -> true
| |
293 } | |
294 } else if (sPrivilegedChildConnectionAllocator == null) { | |
295 sPrivilegedChildConnectionAllocator = new ChildConnectionAllocat or( | |
296 false, getNumberOfServices(context, false, packageName)) ; | |
270 } | 297 } |
271 if (sPrivilegedChildConnectionAllocator == null) { | 298 // TODO(pkotwicz|hanxi): Figure out when old allocators should be re moved from |
272 sPrivilegedChildConnectionAllocator = | 299 // {@code sSandboxedChildConnectionAllocatorMap}. |
273 new ChildConnectionAllocator(false, getNumberOfServices( context, false)); | |
274 } | |
275 } | 300 } |
276 } | 301 } |
277 | 302 |
278 private static ChildConnectionAllocator getConnectionAllocator(boolean inSan dbox) { | 303 /** |
279 return inSandbox | 304 * Note: please make sure that the Allocator has been initialized before cal ling this function. |
280 ? sSandboxedChildConnectionAllocator : sPrivilegedChildConnectio nAllocator; | 305 * Otherwise, always calls {@link initConnectionAllocatorsIfNecessary} first . |
306 */ | |
307 private static ChildConnectionAllocator getConnectionAllocator( | |
308 String packageName, boolean inSandbox) { | |
309 if (!inSandbox) { | |
310 return sPrivilegedChildConnectionAllocator; | |
311 } | |
312 return sSandboxedChildConnectionAllocatorMap.get(packageName); | |
313 } | |
314 | |
315 /** | |
316 * Get the PendingSpawnQueue of the Allocator. Initialize the Allocator if n eeded. | |
317 */ | |
318 private static PendingSpawnQueue getPendingSpawnQueue(Context context, Strin g packageName, | |
319 boolean inSandbox) { | |
320 initConnectionAllocatorsIfNecessary(context, inSandbox, packageName); | |
321 return getConnectionAllocator(packageName, inSandbox).getPendingSpawnQue ue(); | |
281 } | 322 } |
282 | 323 |
283 private static ChildProcessConnection allocateConnection(Context context, bo olean inSandbox, | 324 private static ChildProcessConnection allocateConnection(Context context, bo olean inSandbox, |
284 ChromiumLinkerParams chromiumLinkerParams, boolean alwaysInForegroun d) { | 325 ChromiumLinkerParams chromiumLinkerParams, boolean alwaysInForegroun d, |
326 ChildProcessCreationParams creationParams) { | |
285 ChildProcessConnection.DeathCallback deathCallback = | 327 ChildProcessConnection.DeathCallback deathCallback = |
286 new ChildProcessConnection.DeathCallback() { | 328 new ChildProcessConnection.DeathCallback() { |
287 @Override | 329 @Override |
288 public void onChildProcessDied(ChildProcessConnection connec tion) { | 330 public void onChildProcessDied(ChildProcessConnection connec tion) { |
289 if (connection.getPid() != 0) { | 331 if (connection.getPid() != 0) { |
290 stop(connection.getPid()); | 332 stop(connection.getPid()); |
291 } else { | 333 } else { |
292 freeConnection(connection); | 334 freeConnection(connection); |
293 } | 335 } |
294 } | 336 } |
295 }; | 337 }; |
296 initConnectionAllocatorsIfNecessary(context); | 338 String packageName = creationParams.getPackageName(); |
297 return getConnectionAllocator(inSandbox).allocate(context, deathCallback , | 339 initConnectionAllocatorsIfNecessary(context, inSandbox, packageName); |
298 chromiumLinkerParams, alwaysInForeground, ChildProcessCreationPa rams.get()); | 340 return getConnectionAllocator(packageName, inSandbox) |
341 .allocate(context, deathCallback, chromiumLinkerParams, alwaysIn Foreground, | |
342 creationParams); | |
299 } | 343 } |
300 | 344 |
301 private static boolean sLinkerInitialized = false; | 345 private static boolean sLinkerInitialized = false; |
302 private static long sLinkerLoadAddress = 0; | 346 private static long sLinkerLoadAddress = 0; |
303 | 347 |
304 private static ChromiumLinkerParams getLinkerParamsForNewConnection() { | 348 private static ChromiumLinkerParams getLinkerParamsForNewConnection() { |
305 if (!sLinkerInitialized) { | 349 if (!sLinkerInitialized) { |
306 if (Linker.isUsed()) { | 350 if (Linker.isUsed()) { |
307 sLinkerLoadAddress = Linker.getInstance().getBaseLoadAddress(); | 351 sLinkerLoadAddress = Linker.getInstance().getBaseLoadAddress(); |
308 if (sLinkerLoadAddress == 0) { | 352 if (sLinkerLoadAddress == 0) { |
(...skipping 13 matching lines...) Expand all Loading... | |
322 waitForSharedRelros, | 366 waitForSharedRelros, |
323 linker.getTestRunnerClassNameForTest ing(), | 367 linker.getTestRunnerClassNameForTest ing(), |
324 linker.getImplementationForTesting() ); | 368 linker.getImplementationForTesting() ); |
325 } else { | 369 } else { |
326 return new ChromiumLinkerParams(sLinkerLoadAddress, | 370 return new ChromiumLinkerParams(sLinkerLoadAddress, |
327 waitForSharedRelros); | 371 waitForSharedRelros); |
328 } | 372 } |
329 } | 373 } |
330 | 374 |
331 private static ChildProcessConnection allocateBoundConnection(Context contex t, | 375 private static ChildProcessConnection allocateBoundConnection(Context contex t, |
332 String[] commandLine, boolean inSandbox, boolean alwaysInForeground) { | 376 String[] commandLine, boolean inSandbox, boolean alwaysInForeground, |
377 ChildProcessCreationParams creationParams) { | |
333 ChromiumLinkerParams chromiumLinkerParams = getLinkerParamsForNewConnect ion(); | 378 ChromiumLinkerParams chromiumLinkerParams = getLinkerParamsForNewConnect ion(); |
334 ChildProcessConnection connection = allocateConnection(context, inSandbo x, | 379 ChildProcessConnection connection = allocateConnection( |
335 chromiumLinkerParams, alwaysInForeground); | 380 context, inSandbox, chromiumLinkerParams, alwaysInForeground, cr eationParams); |
336 if (connection != null) { | 381 if (connection != null) { |
337 connection.start(commandLine); | 382 connection.start(commandLine); |
338 | 383 |
339 if (inSandbox && !sSandboxedChildConnectionAllocator.isFreeConnectio nAvailable()) { | 384 if (inSandbox |
385 && !getConnectionAllocator(creationParams.getPackageName(), inSandbox) | |
386 .isFreeConnectionAvailable()) { | |
340 // Proactively releases all the moderate bindings once all the s andboxed services | 387 // Proactively releases all the moderate bindings once all the s andboxed services |
341 // are allocated, which will be very likely to have some of them killed by OOM | 388 // are allocated, which will be very likely to have some of them killed by OOM |
342 // killer. | 389 // killer. |
343 sBindingManager.releaseAllModerateBindings(); | 390 sBindingManager.releaseAllModerateBindings(); |
344 } | 391 } |
345 } | 392 } |
346 return connection; | 393 return connection; |
347 } | 394 } |
348 | 395 |
349 private static final long FREE_CONNECTION_DELAY_MILLIS = 1; | 396 private static final long FREE_CONNECTION_DELAY_MILLIS = 1; |
(...skipping 13 matching lines...) Expand all Loading... | |
363 @Override | 410 @Override |
364 public void run() { | 411 public void run() { |
365 final PendingSpawnData pendingSpawn = freeConnectionAndDequeuePe nding(conn); | 412 final PendingSpawnData pendingSpawn = freeConnectionAndDequeuePe nding(conn); |
366 if (pendingSpawn != null) { | 413 if (pendingSpawn != null) { |
367 new Thread(new Runnable() { | 414 new Thread(new Runnable() { |
368 @Override | 415 @Override |
369 public void run() { | 416 public void run() { |
370 startInternal(pendingSpawn.context(), pendingSpawn.c ommandLine(), | 417 startInternal(pendingSpawn.context(), pendingSpawn.c ommandLine(), |
371 pendingSpawn.childProcessId(), pendingSpawn. filesToBeMapped(), | 418 pendingSpawn.childProcessId(), pendingSpawn. filesToBeMapped(), |
372 pendingSpawn.clientContext(), pendingSpawn.c allbackType(), | 419 pendingSpawn.clientContext(), pendingSpawn.c allbackType(), |
373 pendingSpawn.inSandbox()); | 420 pendingSpawn.inSandbox(), pendingSpawn.getCr eationParams()); |
374 } | 421 } |
375 }).start(); | 422 }).start(); |
376 } | 423 } |
377 } | 424 } |
378 }, FREE_CONNECTION_DELAY_MILLIS); | 425 }, FREE_CONNECTION_DELAY_MILLIS); |
379 } | 426 } |
380 | 427 |
381 private static PendingSpawnData freeConnectionAndDequeuePending(ChildProcess Connection conn) { | 428 private static PendingSpawnData freeConnectionAndDequeuePending(ChildProcess Connection conn) { |
382 synchronized (PendingSpawnQueue.sPendingSpawnsLock) { | 429 ChildConnectionAllocator allocator = getConnectionAllocator( |
383 getConnectionAllocator(conn.isInSandbox()).free(conn); | 430 conn.getCreationParams().getPackageName(), conn.isInSandbox()); |
384 return sPendingSpawnQueue.dequeueLocked(); | 431 assert allocator != null; |
432 PendingSpawnQueue pendingSpawnQueue = allocator.getPendingSpawnQueue(); | |
433 synchronized (pendingSpawnQueue.mPendingSpawnsLock) { | |
434 allocator.free(conn); | |
435 return pendingSpawnQueue.dequeueLocked(); | |
385 } | 436 } |
386 } | 437 } |
387 | 438 |
388 // Represents an invalid process handle; same as base/process/process.h kNul lProcessHandle. | 439 // Represents an invalid process handle; same as base/process/process.h kNul lProcessHandle. |
389 private static final int NULL_PROCESS_HANDLE = 0; | 440 private static final int NULL_PROCESS_HANDLE = 0; |
390 | 441 |
391 // Map from pid to ChildService connection. | 442 // Map from pid to ChildService connection. |
392 private static Map<Integer, ChildProcessConnection> sServiceMap = | 443 private static Map<Integer, ChildProcessConnection> sServiceMap = |
393 new ConcurrentHashMap<Integer, ChildProcessConnection>(); | 444 new ConcurrentHashMap<Integer, ChildProcessConnection>(); |
394 | 445 |
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
506 /** | 557 /** |
507 * Called when the embedding application is sent to background. | 558 * Called when the embedding application is sent to background. |
508 */ | 559 */ |
509 public static void onSentToBackground() { | 560 public static void onSentToBackground() { |
510 sApplicationInForeground = false; | 561 sApplicationInForeground = false; |
511 sBindingManager.onSentToBackground(); | 562 sBindingManager.onSentToBackground(); |
512 } | 563 } |
513 | 564 |
514 /** | 565 /** |
515 * Starts moderate binding management. | 566 * Starts moderate binding management. |
567 * Note: WebAPKs and non WebAPKs share the same moderate binding pool, so th e size of the | |
568 * shared moderate binding pool is always set based on the number of sandbox es processes | |
569 * used by Chrome. | |
516 * @param context Android's context. | 570 * @param context Android's context. |
517 * @param moderateBindingTillBackgrounded true if the BindingManager should add a moderate | 571 * @param moderateBindingTillBackgrounded true if the BindingManager should add a moderate |
518 * binding to a render process when it is created and remove the moderate bi nding when Chrome is | 572 * binding to a render process when it is created and remove the moderate bi nding when Chrome is |
519 * sent to the background. | 573 * sent to the background. |
520 */ | 574 */ |
521 public static void startModerateBindingManagement( | 575 public static void startModerateBindingManagement( |
522 Context context, boolean moderateBindingTillBackgrounded) { | 576 Context context, boolean moderateBindingTillBackgrounded) { |
523 sBindingManager.startModerateBindingManagement( | 577 sBindingManager.startModerateBindingManagement(context, |
524 context, getNumberOfServices(context, true), moderateBindingTill Backgrounded); | 578 getNumberOfServices(context, true, context.getPackageName()), |
579 moderateBindingTillBackgrounded); | |
525 } | 580 } |
526 | 581 |
527 /** | 582 /** |
528 * Called when the embedding application is brought to foreground. | 583 * Called when the embedding application is brought to foreground. |
529 */ | 584 */ |
530 public static void onBroughtToForeground() { | 585 public static void onBroughtToForeground() { |
531 sApplicationInForeground = true; | 586 sApplicationInForeground = true; |
532 sBindingManager.onBroughtToForeground(); | 587 sBindingManager.onBroughtToForeground(); |
533 } | 588 } |
534 | 589 |
535 /** | 590 /** |
536 * Returns whether the application is currently in the foreground. | 591 * Returns whether the application is currently in the foreground. |
537 */ | 592 */ |
538 static boolean isApplicationInForeground() { | 593 static boolean isApplicationInForeground() { |
539 return sApplicationInForeground; | 594 return sApplicationInForeground; |
540 } | 595 } |
541 | 596 |
542 /** | 597 /** |
543 * Should be called early in startup so the work needed to spawn the child p rocess can be done | 598 * Should be called early in startup so the work needed to spawn the child p rocess can be done |
544 * in parallel to other startup work. Must not be called on the UI thread. S pare connection is | 599 * in parallel to other startup work. Must not be called on the UI thread. S pare connection is |
545 * created in sandboxed child process. | 600 * created in sandboxed child process. |
546 * @param context the application context used for the connection. | 601 * @param context the application context used for the connection. |
547 * @param params child process creation params. | |
548 */ | 602 */ |
549 public static void warmUp(Context context, ChildProcessCreationParams params ) { | 603 public static void warmUp(Context context) { |
550 ChildProcessCreationParams.set(params); | |
551 synchronized (ChildProcessLauncher.class) { | 604 synchronized (ChildProcessLauncher.class) { |
552 assert !ThreadUtils.runningOnUiThread(); | 605 assert !ThreadUtils.runningOnUiThread(); |
553 if (sSpareSandboxedConnection == null) { | 606 if (sSpareSandboxedConnection == null) { |
554 sSpareSandboxedConnection = allocateBoundConnection(context, nul l, true, false); | 607 sSpareSandboxedConnection = allocateBoundConnection(context, nul l, true, false, |
608 ChildProcessCreationParams.get().copy()); | |
555 } | 609 } |
556 } | 610 } |
557 } | 611 } |
558 | 612 |
559 @CalledByNative | 613 @CalledByNative |
560 private static FileDescriptorInfo makeFdInfo( | 614 private static FileDescriptorInfo makeFdInfo( |
561 int id, int fd, boolean autoClose, long offset, long size) { | 615 int id, int fd, boolean autoClose, long offset, long size) { |
562 ParcelFileDescriptor pFd; | 616 ParcelFileDescriptor pFd; |
563 if (autoClose) { | 617 if (autoClose) { |
564 // Adopt the FD, it will be closed when we close the ParcelFileDescr iptor. | 618 // Adopt the FD, it will be closed when we close the ParcelFileDescr iptor. |
(...skipping 22 matching lines...) Expand all Loading... | |
587 */ | 641 */ |
588 @CalledByNative | 642 @CalledByNative |
589 private static void start(Context context, final String[] commandLine, int c hildProcessId, | 643 private static void start(Context context, final String[] commandLine, int c hildProcessId, |
590 FileDescriptorInfo[] filesToBeMapped, long clientContext) { | 644 FileDescriptorInfo[] filesToBeMapped, long clientContext) { |
591 assert clientContext != 0; | 645 assert clientContext != 0; |
592 | 646 |
593 int callbackType = CALLBACK_FOR_UNKNOWN_PROCESS; | 647 int callbackType = CALLBACK_FOR_UNKNOWN_PROCESS; |
594 boolean inSandbox = true; | 648 boolean inSandbox = true; |
595 String processType = | 649 String processType = |
596 ContentSwitches.getSwitchValue(commandLine, ContentSwitches.SWIT CH_PROCESS_TYPE); | 650 ContentSwitches.getSwitchValue(commandLine, ContentSwitches.SWIT CH_PROCESS_TYPE); |
651 ChildProcessCreationParams params = ChildProcessCreationParams.get().cop y(); | |
597 if (ContentSwitches.SWITCH_RENDERER_PROCESS.equals(processType)) { | 652 if (ContentSwitches.SWITCH_RENDERER_PROCESS.equals(processType)) { |
598 callbackType = CALLBACK_FOR_RENDERER_PROCESS; | 653 callbackType = CALLBACK_FOR_RENDERER_PROCESS; |
599 } else if (ContentSwitches.SWITCH_GPU_PROCESS.equals(processType)) { | 654 } else if (ContentSwitches.SWITCH_GPU_PROCESS.equals(processType)) { |
600 callbackType = CALLBACK_FOR_GPU_PROCESS; | 655 callbackType = CALLBACK_FOR_GPU_PROCESS; |
601 inSandbox = false; | 656 inSandbox = false; |
657 // For GPU process, always set the Chrome's package name. | |
658 params = new ChildProcessCreationParams( | |
659 context.getPackageName(), 0, LibraryProcessType.PROCESS_CHIL D); | |
602 } else if (ContentSwitches.SWITCH_UTILITY_PROCESS.equals(processType)) { | 660 } else if (ContentSwitches.SWITCH_UTILITY_PROCESS.equals(processType)) { |
603 // We only support sandboxed right now. | 661 // We only support sandboxed right now. |
604 callbackType = CALLBACK_FOR_UTILITY_PROCESS; | 662 callbackType = CALLBACK_FOR_UTILITY_PROCESS; |
605 } else { | 663 } else { |
606 assert false; | 664 assert false; |
607 } | 665 } |
608 | 666 |
609 startInternal(context, commandLine, childProcessId, filesToBeMapped, cli entContext, | 667 startInternal(context, commandLine, childProcessId, filesToBeMapped, cli entContext, |
610 callbackType, inSandbox); | 668 callbackType, inSandbox, params); |
611 } | 669 } |
612 | 670 |
613 /** | 671 /** |
614 * Spawns a background download process if it hasn't been started. The downl oad process will | 672 * Spawns a background download process if it hasn't been started. The downl oad process will |
615 * manage its own lifecyle and can outlive chrome. | 673 * manage its own lifecyle and can outlive chrome. |
616 * | 674 * |
617 * @param context Context used to obtain the application context. | 675 * @param context Context used to obtain the application context. |
618 * @param commandLine The child process command line argv. | 676 * @param commandLine The child process command line argv. |
619 */ | 677 */ |
620 @SuppressLint("NewApi") | 678 @SuppressLint("NewApi") |
(...skipping 22 matching lines...) Expand all Loading... | |
643 context.startService(intent); | 701 context.startService(intent); |
644 } | 702 } |
645 | 703 |
646 private static void startInternal( | 704 private static void startInternal( |
647 Context context, | 705 Context context, |
648 final String[] commandLine, | 706 final String[] commandLine, |
649 int childProcessId, | 707 int childProcessId, |
650 FileDescriptorInfo[] filesToBeMapped, | 708 FileDescriptorInfo[] filesToBeMapped, |
651 long clientContext, | 709 long clientContext, |
652 int callbackType, | 710 int callbackType, |
653 boolean inSandbox) { | 711 boolean inSandbox, |
712 ChildProcessCreationParams creationParams) { | |
654 try { | 713 try { |
655 TraceEvent.begin("ChildProcessLauncher.startInternal"); | 714 TraceEvent.begin("ChildProcessLauncher.startInternal"); |
656 | 715 |
657 ChildProcessConnection allocatedConnection = null; | 716 ChildProcessConnection allocatedConnection = null; |
717 String packageName = creationParams.getPackageName(); | |
658 synchronized (ChildProcessLauncher.class) { | 718 synchronized (ChildProcessLauncher.class) { |
659 if (inSandbox) { | 719 if (inSandbox && sSpareSandboxedConnection != null |
720 && sSpareSandboxedConnection.getCreationParams().getPack ageName().equals( | |
721 packageName)) { | |
660 allocatedConnection = sSpareSandboxedConnection; | 722 allocatedConnection = sSpareSandboxedConnection; |
661 sSpareSandboxedConnection = null; | 723 sSpareSandboxedConnection = null; |
662 } | 724 } |
663 } | 725 } |
664 if (allocatedConnection == null) { | 726 if (allocatedConnection == null) { |
665 boolean alwaysInForeground = false; | 727 boolean alwaysInForeground = false; |
666 if (callbackType == CALLBACK_FOR_GPU_PROCESS) alwaysInForeground = true; | 728 if (callbackType == CALLBACK_FOR_GPU_PROCESS) alwaysInForeground = true; |
667 synchronized (PendingSpawnQueue.sPendingSpawnsLock) { | 729 PendingSpawnQueue pendingSpawnQueue = getPendingSpawnQueue( |
730 context, packageName, inSandbox); | |
731 synchronized (pendingSpawnQueue.mPendingSpawnsLock) { | |
668 allocatedConnection = allocateBoundConnection( | 732 allocatedConnection = allocateBoundConnection( |
669 context, commandLine, inSandbox, alwaysInForeground) ; | 733 context, commandLine, inSandbox, alwaysInForeground, creationParams); |
670 if (allocatedConnection == null) { | 734 if (allocatedConnection == null) { |
671 Log.d(TAG, "Allocation of new service failed. Queuing up pending spawn."); | 735 Log.d(TAG, "Allocation of new service failed. Queuing up pending spawn."); |
672 sPendingSpawnQueue.enqueueLocked(new PendingSpawnData(co ntext, commandLine, | 736 pendingSpawnQueue.enqueueLocked(new PendingSpawnData(con text, commandLine, |
673 childProcessId, filesToBeMapped, clientContext, | 737 childProcessId, filesToBeMapped, clientContext, |
674 callbackType, inSandbox)); | 738 callbackType, inSandbox, creationParams)); |
675 return; | 739 return; |
676 } | 740 } |
677 } | 741 } |
678 } | 742 } |
679 | 743 |
680 Log.d(TAG, "Setting up connection to process: slot=%d", | 744 Log.d(TAG, "Setting up connection to process: slot=%d", |
681 allocatedConnection.getServiceNumber()); | 745 allocatedConnection.getServiceNumber()); |
682 triggerConnectionSetup(allocatedConnection, commandLine, childProces sId, | 746 triggerConnectionSetup(allocatedConnection, commandLine, childProces sId, |
683 filesToBeMapped, callbackType, clientContext); | 747 filesToBeMapped, callbackType, clientContext); |
684 } finally { | 748 } finally { |
(...skipping 156 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
841 } | 905 } |
842 | 906 |
843 static void logPidWarning(int pid, String message) { | 907 static void logPidWarning(int pid, String message) { |
844 // This class is effectively a no-op in single process mode, so don't lo g warnings there. | 908 // This class is effectively a no-op in single process mode, so don't lo g warnings there. |
845 if (pid > 0 && !nativeIsSingleProcess()) { | 909 if (pid > 0 && !nativeIsSingleProcess()) { |
846 Log.w(TAG, "%s, pid=%d", message, pid); | 910 Log.w(TAG, "%s, pid=%d", message, pid); |
847 } | 911 } |
848 } | 912 } |
849 | 913 |
850 @VisibleForTesting | 914 @VisibleForTesting |
851 static ChildProcessConnection allocateBoundConnectionForTesting(Context cont ext) { | 915 static ChildProcessConnection allocateBoundConnectionForTesting(Context cont ext, |
852 return allocateBoundConnection(context, null, true, false); | 916 ChildProcessCreationParams creationParams) { |
917 return allocateBoundConnection(context, null, true, false, creationParam s); | |
853 } | 918 } |
854 | 919 |
855 /** | 920 /** |
856 * Queue up a spawn requests for testing. | 921 * Queue up a spawn requests for testing. |
857 */ | 922 */ |
858 @VisibleForTesting | 923 @VisibleForTesting |
859 static void enqueuePendingSpawnForTesting(Context context, String[] commandL ine) { | 924 static void enqueuePendingSpawnForTesting(Context context, String[] commandL ine, |
860 synchronized (PendingSpawnQueue.sPendingSpawnsLock) { | 925 ChildProcessCreationParams creationParams, boolean inSandbox) { |
861 sPendingSpawnQueue.enqueueLocked(new PendingSpawnData(context, comma ndLine, 1, | 926 PendingSpawnQueue pendingSpawnQueue = getPendingSpawnQueue(context, |
862 new FileDescriptorInfo[0], 0, CALLBACK_FOR_RENDERER_PROCESS, true)); | 927 creationParams.getPackageName(), inSandbox); |
928 synchronized (pendingSpawnQueue.mPendingSpawnsLock) { | |
929 pendingSpawnQueue.enqueueLocked(new PendingSpawnData(context, comman dLine, 1, | |
930 new FileDescriptorInfo[0], 0, CALLBACK_FOR_RENDERER_PROCESS, true, | |
931 creationParams)); | |
863 } | 932 } |
864 } | 933 } |
865 | 934 |
866 /** @return the count of sandboxed connections managed by the allocator */ | 935 /** |
936 * @return the number of sandboxed connections of given {@link packageName} managed by the | |
937 * allocator. | |
938 */ | |
867 @VisibleForTesting | 939 @VisibleForTesting |
868 static int allocatedConnectionsCountForTesting(Context context) { | 940 static int allocatedSandboxedConnectionsCountForTesting(Context context, Str ing packageName) { |
869 initConnectionAllocatorsIfNecessary(context); | 941 initConnectionAllocatorsIfNecessary(context, true, packageName); |
870 return sSandboxedChildConnectionAllocator.allocatedConnectionsCountForTe sting(); | 942 return sSandboxedChildConnectionAllocatorMap.get(packageName) |
943 .allocatedConnectionsCountForTesting(); | |
871 } | 944 } |
872 | 945 |
873 /** @return the count of services set up and working */ | 946 /** @return the count of services set up and working */ |
874 @VisibleForTesting | 947 @VisibleForTesting |
875 static int connectedServicesCountForTesting() { | 948 static int connectedServicesCountForTesting() { |
876 return sServiceMap.size(); | 949 return sServiceMap.size(); |
877 } | 950 } |
878 | 951 |
879 /** @return the count of pending spawns in the queue */ | 952 /** |
953 * @param context The context. | |
954 * @param packageName The package name of the {@link ChildProcessAlocator}. | |
955 * @param inSandbox Whether the connection is sandboxed. | |
956 * @return the count of pending spawns in the queue. | |
957 */ | |
880 @VisibleForTesting | 958 @VisibleForTesting |
881 static int pendingSpawnsCountForTesting() { | 959 static int pendingSpawnsCountForTesting(Context context, String packageName, |
882 synchronized (PendingSpawnQueue.sPendingSpawnsLock) { | 960 boolean inSandbox) { |
883 return sPendingSpawnQueue.sizeLocked(); | 961 PendingSpawnQueue pendingSpawnQueue = getPendingSpawnQueue(context, pack ageName, inSandbox); |
962 synchronized (pendingSpawnQueue.mPendingSpawnsLock) { | |
963 return pendingSpawnQueue.sizeLocked(); | |
884 } | 964 } |
885 } | 965 } |
886 | 966 |
887 /** | 967 /** |
888 * Kills the child process for testing. | 968 * Kills the child process for testing. |
889 * @return true iff the process was killed as expected | 969 * @return true iff the process was killed as expected |
890 */ | 970 */ |
891 @VisibleForTesting | 971 @VisibleForTesting |
892 public static boolean crashProcessForTesting(int pid) { | 972 public static boolean crashProcessForTesting(int pid) { |
893 if (sServiceMap.get(pid) == null) return false; | 973 if (sServiceMap.get(pid) == null) return false; |
894 | 974 |
895 try { | 975 try { |
896 ((ChildProcessConnectionImpl) sServiceMap.get(pid)).crashServiceForT esting(); | 976 ((ChildProcessConnectionImpl) sServiceMap.get(pid)).crashServiceForT esting(); |
897 } catch (RemoteException ex) { | 977 } catch (RemoteException ex) { |
898 return false; | 978 return false; |
899 } | 979 } |
900 | 980 |
901 return true; | 981 return true; |
902 } | 982 } |
903 | 983 |
904 private static native void nativeOnChildProcessStarted(long clientContext, i nt pid); | 984 private static native void nativeOnChildProcessStarted(long clientContext, i nt pid); |
905 private static native void nativeEstablishSurfacePeer( | 985 private static native void nativeEstablishSurfacePeer( |
906 int pid, Surface surface, int primaryID, int secondaryID); | 986 int pid, Surface surface, int primaryID, int secondaryID); |
907 private static native boolean nativeIsSingleProcess(); | 987 private static native boolean nativeIsSingleProcess(); |
908 } | 988 } |
OLD | NEW |