• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.server.wm;
18 
19 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
20 import static android.view.InsetsState.ITYPE_IME;
21 
22 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
23 import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
24 import static com.android.server.wm.DisplayContent.IME_TARGET_INPUT;
25 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
26 import static com.android.server.wm.ImeInsetsSourceProviderProto.IME_TARGET_FROM_IME;
27 import static com.android.server.wm.ImeInsetsSourceProviderProto.INSETS_SOURCE_PROVIDER;
28 import static com.android.server.wm.ImeInsetsSourceProviderProto.IS_IME_LAYOUT_DRAWN;
29 import static com.android.server.wm.WindowManagerService.H.UPDATE_MULTI_WINDOW_STACKS;
30 
31 import android.annotation.NonNull;
32 import android.os.Trace;
33 import android.util.proto.ProtoOutputStream;
34 import android.view.InsetsSource;
35 import android.view.InsetsSourceControl;
36 import android.view.WindowInsets;
37 import android.window.TaskSnapshot;
38 
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.internal.protolog.common.ProtoLog;
41 
42 import java.io.PrintWriter;
43 
44 /**
45  * Controller for IME inset source on the server. It's called provider as it provides the
46  * {@link InsetsSource} to the client that uses it in {@link InsetsSourceConsumer}.
47  */
48 final class ImeInsetsSourceProvider extends InsetsSourceProvider {
49 
50     private InsetsControlTarget mImeRequester;
51     private Runnable mShowImeRunner;
52     private boolean mIsImeLayoutDrawn;
53     private boolean mImeShowing;
54     private final InsetsSource mLastSource = new InsetsSource(ITYPE_IME);
55 
ImeInsetsSourceProvider(InsetsSource source, InsetsStateController stateController, DisplayContent displayContent)56     ImeInsetsSourceProvider(InsetsSource source,
57             InsetsStateController stateController, DisplayContent displayContent) {
58         super(source, stateController, displayContent);
59     }
60 
61     @Override
getControl(InsetsControlTarget target)62     InsetsSourceControl getControl(InsetsControlTarget target) {
63         final InsetsSourceControl control = super.getControl(target);
64         if (control != null && target != null && target.getWindow() != null) {
65             final WindowState targetWin = target.getWindow();
66             // If the control target changes during the app transition with the task snapshot
67             // starting window and the IME snapshot is visible, in case not have duplicated IME
68             // showing animation during transitioning, use a flag to inform IME source control to
69             // skip showing animation once.
70             final TaskSnapshot snapshot = targetWin.getRootTask() != null
71                     ? targetWin.mWmService.getTaskSnapshot(targetWin.getRootTask().mTaskId,
72                         0 /* userId */, false /* isLowResolution */, false /* restoreFromDisk */)
73                     : null;
74             control.setSkipAnimationOnce(targetWin.mActivityRecord != null
75                     && targetWin.mActivityRecord.hasStartingWindow()
76                     && snapshot != null && snapshot.hasImeSurface());
77         }
78         return control;
79     }
80 
81     @Override
updateSourceFrame()82     void updateSourceFrame() {
83         super.updateSourceFrame();
84         onSourceChanged();
85     }
86 
87     @Override
updateVisibility()88     protected void updateVisibility() {
89         super.updateVisibility();
90         onSourceChanged();
91     }
92 
onSourceChanged()93     private void onSourceChanged() {
94         if (mLastSource.equals(mSource)) {
95             return;
96         }
97         mLastSource.set(mSource);
98         mDisplayContent.mWmService.mH.obtainMessage(
99                 UPDATE_MULTI_WINDOW_STACKS, mDisplayContent).sendToTarget();
100     }
101 
102     /**
103      * Called from {@link WindowManagerInternal#showImePostLayout} when {@link InputMethodService}
104      * requests to show IME on {@param imeTarget}.
105      *
106      * @param imeTarget imeTarget on which IME request is coming from.
107      */
scheduleShowImePostLayout(InsetsControlTarget imeTarget)108     void scheduleShowImePostLayout(InsetsControlTarget imeTarget) {
109         boolean targetChanged = isTargetChangedWithinActivity(imeTarget);
110         mImeRequester = imeTarget;
111         if (targetChanged) {
112             // target changed, check if new target can show IME.
113             ProtoLog.d(WM_DEBUG_IME, "IME target changed within ActivityRecord");
114             checkShowImePostLayout();
115             // if IME cannot be shown at this time, it is scheduled to be shown.
116             // once window that called IMM.showSoftInput() and DisplayContent's ImeTarget match,
117             // it will be shown.
118             return;
119         }
120 
121         ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeRequester.getWindow() == null
122                 ? mImeRequester : mImeRequester.getWindow().getName());
123         mShowImeRunner = () -> {
124             ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner");
125             // Target should still be the same.
126             if (isReadyToShowIme()) {
127                 final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
128 
129                 ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s",
130                         target.getWindow() != null ? target.getWindow().getName() : "");
131                 setImeShowing(true);
132                 target.showInsets(WindowInsets.Type.ime(), true /* fromIme */);
133                 Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
134                 if (target != mImeRequester && mImeRequester != null) {
135                     ProtoLog.w(WM_DEBUG_IME,
136                             "showInsets(ime) was requested by different window: %s ",
137                             (mImeRequester.getWindow() != null
138                                     ? mImeRequester.getWindow().getName() : ""));
139                 }
140             }
141             abortShowImePostLayout();
142         };
143         mDisplayContent.mWmService.requestTraversal();
144     }
145 
checkShowImePostLayout()146     void checkShowImePostLayout() {
147         // check if IME is drawn
148         if (mIsImeLayoutDrawn
149                 || (isReadyToShowIme()
150                 && mWin != null
151                 && mWin.isDrawn()
152                 && !mWin.mGivenInsetsPending)) {
153             mIsImeLayoutDrawn = true;
154             // show IME if InputMethodService requested it to be shown.
155             if (mShowImeRunner != null) {
156                 mShowImeRunner.run();
157             }
158         }
159     }
160 
161     /**
162      * Abort any pending request to show IME post layout.
163      */
abortShowImePostLayout()164     void abortShowImePostLayout() {
165         ProtoLog.d(WM_DEBUG_IME, "abortShowImePostLayout");
166         mImeRequester = null;
167         mIsImeLayoutDrawn = false;
168         mShowImeRunner = null;
169     }
170 
171     @VisibleForTesting
isReadyToShowIme()172     boolean isReadyToShowIme() {
173         // IMMS#mLastImeTargetWindow always considers focused window as
174         // IME target, however DisplayContent#computeImeTarget() can compute
175         // a different IME target.
176         // Refer to WindowManagerService#applyImeVisibility(token, false).
177         // If IMMS's imeTarget is child of DisplayContent's imeTarget and child window
178         // is above the parent, we will consider it as the same target for now.
179         // Also, if imeTarget is closing, it would be considered as outdated target.
180         // TODO(b/139861270): Remove the child & sublayer check once IMMS is aware of
181         //  actual IME target.
182         final InsetsControlTarget dcTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING);
183         if (dcTarget == null || mImeRequester == null) {
184             return false;
185         }
186         ProtoLog.d(WM_DEBUG_IME, "dcTarget: %s mImeRequester: %s",
187                 dcTarget.getWindow().getName(), mImeRequester.getWindow() == null
188                         ? mImeRequester : mImeRequester.getWindow().getName());
189 
190         return isImeLayeringTarget(mImeRequester, dcTarget)
191                 || isAboveImeLayeringTarget(mImeRequester, dcTarget)
192                 || isImeFallbackTarget(mImeRequester)
193                 || isImeInputTarget(mImeRequester)
194                 || sameAsImeControlTarget();
195     }
196 
197     // ---------------------------------------------------------------------------------------
198     // Methods for checking IME insets target changing state.
199     //
isImeLayeringTarget(@onNull InsetsControlTarget target, @NonNull InsetsControlTarget dcTarget)200     private static boolean isImeLayeringTarget(@NonNull InsetsControlTarget target,
201             @NonNull InsetsControlTarget dcTarget) {
202         return !dcTarget.getWindow().isClosing() && target == dcTarget;
203     }
204 
isAboveImeLayeringTarget(@onNull InsetsControlTarget target, @NonNull InsetsControlTarget dcTarget)205     private static boolean isAboveImeLayeringTarget(@NonNull InsetsControlTarget target,
206             @NonNull InsetsControlTarget dcTarget) {
207         return target.getWindow() != null
208                 && dcTarget.getWindow().getParentWindow() == target
209                 && dcTarget.getWindow().mSubLayer > target.getWindow().mSubLayer;
210     }
211 
isImeFallbackTarget(InsetsControlTarget target)212     private boolean isImeFallbackTarget(InsetsControlTarget target) {
213         return target == mDisplayContent.getImeFallback();
214     }
215 
isImeInputTarget(InsetsControlTarget target)216     private boolean isImeInputTarget(InsetsControlTarget target) {
217         return target == mDisplayContent.getImeTarget(IME_TARGET_INPUT);
218     }
219 
sameAsImeControlTarget()220     private boolean sameAsImeControlTarget() {
221         final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
222         return target == mImeRequester
223                 && (mImeRequester.getWindow() == null
224                 || !mImeRequester.getWindow().isClosing());
225     }
226 
isTargetChangedWithinActivity(InsetsControlTarget target)227     private boolean isTargetChangedWithinActivity(InsetsControlTarget target) {
228         // We don't consider the target out of the activity.
229         if (target == null || target.getWindow() == null) {
230             return false;
231         }
232         return mImeRequester != target
233                 && mImeRequester != null && mShowImeRunner != null
234                 && mImeRequester.getWindow() != null
235                 && mImeRequester.getWindow().mActivityRecord
236                 == target.getWindow().mActivityRecord;
237     }
238     // ---------------------------------------------------------------------------------------
239 
240     @Override
dump(PrintWriter pw, String prefix)241     public void dump(PrintWriter pw, String prefix) {
242         super.dump(pw, prefix);
243         prefix = prefix + "  ";
244         pw.print(prefix);
245         pw.print("mImeShowing=");
246         pw.print(mImeShowing);
247         if (mImeRequester != null) {
248             pw.print(prefix);
249             pw.print("showImePostLayout pending for mImeRequester=");
250             pw.print(mImeRequester);
251             pw.println();
252         }
253         pw.println();
254     }
255 
256     @Override
dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel)257     void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) {
258         final long token = proto.start(fieldId);
259         super.dumpDebug(proto, INSETS_SOURCE_PROVIDER, logLevel);
260         final WindowState imeRequesterWindow =
261                 mImeRequester != null ? mImeRequester.getWindow() : null;
262         if (imeRequesterWindow != null) {
263             imeRequesterWindow.dumpDebug(proto, IME_TARGET_FROM_IME, logLevel);
264         }
265         proto.write(IS_IME_LAYOUT_DRAWN, mIsImeLayoutDrawn);
266         proto.end(token);
267     }
268 
269     /**
270      * Sets whether the IME is currently supposed to be showing according to
271      * InputMethodManagerService.
272      */
setImeShowing(boolean imeShowing)273     public void setImeShowing(boolean imeShowing) {
274         mImeShowing = imeShowing;
275     }
276 
277     /**
278      * Returns whether the IME is currently supposed to be showing according to
279      * InputMethodManagerService.
280      */
isImeShowing()281     public boolean isImeShowing() {
282         return mImeShowing;
283     }
284 }
285