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:
Jeff Sharkey
2017-12-12 17:26:23 -07:00
parent ff38f236b5
commit 8439ac08b1
10 changed files with 189 additions and 8 deletions

View File

@@ -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();

View 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 {
}

View 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 &#64;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 {
}

View File

@@ -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;
}

View File

@@ -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) ?

View File

@@ -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));
}
});

View File

@@ -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

View File

@@ -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();

View 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");
}
}
}

View File

@@ -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();