• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.RequiresPermission;
23 import android.annotation.SystemApi;
24 import android.annotation.TestApi;
25 import android.annotation.UserIdInt;
26 import android.graphics.Bitmap;
27 import android.os.Binder;
28 import android.os.Bundle;
29 import android.os.RemoteException;
30 import android.util.Log;
31 
32 import com.android.internal.util.SyncResultReceiver;
33 
34 import java.util.List;
35 import java.util.concurrent.Executor;
36 
37 /**
38  * When provided with content from an app, can suggest selections and classifications of that
39  * content.
40  *
41  * <p>The content is mainly a snapshot of a running task, the selections will be text and image
42  * selections with that image content. These mSelections can then be classified to find actions and
43  * entities on those selections.
44  *
45  * <p>Only accessible to blessed components such as Overview.
46  *
47  * @hide
48  */
49 @SystemApi
50 public final class ContentSuggestionsManager {
51     /**
52      * Key into the extras Bundle passed to {@link #provideContextImage(int, Bundle)}.
53      * This can be used to provide the bitmap to
54      * {@link android.service.contentsuggestions.ContentSuggestionsService}.
55      * The value must be a {@link android.graphics.Bitmap} with the
56      * config {@link android.graphics.Bitmap.Config.HARDWARE}.
57      *
58      * @hide
59      */
60     public static final String EXTRA_BITMAP = "android.contentsuggestions.extra.BITMAP";
61 
62     private static final String TAG = ContentSuggestionsManager.class.getSimpleName();
63 
64     /**
65      * Timeout for calls to system_server.
66      */
67     private static final int SYNC_CALLS_TIMEOUT_MS = 5000;
68 
69     @Nullable
70     private final IContentSuggestionsManager mService;
71 
72     @NonNull
73     private final int mUser;
74 
75     /** @hide */
ContentSuggestionsManager( @serIdInt int userId, @Nullable IContentSuggestionsManager service)76     public ContentSuggestionsManager(
77             @UserIdInt int userId, @Nullable IContentSuggestionsManager service) {
78         mService = service;
79         mUser = userId;
80     }
81 
82     /**
83      * Hints to the system that a new context image using the provided bitmap should be sent to
84      * the system content suggestions service.
85      *
86      * @param bitmap the new context image
87      * @param imageContextRequestExtras sent with request to provide implementation specific
88      *                                  extra information.
89      */
provideContextImage( @onNull Bitmap bitmap, @NonNull Bundle imageContextRequestExtras)90     public void provideContextImage(
91             @NonNull Bitmap bitmap, @NonNull Bundle imageContextRequestExtras) {
92         if (mService == null) {
93             Log.e(TAG, "provideContextImage called, but no ContentSuggestionsManager configured");
94             return;
95         }
96 
97         try {
98             mService.provideContextBitmap(mUser, bitmap, imageContextRequestExtras);
99         } catch (RemoteException e) {
100             throw e.rethrowFromSystemServer();
101         }
102     }
103 
104     /**
105      * Hints to the system that a new context image for the provided task should be sent to the
106      * system content suggestions service.
107      *
108      * @param taskId of the task to snapshot.
109      * @param imageContextRequestExtras sent with request to provide implementation specific
110      *                                  extra information.
111      */
provideContextImage( int taskId, @NonNull Bundle imageContextRequestExtras)112     public void provideContextImage(
113             int taskId, @NonNull Bundle imageContextRequestExtras) {
114         if (mService == null) {
115             Log.e(TAG, "provideContextImage called, but no ContentSuggestionsManager configured");
116             return;
117         }
118 
119         try {
120             mService.provideContextImage(mUser, taskId, imageContextRequestExtras);
121         } catch (RemoteException e) {
122             throw e.rethrowFromSystemServer();
123         }
124     }
125 
126     /**
127      * Suggest content selections, based on the provided task id and optional
128      * location on screen provided in the request. Called after provideContextImage().
129      * The result can be passed to
130      * {@link #classifyContentSelections(ClassificationsRequest, Executor, ClassificationsCallback)}
131      *  to classify actions and entities on these selections.
132      *
133      * @param request containing the task and point location.
134      * @param callbackExecutor to execute the provided callback on.
135      * @param callback to receive the selections.
136      */
suggestContentSelections( @onNull SelectionsRequest request, @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull SelectionsCallback callback)137     public void suggestContentSelections(
138             @NonNull SelectionsRequest request,
139             @NonNull @CallbackExecutor Executor callbackExecutor,
140             @NonNull SelectionsCallback callback) {
141         if (mService == null) {
142             Log.e(TAG,
143                     "suggestContentSelections called, but no ContentSuggestionsManager configured");
144             return;
145         }
146 
147         try {
148             mService.suggestContentSelections(
149                     mUser, request, new SelectionsCallbackWrapper(callback, callbackExecutor));
150         } catch (RemoteException e) {
151             throw e.rethrowFromSystemServer();
152         }
153     }
154 
155     /**
156      * Classify actions and entities in content selections, as returned from
157      * suggestContentSelections. Note these selections may be modified by the
158      * caller before being passed here.
159      *
160      * @param request containing the selections to classify.
161      * @param callbackExecutor to execute the provided callback on.
162      * @param callback to receive the classifications.
163      */
classifyContentSelections( @onNull ClassificationsRequest request, @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull ClassificationsCallback callback)164     public void classifyContentSelections(
165             @NonNull ClassificationsRequest request,
166             @NonNull @CallbackExecutor Executor callbackExecutor,
167             @NonNull ClassificationsCallback callback) {
168         if (mService == null) {
169             Log.e(TAG, "classifyContentSelections called, "
170                     + "but no ContentSuggestionsManager configured");
171             return;
172         }
173 
174         try {
175             mService.classifyContentSelections(
176                     mUser, request, new ClassificationsCallbackWrapper(callback, callbackExecutor));
177         } catch (RemoteException e) {
178             throw e.rethrowFromSystemServer();
179         }
180     }
181 
182     /**
183      * Report telemetry for interaction with suggestions / classifications.
184      *
185      * @param requestId the id for the associated interaction
186      * @param interaction to report back to the system content suggestions service.
187      */
notifyInteraction( @onNull String requestId, @NonNull Bundle interaction)188     public void notifyInteraction(
189             @NonNull String requestId, @NonNull Bundle interaction) {
190         if (mService == null) {
191             Log.e(TAG, "notifyInteraction called, but no ContentSuggestionsManager configured");
192             return;
193         }
194 
195         try {
196             mService.notifyInteraction(mUser, requestId, interaction);
197         } catch (RemoteException e) {
198             throw e.rethrowFromSystemServer();
199         }
200     }
201 
202     /**
203      * Indicates that Content Suggestions is available and enabled for the provided user. That is,
204      * has an implementation and not disabled through device management.
205      *
206      * @return {@code true} if Content Suggestions is enabled and available for the provided user.
207      */
isEnabled()208     public boolean isEnabled() {
209         if (mService == null) {
210             return false;
211         }
212 
213         SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
214         try {
215             mService.isEnabled(mUser, receiver);
216             return receiver.getIntResult() != 0;
217         } catch (RemoteException e) {
218             throw e.rethrowFromSystemServer();
219         } catch (SyncResultReceiver.TimeoutException e) {
220             throw new RuntimeException("Fail to get the enable status.");
221         }
222     }
223 
224     /**
225      * Resets the temporary service implementation to the default component.
226      *
227      * @hide
228      */
229     @TestApi
230     @RequiresPermission(android.Manifest.permission.MANAGE_CONTENT_SUGGESTIONS)
resetTemporaryService(@serIdInt int userId)231     public void resetTemporaryService(@UserIdInt int userId) {
232         if (mService == null) {
233             Log.e(TAG, "resetTemporaryService called, but no ContentSuggestionsManager "
234                     + "configured");
235             return;
236         }
237         try {
238             mService.resetTemporaryService(userId);
239         } catch (RemoteException e) {
240             throw e.rethrowFromSystemServer();
241         }
242     }
243 
244     /**
245      * Temporarily sets the service implementation.
246      *
247      * @param userId user Id to set the temporary service on.
248      * @param serviceName name of the new component
249      * @param duration how long the change will be valid (the service will be automatically reset
250      *            to the default component after this timeout expires).
251      *
252      * @hide
253      */
254     @TestApi
255     @RequiresPermission(android.Manifest.permission.MANAGE_CONTENT_SUGGESTIONS)
setTemporaryService( @serIdInt int userId, @NonNull String serviceName, int duration)256     public void setTemporaryService(
257             @UserIdInt int userId, @NonNull String serviceName, int duration) {
258         if (mService == null) {
259             Log.e(TAG, "setTemporaryService called, but no ContentSuggestionsManager "
260                     + "configured");
261             return;
262         }
263         try {
264             mService.setTemporaryService(userId, serviceName, duration);
265         } catch (RemoteException e) {
266             throw e.rethrowFromSystemServer();
267         }
268     }
269 
270     /**
271      * Sets whether the default service should be used.
272      *
273      * @hide
274      */
275     @TestApi
276     @RequiresPermission(android.Manifest.permission.MANAGE_CONTENT_SUGGESTIONS)
setDefaultServiceEnabled(@serIdInt int userId, boolean enabled)277     public void setDefaultServiceEnabled(@UserIdInt int userId, boolean enabled) {
278         if (mService == null) {
279             Log.e(TAG, "setDefaultServiceEnabled called, but no ContentSuggestionsManager "
280                     + "configured");
281             return;
282         }
283         try {
284             mService.setDefaultServiceEnabled(userId, enabled);
285         } catch (RemoteException e) {
286             throw e.rethrowFromSystemServer();
287         }
288     }
289 
290     /**
291      * Callback to receive content selections from
292      *  {@link #suggestContentSelections(SelectionsRequest, Executor, SelectionsCallback)}.
293      */
294     public interface SelectionsCallback {
295         /**
296          * Async callback called when the content suggestions service has selections available.
297          * These can be modified and sent back to the manager for classification. The contents of
298          * the selection is implementation dependent.
299          *
300          * @param statusCode as defined by the implementation of content suggestions service.
301          * @param selections not {@code null}, but can be size {@code 0}.
302          */
onContentSelectionsAvailable( int statusCode, @NonNull List<ContentSelection> selections)303         void onContentSelectionsAvailable(
304                 int statusCode, @NonNull List<ContentSelection> selections);
305     }
306 
307     /**
308      * Callback to receive classifications from
309      * {@link #classifyContentSelections(ClassificationsRequest, Executor, ClassificationsCallback)}
310      */
311     public interface ClassificationsCallback {
312         /**
313          * Async callback called when the content suggestions service has classified selections. The
314          * contents of the classification is implementation dependent.
315          *
316          * @param statusCode as defined by the implementation of content suggestions service.
317          * @param classifications not {@code null}, but can be size {@code 0}.
318          */
onContentClassificationsAvailable(int statusCode, @NonNull List<ContentClassification> classifications)319         void onContentClassificationsAvailable(int statusCode,
320                 @NonNull List<ContentClassification> classifications);
321     }
322 
323     private static class SelectionsCallbackWrapper extends ISelectionsCallback.Stub {
324         private final SelectionsCallback mCallback;
325         private final Executor mExecutor;
326 
SelectionsCallbackWrapper( @onNull SelectionsCallback callback, @NonNull Executor executor)327         SelectionsCallbackWrapper(
328                 @NonNull SelectionsCallback callback, @NonNull Executor executor) {
329             mCallback = callback;
330             mExecutor = executor;
331         }
332 
333         @Override
onContentSelectionsAvailable( int statusCode, List<ContentSelection> selections)334         public void onContentSelectionsAvailable(
335                 int statusCode, List<ContentSelection> selections) {
336             final long identity = Binder.clearCallingIdentity();
337             try {
338                 mExecutor.execute(() ->
339                         mCallback.onContentSelectionsAvailable(statusCode, selections));
340             } finally {
341                 Binder.restoreCallingIdentity(identity);
342             }
343         }
344     }
345 
346     private static final class ClassificationsCallbackWrapper extends
347             IClassificationsCallback.Stub {
348         private final ClassificationsCallback mCallback;
349         private final Executor mExecutor;
350 
ClassificationsCallbackWrapper(@onNull ClassificationsCallback callback, @NonNull Executor executor)351         ClassificationsCallbackWrapper(@NonNull ClassificationsCallback callback,
352                 @NonNull Executor executor) {
353             mCallback = callback;
354             mExecutor = executor;
355         }
356 
357         @Override
onContentClassificationsAvailable( int statusCode, List<ContentClassification> classifications)358         public void onContentClassificationsAvailable(
359                 int statusCode, List<ContentClassification> classifications) {
360             final long identity = Binder.clearCallingIdentity();
361             try {
362                 mExecutor.execute(() ->
363                         mCallback.onContentClassificationsAvailable(statusCode, classifications));
364             } finally {
365                 Binder.restoreCallingIdentity(identity);
366             }
367         }
368     }
369 }
370