Adam He | 0430237 | 2020-01-23 13:40:57 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2020 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 | package android.service.autofill; |
| 17 | |
| 18 | import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; |
| 19 | |
| 20 | import android.annotation.NonNull; |
| 21 | import android.annotation.Nullable; |
| 22 | import android.annotation.SystemApi; |
| 23 | import android.annotation.TestApi; |
| 24 | import android.app.Service; |
Adam He | 0430237 | 2020-01-23 13:40:57 -0800 | [diff] [blame] | 25 | import android.content.Intent; |
Adam He | d41ce92 | 2020-03-19 14:30:32 -0700 | [diff] [blame] | 26 | import android.content.IntentSender; |
Adam He | 4011625 | 2020-02-04 14:55:39 -0800 | [diff] [blame] | 27 | import android.graphics.PixelFormat; |
TYM Tsai | 850c812 | 2020-04-01 02:26:58 +0800 | [diff] [blame] | 28 | import android.os.BaseBundle; |
Feng Cao | 36960ee | 2020-02-18 18:23:30 -0800 | [diff] [blame] | 29 | import android.os.Bundle; |
Adam He | 0430237 | 2020-01-23 13:40:57 -0800 | [diff] [blame] | 30 | import android.os.Handler; |
| 31 | import android.os.IBinder; |
| 32 | import android.os.Looper; |
Adam He | 89afcd6 | 2020-03-18 17:28:32 -0700 | [diff] [blame] | 33 | import android.os.RemoteCallback; |
Adam He | 4011625 | 2020-02-04 14:55:39 -0800 | [diff] [blame] | 34 | import android.os.RemoteException; |
Adam He | 0430237 | 2020-01-23 13:40:57 -0800 | [diff] [blame] | 35 | import android.util.Log; |
Feng Cao | b46851c | 2020-04-29 18:22:49 -0700 | [diff] [blame] | 36 | import android.util.LruCache; |
Feng Cao | bd26abb | 2020-04-24 11:35:58 -0700 | [diff] [blame] | 37 | import android.util.Size; |
Svet Ganov | 02fdd34 | 2020-02-20 20:48:34 -0800 | [diff] [blame] | 38 | import android.view.Display; |
Adam He | 4011625 | 2020-02-04 14:55:39 -0800 | [diff] [blame] | 39 | import android.view.SurfaceControlViewHost; |
Adam He | 0430237 | 2020-01-23 13:40:57 -0800 | [diff] [blame] | 40 | import android.view.View; |
Feng Cao | bd26abb | 2020-04-24 11:35:58 -0700 | [diff] [blame] | 41 | import android.view.ViewGroup; |
Adam He | 4011625 | 2020-02-04 14:55:39 -0800 | [diff] [blame] | 42 | import android.view.WindowManager; |
Adam He | 0430237 | 2020-01-23 13:40:57 -0800 | [diff] [blame] | 43 | |
Feng Cao | b46851c | 2020-04-29 18:22:49 -0700 | [diff] [blame] | 44 | import java.lang.ref.WeakReference; |
| 45 | |
Adam He | 0430237 | 2020-01-23 13:40:57 -0800 | [diff] [blame] | 46 | /** |
Feng Cao | bd26abb | 2020-04-24 11:35:58 -0700 | [diff] [blame] | 47 | * A service that renders an inline presentation view given the {@link InlinePresentation}. |
Adam He | 0430237 | 2020-01-23 13:40:57 -0800 | [diff] [blame] | 48 | * |
| 49 | * {@hide} |
| 50 | */ |
| 51 | @SystemApi |
| 52 | @TestApi |
| 53 | public abstract class InlineSuggestionRenderService extends Service { |
| 54 | |
| 55 | private static final String TAG = "InlineSuggestionRenderService"; |
| 56 | |
| 57 | /** |
| 58 | * The {@link Intent} that must be declared as handled by the service. |
| 59 | * |
| 60 | * <p>To be supported, the service must also require the |
Feng Cao | bd26abb | 2020-04-24 11:35:58 -0700 | [diff] [blame] | 61 | * {@link android.Manifest.permission#BIND_INLINE_SUGGESTION_RENDER_SERVICE} permission so that |
| 62 | * other applications can not abuse it. |
Adam He | 0430237 | 2020-01-23 13:40:57 -0800 | [diff] [blame] | 63 | */ |
| 64 | public static final String SERVICE_INTERFACE = |
| 65 | "android.service.autofill.InlineSuggestionRenderService"; |
| 66 | |
Feng Cao | ffd9aff | 2020-05-26 23:10:26 -0700 | [diff] [blame] | 67 | private final Handler mMainHandler = new Handler(Looper.getMainLooper(), null, true); |
Adam He | 0430237 | 2020-01-23 13:40:57 -0800 | [diff] [blame] | 68 | |
Adam He | d41ce92 | 2020-03-19 14:30:32 -0700 | [diff] [blame] | 69 | private IInlineSuggestionUiCallback mCallback; |
| 70 | |
Feng Cao | b46851c | 2020-04-29 18:22:49 -0700 | [diff] [blame] | 71 | |
| 72 | /** |
| 73 | * A local LRU cache keeping references to the inflated {@link SurfaceControlViewHost}s, so |
| 74 | * they can be released properly when no longer used. Each view needs to be tracked separately, |
| 75 | * therefore for simplicity we use the hash code of the value object as key in the cache. |
| 76 | */ |
| 77 | private final LruCache<InlineSuggestionUiImpl, Boolean> mActiveInlineSuggestions = |
| 78 | new LruCache<InlineSuggestionUiImpl, Boolean>(30) { |
| 79 | @Override |
| 80 | public void entryRemoved(boolean evicted, InlineSuggestionUiImpl key, |
| 81 | Boolean oldValue, |
| 82 | Boolean newValue) { |
| 83 | if (evicted) { |
| 84 | Log.w(TAG, |
| 85 | "Hit max=100 entries in the cache. Releasing oldest one to make " |
| 86 | + "space."); |
| 87 | key.releaseSurfaceControlViewHost(); |
| 88 | } |
| 89 | } |
| 90 | }; |
| 91 | |
Feng Cao | bd26abb | 2020-04-24 11:35:58 -0700 | [diff] [blame] | 92 | /** |
| 93 | * If the specified {@code width}/{@code height} is an exact value, then it will be returned as |
| 94 | * is, otherwise the method tries to measure a size that is just large enough to fit the view |
| 95 | * content, within constraints posed by {@code minSize} and {@code maxSize}. |
| 96 | * |
| 97 | * @param view the view for which we measure the size |
| 98 | * @param width the expected width of the view, either an exact value or {@link |
| 99 | * ViewGroup.LayoutParams#WRAP_CONTENT} |
| 100 | * @param height the expected width of the view, either an exact value or {@link |
| 101 | * ViewGroup.LayoutParams#WRAP_CONTENT} |
| 102 | * @param minSize the lower bound of the size to be returned |
| 103 | * @param maxSize the upper bound of the size to be returned |
| 104 | * @return the measured size of the view based on the given size constraints. |
| 105 | */ |
| 106 | private Size measuredSize(@NonNull View view, int width, int height, @NonNull Size minSize, |
| 107 | @NonNull Size maxSize) { |
| 108 | if (width != ViewGroup.LayoutParams.WRAP_CONTENT |
| 109 | && height != ViewGroup.LayoutParams.WRAP_CONTENT) { |
| 110 | return new Size(width, height); |
| 111 | } |
| 112 | int widthMeasureSpec; |
| 113 | if (width == ViewGroup.LayoutParams.WRAP_CONTENT) { |
| 114 | widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxSize.getWidth(), |
| 115 | View.MeasureSpec.AT_MOST); |
| 116 | } else { |
| 117 | widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); |
| 118 | } |
| 119 | int heightMeasureSpec; |
| 120 | if (height == ViewGroup.LayoutParams.WRAP_CONTENT) { |
| 121 | heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxSize.getHeight(), |
| 122 | View.MeasureSpec.AT_MOST); |
| 123 | } else { |
| 124 | heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); |
| 125 | } |
| 126 | view.measure(widthMeasureSpec, heightMeasureSpec); |
| 127 | return new Size(Math.max(view.getMeasuredWidth(), minSize.getWidth()), |
| 128 | Math.max(view.getMeasuredHeight(), minSize.getHeight())); |
| 129 | } |
| 130 | |
Adam He | 0430237 | 2020-01-23 13:40:57 -0800 | [diff] [blame] | 131 | private void handleRenderSuggestion(IInlineSuggestionUiCallback callback, |
Svet Ganov | 02fdd34 | 2020-02-20 20:48:34 -0800 | [diff] [blame] | 132 | InlinePresentation presentation, int width, int height, IBinder hostInputToken, |
| 133 | int displayId) { |
Adam He | 4011625 | 2020-02-04 14:55:39 -0800 | [diff] [blame] | 134 | if (hostInputToken == null) { |
| 135 | try { |
| 136 | callback.onError(); |
| 137 | } catch (RemoteException e) { |
| 138 | Log.w(TAG, "RemoteException calling onError()"); |
| 139 | } |
| 140 | return; |
| 141 | } |
Svet Ganov | 02fdd34 | 2020-02-20 20:48:34 -0800 | [diff] [blame] | 142 | |
Svet Ganov | 17a647e | 2020-02-24 17:31:15 -0800 | [diff] [blame] | 143 | // When we create the UI it should be for the IME display |
| 144 | updateDisplay(displayId); |
| 145 | try { |
| 146 | final View suggestionView = onRenderSuggestion(presentation, width, height); |
| 147 | if (suggestionView == null) { |
Feng Cao | bd26abb | 2020-04-24 11:35:58 -0700 | [diff] [blame] | 148 | Log.w(TAG, "ExtServices failed to render the inline suggestion view."); |
Svet Ganov | 17a647e | 2020-02-24 17:31:15 -0800 | [diff] [blame] | 149 | try { |
| 150 | callback.onError(); |
| 151 | } catch (RemoteException e) { |
| 152 | Log.w(TAG, "Null suggestion view returned by renderer"); |
| 153 | } |
| 154 | return; |
Adam He | 4011625 | 2020-02-04 14:55:39 -0800 | [diff] [blame] | 155 | } |
Adam He | d41ce92 | 2020-03-19 14:30:32 -0700 | [diff] [blame] | 156 | mCallback = callback; |
Feng Cao | bd26abb | 2020-04-24 11:35:58 -0700 | [diff] [blame] | 157 | final Size measuredSize = measuredSize(suggestionView, width, height, |
| 158 | presentation.getInlinePresentationSpec().getMinSize(), |
| 159 | presentation.getInlinePresentationSpec().getMaxSize()); |
| 160 | Log.v(TAG, "width=" + width + ", height=" + height + ", measuredSize=" + measuredSize); |
Adam He | 4011625 | 2020-02-04 14:55:39 -0800 | [diff] [blame] | 161 | |
Svet Ganov | 17a647e | 2020-02-24 17:31:15 -0800 | [diff] [blame] | 162 | final InlineSuggestionRoot suggestionRoot = new InlineSuggestionRoot(this, callback); |
| 163 | suggestionRoot.addView(suggestionView); |
Feng Cao | bd26abb | 2020-04-24 11:35:58 -0700 | [diff] [blame] | 164 | WindowManager.LayoutParams lp = new WindowManager.LayoutParams(measuredSize.getWidth(), |
| 165 | measuredSize.getHeight(), WindowManager.LayoutParams.TYPE_APPLICATION, 0, |
| 166 | PixelFormat.TRANSPARENT); |
Svet Ganov | 17a647e | 2020-02-24 17:31:15 -0800 | [diff] [blame] | 167 | |
| 168 | final SurfaceControlViewHost host = new SurfaceControlViewHost(this, getDisplay(), |
| 169 | hostInputToken); |
Robert Carr | 271d9c7 | 2020-03-12 12:39:24 -0700 | [diff] [blame] | 170 | host.setView(suggestionRoot, lp); |
Feng Cao | c23bfc78 | 2020-04-29 00:35:10 -0700 | [diff] [blame] | 171 | |
| 172 | // Set the suggestion view to be non-focusable so that if its background is set to a |
| 173 | // ripple drawable, the ripple won't be shown initially. |
| 174 | suggestionView.setFocusable(false); |
| 175 | suggestionView.setOnClickListener((v) -> { |
Svet Ganov | 17a647e | 2020-02-24 17:31:15 -0800 | [diff] [blame] | 176 | try { |
Feng Cao | deec8f0 | 2020-03-10 18:14:13 -0700 | [diff] [blame] | 177 | callback.onClick(); |
Svet Ganov | 17a647e | 2020-02-24 17:31:15 -0800 | [diff] [blame] | 178 | } catch (RemoteException e) { |
Feng Cao | deec8f0 | 2020-03-10 18:14:13 -0700 | [diff] [blame] | 179 | Log.w(TAG, "RemoteException calling onClick()"); |
Svet Ganov | 17a647e | 2020-02-24 17:31:15 -0800 | [diff] [blame] | 180 | } |
| 181 | }); |
Feng Cao | c23bfc78 | 2020-04-29 00:35:10 -0700 | [diff] [blame] | 182 | final View.OnLongClickListener onLongClickListener = |
| 183 | suggestionView.getOnLongClickListener(); |
| 184 | suggestionView.setOnLongClickListener((v) -> { |
| 185 | if (onLongClickListener != null) { |
| 186 | onLongClickListener.onLongClick(v); |
| 187 | } |
Feng Cao | deec8f0 | 2020-03-10 18:14:13 -0700 | [diff] [blame] | 188 | try { |
| 189 | callback.onLongClick(); |
| 190 | } catch (RemoteException e) { |
| 191 | Log.w(TAG, "RemoteException calling onLongClick()"); |
| 192 | } |
| 193 | return true; |
| 194 | }); |
Feng Cao | ffd9aff | 2020-05-26 23:10:26 -0700 | [diff] [blame] | 195 | final InlineSuggestionUiImpl uiImpl = new InlineSuggestionUiImpl(host, mMainHandler); |
| 196 | mActiveInlineSuggestions.put(uiImpl, true); |
Feng Cao | deec8f0 | 2020-03-10 18:14:13 -0700 | [diff] [blame] | 197 | |
Feng Cao | ffd9aff | 2020-05-26 23:10:26 -0700 | [diff] [blame] | 198 | // We post the callback invocation to the end of the main thread handler queue, to make |
| 199 | // sure the callback happens after the views are drawn. This is needed because calling |
| 200 | // {@link SurfaceControlViewHost#setView()} will post a task to the main thread |
| 201 | // to draw the view asynchronously. |
| 202 | mMainHandler.post(() -> { |
| 203 | try { |
| 204 | callback.onContent(new InlineSuggestionUiWrapper(uiImpl), |
| 205 | host.getSurfacePackage(), |
| 206 | measuredSize.getWidth(), measuredSize.getHeight()); |
| 207 | } catch (RemoteException e) { |
| 208 | Log.w(TAG, "RemoteException calling onContent()"); |
| 209 | } |
| 210 | }); |
Svet Ganov | 17a647e | 2020-02-24 17:31:15 -0800 | [diff] [blame] | 211 | } finally { |
| 212 | updateDisplay(Display.DEFAULT_DISPLAY); |
| 213 | } |
Svet Ganov | 02fdd34 | 2020-02-20 20:48:34 -0800 | [diff] [blame] | 214 | } |
| 215 | |
Adam He | 89afcd6 | 2020-03-18 17:28:32 -0700 | [diff] [blame] | 216 | private void handleGetInlineSuggestionsRendererInfo(@NonNull RemoteCallback callback) { |
| 217 | final Bundle rendererInfo = onGetInlineSuggestionsRendererInfo(); |
| 218 | callback.sendResult(rendererInfo); |
| 219 | } |
| 220 | |
Feng Cao | b46851c | 2020-04-29 18:22:49 -0700 | [diff] [blame] | 221 | /** |
| 222 | * A wrapper class around the {@link InlineSuggestionUiImpl} to ensure it's not strongly |
| 223 | * reference by the remote system server process. |
| 224 | */ |
| 225 | private static final class InlineSuggestionUiWrapper extends |
| 226 | android.service.autofill.IInlineSuggestionUi.Stub { |
| 227 | |
| 228 | private final WeakReference<InlineSuggestionUiImpl> mUiImpl; |
| 229 | |
| 230 | InlineSuggestionUiWrapper(InlineSuggestionUiImpl uiImpl) { |
| 231 | mUiImpl = new WeakReference<>(uiImpl); |
| 232 | } |
| 233 | |
| 234 | @Override |
| 235 | public void releaseSurfaceControlViewHost() { |
| 236 | final InlineSuggestionUiImpl uiImpl = mUiImpl.get(); |
| 237 | if (uiImpl != null) { |
| 238 | uiImpl.releaseSurfaceControlViewHost(); |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | @Override |
| 243 | public void getSurfacePackage(ISurfacePackageResultCallback callback) { |
| 244 | final InlineSuggestionUiImpl uiImpl = mUiImpl.get(); |
| 245 | if (uiImpl != null) { |
| 246 | uiImpl.getSurfacePackage(callback); |
| 247 | } |
| 248 | } |
| 249 | } |
| 250 | |
| 251 | /** |
| 252 | * Keeps track of a SurfaceControlViewHost to ensure it's released when its lifecycle ends. |
| 253 | * |
| 254 | * <p>This class is thread safe, because all the outside calls are piped into a single |
| 255 | * handler thread to be processed. |
| 256 | */ |
| 257 | private final class InlineSuggestionUiImpl { |
| 258 | |
| 259 | @Nullable |
| 260 | private SurfaceControlViewHost mViewHost; |
| 261 | @NonNull |
| 262 | private final Handler mHandler; |
| 263 | |
| 264 | InlineSuggestionUiImpl(SurfaceControlViewHost viewHost, Handler handler) { |
| 265 | this.mViewHost = viewHost; |
| 266 | this.mHandler = handler; |
| 267 | } |
| 268 | |
| 269 | /** |
| 270 | * Call {@link SurfaceControlViewHost#release()} to release it. After this, this view is |
| 271 | * not usable, and any further calls to the |
| 272 | * {@link #getSurfacePackage(ISurfacePackageResultCallback)} will get {@code null} result. |
| 273 | */ |
| 274 | public void releaseSurfaceControlViewHost() { |
| 275 | mHandler.post(() -> { |
| 276 | if (mViewHost == null) { |
| 277 | return; |
| 278 | } |
| 279 | Log.v(TAG, "Releasing inline suggestion view host"); |
| 280 | mViewHost.release(); |
| 281 | mViewHost = null; |
| 282 | InlineSuggestionRenderService.this.mActiveInlineSuggestions.remove( |
| 283 | InlineSuggestionUiImpl.this); |
| 284 | Log.v(TAG, "Removed the inline suggestion from the cache, current size=" |
| 285 | + InlineSuggestionRenderService.this.mActiveInlineSuggestions.size()); |
| 286 | }); |
| 287 | } |
| 288 | |
| 289 | /** |
| 290 | * Sends back a new {@link android.view.SurfaceControlViewHost.SurfacePackage} if the view |
| 291 | * is not released, {@code null} otherwise. |
| 292 | */ |
| 293 | public void getSurfacePackage(ISurfacePackageResultCallback callback) { |
| 294 | Log.d(TAG, "getSurfacePackage"); |
| 295 | mHandler.post(() -> { |
| 296 | try { |
| 297 | callback.onResult(mViewHost == null ? null : mViewHost.getSurfacePackage()); |
| 298 | } catch (RemoteException e) { |
| 299 | Log.w(TAG, "RemoteException calling onSurfacePackage"); |
| 300 | } |
| 301 | }); |
Adam He | 4011625 | 2020-02-04 14:55:39 -0800 | [diff] [blame] | 302 | } |
Adam He | 0430237 | 2020-01-23 13:40:57 -0800 | [diff] [blame] | 303 | } |
| 304 | |
| 305 | @Override |
| 306 | @Nullable |
| 307 | public final IBinder onBind(@NonNull Intent intent) { |
TYM Tsai | 850c812 | 2020-04-01 02:26:58 +0800 | [diff] [blame] | 308 | BaseBundle.setShouldDefuse(true); |
Adam He | 0430237 | 2020-01-23 13:40:57 -0800 | [diff] [blame] | 309 | if (SERVICE_INTERFACE.equals(intent.getAction())) { |
| 310 | return new IInlineSuggestionRenderService.Stub() { |
| 311 | @Override |
| 312 | public void renderSuggestion(@NonNull IInlineSuggestionUiCallback callback, |
Adam He | 4011625 | 2020-02-04 14:55:39 -0800 | [diff] [blame] | 313 | @NonNull InlinePresentation presentation, int width, int height, |
Svet Ganov | 02fdd34 | 2020-02-20 20:48:34 -0800 | [diff] [blame] | 314 | @Nullable IBinder hostInputToken, int displayId) { |
Feng Cao | ffd9aff | 2020-05-26 23:10:26 -0700 | [diff] [blame] | 315 | mMainHandler.sendMessage( |
Feng Cao | bd26abb | 2020-04-24 11:35:58 -0700 | [diff] [blame] | 316 | obtainMessage(InlineSuggestionRenderService::handleRenderSuggestion, |
| 317 | InlineSuggestionRenderService.this, callback, presentation, |
| 318 | width, height, hostInputToken, displayId)); |
Adam He | 0430237 | 2020-01-23 13:40:57 -0800 | [diff] [blame] | 319 | } |
Adam He | 89afcd6 | 2020-03-18 17:28:32 -0700 | [diff] [blame] | 320 | |
| 321 | @Override |
| 322 | public void getInlineSuggestionsRendererInfo(@NonNull RemoteCallback callback) { |
Feng Cao | ffd9aff | 2020-05-26 23:10:26 -0700 | [diff] [blame] | 323 | mMainHandler.sendMessage(obtainMessage( |
Adam He | 89afcd6 | 2020-03-18 17:28:32 -0700 | [diff] [blame] | 324 | InlineSuggestionRenderService::handleGetInlineSuggestionsRendererInfo, |
| 325 | InlineSuggestionRenderService.this, callback)); |
| 326 | } |
Adam He | 0430237 | 2020-01-23 13:40:57 -0800 | [diff] [blame] | 327 | }.asBinder(); |
| 328 | } |
| 329 | |
| 330 | Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent); |
| 331 | return null; |
| 332 | } |
| 333 | |
| 334 | /** |
Adam He | d41ce92 | 2020-03-19 14:30:32 -0700 | [diff] [blame] | 335 | * Starts the {@link IntentSender} from the client app. |
| 336 | * |
Feng Cao | bd26abb | 2020-04-24 11:35:58 -0700 | [diff] [blame] | 337 | * @param intentSender the {@link IntentSender} to start the attribution UI from the client |
| 338 | * app. |
Adam He | d41ce92 | 2020-03-19 14:30:32 -0700 | [diff] [blame] | 339 | */ |
| 340 | public final void startIntentSender(@NonNull IntentSender intentSender) { |
| 341 | if (mCallback == null) return; |
| 342 | try { |
| 343 | mCallback.onStartIntentSender(intentSender); |
| 344 | } catch (RemoteException e) { |
| 345 | e.rethrowFromSystemServer(); |
| 346 | } |
| 347 | } |
| 348 | |
| 349 | /** |
Feng Cao | bd26abb | 2020-04-24 11:35:58 -0700 | [diff] [blame] | 350 | * Returns the metadata about the renderer. Returns {@code Bundle.Empty} if no metadata is |
| 351 | * provided. |
Feng Cao | 36960ee | 2020-02-18 18:23:30 -0800 | [diff] [blame] | 352 | */ |
Feng Cao | edb332c | 2020-03-30 14:26:42 -0700 | [diff] [blame] | 353 | @NonNull |
Feng Cao | 36960ee | 2020-02-18 18:23:30 -0800 | [diff] [blame] | 354 | public Bundle onGetInlineSuggestionsRendererInfo() { |
Feng Cao | edb332c | 2020-03-30 14:26:42 -0700 | [diff] [blame] | 355 | return Bundle.EMPTY; |
Feng Cao | 36960ee | 2020-02-18 18:23:30 -0800 | [diff] [blame] | 356 | } |
| 357 | |
| 358 | /** |
Adam He | 0430237 | 2020-01-23 13:40:57 -0800 | [diff] [blame] | 359 | * Renders the slice into a view. |
| 360 | */ |
| 361 | @Nullable |
Feng Cao | bd26abb | 2020-04-24 11:35:58 -0700 | [diff] [blame] | 362 | public View onRenderSuggestion(@NonNull InlinePresentation presentation, int width, |
| 363 | int height) { |
Adam He | 0430237 | 2020-01-23 13:40:57 -0800 | [diff] [blame] | 364 | Log.e(TAG, "service implementation (" + getClass() + " does not implement " |
| 365 | + "onRenderSuggestion()"); |
| 366 | return null; |
| 367 | } |
| 368 | } |