diff options
author | Bo Liu <boliu@google.com> | 2019-03-06 20:21:45 +0000 |
---|---|---|
committer | Bo Liu <boliu@google.com> | 2019-03-06 20:21:45 +0000 |
commit | 58a57667e4d84927f29efb9228e7844bac7937e9 (patch) | |
tree | 05ac601075122074ce1ac7d2d6de163d16c2bfd4 | |
parent | f278d267f5f29db63e5f488f32984fe73949d4b4 (diff) |
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
-rw-r--r-- | api/current.txt | 3 | ||||
-rw-r--r-- | core/java/android/app/ContextImpl.java | 49 | ||||
-rw-r--r-- | core/java/android/app/LoadedApk.java | 49 | ||||
-rw-r--r-- | core/java/android/content/Context.java | 23 | ||||
-rw-r--r-- | core/java/android/content/ContextWrapper.java | 12 | ||||
-rw-r--r-- | test-mock/src/android/test/mock/MockContext.java | 11 | ||||
-rw-r--r-- | test-runner/src/android/test/IsolatedContext.java | 19 |
7 files changed, 129 insertions, 37 deletions
diff --git a/api/current.txt b/api/current.txt index f170b4a9ee66..ffce57113fbe 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index b607f9adebbe..eebac143fd8b 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -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"); } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 5d186a25596f..db8c905eac34 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -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); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index beb1fb68d218..a5a8cd704718 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -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."); } diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 40559d31d631..0859f97e81a1 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -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 */ diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java index ae6cd29fb2de..a95b6f11e98a 100644 --- a/test-mock/src/android/test/mock/MockContext.java +++ b/test-mock/src/android/test/mock/MockContext.java @@ -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(); } diff --git a/test-runner/src/android/test/IsolatedContext.java b/test-runner/src/android/test/IsolatedContext.java index 73db4517e130..dd4a9a3a4d69 100644 --- a/test-runner/src/android/test/IsolatedContext.java +++ b/test-runner/src/android/test/IsolatedContext.java @@ -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; } |