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.annotation.Nullable;
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 InsetsSourceProvider {
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()83     void updateSourceFrame() {
84         super.updateSourceFrame();
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                 mWin.mWmService.mAtmService.mTaskOrganizerController.reportImeDrawnOnTask(
117                         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         // check if IME is drawn
177         if (mIsImeLayoutDrawn
178                 || (isReadyToShowIme()
179                 && mWin != null
180                 && mWin.isDrawn()
181                 && !mWin.mGivenInsetsPending)) {
182             mIsImeLayoutDrawn = true;
183             // show IME if InputMethodService requested it to be shown.
184             if (mShowImeRunner != null) {
185                 mShowImeRunner.run();
186             }
187         }
188     }
189 
190     /**
191      * Abort any pending request to show IME post layout.
192      */
abortShowImePostLayout()193     void abortShowImePostLayout() {
194         ProtoLog.d(WM_DEBUG_IME, "abortShowImePostLayout");
195         mImeRequester = null;
196         mIsImeLayoutDrawn = false;
197         mShowImeRunner = null;
198     }
199 
200     @VisibleForTesting
isReadyToShowIme()201     boolean isReadyToShowIme() {
202         // IMMS#mLastImeTargetWindow always considers focused window as
203         // IME target, however DisplayContent#computeImeTarget() can compute
204         // a different IME target.
205         // Refer to WindowManagerService#applyImeVisibility(token, false).
206         // If IMMS's imeTarget is child of DisplayContent's imeTarget and child window
207         // is above the parent, we will consider it as the same target for now.
208         // Also, if imeTarget is closing, it would be considered as outdated target.
209         // TODO(b/139861270): Remove the child & sublayer check once IMMS is aware of
210         //  actual IME target.
211         final InsetsControlTarget dcTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING);
212         if (dcTarget == null || mImeRequester == null) {
213             return false;
214         }
215         ProtoLog.d(WM_DEBUG_IME, "dcTarget: %s mImeRequester: %s",
216                 dcTarget.getWindow().getName(), mImeRequester.getWindow() == null
217                         ? mImeRequester : mImeRequester.getWindow().getName());
218 
219         return isImeLayeringTarget(mImeRequester, dcTarget)
220                 || isAboveImeLayeringTarget(mImeRequester, dcTarget)
221                 || isImeFallbackTarget(mImeRequester)
222                 || isImeInputTarget(mImeRequester)
223                 || sameAsImeControlTarget();
224     }
225 
226     // ---------------------------------------------------------------------------------------
227     // Methods for checking IME insets target changing state.
228     //
isImeLayeringTarget(@onNull InsetsControlTarget target, @NonNull InsetsControlTarget dcTarget)229     private static boolean isImeLayeringTarget(@NonNull InsetsControlTarget target,
230             @NonNull InsetsControlTarget dcTarget) {
231         return !dcTarget.getWindow().isClosing() && target == dcTarget;
232     }
233 
isAboveImeLayeringTarget(@onNull InsetsControlTarget target, @NonNull InsetsControlTarget dcTarget)234     private static boolean isAboveImeLayeringTarget(@NonNull InsetsControlTarget target,
235             @NonNull InsetsControlTarget dcTarget) {
236         return target.getWindow() != null
237                 && dcTarget.getWindow().getParentWindow() == target
238                 && dcTarget.getWindow().mSubLayer > target.getWindow().mSubLayer;
239     }
240 
isImeFallbackTarget(InsetsControlTarget target)241     private boolean isImeFallbackTarget(InsetsControlTarget target) {
242         return target == mDisplayContent.getImeFallback();
243     }
244 
isImeInputTarget(InsetsControlTarget target)245     private boolean isImeInputTarget(InsetsControlTarget target) {
246         return target == mDisplayContent.getImeTarget(IME_TARGET_INPUT);
247     }
248 
sameAsImeControlTarget()249     private boolean sameAsImeControlTarget() {
250         final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
251         return target == mImeRequester
252                 && (mImeRequester.getWindow() == null
253                 || !mImeRequester.getWindow().isClosing());
254     }
255 
isTargetChangedWithinActivity(InsetsControlTarget target)256     private boolean isTargetChangedWithinActivity(InsetsControlTarget target) {
257         // We don't consider the target out of the activity.
258         if (target == null || target.getWindow() == null) {
259             return false;
260         }
261         return mImeRequester != target
262                 && mImeRequester != null && mShowImeRunner != null
263                 && mImeRequester.getWindow() != null
264                 && mImeRequester.getWindow().mActivityRecord
265                 == target.getWindow().mActivityRecord;
266     }
267     // ---------------------------------------------------------------------------------------
268 
269     @Override
dump(PrintWriter pw, String prefix)270     public void dump(PrintWriter pw, String prefix) {
271         super.dump(pw, prefix);
272         prefix = prefix + "  ";
273         pw.print(prefix);
274         pw.print("mImeShowing=");
275         pw.print(mImeShowing);
276         if (mImeRequester != null) {
277             pw.print(prefix);
278             pw.print("showImePostLayout pending for mImeRequester=");
279             pw.print(mImeRequester);
280             pw.println();
281         }
282         pw.println();
283     }
284 
285     @Override
dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel)286     void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) {
287         final long token = proto.start(fieldId);
288         super.dumpDebug(proto, INSETS_SOURCE_PROVIDER, logLevel);
289         final WindowState imeRequesterWindow =
290                 mImeRequester != null ? mImeRequester.getWindow() : null;
291         if (imeRequesterWindow != null) {
292             imeRequesterWindow.dumpDebug(proto, IME_TARGET_FROM_IME, logLevel);
293         }
294         proto.write(IS_IME_LAYOUT_DRAWN, mIsImeLayoutDrawn);
295         proto.end(token);
296     }
297 
298     /**
299      * Sets whether the IME is currently supposed to be showing according to
300      * InputMethodManagerService.
301      */
setImeShowing(boolean imeShowing)302     public void setImeShowing(boolean imeShowing) {
303         mImeShowing = imeShowing;
304     }
305 
306     /**
307      * Returns whether the IME is currently supposed to be showing according to
308      * InputMethodManagerService.
309      */
isImeShowing()310     public boolean isImeShowing() {
311         return mImeShowing;
312     }
313 }
314