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 17 package com.android.server.autofill; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.ComponentName; 22 import android.os.Bundle; 23 import android.os.Handler; 24 import android.view.autofill.AutofillId; 25 import android.view.inputmethod.InlineSuggestionsRequest; 26 27 import com.android.internal.annotations.GuardedBy; 28 import com.android.server.autofill.ui.InlineFillUi; 29 import com.android.server.inputmethod.InputMethodManagerInternal; 30 31 import java.util.Optional; 32 import java.util.function.Consumer; 33 34 /** 35 * Controls the interaction with the IME for the {@link AutofillInlineSuggestionsRequestSession}s. 36 * 37 * <p>The class maintains the inline suggestion session with the autofill service. There is at most 38 * one active inline suggestion session at any given corresponding to one focused view. New 39 * sessions are created only when {@link #onCreateInlineSuggestionsRequestLocked} is called.</p> 40 * 41 * <p>The class manages the interaction between the {@link com.android.server.autofill.Session} and 42 * the inline suggestion session whenever inline suggestions can be provided. All calls to the 43 * inline suggestion session must be made through this controller.</p> 44 */ 45 final class AutofillInlineSessionController { 46 @NonNull 47 private final InputMethodManagerInternal mInputMethodManagerInternal; 48 private final int mUserId; 49 @NonNull 50 private final ComponentName mComponentName; 51 @NonNull 52 private final Object mLock; 53 @NonNull 54 private final Handler mHandler; 55 @NonNull 56 private final InlineFillUi.InlineUiEventCallback mUiCallback; 57 58 @Nullable 59 @GuardedBy("mLock") 60 private AutofillInlineSuggestionsRequestSession mSession; 61 @Nullable 62 @GuardedBy("mLock") 63 private InlineFillUi mInlineFillUi; 64 AutofillInlineSessionController(InputMethodManagerInternal inputMethodManagerInternal, int userId, ComponentName componentName, Handler handler, Object lock, InlineFillUi.InlineUiEventCallback callback)65 AutofillInlineSessionController(InputMethodManagerInternal inputMethodManagerInternal, 66 int userId, ComponentName componentName, Handler handler, Object lock, 67 InlineFillUi.InlineUiEventCallback callback) { 68 mInputMethodManagerInternal = inputMethodManagerInternal; 69 mUserId = userId; 70 mComponentName = componentName; 71 mHandler = handler; 72 mLock = lock; 73 mUiCallback = callback; 74 } 75 76 /** 77 * Requests the IME to create an {@link InlineSuggestionsRequest} for {@code autofillId}. 78 * 79 * @param autofillId the Id of the field for which the request is for. 80 * @param requestConsumer the callback to be invoked when the IME responds. Note that this is 81 * never invoked if the IME doesn't respond. 82 */ 83 @GuardedBy("mLock") onCreateInlineSuggestionsRequestLocked(@onNull AutofillId autofillId, @NonNull Consumer<InlineSuggestionsRequest> requestConsumer, @NonNull Bundle uiExtras)84 void onCreateInlineSuggestionsRequestLocked(@NonNull AutofillId autofillId, 85 @NonNull Consumer<InlineSuggestionsRequest> requestConsumer, @NonNull Bundle uiExtras) { 86 if (mSession != null) { 87 // Destroy the existing session. 88 mSession.destroySessionLocked(); 89 } 90 mInlineFillUi = null; 91 // TODO(b/151123764): consider reusing the same AutofillInlineSession object for the 92 // same field. 93 mSession = new AutofillInlineSuggestionsRequestSession(mInputMethodManagerInternal, mUserId, 94 mComponentName, mHandler, mLock, autofillId, requestConsumer, uiExtras, 95 mUiCallback); 96 mSession.onCreateInlineSuggestionsRequestLocked(); 97 } 98 99 /** 100 * Destroys the current session. May send an empty response to IME to clear the suggestions if 101 * the focus didn't change to a different field. 102 * 103 * @param autofillId the currently focused view from the autofill session 104 */ 105 @GuardedBy("mLock") destroyLocked(@onNull AutofillId autofillId)106 void destroyLocked(@NonNull AutofillId autofillId) { 107 if (mSession != null) { 108 mSession.onInlineSuggestionsResponseLocked(InlineFillUi.emptyUi(autofillId)); 109 mSession.destroySessionLocked(); 110 mSession = null; 111 } 112 mInlineFillUi = null; 113 } 114 115 /** 116 * Returns the {@link InlineSuggestionsRequest} provided by IME for the last request. 117 * 118 * <p> The caller is responsible for making sure Autofill hears back from IME before calling 119 * this method, using the {@code requestConsumer} provided when calling {@link 120 * #onCreateInlineSuggestionsRequestLocked(AutofillId, Consumer, Bundle)}. 121 */ 122 @GuardedBy("mLock") getInlineSuggestionsRequestLocked()123 Optional<InlineSuggestionsRequest> getInlineSuggestionsRequestLocked() { 124 if (mSession != null) { 125 return mSession.getInlineSuggestionsRequestLocked(); 126 } 127 return Optional.empty(); 128 } 129 130 /** 131 * Requests the IME to hide the current suggestions, if any. Returns true if the message is sent 132 * to the IME. This only hides the UI temporarily. For example if user starts typing/deleting 133 * characters, new filterText will kick in and may revive the suggestion UI. 134 */ 135 @GuardedBy("mLock") hideInlineSuggestionsUiLocked(@onNull AutofillId autofillId)136 boolean hideInlineSuggestionsUiLocked(@NonNull AutofillId autofillId) { 137 if (mSession != null) { 138 return mSession.onInlineSuggestionsResponseLocked(InlineFillUi.emptyUi(autofillId)); 139 } 140 return false; 141 } 142 143 /** 144 * Disables prefix/regex based filtering. Other filtering rules (see {@link 145 * android.service.autofill.Dataset}) still apply. 146 */ 147 @GuardedBy("mLock") disableFilterMatching(@onNull AutofillId autofillId)148 void disableFilterMatching(@NonNull AutofillId autofillId) { 149 if (mInlineFillUi != null && mInlineFillUi.getAutofillId().equals(autofillId)) { 150 mInlineFillUi.disableFilterMatching(); 151 } 152 } 153 154 /** 155 * Clear the locally cached inline fill UI, but don't clear the suggestion in the IME. 156 * 157 * <p>This is called to invalid the locally cached inline suggestions so we don't resend them 158 * to the IME, while assuming that the IME will clean up suggestion on their own when the input 159 * connection is finished. We don't send an empty response to IME so that it doesn't cause UI 160 * flicker on the IME side if it arrives before the input view is finished on the IME. 161 */ 162 @GuardedBy("mLock") resetInlineFillUiLocked()163 void resetInlineFillUiLocked() { 164 mInlineFillUi = null; 165 if (mSession != null) { 166 mSession.resetInlineFillUiLocked(); 167 } 168 } 169 170 /** 171 * Updates the inline fill UI with the filter text. It'll send updated inline suggestions to 172 * the IME. 173 */ 174 @GuardedBy("mLock") filterInlineFillUiLocked(@onNull AutofillId autofillId, @Nullable String filterText)175 boolean filterInlineFillUiLocked(@NonNull AutofillId autofillId, @Nullable String filterText) { 176 if (mInlineFillUi != null && mInlineFillUi.getAutofillId().equals(autofillId)) { 177 mInlineFillUi.setFilterText(filterText); 178 return requestImeToShowInlineSuggestionsLocked(); 179 } 180 return false; 181 } 182 183 /** 184 * Set the current inline fill UI. It'll request the IME to show the inline suggestions when 185 * the IME becomes visible and is focused on the {@code autofillId}. 186 * 187 * @return false if the suggestions are not sent to IME because there is no session, or if the 188 * IME callback is not available in the session. 189 */ 190 @GuardedBy("mLock") setInlineFillUiLocked(@onNull InlineFillUi inlineFillUi)191 boolean setInlineFillUiLocked(@NonNull InlineFillUi inlineFillUi) { 192 mInlineFillUi = inlineFillUi; 193 return requestImeToShowInlineSuggestionsLocked(); 194 } 195 196 /** 197 * Sends the suggestions from the current inline fill UI to the IME. 198 * 199 * @return false if the suggestions are not sent to IME because there is no session, or if the 200 * IME callback is not available in the session. 201 */ 202 @GuardedBy("mLock") requestImeToShowInlineSuggestionsLocked()203 private boolean requestImeToShowInlineSuggestionsLocked() { 204 if (mSession != null && mInlineFillUi != null) { 205 return mSession.onInlineSuggestionsResponseLocked(mInlineFillUi); 206 } 207 return false; 208 } 209 } 210