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.INSETS_SOURCE_CONSUMER;
21 import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL;
22 import static android.view.InsetsController.AnimationType;
23 import static android.view.InsetsState.ITYPE_IME;
24 
25 import android.annotation.Nullable;
26 import android.inputmethodservice.InputMethodService;
27 import android.os.IBinder;
28 import android.os.Trace;
29 import android.util.proto.ProtoOutputStream;
30 import android.view.SurfaceControl.Transaction;
31 import android.view.inputmethod.InputMethodManager;
32 
33 import java.util.function.Supplier;
34 
35 /**
36  * Controls the visibility and animations of IME window insets source.
37  * @hide
38  */
39 public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
40 
41     /**
42      * Tracks whether we have an outstanding request from the IME to show, but weren't able to
43      * execute it because we didn't have control yet.
44      */
45     private boolean mIsRequestedVisibleAwaitingControl;
46 
ImeInsetsSourceConsumer( InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller)47     public ImeInsetsSourceConsumer(
48             InsetsState state, Supplier<Transaction> transactionSupplier,
49             InsetsController controller) {
50         super(ITYPE_IME, state, transactionSupplier, controller);
51     }
52 
53     @Override
onWindowFocusGained(boolean hasViewFocus)54     public void onWindowFocusGained(boolean hasViewFocus) {
55         super.onWindowFocusGained(hasViewFocus);
56         getImm().registerImeConsumer(this);
57     }
58 
59     @Override
onWindowFocusLost()60     public void onWindowFocusLost() {
61         super.onWindowFocusLost();
62         getImm().unregisterImeConsumer(this);
63         mIsRequestedVisibleAwaitingControl = false;
64     }
65 
66     @Override
hide()67     public void hide() {
68         super.hide();
69         mIsRequestedVisibleAwaitingControl = false;
70     }
71 
72     @Override
hide(boolean animationFinished, @AnimationType int animationType)73     void hide(boolean animationFinished, @AnimationType int animationType) {
74         hide();
75 
76         if (animationFinished) {
77             // remove IME surface as IME has finished hide animation.
78             notifyHidden();
79             removeSurface();
80         }
81     }
82 
83     /**
84      * Request {@link InputMethodManager} to show the IME.
85      * @return @see {@link android.view.InsetsSourceConsumer.ShowResult}.
86      */
87     @Override
requestShow(boolean fromIme)88     public @ShowResult int requestShow(boolean fromIme) {
89         // TODO: ResultReceiver for IME.
90         // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag.
91         if (getControl() == null) {
92             // If control is null, schedule to show IME when control is available.
93             mIsRequestedVisibleAwaitingControl = true;
94         }
95         // If we had a request before to show from IME (tracked with mImeRequestedShow), reaching
96         // this code here means that we now got control, so we can start the animation immediately.
97         // If client window is trying to control IME and IME is already visible, it is immediate.
98         if (fromIme || mState.getSource(getType()).isVisible() && getControl() != null) {
99             return ShowResult.SHOW_IMMEDIATELY;
100         }
101 
102         return getImm().requestImeShow(mController.getHost().getWindowToken())
103                 ? ShowResult.IME_SHOW_DELAYED : ShowResult.IME_SHOW_FAILED;
104     }
105 
106     /**
107      * Notify {@link InputMethodService} that IME window is hidden.
108      */
109     @Override
notifyHidden()110     void notifyHidden() {
111         getImm().notifyImeHidden(mController.getHost().getWindowToken());
112         Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0);
113     }
114 
115     @Override
removeSurface()116     public void removeSurface() {
117         final IBinder window = mController.getHost().getWindowToken();
118         if (window != null) {
119             getImm().removeImeSurface(window);
120         }
121     }
122 
123     @Override
setControl(@ullable InsetsSourceControl control, int[] showTypes, int[] hideTypes)124     public void setControl(@Nullable InsetsSourceControl control, int[] showTypes,
125             int[] hideTypes) {
126         super.setControl(control, showTypes, hideTypes);
127         // TODO(b/204524304): clean-up how to deal with the timing issues of hiding IME:
128         //  1) Already requested show IME, in the meantime of WM callback the control but got null
129         //  control when relayout comes first
130         //  2) Make sure no regression on some implicit request IME visibility calls (e.g.
131         //  toggleSoftInput)
132         if (control == null && !mIsRequestedVisibleAwaitingControl) {
133             hide();
134             removeSurface();
135         }
136         if (control != null) {
137             mIsRequestedVisibleAwaitingControl = false;
138         }
139     }
140 
141     @Override
isRequestedVisibleAwaitingControl()142     protected boolean isRequestedVisibleAwaitingControl() {
143         return mIsRequestedVisibleAwaitingControl || isRequestedVisible();
144     }
145 
146     @Override
onPerceptible(boolean perceptible)147     public void onPerceptible(boolean perceptible) {
148         super.onPerceptible(perceptible);
149         final IBinder window = mController.getHost().getWindowToken();
150         if (window != null) {
151             getImm().reportPerceptible(window, perceptible);
152         }
153     }
154 
155     @Override
dumpDebug(ProtoOutputStream proto, long fieldId)156     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
157         final long token = proto.start(fieldId);
158         super.dumpDebug(proto, INSETS_SOURCE_CONSUMER);
159         proto.write(IS_REQUESTED_VISIBLE_AWAITING_CONTROL, mIsRequestedVisibleAwaitingControl);
160         proto.end(token);
161     }
162 
getImm()163     private InputMethodManager getImm() {
164         return mController.getHost().getInputMethodManager();
165     }
166 }
167