1 /*
2  * Copyright (C) 2019 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.view;
18 
19 import static android.os.Trace.TRACE_TAG_VIEW;
20 import static android.view.ImeInsetsSourceConsumerProto.HAS_PENDING_REQUEST;
21 import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER;
22 import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL;
23 
24 import android.annotation.Nullable;
25 import android.os.IBinder;
26 import android.os.Process;
27 import android.os.Trace;
28 import android.util.proto.ProtoOutputStream;
29 import android.view.SurfaceControl.Transaction;
30 import android.view.inputmethod.ImeTracker;
31 import android.view.inputmethod.InputMethodManager;
32 
33 import com.android.internal.inputmethod.ImeTracing;
34 import com.android.internal.inputmethod.SoftInputShowHideReason;
35 
36 import java.util.function.Supplier;
37 
38 /**
39  * Controls the visibility and animations of IME window insets source.
40  * @hide
41  */
42 public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
43 
44     /**
45      * Tracks whether are requested to show during the hide animation or requested to hide during
46      * the show animation. If this is true, we should not remove the surface.
47      */
48     private boolean mHasPendingRequest;
49 
50     /**
51      * Tracks whether we have an outstanding request from the IME to show, but weren't able to
52      * execute it because we didn't have control yet.
53      */
54     private boolean mIsRequestedVisibleAwaitingControl;
55 
ImeInsetsSourceConsumer( int id, InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller)56     public ImeInsetsSourceConsumer(
57             int id, InsetsState state, Supplier<Transaction> transactionSupplier,
58             InsetsController controller) {
59         super(id, WindowInsets.Type.ime(), state, transactionSupplier, controller);
60     }
61 
62     @Override
onAnimationStateChanged(boolean running)63     public boolean onAnimationStateChanged(boolean running) {
64         if (!running) {
65             ImeTracing.getInstance().triggerClientDump(
66                     "ImeInsetsSourceConsumer#onAnimationFinished",
67                     mController.getHost().getInputMethodManager(), null /* icProto */);
68         }
69         final boolean insetsChanged = super.onAnimationStateChanged(running);
70         if (!isShowRequested()) {
71             mIsRequestedVisibleAwaitingControl = false;
72             if (!running && !mHasPendingRequest) {
73                 notifyHidden(null /* statsToken */);
74                 removeSurface();
75             }
76         }
77         // This method is called
78         // (1) after the animation starts.
79         // (2) after the animation ends (including the case of cancel).
80         // (3) if the IME is not controllable (running == false in this case).
81         // We should reset mHasPendingRequest in all cases.
82         mHasPendingRequest = false;
83         return insetsChanged;
84     }
85 
86     @Override
onWindowFocusGained(boolean hasViewFocus)87     public void onWindowFocusGained(boolean hasViewFocus) {
88         super.onWindowFocusGained(hasViewFocus);
89         getImm().registerImeConsumer(this);
90         if ((mController.getRequestedVisibleTypes() & getType()) != 0 && getControl() == null) {
91             mIsRequestedVisibleAwaitingControl = true;
92         }
93     }
94 
95     @Override
onWindowFocusLost()96     public void onWindowFocusLost() {
97         super.onWindowFocusLost();
98         getImm().unregisterImeConsumer(this);
99         mIsRequestedVisibleAwaitingControl = false;
100     }
101 
102     @Override
applyLocalVisibilityOverride()103     public boolean applyLocalVisibilityOverride() {
104         ImeTracing.getInstance().triggerClientDump(
105                 "ImeInsetsSourceConsumer#applyLocalVisibilityOverride",
106                 mController.getHost().getInputMethodManager(), null /* icProto */);
107         return super.applyLocalVisibilityOverride();
108     }
109 
110     /**
111      * Request {@link InputMethodManager} to show the IME.
112      * @return @see {@link android.view.InsetsSourceConsumer.ShowResult}.
113      */
114     @Override
115     @ShowResult
requestShow(boolean fromIme, @Nullable ImeTracker.Token statsToken)116     public int requestShow(boolean fromIme, @Nullable ImeTracker.Token statsToken) {
117         if (fromIme) {
118             ImeTracing.getInstance().triggerClientDump(
119                     "ImeInsetsSourceConsumer#requestShow",
120                     mController.getHost().getInputMethodManager(), null /* icProto */);
121         }
122         onShowRequested();
123 
124         // TODO: ResultReceiver for IME.
125         // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag.
126         ImeTracker.forLogging().onProgress(statsToken,
127                 ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW);
128 
129         if (getControl() == null) {
130             // If control is null, schedule to show IME when control is available.
131             mIsRequestedVisibleAwaitingControl = true;
132         }
133         // If we had a request before to show from IME (tracked with mImeRequestedShow), reaching
134         // this code here means that we now got control, so we can start the animation immediately.
135         // If client window is trying to control IME and IME is already visible, it is immediate.
136         if (fromIme
137                 || (mState.isSourceOrDefaultVisible(getId(), getType()) && getControl() != null)) {
138             return ShowResult.SHOW_IMMEDIATELY;
139         }
140 
141         return getImm().requestImeShow(mController.getHost().getWindowToken(), statsToken)
142                 ? ShowResult.IME_SHOW_DELAYED : ShowResult.IME_SHOW_FAILED;
143     }
144 
requestHide(boolean fromIme, @Nullable ImeTracker.Token statsToken)145     void requestHide(boolean fromIme, @Nullable ImeTracker.Token statsToken) {
146         if (!fromIme) {
147             // The insets might be controlled by a remote target. Let the server know we are
148             // requested to hide.
149             notifyHidden(statsToken);
150         }
151         if (mAnimationState == ANIMATION_STATE_SHOW) {
152             mHasPendingRequest = true;
153         }
154     }
155 
156     /**
157      * Notify {@link com.android.server.inputmethod.InputMethodManagerService} that
158      * IME insets are hidden.
159      *
160      * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
161      */
notifyHidden(@ullable ImeTracker.Token statsToken)162     private void notifyHidden(@Nullable ImeTracker.Token statsToken) {
163         // Create a new stats token to track the hide request when:
164         //  - we do not already have one, or
165         //  - we do already have one, but we have control and use the passed in token
166         //      for the insets animation already.
167         if (statsToken == null || getControl() != null) {
168             statsToken = ImeTracker.forLogging().onRequestHide(null /* component */,
169                     Process.myUid(),
170                     ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
171                     SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
172         }
173 
174         ImeTracker.forLogging().onProgress(statsToken,
175                 ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN);
176 
177         getImm().notifyImeHidden(mController.getHost().getWindowToken(), statsToken);
178         mIsRequestedVisibleAwaitingControl = false;
179         Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0);
180     }
181 
182     @Override
removeSurface()183     public void removeSurface() {
184         final IBinder window = mController.getHost().getWindowToken();
185         if (window != null) {
186             getImm().removeImeSurface(window);
187         }
188     }
189 
190     @Override
setControl(@ullable InsetsSourceControl control, int[] showTypes, int[] hideTypes)191     public boolean setControl(@Nullable InsetsSourceControl control, int[] showTypes,
192             int[] hideTypes) {
193         ImeTracing.getInstance().triggerClientDump("ImeInsetsSourceConsumer#setControl",
194                 mController.getHost().getInputMethodManager(), null /* icProto */);
195         if (!super.setControl(control, showTypes, hideTypes)) {
196             return false;
197         }
198         if (control == null && !mIsRequestedVisibleAwaitingControl) {
199             mController.setRequestedVisibleTypes(0 /* visibleTypes */, getType());
200             removeSurface();
201         }
202         if (control != null) {
203             mIsRequestedVisibleAwaitingControl = false;
204         }
205         return true;
206     }
207 
208     @Override
isRequestedVisibleAwaitingControl()209     protected boolean isRequestedVisibleAwaitingControl() {
210         return super.isRequestedVisibleAwaitingControl() || mIsRequestedVisibleAwaitingControl;
211     }
212 
213     @Override
onPerceptible(boolean perceptible)214     public void onPerceptible(boolean perceptible) {
215         super.onPerceptible(perceptible);
216         final IBinder window = mController.getHost().getWindowToken();
217         if (window != null) {
218             getImm().reportPerceptible(window, perceptible);
219         }
220     }
221 
222     @Override
dumpDebug(ProtoOutputStream proto, long fieldId)223     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
224         final long token = proto.start(fieldId);
225         super.dumpDebug(proto, INSETS_SOURCE_CONSUMER);
226         proto.write(IS_REQUESTED_VISIBLE_AWAITING_CONTROL, mIsRequestedVisibleAwaitingControl);
227         proto.write(HAS_PENDING_REQUEST, mHasPendingRequest);
228         proto.end(token);
229     }
230 
231     /**
232      * Called when {@link #requestShow(boolean, ImeTracker.Token)} or
233      * {@link InputMethodManager#showSoftInput(View, int)} is called.
234      */
onShowRequested()235     public void onShowRequested() {
236         if (mAnimationState == ANIMATION_STATE_HIDE) {
237             mHasPendingRequest = true;
238         }
239     }
240 
getImm()241     private InputMethodManager getImm() {
242         return mController.getHost().getInputMethodManager();
243     }
244 }
245