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