| /* |
| * Copyright (C) 2018 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.app.contentsuggestions; |
| |
| import android.annotation.CallbackExecutor; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.SystemApi; |
| import android.annotation.UserIdInt; |
| import android.os.Binder; |
| import android.os.Bundle; |
| import android.os.RemoteException; |
| import android.util.Log; |
| |
| import com.android.internal.util.SyncResultReceiver; |
| |
| import java.util.List; |
| import java.util.concurrent.Executor; |
| |
| /** |
| * When provided with content from an app, can suggest selections and classifications of that |
| * content. |
| * |
| * <p>The content is mainly a snapshot of a running task, the selections will be text and image |
| * selections with that image content. These mSelections can then be classified to find actions and |
| * entities on those selections. |
| * |
| * <p>Only accessible to blessed components such as Overview. |
| * |
| * @hide |
| */ |
| @SystemApi |
| public final class ContentSuggestionsManager { |
| /** |
| * Key into the extras Bundle passed to {@link #provideContextImage(int, Bundle)}. |
| * This can be used to provide the bitmap to |
| * {@link android.service.contentsuggestions.ContentSuggestionsService}. |
| * The value must be a {@link android.graphics.Bitmap} with the |
| * config {@link android.graphics.Bitmap.Config.HARDWARE}. |
| * |
| * @hide |
| */ |
| public static final String EXTRA_BITMAP = "android.contentsuggestions.extra.BITMAP"; |
| |
| private static final String TAG = ContentSuggestionsManager.class.getSimpleName(); |
| |
| /** |
| * Timeout for calls to system_server. |
| */ |
| private static final int SYNC_CALLS_TIMEOUT_MS = 5000; |
| |
| @Nullable |
| private final IContentSuggestionsManager mService; |
| |
| @NonNull |
| private final int mUser; |
| |
| /** @hide */ |
| public ContentSuggestionsManager( |
| @UserIdInt int userId, @Nullable IContentSuggestionsManager service) { |
| mService = service; |
| mUser = userId; |
| } |
| |
| /** |
| * Hints to the system that a new context image for the provided task should be sent to the |
| * system content suggestions service. |
| * |
| * @param taskId of the task to snapshot. |
| * @param imageContextRequestExtras sent with request to provide implementation specific |
| * extra information. |
| */ |
| public void provideContextImage( |
| int taskId, @NonNull Bundle imageContextRequestExtras) { |
| if (mService == null) { |
| Log.e(TAG, "provideContextImage called, but no ContentSuggestionsManager configured"); |
| return; |
| } |
| |
| try { |
| mService.provideContextImage(mUser, taskId, imageContextRequestExtras); |
| } catch (RemoteException e) { |
| e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Suggest content selections, based on the provided task id and optional |
| * location on screen provided in the request. Called after provideContextImage(). |
| * The result can be passed to |
| * {@link #classifyContentSelections(ClassificationsRequest, Executor, ClassificationsCallback)} |
| * to classify actions and entities on these selections. |
| * |
| * @param request containing the task and point location. |
| * @param callbackExecutor to execute the provided callback on. |
| * @param callback to receive the selections. |
| */ |
| public void suggestContentSelections( |
| @NonNull SelectionsRequest request, |
| @NonNull @CallbackExecutor Executor callbackExecutor, |
| @NonNull SelectionsCallback callback) { |
| if (mService == null) { |
| Log.e(TAG, |
| "suggestContentSelections called, but no ContentSuggestionsManager configured"); |
| return; |
| } |
| |
| try { |
| mService.suggestContentSelections( |
| mUser, request, new SelectionsCallbackWrapper(callback, callbackExecutor)); |
| } catch (RemoteException e) { |
| e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Classify actions and entities in content selections, as returned from |
| * suggestContentSelections. Note these selections may be modified by the |
| * caller before being passed here. |
| * |
| * @param request containing the selections to classify. |
| * @param callbackExecutor to execute the provided callback on. |
| * @param callback to receive the classifications. |
| */ |
| public void classifyContentSelections( |
| @NonNull ClassificationsRequest request, |
| @NonNull @CallbackExecutor Executor callbackExecutor, |
| @NonNull ClassificationsCallback callback) { |
| if (mService == null) { |
| Log.e(TAG, "classifyContentSelections called, " |
| + "but no ContentSuggestionsManager configured"); |
| return; |
| } |
| |
| try { |
| mService.classifyContentSelections( |
| mUser, request, new ClassificationsCallbackWrapper(callback, callbackExecutor)); |
| } catch (RemoteException e) { |
| e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Report telemetry for interaction with suggestions / classifications. |
| * |
| * @param requestId the id for the associated interaction |
| * @param interaction to report back to the system content suggestions service. |
| */ |
| public void notifyInteraction( |
| @NonNull String requestId, @NonNull Bundle interaction) { |
| if (mService == null) { |
| Log.e(TAG, "notifyInteraction called, but no ContentSuggestionsManager configured"); |
| return; |
| } |
| |
| try { |
| mService.notifyInteraction(mUser, requestId, interaction); |
| } catch (RemoteException e) { |
| e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Indicates that Content Suggestions is available and enabled for the provided user. That is, |
| * has an implementation and not disabled through device management. |
| * |
| * @return {@code true} if Content Suggestions is enabled and available for the provided user. |
| */ |
| public boolean isEnabled() { |
| if (mService == null) { |
| return false; |
| } |
| |
| SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); |
| try { |
| mService.isEnabled(mUser, receiver); |
| return receiver.getIntResult() != 0; |
| } catch (RemoteException e) { |
| e.rethrowFromSystemServer(); |
| } |
| return false; |
| } |
| |
| /** |
| * Callback to receive content selections from |
| * {@link #suggestContentSelections(SelectionsRequest, Executor, SelectionsCallback)}. |
| */ |
| public interface SelectionsCallback { |
| /** |
| * Async callback called when the content suggestions service has selections available. |
| * These can be modified and sent back to the manager for classification. The contents of |
| * the selection is implementation dependent. |
| * |
| * @param statusCode as defined by the implementation of content suggestions service. |
| * @param selections not {@code null}, but can be size {@code 0}. |
| */ |
| void onContentSelectionsAvailable( |
| int statusCode, @NonNull List<ContentSelection> selections); |
| } |
| |
| /** |
| * Callback to receive classifications from |
| * {@link #classifyContentSelections(ClassificationsRequest, Executor, ClassificationsCallback)} |
| */ |
| public interface ClassificationsCallback { |
| /** |
| * Async callback called when the content suggestions service has classified selections. The |
| * contents of the classification is implementation dependent. |
| * |
| * @param statusCode as defined by the implementation of content suggestions service. |
| * @param classifications not {@code null}, but can be size {@code 0}. |
| */ |
| void onContentClassificationsAvailable(int statusCode, |
| @NonNull List<ContentClassification> classifications); |
| } |
| |
| private static class SelectionsCallbackWrapper extends ISelectionsCallback.Stub { |
| private final SelectionsCallback mCallback; |
| private final Executor mExecutor; |
| |
| SelectionsCallbackWrapper( |
| @NonNull SelectionsCallback callback, @NonNull Executor executor) { |
| mCallback = callback; |
| mExecutor = executor; |
| } |
| |
| @Override |
| public void onContentSelectionsAvailable( |
| int statusCode, List<ContentSelection> selections) { |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| mExecutor.execute(() -> |
| mCallback.onContentSelectionsAvailable(statusCode, selections)); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| } |
| |
| private static final class ClassificationsCallbackWrapper extends |
| IClassificationsCallback.Stub { |
| private final ClassificationsCallback mCallback; |
| private final Executor mExecutor; |
| |
| ClassificationsCallbackWrapper(@NonNull ClassificationsCallback callback, |
| @NonNull Executor executor) { |
| mCallback = callback; |
| mExecutor = executor; |
| } |
| |
| @Override |
| public void onContentClassificationsAvailable( |
| int statusCode, List<ContentClassification> classifications) { |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| mExecutor.execute(() -> |
| mCallback.onContentClassificationsAvailable(statusCode, classifications)); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| } |
| } |