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.service.contentsuggestions; 18 19 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; 20 21 import android.annotation.CallSuper; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.SystemApi; 25 import android.app.Service; 26 import android.app.contentsuggestions.ClassificationsRequest; 27 import android.app.contentsuggestions.ContentSuggestionsManager; 28 import android.app.contentsuggestions.IClassificationsCallback; 29 import android.app.contentsuggestions.ISelectionsCallback; 30 import android.app.contentsuggestions.SelectionsRequest; 31 import android.content.Intent; 32 import android.graphics.Bitmap; 33 import android.graphics.ColorSpace; 34 import android.hardware.HardwareBuffer; 35 import android.os.Bundle; 36 import android.os.Handler; 37 import android.os.IBinder; 38 import android.os.Looper; 39 import android.os.RemoteException; 40 import android.util.Log; 41 import android.util.Slog; 42 import android.window.TaskSnapshot; 43 44 /** 45 * @hide 46 */ 47 @SystemApi 48 public abstract class ContentSuggestionsService extends Service { 49 50 private static final String TAG = ContentSuggestionsService.class.getSimpleName(); 51 52 private Handler mHandler; 53 54 /** 55 * The action for the intent used to define the content suggestions service. 56 * 57 * <p>To be supported, the service must also require the 58 * * {@link android.Manifest.permission#BIND_CONTENT_SUGGESTIONS_SERVICE} permission so 59 * * that other applications can not abuse it. 60 */ 61 public static final String SERVICE_INTERFACE = 62 "android.service.contentsuggestions.ContentSuggestionsService"; 63 64 private final IContentSuggestionsService mInterface = new IContentSuggestionsService.Stub() { 65 @Override 66 public void provideContextImage(int taskId, TaskSnapshot snapshot, 67 Bundle imageContextRequestExtras) { 68 if (imageContextRequestExtras.containsKey(ContentSuggestionsManager.EXTRA_BITMAP) 69 && snapshot != null) { 70 throw new IllegalArgumentException("Two bitmaps provided; expected one."); 71 } 72 73 Bitmap wrappedBuffer = null; 74 if (imageContextRequestExtras.containsKey(ContentSuggestionsManager.EXTRA_BITMAP)) { 75 wrappedBuffer = imageContextRequestExtras.getParcelable( 76 ContentSuggestionsManager.EXTRA_BITMAP, android.graphics.Bitmap.class); 77 } else { 78 if (snapshot != null) { 79 final HardwareBuffer snapshotBuffer = snapshot.getHardwareBuffer(); 80 ColorSpace colorSpace = snapshot.getColorSpace(); 81 int colorSpaceId = 0; 82 if (colorSpace != null) { 83 colorSpaceId = colorSpace.getId(); 84 } 85 if (colorSpaceId >= 0 && colorSpaceId < ColorSpace.Named.values().length) { 86 colorSpace = ColorSpace.get(ColorSpace.Named.values()[colorSpaceId]); 87 } 88 wrappedBuffer = Bitmap.wrapHardwareBuffer(snapshotBuffer, colorSpace); 89 snapshotBuffer.close(); 90 } 91 } 92 93 mHandler.sendMessage( 94 obtainMessage(ContentSuggestionsService::onProcessContextImage, 95 ContentSuggestionsService.this, taskId, 96 wrappedBuffer, 97 imageContextRequestExtras)); 98 } 99 100 @Override 101 public void suggestContentSelections(SelectionsRequest request, 102 ISelectionsCallback callback) { 103 mHandler.sendMessage(obtainMessage( 104 ContentSuggestionsService::onSuggestContentSelections, 105 ContentSuggestionsService.this, request, wrapSelectionsCallback(callback))); 106 107 } 108 109 @Override 110 public void classifyContentSelections(ClassificationsRequest request, 111 IClassificationsCallback callback) { 112 mHandler.sendMessage(obtainMessage( 113 ContentSuggestionsService::onClassifyContentSelections, 114 ContentSuggestionsService.this, request, wrapClassificationCallback(callback))); 115 } 116 117 @Override 118 public void notifyInteraction(String requestId, Bundle interaction) { 119 mHandler.sendMessage( 120 obtainMessage(ContentSuggestionsService::onNotifyInteraction, 121 ContentSuggestionsService.this, requestId, interaction)); 122 } 123 }; 124 125 @CallSuper 126 @Override onCreate()127 public void onCreate() { 128 super.onCreate(); 129 mHandler = new Handler(Looper.getMainLooper(), null, true); 130 } 131 132 /** @hide */ 133 @Override onBind(Intent intent)134 public final IBinder onBind(Intent intent) { 135 if (SERVICE_INTERFACE.equals(intent.getAction())) { 136 return mInterface.asBinder(); 137 } 138 Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent); 139 return null; 140 } 141 142 /** 143 * Called by the system to provide the snapshot for the task associated with the given 144 * {@param taskId}. 145 */ onProcessContextImage( int taskId, @Nullable Bitmap contextImage, @NonNull Bundle extras)146 public abstract void onProcessContextImage( 147 int taskId, @Nullable Bitmap contextImage, @NonNull Bundle extras); 148 149 /** 150 * Content selections have been request through {@link ContentSuggestionsManager}, implementer 151 * should reply on the callback with selections. 152 */ onSuggestContentSelections(@onNull SelectionsRequest request, @NonNull ContentSuggestionsManager.SelectionsCallback callback)153 public abstract void onSuggestContentSelections(@NonNull SelectionsRequest request, 154 @NonNull ContentSuggestionsManager.SelectionsCallback callback); 155 156 /** 157 * Content classifications have been request through {@link ContentSuggestionsManager}, 158 * implementer should reply on the callback with classifications. 159 */ onClassifyContentSelections(@onNull ClassificationsRequest request, @NonNull ContentSuggestionsManager.ClassificationsCallback callback)160 public abstract void onClassifyContentSelections(@NonNull ClassificationsRequest request, 161 @NonNull ContentSuggestionsManager.ClassificationsCallback callback); 162 163 /** 164 * User interactions have been reported through {@link ContentSuggestionsManager}, implementer 165 * should handle those interactions. 166 */ onNotifyInteraction( @onNull String requestId, @NonNull Bundle interaction)167 public abstract void onNotifyInteraction( 168 @NonNull String requestId, @NonNull Bundle interaction); 169 wrapSelectionsCallback( ISelectionsCallback callback)170 private ContentSuggestionsManager.SelectionsCallback wrapSelectionsCallback( 171 ISelectionsCallback callback) { 172 return (statusCode, selections) -> { 173 try { 174 callback.onContentSelectionsAvailable(statusCode, selections); 175 } catch (RemoteException e) { 176 Slog.e(TAG, "Error sending result: " + e); 177 } 178 }; 179 } 180 181 private ContentSuggestionsManager.ClassificationsCallback wrapClassificationCallback( 182 IClassificationsCallback callback) { 183 return ((statusCode, classifications) -> { 184 try { 185 callback.onContentClassificationsAvailable(statusCode, classifications); 186 } catch (RemoteException e) { 187 Slog.e(TAG, "Error sending result: " + e); 188 } 189 }); 190 } 191 } 192