• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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