diff options
| author | 2024-10-01 10:33:47 +0000 | |
|---|---|---|
| committer | 2024-10-01 10:33:47 +0000 | |
| commit | a8ef5ddac89a91b200a471ded4972d21177a77a4 (patch) | |
| tree | 047640e64b9c33b6413220a930d82f8bf2227a08 | |
| parent | 5fdb4632370c388078581833857f8bce07ae0ab0 (diff) | |
| parent | a620ff83578512539a25b6351d034290850acc7a (diff) | |
Merge "Add a timeout for CancellationSignal issued to AppFunctionService before which it is unbound." into main
8 files changed, 83 insertions, 17 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 664dfe980b49..76209fcf4a6b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8822,6 +8822,7 @@ package android.app.appfunctions { field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionResponse> CREATOR; field public static final String PROPERTY_RETURN_VALUE = "returnValue"; field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2 + field public static final int RESULT_CANCELLED = 7; // 0x7 field public static final int RESULT_DENIED = 1; // 0x1 field public static final int RESULT_DISABLED = 6; // 0x6 field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3 diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java index 4ed0a1b50a08..2851e92bd57f 100644 --- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java @@ -102,6 +102,12 @@ public final class ExecuteAppFunctionResponse implements Parcelable { /** The caller tried to execute a disabled app function. */ public static final int RESULT_DISABLED = 6; + /** + * The operation was cancelled. Use this error code to report that a cancellation is done after + * receiving a cancellation signal. + */ + public static final int RESULT_CANCELLED = 7; + /** The result code of the app function execution. */ @ResultCode private final int mResultCode; @@ -278,6 +284,7 @@ public final class ExecuteAppFunctionResponse implements Parcelable { RESULT_INVALID_ARGUMENT, RESULT_TIMED_OUT, RESULT_DISABLED, + RESULT_CANCELLED }) @Retention(RetentionPolicy.SOURCE) public @interface ResultCode {} diff --git a/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java b/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java index 86fc36960203..00182447e9a8 100644 --- a/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java +++ b/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java @@ -72,4 +72,12 @@ public class SafeOneTimeExecuteAppFunctionCallback { mOnDispatchCallback.accept(result); } } + + /** + * Disables this callback. Subsequent calls to {@link #onResult(ExecuteAppFunctionResponse)} + * will be ignored. + */ + public void disable() { + mOnResultCalled.set(true); + } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index b6c8fc7b80c7..14298f416d87 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -430,6 +430,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { serviceIntent, bindFlags, targetUser, + mServiceConfig.getExecuteAppFunctionCancellationTimeoutMillis(), + cancellationSignal, new RunServiceCallCallback<IAppFunctionService>() { @Override public void onServiceConnected( @@ -470,6 +472,14 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { "Failed to connect to AppFunctionService", /* extras= */ null)); } + + @Override + public void onCancelled() { + // Do not forward the result back to the caller once it has been + // canceled. The caller does not need a notification and should + // proceed after initiating a cancellation. + safeExecuteAppFunctionCallback.disable(); + } }); if (!bindServiceResult) { diff --git a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java index cd5c3831bc0d..55173c350e71 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java +++ b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java @@ -17,12 +17,13 @@ package com.android.server.appfunctions; import android.annotation.NonNull; import android.content.Intent; +import android.os.CancellationSignal; import android.os.UserHandle; /** - * Defines a contract for establishing temporary connections to services and executing operations - * within a specified timeout. Implementations of this interface provide mechanisms to ensure that - * services are properly unbound after the operation completes or a timeout occurs. + * Defines a contract for establishing temporary connections to services and executing operations. + * Implementations of this interface provide mechanisms to ensure that services are properly unbound + * after the operation completes or a cancellation timeout occurs. * * @param <T> Class of wrapped service. */ @@ -30,20 +31,25 @@ public interface RemoteServiceCaller<T> { /** * Initiates service binding and executes a provided method when the service connects. Unbinds - * the service after execution or upon timeout. Returns the result of the bindService API. + * the service after execution or upon cancellation timeout. Returns the result of the + * bindService API. * * <p>When the service connection was made successfully, it's the caller responsibility to * report the usage is completed and can be unbound by calling {@link * ServiceUsageCompleteListener#onCompleted()}. * - * <p>This method includes a timeout mechanism to prevent the system from being stuck in a state - * where a service is bound indefinitely (for example, if the binder method never returns). This - * helps ensure that the calling app does not remain alive unnecessarily. + * <p>This method includes a timeout mechanism for cancellation to prevent the system from being + * stuck in a state where a service is bound indefinitely. If the app to be bound does not + * return the result within `cancellationTimeoutMillis` after the cancellation signal is sent, + * this method will unbind the service connection. * * @param intent An Intent object that describes the service that should be bound. * @param bindFlags Flags used to control the binding process See {@link * android.content.Context#bindService}. * @param userHandle The UserHandle of the user for which the service should be bound. + * @param cancellationTimeoutMillis The timeout before the service is unbound after a + * cancellation signal is issued. + * @param cancellationSignal The cancellation signal forwarded to the service. * @param callback A callback to be invoked for various events. See {@link * RunServiceCallCallback}. */ @@ -51,6 +57,8 @@ public interface RemoteServiceCaller<T> { @NonNull Intent intent, int bindFlags, @NonNull UserHandle userHandle, + long cancellationTimeoutMillis, + @NonNull CancellationSignal cancellationSignal, @NonNull RunServiceCallCallback<T> callback); /** An interface for clients to signal that they have finished using a bound service. */ @@ -73,5 +81,8 @@ public interface RemoteServiceCaller<T> { /** Called when the service connection was failed to establish. */ void onFailedToConnect(); + + /** Called when the caller has cancelled this remote service call. */ + void onCancelled(); } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java index ffca8491abcd..7e3a29afc01c 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java @@ -20,6 +20,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.os.CancellationSignal; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -63,9 +64,17 @@ public class RemoteServiceCallerImpl<T> implements RemoteServiceCaller<T> { @NonNull Intent intent, int bindFlags, @NonNull UserHandle userHandle, + long cancellationTimeoutMillis, + @NonNull CancellationSignal cancellationSignal, @NonNull RunServiceCallCallback<T> callback) { OneOffServiceConnection serviceConnection = - new OneOffServiceConnection(intent, bindFlags, userHandle, callback); + new OneOffServiceConnection( + intent, + bindFlags, + userHandle, + cancellationTimeoutMillis, + cancellationSignal, + callback); return serviceConnection.bindAndRun(); } @@ -76,23 +85,38 @@ public class RemoteServiceCallerImpl<T> implements RemoteServiceCaller<T> { private final int mFlags; private final UserHandle mUserHandle; private final RunServiceCallCallback<T> mCallback; + private final long mCancellationTimeoutMillis; + private final CancellationSignal mCancellationSignal; + private final Runnable mCancellationTimeoutRunnable; OneOffServiceConnection( @NonNull Intent intent, int flags, @NonNull UserHandle userHandle, + long cancellationTimeoutMillis, + @NonNull CancellationSignal cancellationSignal, @NonNull RunServiceCallCallback<T> callback) { mIntent = intent; mFlags = flags; mCallback = callback; mUserHandle = userHandle; + mCancellationTimeoutMillis = cancellationTimeoutMillis; + mCancellationSignal = cancellationSignal; + mCancellationTimeoutRunnable = this::safeUnbind; } public boolean bindAndRun() { boolean bindServiceResult = mContext.bindServiceAsUser(mIntent, this, mFlags, mUserHandle); - if (!bindServiceResult) { + if (bindServiceResult) { + mCancellationSignal.setOnCancelListener( + () -> { + mCallback.onCancelled(); + mHandler.postDelayed( + mCancellationTimeoutRunnable, mCancellationTimeoutMillis); + }); + } else { safeUnbind(); } @@ -126,6 +150,7 @@ public class RemoteServiceCallerImpl<T> implements RemoteServiceCaller<T> { private void safeUnbind() { try { + mHandler.removeCallbacks(mCancellationTimeoutRunnable); mContext.unbindService(this); } catch (Exception ex) { Log.w(TAG, "Failed to unbind", ex); diff --git a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java index caa4bf0e9d27..8d2d1b2dfd00 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java +++ b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java @@ -21,6 +21,9 @@ public interface ServiceConfig { // TODO(b/357551503): Obtain namespace from DeviceConfig. String NAMESPACE_APP_FUNCTIONS = "appfunctions"; - /** Returns the maximum time to wait for an app function execution to be complete. */ - long getExecuteAppFunctionTimeoutMillis(); + /** + * Returns the timeout for which the system server waits for the app function service to + * successfully cancel the execution of an app function before forcefully unbinding the service. + */ + long getExecuteAppFunctionCancellationTimeoutMillis(); } diff --git a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java index f18789be5ce8..787f52afda4b 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java @@ -20,15 +20,16 @@ import android.provider.DeviceConfig; /** Implementation of {@link ServiceConfig} */ public class ServiceConfigImpl implements ServiceConfig { - static final String DEVICE_CONFIG_PROPERTY_EXECUTION_TIMEOUT = - "execute_app_function_timeout_millis"; - static final long DEFAULT_EXECUTE_APP_FUNCTION_TIMEOUT_MS = 5000L; + static final String DEVICE_CONFIG_PROPERTY_EXECUTION_CANCELLATION_TIMEOUT = + "execute_app_function_cancellation_timeout_millis"; + static final long DEFAULT_EXECUTE_APP_FUNCTION_CANCELLATION_TIMEOUT_MS = 5000L; + @Override - public long getExecuteAppFunctionTimeoutMillis() { + public long getExecuteAppFunctionCancellationTimeoutMillis() { return DeviceConfig.getLong( NAMESPACE_APP_FUNCTIONS, - DEVICE_CONFIG_PROPERTY_EXECUTION_TIMEOUT, - DEFAULT_EXECUTE_APP_FUNCTION_TIMEOUT_MS); + DEVICE_CONFIG_PROPERTY_EXECUTION_CANCELLATION_TIMEOUT, + DEFAULT_EXECUTE_APP_FUNCTION_CANCELLATION_TIMEOUT_MS); } } |