Browse Source

Add Context.bindService with executor parameter

Allow app to control the thread where ServiceConnection methods are
called on.

Bug: 111434506
Test: Used new bindContext method in chrome and checked callbacks
are on the correct thread.

Change-Id: I480e5bd6773a530fb9e8e73e3a2a2a88b76569ec
ten
Bo Liu 2 years ago
parent
commit
58a57667e4

+ 2
- 1
api/current.txt View File

@@ -9651,8 +9651,9 @@ package android.content {

public abstract class Context {
ctor public Context();
method public boolean bindIsolatedService(@RequiresPermission @NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String);
method public boolean bindIsolatedService(@RequiresPermission @NonNull android.content.Intent, int, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection);
method public abstract boolean bindService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int);
method public boolean bindService(@RequiresPermission @NonNull android.content.Intent, int, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection);
method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)") public abstract int checkCallingOrSelfPermission(@NonNull String);
method @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int);
method @CheckResult(suggest="#enforceCallingPermission(String,String)") public abstract int checkCallingPermission(@NonNull String);

+ 34
- 15
core/java/android/app/ContextImpl.java View File

@@ -144,10 +144,17 @@ class ReceiverRestrictedContext extends ContextWrapper {
}

@Override
public boolean bindIsolatedService(Intent service, ServiceConnection conn, int flags,
String instanceName) {
public boolean bindService(
Intent service, int flags, Executor executor, ServiceConnection conn) {
throw new ReceiverCallNotAllowedException(
"BroadcastReceiver components are not allowed to bind to services");
"BroadcastReceiver components are not allowed to bind to services");
}

@Override
public boolean bindIsolatedService(Intent service, int flags, String instanceName,
Executor executor, ServiceConnection conn) {
throw new ReceiverCallNotAllowedException(
"BroadcastReceiver components are not allowed to bind to services");
}
}

@@ -1638,28 +1645,34 @@ class ContextImpl extends Context {
}

@Override
public boolean bindService(Intent service, ServiceConnection conn,
int flags) {
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
warnIfCallingFromSystemProcess();
return bindServiceCommon(service, conn, flags, null, mMainThread.getHandler(), null,
getUser());
}

@Override
public boolean bindService(
Intent service, int flags, Executor executor, ServiceConnection conn) {
warnIfCallingFromSystemProcess();
return bindServiceCommon(service, conn, flags, null, mMainThread.getHandler(), getUser());
return bindServiceCommon(service, conn, flags, null, null, executor, getUser());
}

@Override
public boolean bindIsolatedService(Intent service, ServiceConnection conn,
int flags, String instanceName) {
public boolean bindIsolatedService(Intent service, int flags, String instanceName,
Executor executor, ServiceConnection conn) {
warnIfCallingFromSystemProcess();
if (instanceName == null) {
throw new NullPointerException("null instanceName");
}
return bindServiceCommon(service, conn, flags, instanceName, mMainThread.getHandler(),
getUser());
return bindServiceCommon(service, conn, flags, instanceName, null, executor, getUser());
}

/** @hide */
@Override
public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
UserHandle user) {
return bindServiceCommon(service, conn, flags, null, mMainThread.getHandler(), user);
return bindServiceCommon(service, conn, flags, null, mMainThread.getHandler(), null, user);
}

/** @hide */
@@ -1669,7 +1682,7 @@ class ContextImpl extends Context {
if (handler == null) {
throw new IllegalArgumentException("handler must not be null.");
}
return bindServiceCommon(service, conn, flags, null, handler, user);
return bindServiceCommon(service, conn, flags, null, handler, null, user);
}

/** @hide */
@@ -1692,15 +1705,21 @@ class ContextImpl extends Context {
}

private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
String instanceName, Handler
handler, UserHandle user) {
String instanceName, Handler handler, Executor executor, UserHandle user) {
// Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser.
IServiceConnection sd;
if (conn == null) {
throw new IllegalArgumentException("connection is null");
}
if (handler != null && executor != null) {
throw new IllegalArgumentException("Handler and Executor both supplied");
}
if (mPackageInfo != null) {
sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags);
if (executor != null) {
sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), executor, flags);
} else {
sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags);
}
} else {
throw new RuntimeException("Not supported in system context");
}

+ 44
- 5
core/java/android/app/LoadedApk.java View File

@@ -76,6 +76,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;

final class IntentReceiverLeaked extends AndroidRuntimeException {
@UnsupportedAppUsage
@@ -1651,6 +1652,16 @@ public final class LoadedApk {
@UnsupportedAppUsage
public final IServiceConnection getServiceDispatcher(ServiceConnection c,
Context context, Handler handler, int flags) {
return getServiceDispatcherCommon(c, context, handler, null, flags);
}

public final IServiceConnection getServiceDispatcher(ServiceConnection c,
Context context, Executor executor, int flags) {
return getServiceDispatcherCommon(c, context, null, executor, flags);
}

private IServiceConnection getServiceDispatcherCommon(ServiceConnection c,
Context context, Handler handler, Executor executor, int flags) {
synchronized (mServices) {
LoadedApk.ServiceDispatcher sd = null;
ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> map = mServices.get(context);
@@ -1659,7 +1670,11 @@ public final class LoadedApk {
sd = map.get(c);
}
if (sd == null) {
sd = new ServiceDispatcher(c, context, handler, flags);
if (executor != null) {
sd = new ServiceDispatcher(c, context, executor, flags);
} else {
sd = new ServiceDispatcher(c, context, handler, flags);
}
if (DEBUG) Slog.d(TAG, "Creating new dispatcher " + sd + " for conn " + c);
if (map == null) {
map = new ArrayMap<>();
@@ -1667,7 +1682,7 @@ public final class LoadedApk {
}
map.put(c, sd);
} else {
sd.validate(context, handler);
sd.validate(context, handler, executor);
}
return sd.getIServiceConnection();
}
@@ -1744,6 +1759,7 @@ public final class LoadedApk {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private final Context mContext;
private final Handler mActivityThread;
private final Executor mActivityExecutor;
private final ServiceConnectionLeaked mLocation;
private final int mFlags;

@@ -1783,12 +1799,25 @@ public final class LoadedApk {
mConnection = conn;
mContext = context;
mActivityThread = activityThread;
mActivityExecutor = null;
mLocation = new ServiceConnectionLeaked(null);
mLocation.fillInStackTrace();
mFlags = flags;
}

void validate(Context context, Handler activityThread) {
ServiceDispatcher(ServiceConnection conn,
Context context, Executor activityExecutor, int flags) {
mIServiceConnection = new InnerConnection(this);
mConnection = conn;
mContext = context;
mActivityThread = null;
mActivityExecutor = activityExecutor;
mLocation = new ServiceConnectionLeaked(null);
mLocation.fillInStackTrace();
mFlags = flags;
}

void validate(Context context, Handler activityThread, Executor activityExecutor) {
if (mContext != context) {
throw new RuntimeException(
"ServiceConnection " + mConnection +
@@ -1801,6 +1830,12 @@ public final class LoadedApk {
" registered with differing handler (was " +
mActivityThread + " now " + activityThread + ")");
}
if (mActivityExecutor != activityExecutor) {
throw new RuntimeException(
"ServiceConnection " + mConnection +
" registered with differing executor (was " +
mActivityExecutor + " now " + activityExecutor + ")");
}
}

void doForget() {
@@ -1840,7 +1875,9 @@ public final class LoadedApk {
}

public void connected(ComponentName name, IBinder service, boolean dead) {
if (mActivityThread != null) {
if (mActivityExecutor != null) {
mActivityExecutor.execute(new RunConnection(name, service, 0, dead));
} else if (mActivityThread != null) {
mActivityThread.post(new RunConnection(name, service, 0, dead));
} else {
doConnected(name, service, dead);
@@ -1848,7 +1885,9 @@ public final class LoadedApk {
}

public void death(ComponentName name, IBinder service) {
if (mActivityThread != null) {
if (mActivityExecutor != null) {
mActivityExecutor.execute(new RunConnection(name, service, 1, false));
} else if (mActivityThread != null) {
mActivityThread.post(new RunConnection(name, service, 1, false));
} else {
doDeath(name, service);

+ 19
- 4
core/java/android/content/Context.java View File

@@ -17,6 +17,7 @@
package android.content;

import android.annotation.AttrRes;
import android.annotation.CallbackExecutor;
import android.annotation.CheckResult;
import android.annotation.ColorInt;
import android.annotation.ColorRes;
@@ -2966,26 +2967,40 @@ public abstract class Context {
@NonNull ServiceConnection conn, @BindServiceFlags int flags);

/**
* Same as {@link #bindService(Intent, ServiceConnection, int)} with executor to control
* ServiceConnection callbacks.
* @param executor Callbacks on ServiceConnection will be called on executor. Must use same
* instance for the same instance of ServiceConnection.
*/
public boolean bindService(@RequiresPermission @NonNull Intent service,
@BindServiceFlags int flags, @NonNull @CallbackExecutor Executor executor,
@NonNull ServiceConnection conn) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}

/**
* Variation of {@link #bindService} that, in the specific case of isolated
* services, allows the caller to generate multiple instances of a service
* from a single component declaration.
*
* @param service Identifies the service to connect to. The Intent must
* specify an explicit component name.
* @param conn Receives information as the service is started and stopped.
* This must be a valid ServiceConnection object; it must not be null.
* @param flags Operation options for the binding as per {@link #bindService}.
* @param instanceName Unique identifier for the service instance. Each unique
* name here will result in a different service instance being created.
* @return Returns success of binding as per {@link #bindService}.
* @param executor Callbacks on ServiceConnection will be called on executor.
* Must use same instance for the same instance of ServiceConnection.
* @param conn Receives information as the service is started and stopped.
* This must be a valid ServiceConnection object; it must not be null.
*
* @throws SecurityException If the caller does not have permission to access the service
*
* @see #bindService
*/
public boolean bindIsolatedService(@RequiresPermission @NonNull Intent service,
@NonNull ServiceConnection conn, @BindServiceFlags int flags,
@NonNull String instanceName) {
@BindServiceFlags int flags, @NonNull String instanceName,
@NonNull @CallbackExecutor Executor executor, @NonNull ServiceConnection conn) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}


+ 9
- 3
core/java/android/content/ContextWrapper.java View File

@@ -706,9 +706,15 @@ public class ContextWrapper extends Context {
}

@Override
public boolean bindIsolatedService(Intent service, ServiceConnection conn,
int flags, String instanceName) {
return mBase.bindIsolatedService(service, conn, flags, instanceName);
public boolean bindService(Intent service, int flags, Executor executor,
ServiceConnection conn) {
return mBase.bindService(service, flags, executor, conn);
}

@Override
public boolean bindIsolatedService(Intent service, int flags, String instanceName,
Executor executor, ServiceConnection conn) {
return mBase.bindIsolatedService(service, flags, instanceName, executor, conn);
}

/** @hide */

+ 8
- 3
test-mock/src/android/test/mock/MockContext.java View File

@@ -577,9 +577,14 @@ public class MockContext extends Context {
}

@Override
public boolean bindIsolatedService(Intent service,
ServiceConnection conn, int flags,
String instanceName) {
public boolean bindService(Intent service, int flags, Executor executor,
ServiceConnection conn) {
throw new UnsupportedOperationException();
}

@Override
public boolean bindIsolatedService(Intent service, int flags, String instanceName,
Executor executor, ServiceConnection conn) {
throw new UnsupportedOperationException();
}


+ 13
- 6
test-runner/src/android/test/IsolatedContext.java View File

@@ -17,13 +17,13 @@
package android.test;

import android.accounts.AccountManager;
import android.content.ContextWrapper;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.Context;
import android.content.ServiceConnection;
import android.content.BroadcastReceiver;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.test.mock.MockAccountManager;
@@ -31,6 +31,7 @@ import android.test.mock.MockAccountManager;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;


/**
@@ -75,8 +76,14 @@ public class IsolatedContext extends ContextWrapper {
}

@Override
public boolean bindIsolatedService(Intent service, ServiceConnection conn, int flags,
String instanceName) {
public boolean bindService(Intent service, int flags, Executor executor,
ServiceConnection conn) {
return false;
}

@Override
public boolean bindIsolatedService(Intent service, int flags, String instanceName,
Executor executor, ServiceConnection conn) {
return false;
}


Loading…
Cancel
Save