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