Add new method 'requestServiceFeatures' in AppPredictionService. It allows Frontend to get backend service impl features info (e.g. feature readiness)
Bug: 292565550
Test: atest AppPredictionServiceTest
Change-Id: I0f2ec9ba0c1b332fa73afb461c29fe25de9b01e2
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index f5bf437..60ef8b4 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -48,6 +48,7 @@
":android.service.controls.flags-aconfig-java{.generated_srcjars}",
":android.service.dreams.flags-aconfig-java{.generated_srcjars}",
":android.service.notification.flags-aconfig-java{.generated_srcjars}",
+ ":android.service.appprediction.flags-aconfig-java{.generated_srcjars}",
":android.service.voice.flags-aconfig-java{.generated_srcjars}",
":android.speech.flags-aconfig-java{.generated_srcjars}",
":android.tracing.flags-aconfig-java{.generated_srcjars}",
@@ -112,6 +113,7 @@
"android.provider.flags-aconfig",
"android.security.flags-aconfig",
"android.server.app.flags-aconfig",
+ "android.service.appprediction.flags-aconfig",
"android.service.autofill.flags-aconfig",
"android.service.chooser.flags-aconfig",
"android.service.controls.flags-aconfig",
@@ -698,6 +700,19 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// App prediction
+aconfig_declarations {
+ name: "android.service.appprediction.flags-aconfig",
+ package: "android.service.appprediction.flags",
+ srcs: ["core/java/android/service/appprediction/flags/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.service.appprediction.flags-aconfig-java",
+ aconfig_declarations: "android.service.appprediction.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Controls
aconfig_declarations {
name: "android.service.controls.flags-aconfig",
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 318badf..783180a 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2193,6 +2193,7 @@
method public void notifyLaunchLocationShown(@NonNull String, @NonNull java.util.List<android.app.prediction.AppTargetId>);
method public void registerPredictionUpdates(@NonNull java.util.concurrent.Executor, @NonNull android.app.prediction.AppPredictor.Callback);
method public void requestPredictionUpdate();
+ method @FlaggedApi("android.service.appprediction.flags.service_features_api") public void requestServiceFeatures(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.os.Bundle>);
method @Nullable public void sortTargets(@NonNull java.util.List<android.app.prediction.AppTarget>, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.prediction.AppTarget>>);
method public void unregisterPredictionUpdates(@NonNull android.app.prediction.AppPredictor.Callback);
}
@@ -11833,6 +11834,7 @@
method @MainThread public void onDestroyPredictionSession(@NonNull android.app.prediction.AppPredictionSessionId);
method @MainThread public abstract void onLaunchLocationShown(@NonNull android.app.prediction.AppPredictionSessionId, @NonNull String, @NonNull java.util.List<android.app.prediction.AppTargetId>);
method @MainThread public abstract void onRequestPredictionUpdate(@NonNull android.app.prediction.AppPredictionSessionId);
+ method @FlaggedApi("android.service.appprediction.flags.service_features_api") @MainThread public void onRequestServiceFeatures(@NonNull android.app.prediction.AppPredictionSessionId, @NonNull java.util.function.Consumer<android.os.Bundle>);
method @MainThread public abstract void onSortAppTargets(@NonNull android.app.prediction.AppPredictionSessionId, @NonNull java.util.List<android.app.prediction.AppTarget>, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<java.util.List<android.app.prediction.AppTarget>>);
method @MainThread public void onStartPredictionUpdates();
method @MainThread public void onStopPredictionUpdates();
diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java
index d628b7f..0c1a28a 100644
--- a/core/java/android/app/prediction/AppPredictor.java
+++ b/core/java/android/app/prediction/AppPredictor.java
@@ -16,6 +16,7 @@
package android.app.prediction;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -24,9 +25,12 @@
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
+import android.os.IRemoteCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.service.appprediction.flags.Flags;
import android.util.ArrayMap;
import android.util.Log;
@@ -263,6 +267,34 @@
}
/**
+ * Requests a Bundle which includes service features info or {@code null} if the service is not
+ * available.
+ *
+ * @param callbackExecutor The callback executor to use when calling the callback. It cannot be
+ * null.
+ * @param callback The callback to return the Bundle which includes service features info. It
+ * cannot be null.
+ *
+ * @throws IllegalStateException If this AppPredictor has already been destroyed.
+ * @throws RuntimeException If there is a failure communicating with the remote service.
+ */
+ @FlaggedApi(Flags.FLAG_SERVICE_FEATURES_API)
+ public void requestServiceFeatures(@NonNull Executor callbackExecutor,
+ @NonNull Consumer<Bundle> callback) {
+ if (mIsClosed.get()) {
+ throw new IllegalStateException("This client has already been destroyed.");
+ }
+
+ try {
+ mPredictionManager.requestServiceFeatures(mSessionId,
+ new RemoteCallbackWrapper(callbackExecutor, callback));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to request service feature info", e);
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* Destroys the client and unregisters the callback. Any method on this class after this call
* with throw {@link IllegalStateException}.
*/
@@ -347,6 +379,28 @@
}
}
+ static class RemoteCallbackWrapper extends IRemoteCallback.Stub {
+
+ private final Consumer<Bundle> mCallback;
+ private final Executor mExecutor;
+
+ RemoteCallbackWrapper(@NonNull Executor callbackExecutor,
+ @NonNull Consumer<Bundle> callback) {
+ mExecutor = callbackExecutor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void sendResult(Bundle result) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.accept(result));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
private static class Token {
static final IBinder sBinder = new Binder(TAG);
}
diff --git a/core/java/android/app/prediction/IPredictionManager.aidl b/core/java/android/app/prediction/IPredictionManager.aidl
index 863fc6f9..94b4f5b 100644
--- a/core/java/android/app/prediction/IPredictionManager.aidl
+++ b/core/java/android/app/prediction/IPredictionManager.aidl
@@ -22,6 +22,7 @@
import android.app.prediction.AppPredictionSessionId;
import android.app.prediction.IPredictionCallback;
import android.content.pm.ParceledListSlice;
+import android.os.IRemoteCallback;
/**
* @hide
@@ -48,4 +49,6 @@
void requestPredictionUpdate(in AppPredictionSessionId sessionId);
void onDestroyPredictionSession(in AppPredictionSessionId sessionId);
+
+ void requestServiceFeatures(in AppPredictionSessionId sessionId, in IRemoteCallback callback);
}
diff --git a/core/java/android/service/appprediction/AppPredictionService.java b/core/java/android/service/appprediction/AppPredictionService.java
index a2ffa5d..2402cfd 100644
--- a/core/java/android/service/appprediction/AppPredictionService.java
+++ b/core/java/android/service/appprediction/AppPredictionService.java
@@ -18,6 +18,7 @@
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -31,12 +32,15 @@
import android.app.prediction.IPredictionCallback;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
+import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.RemoteException;
import android.service.appprediction.IPredictionService.Stub;
+import android.service.appprediction.flags.Flags;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
@@ -134,6 +138,16 @@
obtainMessage(AppPredictionService::doDestroyPredictionSession,
AppPredictionService.this, sessionId));
}
+
+ @FlaggedApi(Flags.FLAG_SERVICE_FEATURES_API)
+ @Override
+ public void requestServiceFeatures(AppPredictionSessionId sessionId,
+ IRemoteCallback callback) {
+ mHandler.sendMessage(
+ obtainMessage(AppPredictionService::onRequestServiceFeatures,
+ AppPredictionService.this, sessionId,
+ new RemoteCallbackWrapper(callback, null)));
+ }
};
@CallSuper
@@ -277,6 +291,18 @@
public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) {}
/**
+ * Called by the client app to request {@link AppPredictionService} features info.
+ *
+ * @param sessionId the session's Id. It is @NonNull.
+ * @param callback the callback to return the Bundle which includes service features info. It
+ * is @NonNull.
+ */
+ @FlaggedApi(Flags.FLAG_SERVICE_FEATURES_API)
+ @MainThread
+ public void onRequestServiceFeatures(@NonNull AppPredictionSessionId sessionId,
+ @NonNull Consumer<Bundle> callback) {}
+
+ /**
* Used by the prediction factory to send back results the client app. The can be called
* in response to {@link #onRequestPredictionUpdate(AppPredictionSessionId)} or proactively as
* a result of changes in predictions.
@@ -357,4 +383,50 @@
}
}
}
+
+ private static final class RemoteCallbackWrapper implements Consumer<Bundle>,
+ IBinder.DeathRecipient {
+
+ private IRemoteCallback mCallback;
+ private final Consumer<RemoteCallbackWrapper> mOnBinderDied;
+
+ RemoteCallbackWrapper(IRemoteCallback callback,
+ @Nullable Consumer<RemoteCallbackWrapper> onBinderDied) {
+ mCallback = callback;
+ mOnBinderDied = onBinderDied;
+ if (mOnBinderDied != null) {
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death: " + e);
+ }
+ }
+ }
+
+ public void destroy() {
+ if (mCallback != null && mOnBinderDied != null) {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+
+ @Override
+ public void accept(Bundle bundle) {
+ try {
+ if (mCallback != null) {
+ mCallback.sendResult(bundle);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result:" + e);
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ destroy();
+ mCallback = null;
+ if (mOnBinderDied != null) {
+ mOnBinderDied.accept(this);
+ }
+ }
+ }
}
diff --git a/core/java/android/service/appprediction/IPredictionService.aidl b/core/java/android/service/appprediction/IPredictionService.aidl
index 0f3df85..e144dfa 100644
--- a/core/java/android/service/appprediction/IPredictionService.aidl
+++ b/core/java/android/service/appprediction/IPredictionService.aidl
@@ -22,6 +22,7 @@
import android.app.prediction.AppPredictionSessionId;
import android.app.prediction.IPredictionCallback;
import android.content.pm.ParceledListSlice;
+import android.os.IRemoteCallback;
/**
* Interface from the system to a prediction service.
@@ -50,4 +51,6 @@
void requestPredictionUpdate(in AppPredictionSessionId sessionId);
void onDestroyPredictionSession(in AppPredictionSessionId sessionId);
+
+ void requestServiceFeatures(in AppPredictionSessionId sessionId, in IRemoteCallback callback);
}
diff --git a/core/java/android/service/appprediction/flags/flags.aconfig b/core/java/android/service/appprediction/flags/flags.aconfig
new file mode 100644
index 0000000..c7e47d4
--- /dev/null
+++ b/core/java/android/service/appprediction/flags/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.service.appprediction.flags"
+
+flag {
+ name: "service_features_api"
+ namespace: "systemui"
+ description: "Guards the new requestServiceFeatures api"
+ bug: "292565550"
+}
\ No newline at end of file
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
index 2c50389..df4e699 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
@@ -35,6 +35,7 @@
import android.content.pm.ParceledListSlice;
import android.os.Binder;
import android.os.IBinder;
+import android.os.IRemoteCallback;
import android.os.Process;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -162,6 +163,13 @@
(service) -> service.onDestroyPredictionSessionLocked(sessionId));
}
+ @Override
+ public void requestServiceFeatures(@NonNull AppPredictionSessionId sessionId,
+ IRemoteCallback callback) {
+ runForUserLocked("requestServiceFeatures", sessionId,
+ (service) -> service.requestServiceFeaturesLocked(sessionId, callback));
+ }
+
public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
@Nullable FileDescriptor err,
@NonNull String[] args, @Nullable ShellCallback callback,
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
index 84707a8..a0198f2 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -31,6 +31,7 @@
import android.content.pm.ParceledListSlice;
import android.content.pm.ServiceInfo;
import android.os.IBinder;
+import android.os.IRemoteCallback;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.provider.DeviceConfig;
@@ -237,6 +238,18 @@
sessionInfo.destroy();
}
+ /**
+ * Requests the service to provide AppPredictionService features info.
+ */
+ @GuardedBy("mLock")
+ public void requestServiceFeaturesLocked(@NonNull AppPredictionSessionId sessionId,
+ @NonNull IRemoteCallback callback) {
+ final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return;
+ resolveService(sessionId, true, sessionInfo.mUsesPeopleService,
+ s -> s.requestServiceFeatures(sessionId, callback));
+ }
+
@Override
public void onFailureOrTimeout(boolean timedOut) {
if (isDebug()) {
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index 885ed35..b9f00d7 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -38,6 +38,7 @@
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.IBinder;
+import android.os.IRemoteCallback;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -474,6 +475,10 @@
getDataManager().restore(userId, payload);
}
+ @Override
+ public void requestServiceFeatures(AppPredictionSessionId sessionId,
+ IRemoteCallback callback) {}
+
@VisibleForTesting
SessionInfo getSessionInfo(AppPredictionSessionId sessionId) {
return mSessions.get(sessionId);