Start accepting Executors instead of Handlers.
After discussion in API council, our new best-practices are to have developers provide an Executor to dispatch callbacks/listeners on, instead of the previous guidance of using a Handler. Define Context.getMainExecutor() to easily obtain an Executor associated with the main thread of application. This allows new APIs to require a @NonNull Executor. Also define a new @CallbackExecutor auto-doc annotation that explains background and points developers at new Context method above. Test: cts-tradefed run commandAndExit cts-dev -m CtsContentTestCases -t android.content.cts.ContextTest#testMainLooper Test: cts-tradefed run commandAndExit cts-dev -m CtsContentTestCases -t android.content.cts.ContextTest#testMainExecutor Bug: 70348426 Change-Id: I536892bcd80fcfba1bb1ddf67648c08a26d7ddd2
This commit is contained in:
@@ -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();
|
||||
|
||||
41
core/java/android/annotation/CallbackExecutor.java
Normal file
41
core/java/android/annotation/CallbackExecutor.java
Normal file
@@ -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 {
|
||||
}
|
||||
44
core/java/android/annotation/Condemned.java
Normal file
44
core/java/android/annotation/Condemned.java
Normal file
@@ -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 {
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
@@ -249,6 +250,11 @@ class ContextImpl extends Context {
|
||||
return mMainThread.getLooper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Executor getMainExecutor() {
|
||||
return mMainThread.getExecutor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getApplicationContext() {
|
||||
return (mPackageInfo != null) ?
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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
|
||||
@@ -461,6 +463,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
|
||||
|
||||
@@ -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();
|
||||
|
||||
45
core/java/android/os/HandlerExecutor.java
Normal file
45
core/java/android/os/HandlerExecutor.java
Normal file
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -86,6 +86,11 @@ public class MockContext extends Context {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Executor getMainExecutor() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getApplicationContext() {
|
||||
throw new UnsupportedOperationException();
|
||||
|
||||
Reference in New Issue
Block a user