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