1 /*
2  * Copyright (C) 2023 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 
20 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.os.IBinder;
25 import android.view.WindowManager;
26 
27 /**
28  * A class for {@link com.android.server.inputmethod.InputMethodManagerService} to
29  * control IME visibility operations in {@link WindowManagerService}.
30  */
31 public abstract class ImeTargetVisibilityPolicy {
32 
33     /**
34      * Shows the IME screenshot and attach it to the given IME target window.
35      *
36      * @param imeTarget The target window to show the IME screenshot.
37      * @param displayId A unique id to identify the display.
38      * @return {@code true} if success, {@code false} otherwise.
39      */
showImeScreenshot(@onNull IBinder imeTarget, int displayId)40     public abstract boolean showImeScreenshot(@NonNull IBinder imeTarget, int displayId);
41 
42     /**
43      * Removes the IME screenshot on the given display.
44      *
45      * @param displayId The target display of showing IME screenshot.
46      * @return {@code true} if success, {@code false} otherwise.
47      */
removeImeScreenshot(int displayId)48     public abstract boolean removeImeScreenshot(int displayId);
49 
50     /**
51      * Called when {@link DisplayContent#computeImeParent()} to check if it's valid to keep
52      * computing the ime parent.
53      *
54      * @param imeLayeringTarget The window which IME target to layer on top of it.
55      * @param imeInputTarget The window which start the input connection, receive input from IME.
56      * @return {@code true} to keep computing the ime parent, {@code false} to defer this operation
57      */
canComputeImeParent(@ullable WindowState imeLayeringTarget, @Nullable InputTarget imeInputTarget)58     public static boolean canComputeImeParent(@Nullable WindowState imeLayeringTarget,
59             @Nullable InputTarget imeInputTarget) {
60         if (imeLayeringTarget == null) {
61             return false;
62         }
63         if (shouldComputeImeParentForEmbeddedActivity(imeLayeringTarget, imeInputTarget)) {
64             return true;
65         }
66         // Ensure changing the IME parent when the layering target that may use IME has
67         // became to the input target for preventing IME flickers.
68         // Note that:
69         // 1) For the imeLayeringTarget that may not use IME but requires IME on top
70         // of it (e.g. an overlay window with NOT_FOCUSABLE|ALT_FOCUSABLE_IM flags), we allow
71         // it to re-parent the IME on top the display to keep the legacy behavior.
72         // 2) Even though the starting window won't use IME, the associated activity
73         // behind the starting window may request the input. If so, then we should still hold
74         // the IME parent change until the activity started the input.
75         boolean imeLayeringTargetMayUseIme =
76                 WindowManager.LayoutParams.mayUseInputMethod(imeLayeringTarget.mAttrs.flags)
77                         || imeLayeringTarget.mAttrs.type == TYPE_APPLICATION_STARTING;
78         // Do not change parent if the window hasn't requested IME.
79         var inputAndLayeringTargetsDisagree = (imeInputTarget == null
80                 || imeLayeringTarget.mActivityRecord != imeInputTarget.getActivityRecord());
81         var inputTargetStale = imeLayeringTargetMayUseIme && inputAndLayeringTargetsDisagree;
82 
83         return !inputTargetStale;
84     }
85 
86 
87     /**
88      * Called from {@link DisplayContent#computeImeParent()} to check the given IME targets if the
89      * IME surface parent should be updated in ActivityEmbeddings.
90      *
91      * As the IME layering target is calculated according to the window hierarchy by
92      * {@link DisplayContent#computeImeTarget}, the layering target and input target may be
93      * different when the window hasn't started input connection, WindowManagerService hasn't yet
94      * received the input target which reported from InputMethodManagerService. To make the IME
95      * surface will be shown on the best fit IME layering target, we basically won't update IME
96      * parent until both IME input and layering target updated for better IME transition.
97      *
98      * However, in activity embedding, tapping a window won't update it to the top window so the
99      * calculated IME layering target may higher than input target. Update IME parent for this case.
100      *
101      * @return {@code true} means the layer of IME layering target is higher than the input target
102      * and {@link DisplayContent#computeImeParent()} should keep progressing to update the IME
103      * surface parent on the display in case the IME surface left behind.
104      */
shouldComputeImeParentForEmbeddedActivity( @ullable WindowState imeLayeringTarget, @Nullable InputTarget imeInputTarget)105     private static boolean shouldComputeImeParentForEmbeddedActivity(
106             @Nullable WindowState imeLayeringTarget, @Nullable InputTarget imeInputTarget) {
107         if (imeInputTarget == null || imeLayeringTarget == null) {
108             return false;
109         }
110         final WindowState inputTargetWindow = imeInputTarget.getWindowState();
111         if (inputTargetWindow == null || !imeLayeringTarget.isAttached()
112                 || !inputTargetWindow.isAttached()) {
113             return false;
114         }
115 
116         final ActivityRecord inputTargetRecord = imeInputTarget.getActivityRecord();
117         final ActivityRecord layeringTargetRecord = imeLayeringTarget.getActivityRecord();
118         if (inputTargetRecord == null || layeringTargetRecord == null
119                 || inputTargetRecord == layeringTargetRecord
120                 || (inputTargetRecord.getTask() != layeringTargetRecord.getTask())
121                 || !inputTargetRecord.isEmbedded() || !layeringTargetRecord.isEmbedded()) {
122             // Check whether the input target and layering target are embedded in the same Task.
123             return false;
124         }
125         return imeLayeringTarget.compareTo(inputTargetWindow) > 0;
126     }
127 }
128