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.InsetsSource.ID_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.InsetsSourceConsumer;
37 import android.view.InsetsSourceControl;
38 import android.view.WindowInsets;
39 import android.view.inputmethod.ImeTracker;
40 import android.window.TaskSnapshot;
41 
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.internal.protolog.common.ProtoLog;
44 
45 import java.io.PrintWriter;
46 
47 /**
48  * Controller for IME inset source on the server. It's called provider as it provides the
49  * {@link InsetsSource} to the client that uses it in {@link InsetsSourceConsumer}.
50  */
51 final class ImeInsetsSourceProvider extends InsetsSourceProvider {
52 
53     /** The token tracking the current IME request or {@code null} otherwise. */
54     @Nullable
55     private ImeTracker.Token mImeRequesterStatsToken;
56     private InsetsControlTarget mImeRequester;
57     private Runnable mShowImeRunner;
58     private boolean mIsImeLayoutDrawn;
59     private boolean mImeShowing;
60     private final InsetsSource mLastSource = new InsetsSource(ID_IME, WindowInsets.Type.ime());
61 
62     /** @see #setFrozen(boolean) */
63     private boolean mFrozen;
64 
65     /** @see #setServerVisible(boolean) */
66     private boolean mServerVisible;
67 
ImeInsetsSourceProvider(InsetsSource source, InsetsStateController stateController, DisplayContent displayContent)68     ImeInsetsSourceProvider(InsetsSource source,
69             InsetsStateController stateController, DisplayContent displayContent) {
70         super(source, stateController, displayContent);
71     }
72 
73     @Override
getControl(InsetsControlTarget target)74     InsetsSourceControl getControl(InsetsControlTarget target) {
75         final InsetsSourceControl control = super.getControl(target);
76         if (control != null && target != null && target.getWindow() != null) {
77             final WindowState targetWin = target.getWindow();
78             // If the control target changes during the app transition with the task snapshot
79             // starting window and the IME snapshot is visible, in case not have duplicated IME
80             // showing animation during transitioning, use a flag to inform IME source control to
81             // skip showing animation once.
82             final TaskSnapshot snapshot = targetWin.getRootTask() != null
83                     ? targetWin.mWmService.getTaskSnapshot(targetWin.getRootTask().mTaskId,
84                         0 /* userId */, false /* isLowResolution */, false /* restoreFromDisk */)
85                     : null;
86             control.setSkipAnimationOnce(targetWin.mActivityRecord != null
87                     && targetWin.mActivityRecord.hasStartingWindow()
88                     && snapshot != null && snapshot.hasImeSurface());
89         }
90         return control;
91     }
92 
93     @Override
setClientVisible(boolean clientVisible)94     void setClientVisible(boolean clientVisible) {
95         final boolean wasClientVisible = isClientVisible();
96         super.setClientVisible(clientVisible);
97         // The layer of ImePlaceholder needs to be updated on a higher z-order for
98         // non-activity window (For activity window, IME is already on top of it).
99         if (!wasClientVisible && isClientVisible()) {
100             final InsetsControlTarget imeControlTarget = getControlTarget();
101             if (imeControlTarget != null && imeControlTarget.getWindow() != null
102                     && imeControlTarget.getWindow().mActivityRecord == null) {
103                 mDisplayContent.assignWindowLayers(false /* setLayoutNeeded */);
104             }
105         }
106     }
107 
108     @Override
setServerVisible(boolean serverVisible)109     void setServerVisible(boolean serverVisible) {
110         mServerVisible = serverVisible;
111         if (!mFrozen) {
112             super.setServerVisible(serverVisible);
113         }
114     }
115 
116     /**
117      * Freeze IME insets source state when required.
118      *
119      * When setting {@param frozen} as {@code true}, the IME insets provider will freeze the
120      * current IME insets state and pending the IME insets state update until setting
121      * {@param frozen} as {@code false}.
122      */
setFrozen(boolean frozen)123     void setFrozen(boolean frozen) {
124         if (mFrozen == frozen) {
125             return;
126         }
127         mFrozen = frozen;
128         if (!frozen) {
129             // Unfreeze and process the pending IME insets states.
130             super.setServerVisible(mServerVisible);
131         }
132     }
133 
134     @Override
updateSourceFrame(Rect frame)135     void updateSourceFrame(Rect frame) {
136         super.updateSourceFrame(frame);
137         onSourceChanged();
138     }
139 
140     @Override
updateVisibility()141     protected void updateVisibility() {
142         super.updateVisibility();
143         onSourceChanged();
144     }
145 
146     @Override
updateControlForTarget(@ullable InsetsControlTarget target, boolean force)147     void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force) {
148         if (target != null && target.getWindow() != null) {
149             // ime control target could be a different window.
150             // Refer WindowState#getImeControlTarget().
151             target = target.getWindow().getImeControlTarget();
152         }
153         super.updateControlForTarget(target, force);
154     }
155 
156     @Override
updateClientVisibility(InsetsControlTarget caller)157     protected boolean updateClientVisibility(InsetsControlTarget caller) {
158         if (caller != getControlTarget()) {
159             return false;
160         }
161         boolean changed = super.updateClientVisibility(caller);
162         if (changed && caller.isRequestedVisible(mSource.getType())) {
163             reportImeDrawnForOrganizerIfNeeded(caller);
164         }
165         changed |= mDisplayContent.onImeInsetsClientVisibilityUpdate();
166         return changed;
167     }
168 
reportImeDrawnForOrganizerIfNeeded(@onNull InsetsControlTarget caller)169     private void reportImeDrawnForOrganizerIfNeeded(@NonNull InsetsControlTarget caller) {
170         final WindowState callerWindow = caller.getWindow();
171         if (callerWindow == null) {
172             return;
173         }
174         WindowToken imeToken = mWindowContainer.asWindowState() != null
175                 ? mWindowContainer.asWindowState().mToken : null;
176         if (mDisplayContent.getAsyncRotationController() != null
177                 && mDisplayContent.getAsyncRotationController().isTargetToken(imeToken)) {
178             // Skip reporting IME drawn state when the control target is in fixed
179             // rotation, AsyncRotationController will report after the animation finished.
180             return;
181         }
182         reportImeDrawnForOrganizer(caller);
183     }
184 
reportImeDrawnForOrganizer(@onNull InsetsControlTarget caller)185     private void reportImeDrawnForOrganizer(@NonNull InsetsControlTarget caller) {
186         final WindowState callerWindow = caller.getWindow();
187         if (callerWindow == null || callerWindow.getTask() == null) {
188             return;
189         }
190         if (callerWindow.getTask().isOrganized()) {
191             mWindowContainer.mWmService.mAtmService.mTaskOrganizerController
192                     .reportImeDrawnOnTask(caller.getWindow().getTask());
193         }
194     }
195 
196     /** Report the IME has drawn on the current IME control target for its task organizer */
reportImeDrawnForOrganizer()197     void reportImeDrawnForOrganizer() {
198         final InsetsControlTarget imeControlTarget = getControlTarget();
199         if (imeControlTarget != null) {
200             reportImeDrawnForOrganizer(imeControlTarget);
201         }
202     }
203 
onSourceChanged()204     private void onSourceChanged() {
205         if (mLastSource.equals(mSource)) {
206             return;
207         }
208         mLastSource.set(mSource);
209         mDisplayContent.mWmService.mH.obtainMessage(
210                 UPDATE_MULTI_WINDOW_STACKS, mDisplayContent).sendToTarget();
211     }
212 
213     /**
214      * Called from {@link WindowManagerInternal#showImePostLayout}
215      * when {@link android.inputmethodservice.InputMethodService} requests to show IME
216      * on {@param imeTarget}.
217      *
218      * @param imeTarget imeTarget on which IME show request is coming from.
219      * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
220      */
scheduleShowImePostLayout(InsetsControlTarget imeTarget, @Nullable ImeTracker.Token statsToken)221     void scheduleShowImePostLayout(InsetsControlTarget imeTarget,
222             @Nullable ImeTracker.Token statsToken) {
223         boolean targetChanged = isTargetChangedWithinActivity(imeTarget);
224         mImeRequester = imeTarget;
225         // There was still a stats token, so that request presumably failed.
226         ImeTracker.forLogging().onFailed(
227                 mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
228         mImeRequesterStatsToken = statsToken;
229         if (targetChanged) {
230             // target changed, check if new target can show IME.
231             ProtoLog.d(WM_DEBUG_IME, "IME target changed within ActivityRecord");
232             checkShowImePostLayout();
233             // if IME cannot be shown at this time, it is scheduled to be shown.
234             // once window that called IMM.showSoftInput() and DisplayContent's ImeTarget match,
235             // it will be shown.
236             return;
237         }
238 
239         ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeRequester.getWindow() == null
240                 ? mImeRequester : mImeRequester.getWindow().getName());
241         mShowImeRunner = () -> {
242             ImeTracker.forLogging().onProgress(mImeRequesterStatsToken,
243                     ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
244             ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner");
245             // Target should still be the same.
246             if (isReadyToShowIme()) {
247                 ImeTracker.forLogging().onProgress(mImeRequesterStatsToken,
248                         ImeTracker.PHASE_WM_SHOW_IME_READY);
249                 final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
250 
251                 ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s",
252                         target.getWindow() != null ? target.getWindow().getName() : "");
253                 setImeShowing(true);
254                 target.showInsets(WindowInsets.Type.ime(), true /* fromIme */,
255                         mImeRequesterStatsToken);
256                 Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
257                 if (target != mImeRequester && mImeRequester != null) {
258                     ProtoLog.w(WM_DEBUG_IME,
259                             "showInsets(ime) was requested by different window: %s ",
260                             (mImeRequester.getWindow() != null
261                                     ? mImeRequester.getWindow().getName() : ""));
262                 }
263             } else {
264                 ImeTracker.forLogging().onFailed(mImeRequesterStatsToken,
265                         ImeTracker.PHASE_WM_SHOW_IME_READY);
266             }
267             // Clear token here so we don't report an error in abortShowImePostLayout().
268             mImeRequesterStatsToken = null;
269             abortShowImePostLayout();
270         };
271         mDisplayContent.mWmService.requestTraversal();
272     }
273 
checkShowImePostLayout()274     void checkShowImePostLayout() {
275         if (mWindowContainer == null) {
276             return;
277         }
278         WindowState windowState =  mWindowContainer.asWindowState();
279         if (windowState == null) {
280             throw new IllegalArgumentException("IME insets must be provided by a window.");
281         }
282         // check if IME is drawn
283         if (mIsImeLayoutDrawn
284                 || (isReadyToShowIme()
285                 && windowState.isDrawn()
286                 && !windowState.mGivenInsetsPending)) {
287             mIsImeLayoutDrawn = true;
288             // show IME if InputMethodService requested it to be shown.
289             if (mShowImeRunner != null) {
290                 mShowImeRunner.run();
291             }
292         }
293     }
294 
295     /**
296      * Abort any pending request to show IME post layout.
297      */
abortShowImePostLayout()298     void abortShowImePostLayout() {
299         ProtoLog.d(WM_DEBUG_IME, "abortShowImePostLayout");
300         mImeRequester = null;
301         mIsImeLayoutDrawn = false;
302         mShowImeRunner = null;
303         ImeTracker.forLogging().onCancelled(
304                 mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
305         mImeRequesterStatsToken = null;
306     }
307 
308     @VisibleForTesting
isReadyToShowIme()309     boolean isReadyToShowIme() {
310         // IMMS#mLastImeTargetWindow always considers focused window as
311         // IME target, however DisplayContent#computeImeTarget() can compute
312         // a different IME target.
313         // Refer to WindowManagerService#applyImeVisibility(token, false).
314         // If IMMS's imeTarget is child of DisplayContent's imeTarget and child window
315         // is above the parent, we will consider it as the same target for now.
316         // Also, if imeTarget is closing, it would be considered as outdated target.
317         // TODO(b/139861270): Remove the child & sublayer check once IMMS is aware of
318         //  actual IME target.
319         final InsetsControlTarget dcTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING);
320         if (dcTarget == null || mImeRequester == null) {
321             return false;
322         }
323         // Not ready to show if there is no IME control target.
324         final InsetsControlTarget controlTarget = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
325         if (controlTarget == null) {
326             return false;
327         }
328 
329         ProtoLog.d(WM_DEBUG_IME, "dcTarget: %s mImeRequester: %s",
330                 dcTarget.getWindow().getName(), mImeRequester.getWindow() == null
331                         ? mImeRequester : mImeRequester.getWindow().getName());
332 
333         return isImeLayeringTarget(mImeRequester, dcTarget)
334                 || isAboveImeLayeringTarget(mImeRequester, dcTarget)
335                 || isImeFallbackTarget(mImeRequester)
336                 || isImeInputTarget(mImeRequester)
337                 || sameAsImeControlTarget();
338     }
339 
340     // ---------------------------------------------------------------------------------------
341     // Methods for checking IME insets target changing state.
342     //
isImeLayeringTarget(@onNull InsetsControlTarget target, @NonNull InsetsControlTarget dcTarget)343     private static boolean isImeLayeringTarget(@NonNull InsetsControlTarget target,
344             @NonNull InsetsControlTarget dcTarget) {
345         return !isImeTargetWindowClosing(dcTarget.getWindow()) && target == dcTarget;
346     }
347 
isAboveImeLayeringTarget(@onNull InsetsControlTarget target, @NonNull InsetsControlTarget dcTarget)348     private static boolean isAboveImeLayeringTarget(@NonNull InsetsControlTarget target,
349             @NonNull InsetsControlTarget dcTarget) {
350         return target.getWindow() != null
351                 && dcTarget.getWindow().getParentWindow() == target
352                 && dcTarget.getWindow().mSubLayer > target.getWindow().mSubLayer;
353     }
354 
isImeFallbackTarget(InsetsControlTarget target)355     private boolean isImeFallbackTarget(InsetsControlTarget target) {
356         return target == mDisplayContent.getImeFallback();
357     }
358 
isImeInputTarget(InsetsControlTarget target)359     private boolean isImeInputTarget(InsetsControlTarget target) {
360         return target == mDisplayContent.getImeInputTarget();
361     }
362 
sameAsImeControlTarget()363     private boolean sameAsImeControlTarget() {
364         final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
365         return target == mImeRequester
366                 && (mImeRequester.getWindow() == null
367                 || !isImeTargetWindowClosing(mImeRequester.getWindow()));
368     }
369 
isImeTargetWindowClosing(@onNull WindowState win)370     private static boolean isImeTargetWindowClosing(@NonNull WindowState win) {
371         return win.mAnimatingExit || win.mActivityRecord != null
372                 && (win.mActivityRecord.isInTransition()
373                     && !win.mActivityRecord.isVisibleRequested()
374                     || win.mActivityRecord.willCloseOrEnterPip());
375     }
376 
isTargetChangedWithinActivity(InsetsControlTarget target)377     private boolean isTargetChangedWithinActivity(InsetsControlTarget target) {
378         // We don't consider the target out of the activity.
379         if (target == null || target.getWindow() == null) {
380             return false;
381         }
382         return mImeRequester != target
383                 && mImeRequester != null && mShowImeRunner != null
384                 && mImeRequester.getWindow() != null
385                 && mImeRequester.getWindow().mActivityRecord
386                 == target.getWindow().mActivityRecord;
387     }
388     // ---------------------------------------------------------------------------------------
389 
390     @Override
dump(PrintWriter pw, String prefix)391     public void dump(PrintWriter pw, String prefix) {
392         super.dump(pw, prefix);
393         prefix = prefix + "  ";
394         pw.print(prefix);
395         pw.print("mImeShowing=");
396         pw.print(mImeShowing);
397         if (mImeRequester != null) {
398             pw.print(prefix);
399             pw.print("showImePostLayout pending for mImeRequester=");
400             pw.print(mImeRequester);
401             pw.println();
402         }
403         pw.println();
404     }
405 
406     @Override
dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel)407     void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) {
408         final long token = proto.start(fieldId);
409         super.dumpDebug(proto, INSETS_SOURCE_PROVIDER, logLevel);
410         final WindowState imeRequesterWindow =
411                 mImeRequester != null ? mImeRequester.getWindow() : null;
412         if (imeRequesterWindow != null) {
413             imeRequesterWindow.dumpDebug(proto, IME_TARGET_FROM_IME, logLevel);
414         }
415         proto.write(IS_IME_LAYOUT_DRAWN, mIsImeLayoutDrawn);
416         proto.end(token);
417     }
418 
419     /**
420      * Sets whether the IME is currently supposed to be showing according to
421      * InputMethodManagerService.
422      */
setImeShowing(boolean imeShowing)423     public void setImeShowing(boolean imeShowing) {
424         mImeShowing = imeShowing;
425     }
426 
427     /**
428      * Returns whether the IME is currently supposed to be showing according to
429      * InputMethodManagerService.
430      */
isImeShowing()431     public boolean isImeShowing() {
432         return mImeShowing;
433     }
434 }
435