diff options
-rw-r--r-- | api/current.txt | 3 | ||||
-rw-r--r-- | core/java/android/annotation/CallbackExecutor.java | 41 | ||||
-rw-r--r-- | core/java/android/annotation/Condemned.java | 44 | ||||
-rw-r--r-- | core/java/android/app/ActivityThread.java | 7 | ||||
-rw-r--r-- | core/java/android/app/ContextImpl.java | 6 | ||||
-rw-r--r-- | core/java/android/app/admin/DevicePolicyManager.java | 20 | ||||
-rw-r--r-- | core/java/android/content/Context.java | 12 | ||||
-rw-r--r-- | core/java/android/content/ContextWrapper.java | 8 | ||||
-rw-r--r-- | core/java/android/os/HandlerExecutor.java | 45 | ||||
-rw-r--r-- | test-mock/src/android/test/mock/MockContext.java | 11 |
10 files changed, 189 insertions, 8 deletions
diff --git a/api/current.txt b/api/current.txt index dd102f6563d9..1a67e606e434 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6313,7 +6313,7 @@ package android.app.admin { method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName); method public void addUserRestriction(android.content.ComponentName, java.lang.String); method public boolean bindDeviceAdminServiceAsUser(android.content.ComponentName, android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle); - method public boolean clearApplicationUserData(android.content.ComponentName, java.lang.String, android.app.admin.DevicePolicyManager.OnClearApplicationUserDataListener, android.os.Handler); + method public boolean clearApplicationUserData(android.content.ComponentName, java.lang.String, android.app.admin.DevicePolicyManager.OnClearApplicationUserDataListener, java.util.concurrent.Executor); method public void clearCrossProfileIntentFilters(android.content.ComponentName); method public deprecated void clearDeviceOwnerApp(java.lang.String); method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String); @@ -9050,6 +9050,7 @@ package android.content { method public abstract java.io.File[] getExternalMediaDirs(); method public abstract java.io.File getFileStreamPath(java.lang.String); method public abstract java.io.File getFilesDir(); + method public java.util.concurrent.Executor getMainExecutor(); method public abstract android.os.Looper getMainLooper(); method public abstract java.io.File getNoBackupFilesDir(); method public abstract java.io.File getObbDir(); diff --git a/core/java/android/annotation/CallbackExecutor.java b/core/java/android/annotation/CallbackExecutor.java new file mode 100644 index 000000000000..5671a3d2b6d6 --- /dev/null +++ b/core/java/android/annotation/CallbackExecutor.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.annotation; + +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.content.Context; +import android.os.AsyncTask; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.concurrent.Executor; + +/** + * @paramDoc Callback and listener events are dispatched through this + * {@link Executor}, providing an easy way to control which thread is + * used. To dispatch events through the main thread of your + * application, you can use {@link Context#getMainExecutor()}. To + * dispatch events through a shared thread pool, you can use + * {@link AsyncTask#THREAD_POOL_EXECUTOR}. + * @hide + */ +@Retention(SOURCE) +@Target(PARAMETER) +public @interface CallbackExecutor { +} diff --git a/core/java/android/annotation/Condemned.java b/core/java/android/annotation/Condemned.java new file mode 100644 index 000000000000..186409b3e204 --- /dev/null +++ b/core/java/android/annotation/Condemned.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.annotation; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PACKAGE; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * A program element annotated @Condemned is one that programmers are + * blocked from using, typically because it's about to be completely destroyed. + * <p> + * This is a stronger version of @Deprecated, and it's typically used to + * mark APIs that only existed temporarily in a preview SDK, and which only + * continue to exist temporarily to support binary compatibility. + * + * @hide + */ +@Retention(SOURCE) +@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) +public @interface Condemned { +} diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 5369adf4aad1..90f10c9c2934 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -80,6 +80,7 @@ import android.os.DropBoxManager; import android.os.Environment; import android.os.GraphicsEnvironment; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.LocaleList; import android.os.Looper; @@ -172,6 +173,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.TimeZone; +import java.util.concurrent.Executor; final class RemoteServiceException extends AndroidRuntimeException { public RemoteServiceException(String msg) { @@ -242,6 +244,7 @@ public final class ActivityThread extends ClientTransactionHandler { final ApplicationThread mAppThread = new ApplicationThread(); final Looper mLooper = Looper.myLooper(); final H mH = new H(); + final Executor mExecutor = new HandlerExecutor(mH); final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>(); // List of new activities (via ActivityRecord.nextIdle) that should // be reported when next we idle. @@ -1943,6 +1946,10 @@ public final class ActivityThread extends ClientTransactionHandler { return mLooper; } + public Executor getExecutor() { + return mExecutor; + } + public Application getApplication() { return mInitialApplication; } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index b0d020a7e328..a2de0f44962a 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -90,6 +90,7 @@ import java.io.InputStream; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Objects; +import java.util.concurrent.Executor; class ReceiverRestrictedContext extends ContextWrapper { ReceiverRestrictedContext(Context base) { @@ -250,6 +251,11 @@ class ContextImpl extends Context { } @Override + public Executor getMainExecutor() { + return mMainThread.getExecutor(); + } + + @Override public Context getApplicationContext() { return (mPackageInfo != null) ? mPackageInfo.getApplication() : mMainThread.getApplication(); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index cfff3614a8e4..513e7f82af55 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -16,7 +16,9 @@ package android.app.admin; +import android.annotation.CallbackExecutor; import android.annotation.ColorInt; +import android.annotation.Condemned; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -49,6 +51,7 @@ import android.net.ProxyInfo; import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Parcelable; import android.os.PersistableBundle; import android.os.Process; @@ -94,6 +97,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.concurrent.Executor; /** * Public interface for managing policies enforced on a device. Most clients of this class must be @@ -8617,6 +8621,15 @@ public class DevicePolicyManager { } } + /** {@hide} */ + @Condemned + @Deprecated + public boolean clearApplicationUserData(@NonNull ComponentName admin, + @NonNull String packageName, @NonNull OnClearApplicationUserDataListener listener, + @NonNull Handler handler) { + return clearApplicationUserData(admin, packageName, listener, new HandlerExecutor(handler)); + } + /** * Called by the device owner or profile owner to clear application user data of a given * package. The behaviour of this is equivalent to the target application calling @@ -8628,19 +8641,20 @@ public class DevicePolicyManager { * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param packageName The name of the package which will have its user data wiped. * @param listener A callback object that will inform the caller when the clearing is done. - * @param handler The handler indicating the thread on which the listener should be invoked. + * @param executor The executor through which the listener should be invoked. * @throws SecurityException if the caller is not the device owner/profile owner. * @return whether the clearing succeeded. */ public boolean clearApplicationUserData(@NonNull ComponentName admin, @NonNull String packageName, @NonNull OnClearApplicationUserDataListener listener, - @NonNull Handler handler) { + @NonNull @CallbackExecutor Executor executor) { throwIfParentInstance("clearAppData"); + Preconditions.checkNotNull(executor); try { return mService.clearApplicationUserData(admin, packageName, new IPackageDataObserver.Stub() { public void onRemoveCompleted(String pkg, boolean succeeded) { - handler.post(() -> + executor.execute(() -> listener.onApplicationUserDataCleared(pkg, succeeded)); } }); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index a47433093988..458ba05015ba 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -51,6 +51,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.Looper; import android.os.StatFs; @@ -75,6 +76,7 @@ import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; /** * Interface to global information about an application environment. This is @@ -462,6 +464,16 @@ public abstract class Context { public abstract Looper getMainLooper(); /** + * Return an {@link Executor} that will run enqueued tasks on the main + * thread associated with this context. This is the thread used to dispatch + * calls to application components (activities, services, etc). + */ + public Executor getMainExecutor() { + // This is pretty inefficient, which is why ContextImpl overrides it + return new HandlerExecutor(new Handler(getMainLooper())); + } + + /** * Return the context of the single, global Application object of the * current process. This generally should only be used if you need a * Context whose lifecycle is separate from the current context, that is diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 85acdc6b8101..67de4fe6bc4b 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -45,6 +45,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.concurrent.Executor; /** * Proxying implementation of Context that simply delegates all of its calls to @@ -103,7 +104,12 @@ public class ContextWrapper extends Context { public Looper getMainLooper() { return mBase.getMainLooper(); } - + + @Override + public Executor getMainExecutor() { + return mBase.getMainExecutor(); + } + @Override public Context getApplicationContext() { return mBase.getApplicationContext(); diff --git a/core/java/android/os/HandlerExecutor.java b/core/java/android/os/HandlerExecutor.java new file mode 100644 index 000000000000..416b24b5ed05 --- /dev/null +++ b/core/java/android/os/HandlerExecutor.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.annotation.NonNull; + +import com.android.internal.util.Preconditions; + +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; + +/** + * An adapter {@link Executor} that posts all executed tasks onto the given + * {@link Handler}. + * + * @hide + */ +public class HandlerExecutor implements Executor { + private final Handler mHandler; + + public HandlerExecutor(@NonNull Handler handler) { + mHandler = Preconditions.checkNotNull(handler); + } + + @Override + public void execute(Runnable command) { + if (!mHandler.post(command)) { + throw new RejectedExecutionException(mHandler + " is shutting down"); + } + } +} diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java index 5e5ba462cfca..4dfd0507f351 100644 --- a/test-mock/src/android/test/mock/MockContext.java +++ b/test-mock/src/android/test/mock/MockContext.java @@ -19,13 +19,12 @@ package android.test.mock; import android.annotation.SystemApi; import android.app.IApplicationThread; import android.app.IServiceConnection; -import android.app.Notification; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.BroadcastReceiver; import android.content.IntentSender; import android.content.ServiceConnection; import android.content.SharedPreferences; @@ -44,8 +43,8 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.UserHandle; -import android.view.DisplayAdjustments; import android.view.Display; +import android.view.DisplayAdjustments; import java.io.File; import java.io.FileInputStream; @@ -53,6 +52,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.concurrent.Executor; /** * A mock {@link android.content.Context} class. All methods are non-functional and throw @@ -87,6 +87,11 @@ public class MockContext extends Context { } @Override + public Executor getMainExecutor() { + throw new UnsupportedOperationException(); + } + + @Override public Context getApplicationContext() { throw new UnsupportedOperationException(); } |