1 /* 2 * Copyright (C) 2018 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.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 21 import static android.view.InsetsState.ITYPE_CAPTION_BAR; 22 import static android.view.InsetsState.ITYPE_CLIMATE_BAR; 23 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; 24 import static android.view.InsetsState.ITYPE_IME; 25 import static android.view.InsetsState.ITYPE_INVALID; 26 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; 27 import static android.view.InsetsState.ITYPE_STATUS_BAR; 28 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; 29 import static android.view.ViewRootImpl.sNewInsetsMode; 30 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; 31 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; 32 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; 33 34 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME; 35 36 import android.annotation.NonNull; 37 import android.annotation.Nullable; 38 import android.app.WindowConfiguration; 39 import android.app.WindowConfiguration.WindowingMode; 40 import android.util.ArrayMap; 41 import android.util.ArraySet; 42 import android.util.SparseArray; 43 import android.view.InsetsSource; 44 import android.view.InsetsSourceControl; 45 import android.view.InsetsState; 46 import android.view.InsetsState.InternalInsetsType; 47 import android.view.WindowManager; 48 import android.view.WindowManager.LayoutParams.WindowType; 49 50 import com.android.server.inputmethod.InputMethodManagerInternal; 51 import com.android.server.protolog.common.ProtoLog; 52 53 import java.io.PrintWriter; 54 import java.util.ArrayList; 55 import java.util.function.Consumer; 56 57 /** 58 * Manages global window inset state in the system represented by {@link InsetsState}. 59 */ 60 class InsetsStateController { 61 62 private final InsetsState mLastState = new InsetsState(); 63 private final InsetsState mState = new InsetsState(); 64 private final DisplayContent mDisplayContent; 65 66 private final ArrayMap<Integer, InsetsSourceProvider> mProviders = new ArrayMap<>(); 67 private final ArrayMap<InsetsControlTarget, ArrayList<Integer>> mControlTargetTypeMap = 68 new ArrayMap<>(); 69 private final SparseArray<InsetsControlTarget> mTypeControlTargetMap = new SparseArray<>(); 70 71 /** @see #onControlFakeTargetChanged */ 72 private final SparseArray<InsetsControlTarget> mTypeFakeControlTargetMap = new SparseArray<>(); 73 74 private final ArraySet<InsetsControlTarget> mPendingControlChanged = new ArraySet<>(); 75 76 private final Consumer<WindowState> mDispatchInsetsChanged = w -> { 77 if (w.isVisible()) { 78 w.notifyInsetsChanged(); 79 } 80 }; 81 private final InsetsControlTarget mEmptyImeControlTarget = new InsetsControlTarget() { 82 @Override 83 public void notifyInsetsControlChanged() { 84 InsetsSourceControl[] controls = getControlsForDispatch(this); 85 if (controls == null) { 86 return; 87 } 88 for (InsetsSourceControl control : controls) { 89 if (control.getType() == ITYPE_IME) { 90 mDisplayContent.mWmService.mH.post(() -> 91 InputMethodManagerInternal.get().removeImeSurface()); 92 } 93 } 94 } 95 }; 96 InsetsStateController(DisplayContent displayContent)97 InsetsStateController(DisplayContent displayContent) { 98 mDisplayContent = displayContent; 99 } 100 101 /** 102 * When dispatching window state to the client, we'll need to exclude the source that represents 103 * the window that is being dispatched. We also need to exclude certain types of insets source 104 * for client within specific windowing modes. 105 * 106 * @param target The client we dispatch the state to. 107 * @return The state stripped of the necessary information. 108 */ getInsetsForDispatch(@onNull WindowState target)109 InsetsState getInsetsForDispatch(@NonNull WindowState target) { 110 final InsetsState rotatedState = target.mToken.getFixedRotationTransformInsetsState(); 111 if (rotatedState != null) { 112 return rotatedState; 113 } 114 final InsetsSourceProvider provider = target.getControllableInsetProvider(); 115 final @InternalInsetsType int type = provider != null 116 ? provider.getSource().getType() : ITYPE_INVALID; 117 return getInsetsForDispatchInner(type, target.getWindowingMode(), target.isAlwaysOnTop(), 118 isAboveIme(target)); 119 } 120 getInsetsForWindowMetrics(@onNull WindowManager.LayoutParams attrs)121 InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) { 122 final @InternalInsetsType int type = getInsetsTypeForLayoutParams(attrs); 123 final WindowToken token = mDisplayContent.getWindowToken(attrs.token); 124 final @WindowingMode int windowingMode = token != null 125 ? token.getWindowingMode() : WINDOWING_MODE_UNDEFINED; 126 final boolean alwaysOnTop = token != null && token.isAlwaysOnTop(); 127 return getInsetsForDispatchInner(type, windowingMode, alwaysOnTop, isAboveIme(token)); 128 } 129 isAboveIme(WindowContainer target)130 private boolean isAboveIme(WindowContainer target) { 131 final WindowState imeWindow = mDisplayContent.mInputMethodWindow; 132 if (target == null || imeWindow == null) { 133 return false; 134 } 135 if (target instanceof WindowState) { 136 final WindowState win = (WindowState) target; 137 return win.needsRelativeLayeringToIme() || !win.mBehindIme; 138 } 139 return false; 140 } 141 142 private static @InternalInsetsType getInsetsTypeForLayoutParams(WindowManager.LayoutParams attrs)143 int getInsetsTypeForLayoutParams(WindowManager.LayoutParams attrs) { 144 @WindowType int type = attrs.type; 145 switch (type) { 146 case TYPE_STATUS_BAR: 147 return ITYPE_STATUS_BAR; 148 case TYPE_NAVIGATION_BAR: 149 return ITYPE_NAVIGATION_BAR; 150 case TYPE_INPUT_METHOD: 151 return ITYPE_IME; 152 } 153 154 // If not one of the types above, check whether an internal inset mapping is specified. 155 if (attrs.providesInsetsTypes != null) { 156 for (@InternalInsetsType int insetsType : attrs.providesInsetsTypes) { 157 switch (insetsType) { 158 case ITYPE_STATUS_BAR: 159 case ITYPE_NAVIGATION_BAR: 160 case ITYPE_CLIMATE_BAR: 161 case ITYPE_EXTRA_NAVIGATION_BAR: 162 return insetsType; 163 } 164 } 165 } 166 167 return ITYPE_INVALID; 168 } 169 170 /** @see #getInsetsForDispatch */ getInsetsForDispatchInner(@nternalInsetsType int type, @WindowingMode int windowingMode, boolean isAlwaysOnTop, boolean aboveIme)171 private InsetsState getInsetsForDispatchInner(@InternalInsetsType int type, 172 @WindowingMode int windowingMode, boolean isAlwaysOnTop, boolean aboveIme) { 173 InsetsState state = mState; 174 175 if (type != ITYPE_INVALID) { 176 state = new InsetsState(state); 177 state.removeSource(type); 178 179 // Navigation bar doesn't get influenced by anything else 180 if (type == ITYPE_NAVIGATION_BAR || type == ITYPE_EXTRA_NAVIGATION_BAR) { 181 state.removeSource(ITYPE_IME); 182 state.removeSource(ITYPE_STATUS_BAR); 183 state.removeSource(ITYPE_CLIMATE_BAR); 184 state.removeSource(ITYPE_CAPTION_BAR); 185 } 186 187 // Status bar doesn't get influenced by caption bar 188 if (type == ITYPE_STATUS_BAR || type == ITYPE_CLIMATE_BAR) { 189 state.removeSource(ITYPE_CAPTION_BAR); 190 } 191 192 // IME needs different frames for certain cases (e.g. navigation bar in gesture nav). 193 if (type == ITYPE_IME) { 194 for (int i = mProviders.size() - 1; i >= 0; i--) { 195 InsetsSourceProvider otherProvider = mProviders.valueAt(i); 196 if (otherProvider.overridesImeFrame()) { 197 InsetsSource override = 198 new InsetsSource( 199 state.getSource(otherProvider.getSource().getType())); 200 override.setFrame(otherProvider.getImeOverrideFrame()); 201 state.addSource(override); 202 } 203 } 204 } 205 } 206 207 if (WindowConfiguration.isFloating(windowingMode) 208 || (windowingMode == WINDOWING_MODE_MULTI_WINDOW && isAlwaysOnTop)) { 209 state = new InsetsState(state); 210 state.removeSource(ITYPE_STATUS_BAR); 211 state.removeSource(ITYPE_NAVIGATION_BAR); 212 } 213 214 if (aboveIme) { 215 InsetsSource imeSource = state.peekSource(ITYPE_IME); 216 if (imeSource != null && imeSource.isVisible()) { 217 imeSource = new InsetsSource(imeSource); 218 imeSource.setVisible(false); 219 imeSource.setFrame(0, 0, 0, 0); 220 state = new InsetsState(state); 221 state.addSource(imeSource); 222 } 223 } 224 225 return state; 226 } 227 getRawInsetsState()228 InsetsState getRawInsetsState() { 229 return mState; 230 } 231 getControlsForDispatch(InsetsControlTarget target)232 @Nullable InsetsSourceControl[] getControlsForDispatch(InsetsControlTarget target) { 233 ArrayList<Integer> controlled = mControlTargetTypeMap.get(target); 234 if (controlled == null) { 235 return null; 236 } 237 final int size = controlled.size(); 238 final InsetsSourceControl[] result = new InsetsSourceControl[size]; 239 for (int i = 0; i < size; i++) { 240 result[i] = mProviders.get(controlled.get(i)).getControl(target); 241 } 242 return result; 243 } 244 245 /** 246 * @return The provider of a specific type. 247 */ getSourceProvider(@nternalInsetsType int type)248 InsetsSourceProvider getSourceProvider(@InternalInsetsType int type) { 249 if (type == ITYPE_IME) { 250 return mProviders.computeIfAbsent(type, 251 key -> new ImeInsetsSourceProvider( 252 mState.getSource(key), this, mDisplayContent)); 253 } else { 254 return mProviders.computeIfAbsent(type, 255 key -> new InsetsSourceProvider(mState.getSource(key), this, mDisplayContent)); 256 } 257 } 258 getImeSourceProvider()259 ImeInsetsSourceProvider getImeSourceProvider() { 260 return (ImeInsetsSourceProvider) getSourceProvider(ITYPE_IME); 261 } 262 263 /** 264 * @return The provider of a specific type or null if we don't have it. 265 */ peekSourceProvider(@nternalInsetsType int type)266 @Nullable InsetsSourceProvider peekSourceProvider(@InternalInsetsType int type) { 267 return mProviders.get(type); 268 } 269 270 /** 271 * Called when a layout pass has occurred. 272 */ onPostLayout()273 void onPostLayout() { 274 mState.setDisplayFrame(mDisplayContent.getBounds()); 275 for (int i = mProviders.size() - 1; i >= 0; i--) { 276 mProviders.valueAt(i).onPostLayout(); 277 } 278 final ArrayList<WindowState> winInsetsChanged = mDisplayContent.mWinInsetsChanged; 279 if (!mLastState.equals(mState)) { 280 mLastState.set(mState, true /* copySources */); 281 notifyInsetsChanged(); 282 } else { 283 // The global insets state has not changed but there might be windows whose conditions 284 // (e.g., z-order) have changed. They can affect the insets states that we dispatch to 285 // the clients. 286 for (int i = winInsetsChanged.size() - 1; i >= 0; i--) { 287 mDispatchInsetsChanged.accept(winInsetsChanged.get(i)); 288 } 289 } 290 winInsetsChanged.clear(); 291 } 292 onInsetsModified(InsetsControlTarget windowState, InsetsState state)293 void onInsetsModified(InsetsControlTarget windowState, InsetsState state) { 294 boolean changed = false; 295 for (int i = 0; i < InsetsState.SIZE; i++) { 296 final InsetsSource source = state.peekSource(i); 297 if (source == null) continue; 298 final InsetsSourceProvider provider = mProviders.get(source.getType()); 299 if (provider == null) { 300 continue; 301 } 302 changed |= provider.onInsetsModified(windowState, source); 303 } 304 if (changed) { 305 notifyInsetsChanged(); 306 mDisplayContent.updateSystemGestureExclusion(); 307 mDisplayContent.getDisplayPolicy().updateSystemUiVisibilityLw(); 308 } 309 } 310 311 /** 312 * Computes insets state of the insets provider window in the display frames. 313 * 314 * @param state The output state. 315 * @param win The owner window of insets provider. 316 * @param displayFrames The display frames to create insets source. 317 * @param windowFrames The specified frames to represent the owner window. 318 */ computeSimulatedState(InsetsState state, WindowState win, DisplayFrames displayFrames, WindowFrames windowFrames)319 void computeSimulatedState(InsetsState state, WindowState win, DisplayFrames displayFrames, 320 WindowFrames windowFrames) { 321 for (int i = mProviders.size() - 1; i >= 0; i--) { 322 final InsetsSourceProvider provider = mProviders.valueAt(i); 323 if (provider.mWin == win) { 324 state.addSource(provider.createSimulatedSource(displayFrames, windowFrames)); 325 } 326 } 327 } 328 isFakeTarget(@nternalInsetsType int type, InsetsControlTarget target)329 boolean isFakeTarget(@InternalInsetsType int type, InsetsControlTarget target) { 330 return mTypeFakeControlTargetMap.get(type) == target; 331 } 332 onImeControlTargetChanged(@ullable InsetsControlTarget imeTarget)333 void onImeControlTargetChanged(@Nullable InsetsControlTarget imeTarget) { 334 335 // Make sure that we always have a control target for the IME, even if the IME target is 336 // null. Otherwise there is no leash that will hide it and IME becomes "randomly" visible. 337 InsetsControlTarget target = imeTarget != null ? imeTarget : mEmptyImeControlTarget; 338 onControlChanged(ITYPE_IME, target); 339 ProtoLog.d(WM_DEBUG_IME, "onImeControlTargetChanged %s", 340 target != null ? target.getWindow() : "null"); 341 notifyPendingInsetsControlChanged(); 342 } 343 344 /** 345 * Called when the focused window that is able to control the system bars changes. 346 * 347 * @param statusControlling The target that is now able to control the status bar appearance 348 * and visibility. 349 * @param navControlling The target that is now able to control the nav bar appearance 350 * and visibility. 351 */ onBarControlTargetChanged(@ullable InsetsControlTarget statusControlling, @Nullable InsetsControlTarget fakeStatusControlling, @Nullable InsetsControlTarget navControlling, @Nullable InsetsControlTarget fakeNavControlling)352 void onBarControlTargetChanged(@Nullable InsetsControlTarget statusControlling, 353 @Nullable InsetsControlTarget fakeStatusControlling, 354 @Nullable InsetsControlTarget navControlling, 355 @Nullable InsetsControlTarget fakeNavControlling) { 356 onControlChanged(ITYPE_STATUS_BAR, statusControlling); 357 onControlChanged(ITYPE_NAVIGATION_BAR, navControlling); 358 onControlChanged(ITYPE_CLIMATE_BAR, statusControlling); 359 onControlChanged(ITYPE_EXTRA_NAVIGATION_BAR, navControlling); 360 onControlFakeTargetChanged(ITYPE_STATUS_BAR, fakeStatusControlling); 361 onControlFakeTargetChanged(ITYPE_NAVIGATION_BAR, fakeNavControlling); 362 onControlFakeTargetChanged(ITYPE_CLIMATE_BAR, fakeStatusControlling); 363 onControlFakeTargetChanged(ITYPE_EXTRA_NAVIGATION_BAR, fakeNavControlling); 364 notifyPendingInsetsControlChanged(); 365 } 366 notifyControlRevoked(@onNull InsetsControlTarget previousControlTarget, InsetsSourceProvider provider)367 void notifyControlRevoked(@NonNull InsetsControlTarget previousControlTarget, 368 InsetsSourceProvider provider) { 369 removeFromControlMaps(previousControlTarget, provider.getSource().getType(), 370 false /* fake */); 371 } 372 onControlChanged(@nternalInsetsType int type, @Nullable InsetsControlTarget target)373 private void onControlChanged(@InternalInsetsType int type, 374 @Nullable InsetsControlTarget target) { 375 final InsetsControlTarget previous = mTypeControlTargetMap.get(type); 376 if (target == previous) { 377 return; 378 } 379 final InsetsSourceProvider provider = mProviders.get(type); 380 if (provider == null) { 381 return; 382 } 383 if (!provider.isControllable()) { 384 return; 385 } 386 provider.updateControlForTarget(target, false /* force */); 387 target = provider.getControlTarget(); 388 if (previous != null) { 389 removeFromControlMaps(previous, type, false /* fake */); 390 mPendingControlChanged.add(previous); 391 } 392 if (target != null) { 393 addToControlMaps(target, type, false /* fake */); 394 mPendingControlChanged.add(target); 395 } 396 } 397 398 /** 399 * The fake target saved here will be used to pretend to the app that it's still under control 400 * of the bars while it's not really, but we still need to find out the apps intentions around 401 * showing/hiding. For example, when the transient bars are showing, and the fake target 402 * requests to show system bars, the transient state will be aborted. 403 */ onControlFakeTargetChanged(@nternalInsetsType int type, @Nullable InsetsControlTarget fakeTarget)404 void onControlFakeTargetChanged(@InternalInsetsType int type, 405 @Nullable InsetsControlTarget fakeTarget) { 406 if (sNewInsetsMode != NEW_INSETS_MODE_FULL) { 407 return; 408 } 409 final InsetsControlTarget previous = mTypeFakeControlTargetMap.get(type); 410 if (fakeTarget == previous) { 411 return; 412 } 413 final InsetsSourceProvider provider = mProviders.get(type); 414 if (provider == null) { 415 return; 416 } 417 provider.updateControlForFakeTarget(fakeTarget); 418 if (previous != null) { 419 removeFromControlMaps(previous, type, true /* fake */); 420 mPendingControlChanged.add(previous); 421 } 422 if (fakeTarget != null) { 423 addToControlMaps(fakeTarget, type, true /* fake */); 424 mPendingControlChanged.add(fakeTarget); 425 } 426 } 427 removeFromControlMaps(@onNull InsetsControlTarget target, @InternalInsetsType int type, boolean fake)428 private void removeFromControlMaps(@NonNull InsetsControlTarget target, 429 @InternalInsetsType int type, boolean fake) { 430 final ArrayList<Integer> array = mControlTargetTypeMap.get(target); 431 if (array == null) { 432 return; 433 } 434 array.remove((Integer) type); 435 if (array.isEmpty()) { 436 mControlTargetTypeMap.remove(target); 437 } 438 if (fake) { 439 mTypeFakeControlTargetMap.remove(type); 440 } else { 441 mTypeControlTargetMap.remove(type); 442 } 443 } 444 addToControlMaps(@onNull InsetsControlTarget target, @InternalInsetsType int type, boolean fake)445 private void addToControlMaps(@NonNull InsetsControlTarget target, 446 @InternalInsetsType int type, boolean fake) { 447 final ArrayList<Integer> array = mControlTargetTypeMap.computeIfAbsent(target, 448 key -> new ArrayList<>()); 449 array.add(type); 450 if (fake) { 451 mTypeFakeControlTargetMap.put(type, target); 452 } else { 453 mTypeControlTargetMap.put(type, target); 454 } 455 } 456 notifyControlChanged(InsetsControlTarget target)457 void notifyControlChanged(InsetsControlTarget target) { 458 mPendingControlChanged.add(target); 459 notifyPendingInsetsControlChanged(); 460 } 461 notifyPendingInsetsControlChanged()462 private void notifyPendingInsetsControlChanged() { 463 if (mPendingControlChanged.isEmpty()) { 464 return; 465 } 466 mDisplayContent.mWmService.mAnimator.addAfterPrepareSurfacesRunnable(() -> { 467 for (int i = mProviders.size() - 1; i >= 0; i--) { 468 final InsetsSourceProvider provider = mProviders.valueAt(i); 469 provider.onSurfaceTransactionApplied(); 470 } 471 for (int i = mPendingControlChanged.size() - 1; i >= 0; i--) { 472 final InsetsControlTarget controlTarget = mPendingControlChanged.valueAt(i); 473 controlTarget.notifyInsetsControlChanged(); 474 } 475 mPendingControlChanged.clear(); 476 }); 477 } 478 notifyInsetsChanged()479 void notifyInsetsChanged() { 480 mDisplayContent.notifyInsetsChanged(mDispatchInsetsChanged); 481 } 482 dump(String prefix, PrintWriter pw)483 void dump(String prefix, PrintWriter pw) { 484 pw.println(prefix + "WindowInsetsStateController"); 485 mState.dump(prefix + " ", pw); 486 pw.println(prefix + " " + "Control map:"); 487 for (int i = mTypeControlTargetMap.size() - 1; i >= 0; i--) { 488 pw.print(prefix + " "); 489 pw.println(InsetsState.typeToString(mTypeControlTargetMap.keyAt(i)) + " -> " 490 + mTypeControlTargetMap.valueAt(i)); 491 } 492 pw.println(prefix + " " + "InsetsSourceProviders map:"); 493 for (int i = mProviders.size() - 1; i >= 0; i--) { 494 pw.print(prefix + " "); 495 pw.println(InsetsState.typeToString(mProviders.keyAt(i)) + " -> "); 496 mProviders.valueAt(i).dump(pw, prefix); 497 } 498 } 499 } 500