Index: content/public/android/java/src/org/chromium/content/browser/BaseChildProcessConnection.java |
diff --git a/content/public/android/java/src/org/chromium/content/browser/ChildProcessConnectionImpl.java b/content/public/android/java/src/org/chromium/content/browser/BaseChildProcessConnection.java |
similarity index 61% |
rename from content/public/android/java/src/org/chromium/content/browser/ChildProcessConnectionImpl.java |
rename to content/public/android/java/src/org/chromium/content/browser/BaseChildProcessConnection.java |
index 710741b69c86a045512bf88f0459e6b9fd7934e7..52bfe5e5a7876a9868afff7493a07b436c6e7a19 100644 |
--- a/content/public/android/java/src/org/chromium/content/browser/ChildProcessConnectionImpl.java |
+++ b/content/public/android/java/src/org/chromium/content/browser/BaseChildProcessConnection.java |
@@ -26,97 +26,72 @@ import org.chromium.base.process_launcher.IChildProcessService; |
import java.io.IOException; |
import javax.annotation.Nullable; |
+import javax.annotation.concurrent.GuardedBy; |
/** |
* Manages a connection between the browser activity and a child service. |
*/ |
-public class ChildProcessConnectionImpl implements ChildProcessConnection { |
- private final Context mContext; |
- private final int mServiceNumber; |
- private final boolean mInSandbox; |
- private final ChildProcessConnection.DeathCallback mDeathCallback; |
- private final ComponentName mServiceName; |
- |
- // Synchronization: While most internal flow occurs on the UI thread, the public API |
- // (specifically start and stop) may be called from any thread, hence all entry point methods |
- // into the class are synchronized on the lock to protect access to these members. |
- private final Object mLock = new Object(); |
- private IChildProcessService mService; |
- // Set to true when the service connection callback runs. This differs from |
- // mServiceConnectComplete, which tracks that the connection completed successfully. |
- private boolean mDidOnServiceConnected; |
- // Set to true when the service connected successfully. |
- private boolean mServiceConnectComplete; |
- // Set to true when the service disconnects, as opposed to being properly closed. This happens |
- // when the process crashes or gets killed by the system out-of-memory killer. |
- private boolean mServiceDisconnected; |
- // When the service disconnects (i.e. mServiceDisconnected is set to true), the status of the |
- // oom bindings is stashed here for future inspection. |
- private boolean mWasOomProtected; |
- private int mPid; // Process ID of the corresponding child process. |
- // Initial binding protects the newly spawned process from being killed before it is put to use, |
- // it is maintained between calls to start() and removeInitialBinding(). |
- private ChildServiceConnection mInitialBinding; |
- // Strong binding will make the service priority equal to the priority of the activity. We want |
- // the OS to be able to kill background renderers as it kills other background apps, so strong |
- // bindings are maintained only for services that are active at the moment (between |
- // addStrongBinding() and removeStrongBinding()). |
- private ChildServiceConnection mStrongBinding; |
- // Low priority binding maintained in the entire lifetime of the connection, i.e. between calls |
- // to start() and stop(). |
- private ChildServiceConnection mWaivedBinding; |
- // Incremented on addStrongBinding(), decremented on removeStrongBinding(). |
- private int mStrongBindingCount; |
- // Moderate binding will make the service priority equal to the priority of a visible process |
- // while the app is in the foreground. It will stay bound only while the app is in the |
- // foreground to protect a background process from the system out-of-memory killer. |
- private ChildServiceConnection mModerateBinding; |
- |
- // Parameters passed to the child process through the service binding intent. |
- // If the service gets recreated by the framework the intent will be reused, so these parameters |
- // should be common to all processes of that type. |
- private final Bundle mChildProcessCommonParameters; |
- |
- private final boolean mAlwaysInForeground; |
- private final ChildProcessCreationParams mCreationParams; |
+public abstract class BaseChildProcessConnection { |
+ private static final String TAG = "BaseChildProcessConn"; |
- // Caches whether non-sandboxed and sandboxed services require an extra |
- // binding flag provided via ChildProcessCreationParams. |
- // TODO(mnaganov): Get rid of it after the release of the next Android SDK. |
- private static Boolean sNeedsExtrabindFlags[] = new Boolean[2]; |
- |
- private static final String TAG = "ChildProcessConnect"; |
- |
- private static class ConnectionParams { |
- final String[] mCommandLine; |
- final FileDescriptorInfo[] mFilesToBeMapped; |
- final IBinder mCallback; |
- |
- ConnectionParams( |
- String[] commandLine, FileDescriptorInfo[] filesToBeMapped, IBinder callback) { |
- mCommandLine = commandLine; |
- mFilesToBeMapped = filesToBeMapped; |
- mCallback = callback; |
- } |
+ /** |
+ * Used to notify the consumer about disconnection of the service. This callback is provided |
+ * earlier than ConnectionCallbacks below, as a child process might die before the connection is |
+ * fully set up. |
+ */ |
+ interface DeathCallback { |
+ // Called on Launcher thread. |
+ void onChildProcessDied(BaseChildProcessConnection connection); |
} |
- // This is set in start() and is used in onServiceConnected(). |
- private ChildProcessConnection.StartCallback mStartCallback; |
+ /** |
+ * Used to notify the consumer about the process start. These callbacks will be invoked before |
+ * the ConnectionCallbacks. |
+ */ |
+ interface StartCallback { |
+ /** |
+ * Called when the child process has successfully started and is ready for connection |
+ * setup. |
+ */ |
+ void onChildStarted(); |
+ |
+ /** |
+ * Called when the child process failed to start. This can happen if the process is already |
+ * in use by another client. |
+ */ |
+ void onChildStartFailed(); |
+ } |
- // This is set in setupConnection() and is later used in doConnectionSetupLocked(), after which |
- // the variable is cleared. Therefore this is only valid while the connection is being set up. |
- private ConnectionParams mConnectionParams; |
+ /** |
+ * Used to notify the consumer about the connection being established. |
+ */ |
+ interface ConnectionCallback { |
+ /** |
+ * Called when the connection to the service is established. |
+ * @param connecion the connection object to the child process |
+ */ |
+ void onConnected(BaseChildProcessConnection connection); |
+ } |
- // Callback provided in setupConnection() that will communicate the result to the caller. This |
- // has to be called exactly once after setupConnection(), even if setup fails, so that the |
- // caller can free up resources associated with the setup attempt. This is set to null after the |
- // call. |
- private ChildProcessConnection.ConnectionCallback mConnectionCallback; |
+ /** Used to create specialization connection instances. */ |
+ interface Factory { |
+ BaseChildProcessConnection create(Context context, int number, boolean sandboxed, |
+ DeathCallback deathCallback, String serviceClassName, |
+ Bundle childProcessCommonParameters, ChildProcessCreationParams creationParams); |
+ } |
- private class ChildServiceConnection implements ServiceConnection { |
- private boolean mBound; |
+ /** Interface representing a connection to the Android service. Can be mocked in unit-tests. */ |
+ protected interface ChildServiceConnection { |
+ boolean bind(); |
+ void unbind(); |
+ boolean isBound(); |
+ } |
+ /** Implementation of ChildServiceConnection that does connect to a service. */ |
+ protected class ChildServiceConnectionImpl |
+ implements ChildServiceConnection, ServiceConnection { |
private final int mBindFlags; |
+ private boolean mBound; |
private Intent createServiceBindIntent() { |
Intent intent = new Intent(); |
@@ -127,34 +102,37 @@ public class ChildProcessConnectionImpl implements ChildProcessConnection { |
return intent; |
} |
- public ChildServiceConnection(int bindFlags) { |
+ private ChildServiceConnectionImpl(int bindFlags) { |
mBindFlags = bindFlags; |
} |
- boolean bind() { |
+ @Override |
+ public boolean bind() { |
if (!mBound) { |
try { |
- TraceEvent.begin("ChildProcessConnectionImpl.ChildServiceConnection.bind"); |
+ TraceEvent.begin("BaseChildProcessConnection.ChildServiceConnection.bind"); |
Intent intent = createServiceBindIntent(); |
if (mChildProcessCommonParameters != null) { |
intent.putExtras(mChildProcessCommonParameters); |
} |
mBound = mContext.bindService(intent, this, mBindFlags); |
} finally { |
- TraceEvent.end("ChildProcessConnectionImpl.ChildServiceConnection.bind"); |
+ TraceEvent.end("BaseChildProcessConnection.ChildServiceConnection.bind"); |
} |
} |
return mBound; |
} |
- void unbind() { |
+ @Override |
+ public void unbind() { |
if (mBound) { |
mContext.unbindService(this); |
mBound = false; |
} |
} |
- boolean isBound() { |
+ @Override |
+ public boolean isBound() { |
return mBound; |
} |
@@ -163,7 +141,7 @@ public class ChildProcessConnectionImpl implements ChildProcessConnection { |
LauncherThread.post(new Runnable() { |
@Override |
public void run() { |
- ChildProcessConnectionImpl.this.onServiceConnectedOnLauncherThread(service); |
+ BaseChildProcessConnection.this.onServiceConnectedOnLauncherThread(service); |
} |
}); |
} |
@@ -174,123 +152,178 @@ public class ChildProcessConnectionImpl implements ChildProcessConnection { |
LauncherThread.post(new Runnable() { |
@Override |
public void run() { |
- ChildProcessConnectionImpl.this.onServiceDisconnectedOnLauncherThread(); |
+ BaseChildProcessConnection.this.onServiceDisconnectedOnLauncherThread(); |
} |
}); |
} |
} |
- ChildProcessConnectionImpl(Context context, int number, boolean inSandbox, |
- ChildProcessConnection.DeathCallback deathCallback, String serviceClassName, |
- Bundle childProcessCommonParameters, boolean alwaysInForeground, |
- ChildProcessCreationParams creationParams) { |
+ // Caches whether non-sandboxed and sandboxed services require an extra |
+ // binding flag provided via ChildProcessCreationParams. |
+ // TODO(mnaganov): Get rid of it after the release of the next Android SDK. |
+ private static Boolean sNeedsExtrabindFlags[] = new Boolean[2]; |
+ private final Context mContext; |
+ private final int mServiceNumber; |
+ private final boolean mSandboxed; |
+ private final BaseChildProcessConnection.DeathCallback mDeathCallback; |
+ private final ComponentName mServiceName; |
+ |
+ // Parameters passed to the child process through the service binding intent. |
+ // If the service gets recreated by the framework the intent will be reused, so these parameters |
+ // should be common to all processes of that type. |
+ private final Bundle mChildProcessCommonParameters; |
+ |
+ private final ChildProcessCreationParams mCreationParams; |
+ |
+ private static class ConnectionParams { |
+ final String[] mCommandLine; |
+ final FileDescriptorInfo[] mFilesToBeMapped; |
+ final IBinder mCallback; |
+ |
+ ConnectionParams( |
+ String[] commandLine, FileDescriptorInfo[] filesToBeMapped, IBinder callback) { |
+ mCommandLine = commandLine; |
+ mFilesToBeMapped = filesToBeMapped; |
+ mCallback = callback; |
+ } |
+ } |
+ |
+ // Synchronization: While most internal flow occurs on the UI thread, the public API |
+ // (specifically start and stop) may be called from any thread, hence all entry point methods |
+ // into the class are synchronized on the lock to protect access to these members. |
+ // TODO(jcivelli): crbug.com/714657 remove this lock. |
+ private final Object mLock = new Object(); |
+ |
+ // This is set in start() and is used in onServiceConnected(). |
+ @GuardedBy("mLock") |
+ private StartCallback mStartCallback; |
+ |
+ // This is set in setupConnection() and is later used in doConnectionSetupLocked(), after which |
+ // the variable is cleared. Therefore this is only valid while the connection is being set up. |
+ @GuardedBy("mLock") |
+ private ConnectionParams mConnectionParams; |
+ |
+ // Callback provided in setupConnection() that will communicate the result to the caller. This |
+ // has to be called exactly once after setupConnection(), even if setup fails, so that the |
+ // caller can free up resources associated with the setup attempt. This is set to null after the |
+ // call. |
+ @GuardedBy("mLock") |
+ private ConnectionCallback mConnectionCallback; |
+ |
+ @GuardedBy("mLock") |
+ private IChildProcessService mService; |
+ |
+ // Set to true when the service connection callback runs. This differs from |
+ // mServiceConnectComplete, which tracks that the connection completed successfully. |
+ @GuardedBy("mLock") |
+ private boolean mDidOnServiceConnected; |
+ |
+ // Set to true when the service connected successfully. |
+ @GuardedBy("mLock") |
+ private boolean mServiceConnectComplete; |
+ |
+ // Set to true when the service disconnects, as opposed to being properly closed. This happens |
+ // when the process crashes or gets killed by the system out-of-memory killer. |
+ @GuardedBy("mLock") |
+ private boolean mServiceDisconnected; |
+ |
+ // Process ID of the corresponding child process. |
+ @GuardedBy("mLock") |
+ private int mPid; |
+ |
+ protected BaseChildProcessConnection(Context context, int number, boolean sandboxed, |
+ DeathCallback deathCallback, String serviceClassName, |
+ Bundle childProcessCommonParameters, ChildProcessCreationParams creationParams) { |
mContext = context; |
mServiceNumber = number; |
- mInSandbox = inSandbox; |
+ mSandboxed = sandboxed; |
mDeathCallback = deathCallback; |
String packageName = |
creationParams != null ? creationParams.getPackageName() : context.getPackageName(); |
mServiceName = new ComponentName(packageName, serviceClassName + mServiceNumber); |
mChildProcessCommonParameters = childProcessCommonParameters; |
- mAlwaysInForeground = alwaysInForeground; |
mCreationParams = creationParams; |
- int initialFlags = Context.BIND_AUTO_CREATE; |
- if (mAlwaysInForeground) initialFlags |= Context.BIND_IMPORTANT; |
- int extraBindFlags = 0; |
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && mCreationParams != null |
- && mCreationParams.getIsExternalService() |
- && isExportedService(inSandbox, mContext, mServiceName)) { |
- extraBindFlags = Context.BIND_EXTERNAL_SERVICE; |
- } |
- mInitialBinding = new ChildServiceConnection(initialFlags | extraBindFlags); |
- mStrongBinding = new ChildServiceConnection( |
- Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT | extraBindFlags); |
- mWaivedBinding = new ChildServiceConnection( |
- Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY | extraBindFlags); |
- mModerateBinding = new ChildServiceConnection(Context.BIND_AUTO_CREATE | extraBindFlags); |
} |
- private static boolean isExportedService(boolean inSandbox, Context context, |
- ComponentName serviceName) { |
- // Check for the cached value first. It is assumed that all pooled child services |
- // have identical attributes in the manifest. |
- final int arrayIndex = inSandbox ? 1 : 0; |
- if (sNeedsExtrabindFlags[arrayIndex] != null) { |
- return sNeedsExtrabindFlags[arrayIndex].booleanValue(); |
- } |
- boolean result = false; |
- try { |
- PackageManager packageManager = context.getPackageManager(); |
- ServiceInfo serviceInfo = packageManager.getServiceInfo(serviceName, 0); |
- result = serviceInfo.exported; |
- } catch (PackageManager.NameNotFoundException e) { |
- Log.e(TAG, "Could not retrieve info about service %s", serviceName, e); |
- } |
- sNeedsExtrabindFlags[arrayIndex] = Boolean.valueOf(result); |
- return result; |
+ public final Context getContext() { |
+ return mContext; |
} |
- @Override |
- public int getServiceNumber() { |
+ public final int getServiceNumber() { |
return mServiceNumber; |
} |
- @Override |
- public boolean isInSandbox() { |
- return mInSandbox; |
+ public final boolean isSandboxed() { |
+ return mSandboxed; |
} |
- @Override |
- public String getPackageName() { |
+ public final String getPackageName() { |
return mCreationParams != null ? mCreationParams.getPackageName() |
- : mContext.getPackageName(); |
+ : mContext.getPackageName(); |
} |
- @Override |
- public ChildProcessCreationParams getCreationParams() { |
+ public final ChildProcessCreationParams getCreationParams() { |
return mCreationParams; |
} |
- @Override |
- public IChildProcessService getService() { |
+ public final IChildProcessService getService() { |
synchronized (mLock) { |
return mService; |
} |
} |
- @Override |
+ public final ComponentName getServiceName() { |
+ return mServiceName; |
+ } |
+ |
+ /** |
+ * @return the connection pid, or 0 if not yet connected |
+ */ |
public int getPid() { |
synchronized (mLock) { |
return mPid; |
} |
} |
- @Override |
- public void start(ChildProcessConnection.StartCallback startCallback) { |
+ /** |
+ * Starts a connection to an IChildProcessService. This must be followed by a call to |
+ * setupConnection() to setup the connection parameters. start() and setupConnection() are |
+ * separate to allow to pass whatever parameters are available in start(), and complete the |
+ * remainder later while reducing the connection setup latency. |
+ * @param startCallback (optional) callback when the child process starts or fails to start. |
+ */ |
+ public void start(StartCallback startCallback) { |
try { |
- TraceEvent.begin("ChildProcessConnectionImpl.start"); |
+ TraceEvent.begin("BaseChildProcessConnection.start"); |
assert LauncherThread.runningOnLauncherThread(); |
synchronized (mLock) { |
- assert mConnectionParams == null : |
- "setupConnection() called before start() in ChildProcessConnectionImpl."; |
+ assert mConnectionParams |
+ == null |
+ : "setupConnection() called before start() in BaseChildProcessConnection."; |
mStartCallback = startCallback; |
- if (!mInitialBinding.bind()) { |
+ if (!bind()) { |
Log.e(TAG, "Failed to establish the service connection."); |
// We have to notify the caller so that they can free-up associated resources. |
// TODO(ppi): Can we hard-fail here? |
- mDeathCallback.onChildProcessDied(ChildProcessConnectionImpl.this); |
- } else { |
- mWaivedBinding.bind(); |
+ mDeathCallback.onChildProcessDied(BaseChildProcessConnection.this); |
} |
} |
} finally { |
- TraceEvent.end("ChildProcessConnectionImpl.start"); |
+ TraceEvent.end("BaseChildProcessConnection.start"); |
} |
} |
- @Override |
+ /** |
+ * Setups the connection after it was started with start(). |
+ * @param commandLine (optional) will be ignored if the command line was already sent in start() |
+ * @param filesToBeMapped a list of file descriptors that should be registered |
+ * @param callback optional client specified callbacks that the child can use to communicate |
+ * with the parent process |
+ * @param connectionCallback will be called exactly once after the connection is set up or the |
+ * setup fails |
+ */ |
public void setupConnection(String[] commandLine, FileDescriptorInfo[] filesToBeMapped, |
@Nullable IBinder callback, ConnectionCallback connectionCallback) { |
assert LauncherThread.runningOnLauncherThread(); |
@@ -298,11 +331,11 @@ public class ChildProcessConnectionImpl implements ChildProcessConnection { |
assert mConnectionParams == null; |
if (mServiceDisconnected) { |
Log.w(TAG, "Tried to setup a connection that already disconnected."); |
- connectionCallback.onConnected(0); |
+ connectionCallback.onConnected(null); |
return; |
} |
try { |
- TraceEvent.begin("ChildProcessConnectionImpl.setupConnection"); |
+ TraceEvent.begin("BaseChildProcessConnection.setupConnection"); |
mConnectionCallback = connectionCallback; |
mConnectionParams = new ConnectionParams(commandLine, filesToBeMapped, callback); |
// Run the setup if the service is already connected. If not, |
@@ -311,22 +344,19 @@ public class ChildProcessConnectionImpl implements ChildProcessConnection { |
doConnectionSetupLocked(); |
} |
} finally { |
- TraceEvent.end("ChildProcessConnectionImpl.setupConnection"); |
+ TraceEvent.end("BaseChildProcessConnection.setupConnection"); |
} |
} |
} |
- @Override |
+ /** |
+ * Terminates the connection to IChildProcessService, closing all bindings. It is safe to call |
+ * this multiple times. |
+ */ |
public void stop() { |
synchronized (mLock) { |
- mInitialBinding.unbind(); |
- mStrongBinding.unbind(); |
- mWaivedBinding.unbind(); |
- mModerateBinding.unbind(); |
- mStrongBindingCount = 0; |
- if (mService != null) { |
- mService = null; |
- } |
+ unbind(); |
+ mService = null; |
mConnectionParams = null; |
} |
} |
@@ -341,7 +371,7 @@ public class ChildProcessConnectionImpl implements ChildProcessConnection { |
} |
try { |
TraceEvent.begin( |
- "ChildProcessConnectionImpl.ChildServiceConnection.onServiceConnected"); |
+ "BaseChildProcessConnection.ChildServiceConnection.onServiceConnected"); |
mDidOnServiceConnected = true; |
mService = IChildProcessService.Stub.asInterface(service); |
@@ -381,7 +411,7 @@ public class ChildProcessConnectionImpl implements ChildProcessConnection { |
} |
} finally { |
TraceEvent.end( |
- "ChildProcessConnectionImpl.ChildServiceConnection.onServiceConnected"); |
+ "BaseChildProcessConnection.ChildServiceConnection.onServiceConnected"); |
} |
} |
} |
@@ -394,16 +424,14 @@ public class ChildProcessConnectionImpl implements ChildProcessConnection { |
if (mServiceDisconnected) { |
return; |
} |
- // Stash the status of the oom bindings, since stop() will release all bindings. |
- mWasOomProtected = isCurrentlyOomProtected(); |
mServiceDisconnected = true; |
Log.w(TAG, "onServiceDisconnected (crash or killed by oom): pid=%d", mPid); |
stop(); // We don't want to auto-restart on crash. Let the browser do that. |
- mDeathCallback.onChildProcessDied(ChildProcessConnectionImpl.this); |
+ mDeathCallback.onChildProcessDied(BaseChildProcessConnection.this); |
// If we have a pending connection callback, we need to communicate the failure to |
// the caller. |
if (mConnectionCallback != null) { |
- mConnectionCallback.onConnected(0); |
+ mConnectionCallback.onConnected(null); |
} |
mConnectionCallback = null; |
} |
@@ -415,7 +443,7 @@ public class ChildProcessConnectionImpl implements ChildProcessConnection { |
assert mPid != 0 : "Child service claims to be run by a process of pid=0."; |
if (mConnectionCallback != null) { |
- mConnectionCallback.onConnected(mPid); |
+ mConnectionCallback.onConnected(this); |
} |
mConnectionCallback = null; |
} |
@@ -426,9 +454,10 @@ public class ChildProcessConnectionImpl implements ChildProcessConnection { |
* connection has been established (as signaled by onServiceConnected()). These two events can |
* happen in any order. Has to be called with mLock. |
*/ |
+ @GuardedBy("mLock") |
private void doConnectionSetupLocked() { |
try { |
- TraceEvent.begin("ChildProcessConnectionImpl.doConnectionSetupLocked"); |
+ TraceEvent.begin("BaseChildProcessConnection.doConnectionSetupLocked"); |
assert mServiceConnectComplete && mService != null; |
assert mConnectionParams != null; |
@@ -460,129 +489,55 @@ public class ChildProcessConnectionImpl implements ChildProcessConnection { |
} |
mConnectionParams = null; |
} finally { |
- TraceEvent.end("ChildProcessConnectionImpl.doConnectionSetupLocked"); |
- } |
- } |
- |
- @Override |
- public boolean isInitialBindingBound() { |
- synchronized (mLock) { |
- return mInitialBinding.isBound(); |
- } |
- } |
- |
- @Override |
- public boolean isStrongBindingBound() { |
- synchronized (mLock) { |
- return mStrongBinding.isBound(); |
- } |
- } |
- |
- @Override |
- public void removeInitialBinding() { |
- synchronized (mLock) { |
- assert !mAlwaysInForeground; |
- mInitialBinding.unbind(); |
- } |
- } |
- |
- @Override |
- public boolean isOomProtectedOrWasWhenDied() { |
- synchronized (mLock) { |
- if (mServiceDisconnected) { |
- return mWasOomProtected; |
- } else { |
- return isCurrentlyOomProtected(); |
- } |
- } |
- } |
- |
- private boolean isCurrentlyOomProtected() { |
- synchronized (mLock) { |
- assert !mServiceDisconnected; |
- if (mAlwaysInForeground) return ChildProcessLauncher.isApplicationInForeground(); |
- return mInitialBinding.isBound() || mStrongBinding.isBound(); |
+ TraceEvent.end("BaseChildProcessConnection.doConnectionSetupLocked"); |
} |
} |
- @Override |
- public void dropOomBindings() { |
- synchronized (mLock) { |
- assert !mAlwaysInForeground; |
- mInitialBinding.unbind(); |
- |
- mStrongBindingCount = 0; |
- mStrongBinding.unbind(); |
+ /** Subclasses should implement this method to bind/unbind to the actual service. */ |
+ protected abstract boolean bind(); |
+ protected abstract void unbind(); |
- mModerateBinding.unbind(); |
- } |
+ protected ChildServiceConnection createServiceConnection(int bindFlags) { |
+ return new ChildServiceConnectionImpl(bindFlags); |
} |
- @Override |
- public void addStrongBinding() { |
- synchronized (mLock) { |
- if (mService == null) { |
- Log.w(TAG, "The connection is not bound for %d", mPid); |
- return; |
- } |
- if (mStrongBindingCount == 0) { |
- mStrongBinding.bind(); |
- } |
- mStrongBindingCount++; |
- } |
- } |
- |
- @Override |
- public void removeStrongBinding() { |
- synchronized (mLock) { |
- if (mService == null) { |
- Log.w(TAG, "The connection is not bound for %d", mPid); |
- return; |
- } |
- assert mStrongBindingCount > 0; |
- mStrongBindingCount--; |
- if (mStrongBindingCount == 0) { |
- mStrongBinding.unbind(); |
- } |
- } |
- } |
- |
- @Override |
- public boolean isModerateBindingBound() { |
- synchronized (mLock) { |
- return mModerateBinding.isBound(); |
- } |
+ protected boolean shouldBindAsExportedService() { |
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && getCreationParams() != null |
+ && getCreationParams().getIsExternalService() |
+ && isExportedService(isSandboxed(), getContext(), getServiceName()); |
} |
- @Override |
- public void addModerateBinding() { |
- synchronized (mLock) { |
- if (mService == null) { |
- Log.w(TAG, "The connection is not bound for %d", mPid); |
- return; |
- } |
- mModerateBinding.bind(); |
+ private static boolean isExportedService( |
+ boolean inSandbox, Context context, ComponentName serviceName) { |
+ // Check for the cached value first. It is assumed that all pooled child services |
+ // have identical attributes in the manifest. |
+ final int arrayIndex = inSandbox ? 1 : 0; |
+ if (sNeedsExtrabindFlags[arrayIndex] != null) { |
+ return sNeedsExtrabindFlags[arrayIndex].booleanValue(); |
} |
- } |
- |
- @Override |
- public void removeModerateBinding() { |
- synchronized (mLock) { |
- if (mService == null) { |
- Log.w(TAG, "The connection is not bound for %d", mPid); |
- return; |
- } |
- mModerateBinding.unbind(); |
+ boolean result = false; |
+ try { |
+ PackageManager packageManager = context.getPackageManager(); |
+ ServiceInfo serviceInfo = packageManager.getServiceInfo(serviceName, 0); |
+ result = serviceInfo.exported; |
+ } catch (PackageManager.NameNotFoundException e) { |
+ Log.e(TAG, "Could not retrieve info about service %s", serviceName, e); |
} |
+ sNeedsExtrabindFlags[arrayIndex] = Boolean.valueOf(result); |
+ return result; |
} |
@VisibleForTesting |
public void crashServiceForTesting() throws RemoteException { |
- mService.crashIntentionallyForTesting(); |
+ synchronized (mLock) { |
+ mService.crashIntentionallyForTesting(); |
+ } |
} |
@VisibleForTesting |
public boolean isConnected() { |
- return mService != null; |
+ synchronized (mLock) { |
+ return mService != null; |
+ } |
} |
} |