1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.app.contentsuggestions; 18 19 import android.annotation.CallbackExecutor; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.SystemApi; 23 import android.annotation.UserIdInt; 24 import android.os.Binder; 25 import android.os.Bundle; 26 import android.os.RemoteException; 27 import android.util.Log; 28 29 import com.android.internal.util.SyncResultReceiver; 30 31 import java.util.List; 32 import java.util.concurrent.Executor; 33 34 /** 35 * When provided with content from an app, can suggest selections and classifications of that 36 * content. 37 * 38 * <p>The content is mainly a snapshot of a running task, the selections will be text and image 39 * selections with that image content. These mSelections can then be classified to find actions and 40 * entities on those selections. 41 * 42 * <p>Only accessible to blessed components such as Overview. 43 * 44 * @hide 45 */ 46 @SystemApi 47 public final class ContentSuggestionsManager { 48 /** 49 * Key into the extras Bundle passed to {@link #provideContextImage(int, Bundle)}. 50 * This can be used to provide the bitmap to 51 * {@link android.service.contentsuggestions.ContentSuggestionsService}. 52 * The value must be a {@link android.graphics.Bitmap} with the 53 * config {@link android.graphics.Bitmap.Config.HARDWARE}. 54 * 55 * @hide 56 */ 57 public static final String EXTRA_BITMAP = "android.contentsuggestions.extra.BITMAP"; 58 59 private static final String TAG = ContentSuggestionsManager.class.getSimpleName(); 60 61 /** 62 * Timeout for calls to system_server. 63 */ 64 private static final int SYNC_CALLS_TIMEOUT_MS = 5000; 65 66 @Nullable 67 private final IContentSuggestionsManager mService; 68 69 @NonNull 70 private final int mUser; 71 72 /** @hide */ ContentSuggestionsManager( @serIdInt int userId, @Nullable IContentSuggestionsManager service)73 public ContentSuggestionsManager( 74 @UserIdInt int userId, @Nullable IContentSuggestionsManager service) { 75 mService = service; 76 mUser = userId; 77 } 78 79 /** 80 * Hints to the system that a new context image for the provided task should be sent to the 81 * system content suggestions service. 82 * 83 * @param taskId of the task to snapshot. 84 * @param imageContextRequestExtras sent with request to provide implementation specific 85 * extra information. 86 */ provideContextImage( int taskId, @NonNull Bundle imageContextRequestExtras)87 public void provideContextImage( 88 int taskId, @NonNull Bundle imageContextRequestExtras) { 89 if (mService == null) { 90 Log.e(TAG, "provideContextImage called, but no ContentSuggestionsManager configured"); 91 return; 92 } 93 94 try { 95 mService.provideContextImage(mUser, taskId, imageContextRequestExtras); 96 } catch (RemoteException e) { 97 e.rethrowFromSystemServer(); 98 } 99 } 100 101 /** 102 * Suggest content selections, based on the provided task id and optional 103 * location on screen provided in the request. Called after provideContextImage(). 104 * The result can be passed to 105 * {@link #classifyContentSelections(ClassificationsRequest, Executor, ClassificationsCallback)} 106 * to classify actions and entities on these selections. 107 * 108 * @param request containing the task and point location. 109 * @param callbackExecutor to execute the provided callback on. 110 * @param callback to receive the selections. 111 */ suggestContentSelections( @onNull SelectionsRequest request, @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull SelectionsCallback callback)112 public void suggestContentSelections( 113 @NonNull SelectionsRequest request, 114 @NonNull @CallbackExecutor Executor callbackExecutor, 115 @NonNull SelectionsCallback callback) { 116 if (mService == null) { 117 Log.e(TAG, 118 "suggestContentSelections called, but no ContentSuggestionsManager configured"); 119 return; 120 } 121 122 try { 123 mService.suggestContentSelections( 124 mUser, request, new SelectionsCallbackWrapper(callback, callbackExecutor)); 125 } catch (RemoteException e) { 126 e.rethrowFromSystemServer(); 127 } 128 } 129 130 /** 131 * Classify actions and entities in content selections, as returned from 132 * suggestContentSelections. Note these selections may be modified by the 133 * caller before being passed here. 134 * 135 * @param request containing the selections to classify. 136 * @param callbackExecutor to execute the provided callback on. 137 * @param callback to receive the classifications. 138 */ classifyContentSelections( @onNull ClassificationsRequest request, @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull ClassificationsCallback callback)139 public void classifyContentSelections( 140 @NonNull ClassificationsRequest request, 141 @NonNull @CallbackExecutor Executor callbackExecutor, 142 @NonNull ClassificationsCallback callback) { 143 if (mService == null) { 144 Log.e(TAG, "classifyContentSelections called, " 145 + "but no ContentSuggestionsManager configured"); 146 return; 147 } 148 149 try { 150 mService.classifyContentSelections( 151 mUser, request, new ClassificationsCallbackWrapper(callback, callbackExecutor)); 152 } catch (RemoteException e) { 153 e.rethrowFromSystemServer(); 154 } 155 } 156 157 /** 158 * Report telemetry for interaction with suggestions / classifications. 159 * 160 * @param requestId the id for the associated interaction 161 * @param interaction to report back to the system content suggestions service. 162 */ notifyInteraction( @onNull String requestId, @NonNull Bundle interaction)163 public void notifyInteraction( 164 @NonNull String requestId, @NonNull Bundle interaction) { 165 if (mService == null) { 166 Log.e(TAG, "notifyInteraction called, but no ContentSuggestionsManager configured"); 167 return; 168 } 169 170 try { 171 mService.notifyInteraction(mUser, requestId, interaction); 172 } catch (RemoteException e) { 173 e.rethrowFromSystemServer(); 174 } 175 } 176 177 /** 178 * Indicates that Content Suggestions is available and enabled for the provided user. That is, 179 * has an implementation and not disabled through device management. 180 * 181 * @return {@code true} if Content Suggestions is enabled and available for the provided user. 182 */ isEnabled()183 public boolean isEnabled() { 184 if (mService == null) { 185 return false; 186 } 187 188 SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); 189 try { 190 mService.isEnabled(mUser, receiver); 191 return receiver.getIntResult() != 0; 192 } catch (RemoteException e) { 193 e.rethrowFromSystemServer(); 194 } 195 return false; 196 } 197 198 /** 199 * Callback to receive content selections from 200 * {@link #suggestContentSelections(SelectionsRequest, Executor, SelectionsCallback)}. 201 */ 202 public interface SelectionsCallback { 203 /** 204 * Async callback called when the content suggestions service has selections available. 205 * These can be modified and sent back to the manager for classification. The contents of 206 * the selection is implementation dependent. 207 * 208 * @param statusCode as defined by the implementation of content suggestions service. 209 * @param selections not {@code null}, but can be size {@code 0}. 210 */ onContentSelectionsAvailable( int statusCode, @NonNull List<ContentSelection> selections)211 void onContentSelectionsAvailable( 212 int statusCode, @NonNull List<ContentSelection> selections); 213 } 214 215 /** 216 * Callback to receive classifications from 217 * {@link #classifyContentSelections(ClassificationsRequest, Executor, ClassificationsCallback)} 218 */ 219 public interface ClassificationsCallback { 220 /** 221 * Async callback called when the content suggestions service has classified selections. The 222 * contents of the classification is implementation dependent. 223 * 224 * @param statusCode as defined by the implementation of content suggestions service. 225 * @param classifications not {@code null}, but can be size {@code 0}. 226 */ onContentClassificationsAvailable(int statusCode, @NonNull List<ContentClassification> classifications)227 void onContentClassificationsAvailable(int statusCode, 228 @NonNull List<ContentClassification> classifications); 229 } 230 231 private static class SelectionsCallbackWrapper extends ISelectionsCallback.Stub { 232 private final SelectionsCallback mCallback; 233 private final Executor mExecutor; 234 SelectionsCallbackWrapper( @onNull SelectionsCallback callback, @NonNull Executor executor)235 SelectionsCallbackWrapper( 236 @NonNull SelectionsCallback callback, @NonNull Executor executor) { 237 mCallback = callback; 238 mExecutor = executor; 239 } 240 241 @Override onContentSelectionsAvailable( int statusCode, List<ContentSelection> selections)242 public void onContentSelectionsAvailable( 243 int statusCode, List<ContentSelection> selections) { 244 final long identity = Binder.clearCallingIdentity(); 245 try { 246 mExecutor.execute(() -> 247 mCallback.onContentSelectionsAvailable(statusCode, selections)); 248 } finally { 249 Binder.restoreCallingIdentity(identity); 250 } 251 } 252 } 253 254 private static final class ClassificationsCallbackWrapper extends 255 IClassificationsCallback.Stub { 256 private final ClassificationsCallback mCallback; 257 private final Executor mExecutor; 258 ClassificationsCallbackWrapper(@onNull ClassificationsCallback callback, @NonNull Executor executor)259 ClassificationsCallbackWrapper(@NonNull ClassificationsCallback callback, 260 @NonNull Executor executor) { 261 mCallback = callback; 262 mExecutor = executor; 263 } 264 265 @Override onContentClassificationsAvailable( int statusCode, List<ContentClassification> classifications)266 public void onContentClassificationsAvailable( 267 int statusCode, List<ContentClassification> classifications) { 268 final long identity = Binder.clearCallingIdentity(); 269 try { 270 mExecutor.execute(() -> 271 mCallback.onContentClassificationsAvailable(statusCode, classifications)); 272 } finally { 273 Binder.restoreCallingIdentity(identity); 274 } 275 } 276 } 277 } 278