Merge "Revert "WindowList be gone!""
This commit is contained in:
@@ -411,7 +411,7 @@ public class AppWindowAnimator {
|
||||
}
|
||||
if (mService.mInputMethodTarget != null
|
||||
&& mService.mInputMethodTarget.mAppToken == mAppToken) {
|
||||
mAppToken.getDisplayContent().computeImeTarget(true /* updateImeTarget */);
|
||||
mAppToken.getDisplayContent().moveInputMethodWindowsIfNeeded(true);
|
||||
}
|
||||
|
||||
if (DEBUG_ANIM) Slog.v(TAG, "Animation done in " + mAppToken
|
||||
|
||||
@@ -1006,7 +1006,10 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
|
||||
tStartingWindow.mToken = this;
|
||||
tStartingWindow.mAppToken = this;
|
||||
|
||||
if (DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
|
||||
if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
|
||||
"Removing starting window: " + tStartingWindow);
|
||||
getDisplayContent().removeFromWindowList(tStartingWindow);
|
||||
if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
|
||||
"Removing starting " + tStartingWindow + " from " + fromToken);
|
||||
fromToken.removeChild(tStartingWindow);
|
||||
fromToken.postWindowRemoveStartingWindowCleanup(tStartingWindow);
|
||||
@@ -1256,6 +1259,18 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
|
||||
}
|
||||
}
|
||||
|
||||
int rebuildWindowListUnchecked(int addIndex) {
|
||||
return super.rebuildWindowList(addIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
int rebuildWindowList(int addIndex) {
|
||||
if (mIsExiting && !waitingForReplacement()) {
|
||||
return addIndex;
|
||||
}
|
||||
return rebuildWindowListUnchecked(addIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
|
||||
// For legacy reasons we process the TaskStack.mExitingAppTokens first in DisplayContent
|
||||
@@ -1317,32 +1332,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
|
||||
mLastContainsShowWhenLockedWindow = containsShowWhenLocked;
|
||||
}
|
||||
|
||||
WindowState getImeTargetBelowWindow(WindowState w) {
|
||||
final int index = mChildren.indexOf(w);
|
||||
if (index > 0) {
|
||||
final WindowState target = mChildren.get(index - 1);
|
||||
if (target.canBeImeTarget()) {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
WindowState getHighestAnimLayerWindow(WindowState currentTarget) {
|
||||
WindowState candidate = null;
|
||||
for (int i = mChildren.indexOf(currentTarget); i >= 0; i--) {
|
||||
final WindowState w = mChildren.get(i);
|
||||
if (w.mRemoved) {
|
||||
continue;
|
||||
}
|
||||
if (candidate == null || w.mWinAnimator.mAnimLayer >
|
||||
candidate.mWinAnimator.mAnimLayer) {
|
||||
candidate = w;
|
||||
}
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
@Override
|
||||
void dump(PrintWriter pw, String prefix) {
|
||||
super.dump(pw, prefix);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,15 +16,10 @@
|
||||
|
||||
package com.android.server.wm;
|
||||
|
||||
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION;
|
||||
import static android.view.WindowManager.INPUT_CONSUMER_PIP;
|
||||
import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER;
|
||||
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
|
||||
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS;
|
||||
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
|
||||
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
|
||||
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
|
||||
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
|
||||
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
|
||||
@@ -75,10 +70,6 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
|
||||
// Array of window handles to provide to the input dispatcher.
|
||||
private InputWindowHandle[] mInputWindowHandles;
|
||||
private int mInputWindowHandleCount;
|
||||
private boolean mAddInputConsumerHandle;
|
||||
private boolean mAddPipInputConsumerHandle;
|
||||
private boolean mAddWallpaperInputConsumerHandle;
|
||||
private boolean mDisableWallpaperTouchEvents;
|
||||
|
||||
// Set to true when the first input device configuration change notification
|
||||
// is received to indicate that the input devices are ready.
|
||||
@@ -332,12 +323,12 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
|
||||
}
|
||||
}
|
||||
|
||||
void setUpdateInputWindowsNeededLw() {
|
||||
public void setUpdateInputWindowsNeededLw() {
|
||||
mUpdateInputWindowsNeeded = true;
|
||||
}
|
||||
|
||||
/* Updates the cached window information provided to the input dispatcher. */
|
||||
void updateInputWindowsLw(boolean force) {
|
||||
public void updateInputWindowsLw(boolean force) {
|
||||
if (!force && !mUpdateInputWindowsNeeded) {
|
||||
return;
|
||||
}
|
||||
@@ -381,92 +372,15 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
|
||||
}
|
||||
|
||||
// Add all windows on the default display.
|
||||
updateInputWindows(inDrag);
|
||||
|
||||
if (false) Slog.d(TAG_WM, "<<<<<<< EXITED updateInputWindowsLw");
|
||||
}
|
||||
|
||||
private void updateInputWindows(boolean inDrag) {
|
||||
|
||||
clearInputWindowHandlesLw();
|
||||
|
||||
// TODO: multi-display
|
||||
final InputConsumerImpl navInputConsumer =
|
||||
getInputConsumer(INPUT_CONSUMER_NAVIGATION, DEFAULT_DISPLAY);
|
||||
final InputConsumerImpl pipInputConsumer =
|
||||
getInputConsumer(INPUT_CONSUMER_PIP, DEFAULT_DISPLAY);
|
||||
final InputConsumerImpl wallpaperInputConsumer =
|
||||
getInputConsumer(INPUT_CONSUMER_WALLPAPER, DEFAULT_DISPLAY);
|
||||
mAddInputConsumerHandle = navInputConsumer != null;
|
||||
mAddPipInputConsumerHandle = pipInputConsumer != null;
|
||||
mAddWallpaperInputConsumerHandle = wallpaperInputConsumer != null;
|
||||
final Rect pipTouchableBounds = mAddPipInputConsumerHandle ? new Rect() : null;
|
||||
mDisableWallpaperTouchEvents = false;
|
||||
|
||||
final WallpaperController wallpaperController = mService.mRoot.mWallpaperController;
|
||||
mService.mRoot.forAllWindows(w -> {
|
||||
final InputChannel inputChannel = w.mInputChannel;
|
||||
final InputWindowHandle inputWindowHandle = w.mInputWindowHandle;
|
||||
if (inputChannel == null || inputWindowHandle == null || w.mRemoved
|
||||
|| w.isAdjustedForMinimizedDock()) {
|
||||
// Skip this window because it cannot possibly receive input.
|
||||
return;
|
||||
}
|
||||
|
||||
if (mAddPipInputConsumerHandle
|
||||
&& w.getStackId() == PINNED_STACK_ID
|
||||
&& inputWindowHandle.layer <= pipInputConsumer.mWindowHandle.layer) {
|
||||
// Update the bounds of the Pip input consumer to match the Pinned stack
|
||||
w.getStack().getBounds(pipTouchableBounds);
|
||||
pipInputConsumer.mWindowHandle.touchableRegion.set(pipTouchableBounds);
|
||||
addInputWindowHandle(pipInputConsumer.mWindowHandle);
|
||||
mAddPipInputConsumerHandle = false;
|
||||
}
|
||||
|
||||
if (mAddInputConsumerHandle
|
||||
&& inputWindowHandle.layer <= navInputConsumer.mWindowHandle.layer) {
|
||||
addInputWindowHandle(navInputConsumer.mWindowHandle);
|
||||
mAddInputConsumerHandle = false;
|
||||
}
|
||||
|
||||
if (mAddWallpaperInputConsumerHandle) {
|
||||
if (w.mAttrs.type == TYPE_WALLPAPER && w.isVisibleLw()) {
|
||||
// Add the wallpaper input consumer above the first visible wallpaper.
|
||||
addInputWindowHandle(wallpaperInputConsumer.mWindowHandle);
|
||||
mAddWallpaperInputConsumerHandle = false;
|
||||
}
|
||||
}
|
||||
|
||||
final int flags = w.mAttrs.flags;
|
||||
final int privateFlags = w.mAttrs.privateFlags;
|
||||
final int type = w.mAttrs.type;
|
||||
|
||||
final boolean hasFocus = w == mInputFocus;
|
||||
final boolean isVisible = w.isVisibleLw();
|
||||
if ((privateFlags & PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS) != 0) {
|
||||
mDisableWallpaperTouchEvents = true;
|
||||
}
|
||||
final boolean hasWallpaper = wallpaperController.isWallpaperTarget(w)
|
||||
&& (privateFlags & PRIVATE_FLAG_KEYGUARD) == 0
|
||||
&& !mDisableWallpaperTouchEvents;
|
||||
|
||||
// If there's a drag in progress and 'child' is a potential drop target,
|
||||
// make sure it's been told about the drag
|
||||
if (inDrag && isVisible && w.getDisplayContent().isDefaultDisplay) {
|
||||
mService.mDragState.sendDragStartedIfNeededLw(w);
|
||||
}
|
||||
|
||||
addInputWindowHandle(
|
||||
inputWindowHandle, w, flags, type, isVisible, hasFocus, hasWallpaper);
|
||||
}, true /* traverseTopToBottom */);
|
||||
|
||||
if (mAddWallpaperInputConsumerHandle) {
|
||||
// No visible wallpaper found, add the wallpaper input consumer at the end.
|
||||
addInputWindowHandle(wallpaperInputConsumer.mWindowHandle);
|
||||
}
|
||||
mService.mRoot.updateInputWindows(this, mInputFocus, inDrag);
|
||||
|
||||
// Send windows to native code.
|
||||
mService.mInputManager.setInputWindows(mInputWindowHandles);
|
||||
|
||||
// Clear the list in preparation for the next round.
|
||||
clearInputWindowHandlesLw();
|
||||
|
||||
if (false) Slog.d(TAG_WM, "<<<<<<< EXITED updateInputWindowsLw");
|
||||
}
|
||||
|
||||
/* Notifies that the input device configuration has changed. */
|
||||
|
||||
@@ -219,7 +219,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> {
|
||||
return false;
|
||||
}
|
||||
|
||||
void getWindowsByName(ArrayList<WindowState> output, String name) {
|
||||
void getWindowsByName(WindowList output, String name) {
|
||||
int objectId = 0;
|
||||
// See if this is an object ID.
|
||||
try {
|
||||
@@ -231,7 +231,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> {
|
||||
getWindowsByName(output, name, objectId);
|
||||
}
|
||||
|
||||
private void getWindowsByName(ArrayList<WindowState> output, String name, int objectId) {
|
||||
private void getWindowsByName(WindowList output, String name, int objectId) {
|
||||
forAllWindows((w) -> {
|
||||
if (name != null) {
|
||||
if (w.mAttrs.getTitle().toString().contains(name)) {
|
||||
@@ -276,6 +276,15 @@ class RootWindowContainer extends WindowContainer<DisplayContent> {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: Users would have their own window containers under the display container?
|
||||
void switchUser() {
|
||||
final int count = mChildren.size();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
final DisplayContent dc = mChildren.get(i);
|
||||
dc.switchUser();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set new display override config and return array of ids of stacks that were changed during
|
||||
* update. If called for the default display, global configuration will also be updated.
|
||||
@@ -420,6 +429,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent> {
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
void updateInputWindows(InputMonitor inputMonitor, WindowState inputFocus, boolean inDrag) {
|
||||
final int count = mChildren.size();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
final DisplayContent dc = mChildren.get(i);
|
||||
dc.updateInputWindows(inputMonitor, inputFocus, inDrag);
|
||||
}
|
||||
}
|
||||
|
||||
boolean reclaimSomeSurfaceMemory(WindowStateAnimator winAnimator, String operation,
|
||||
boolean secure) {
|
||||
final WindowSurfaceController surfaceController = winAnimator.mSurfaceController;
|
||||
|
||||
@@ -589,6 +589,46 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
|
||||
}
|
||||
}
|
||||
|
||||
void getWindowOnDisplayBeforeToken(DisplayContent dc, WindowToken token,
|
||||
DisplayContent.GetWindowOnDisplaySearchResult result) {
|
||||
for (int i = mChildren.size() - 1; i >= 0; --i) {
|
||||
final AppWindowToken current = mChildren.get(i);
|
||||
if (current == token) {
|
||||
// We have reach the token we are interested in. End search.
|
||||
result.reachedToken = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// We haven't reached the token yet; if this token is not going to the bottom and
|
||||
// has windows on this display, then it is a candidate for what we are looking for.
|
||||
final WindowList tokenWindowList = dc.getTokenWindowsOnDisplay(current);
|
||||
if (!current.sendingToBottom && tokenWindowList.size() > 0) {
|
||||
result.foundWindow = tokenWindowList.get(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void getWindowOnDisplayAfterToken(DisplayContent dc, WindowToken token,
|
||||
DisplayContent.GetWindowOnDisplaySearchResult result) {
|
||||
for (int i = mChildren.size() - 1; i >= 0; --i) {
|
||||
final AppWindowToken current = mChildren.get(i);
|
||||
if (!result.reachedToken) {
|
||||
if (current == token) {
|
||||
// We have reached the token we are interested in. Get whichever window occurs
|
||||
// after it that is on the same display.
|
||||
result.reachedToken = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
final WindowList tokenWindowList = dc.getTokenWindowsOnDisplay(current);
|
||||
if (tokenWindowList.size() > 0) {
|
||||
result.foundWindow = tokenWindowList.get(tokenWindowList.size() - 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean fillsParent() {
|
||||
return mFillsParent || !StackId.isTaskResizeAllowed(mStack.mStackId);
|
||||
|
||||
@@ -859,9 +859,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
|
||||
}
|
||||
|
||||
// TODO: Should each user have there own stacks?
|
||||
@Override
|
||||
void switchUser() {
|
||||
super.switchUser();
|
||||
int top = mChildren.size();
|
||||
for (int taskNdx = 0; taskNdx < top; ++taskNdx) {
|
||||
Task task = mChildren.get(taskNdx);
|
||||
@@ -1490,6 +1488,30 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
|
||||
}
|
||||
}
|
||||
|
||||
void getWindowOnDisplayBeforeToken(DisplayContent dc, WindowToken token,
|
||||
DisplayContent.GetWindowOnDisplaySearchResult result) {
|
||||
for (int i = mChildren.size() - 1; i >= 0; --i) {
|
||||
final Task task = mChildren.get(i);
|
||||
task.getWindowOnDisplayBeforeToken(dc, token, result);
|
||||
if (result.reachedToken) {
|
||||
// We have reach the token we are interested in. End search.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void getWindowOnDisplayAfterToken(DisplayContent dc, WindowToken token,
|
||||
DisplayContent.GetWindowOnDisplaySearchResult result) {
|
||||
for (int i = mChildren.size() - 1; i >= 0; --i) {
|
||||
final Task task = mChildren.get(i);
|
||||
task.getWindowOnDisplayAfterToken(dc, token, result);
|
||||
if (result.foundWindow != null) {
|
||||
// We have found a window after the token. End search.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
int getOrientation() {
|
||||
return (StackId.canSpecifyOrientation(mStackId))
|
||||
|
||||
@@ -683,6 +683,24 @@ class WallpaperController {
|
||||
}
|
||||
}
|
||||
|
||||
void dumpTokens(PrintWriter pw, String prefix, boolean dumpAll) {
|
||||
if (!mWallpaperTokens.isEmpty()) {
|
||||
pw.println();
|
||||
pw.print(prefix); pw.println("Wallpaper tokens:");
|
||||
for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
|
||||
WindowToken token = mWallpaperTokens.get(i);
|
||||
pw.print(prefix); pw.print("Wallpaper #"); pw.print(i);
|
||||
pw.print(' '); pw.print(token);
|
||||
if (dumpAll) {
|
||||
pw.println(':');
|
||||
token.dump(pw, " ");
|
||||
} else {
|
||||
pw.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Helper class for storing the results of a wallpaper target find operation. */
|
||||
final private static class FindWallpaperTargetResult {
|
||||
WindowState topWallpaper = null;
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.server.wm;
|
||||
|
||||
import android.annotation.CallSuper;
|
||||
import android.content.res.Configuration;
|
||||
import android.view.animation.Animation;
|
||||
import com.android.internal.util.ToBooleanFunction;
|
||||
|
||||
import java.util.Comparator;
|
||||
@@ -482,11 +483,19 @@ class WindowContainer<E extends WindowContainer> implements Comparable<WindowCon
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Users would have their own window containers under the display container?
|
||||
void switchUser() {
|
||||
for (int i = mChildren.size() - 1; i >= 0; --i) {
|
||||
mChildren.get(i).switchUser();
|
||||
/**
|
||||
* Rebuilds the WindowList for the input display content.
|
||||
* @param addIndex The index in the window list to add the next entry to.
|
||||
* @return The next index in the window list to.
|
||||
*/
|
||||
// TODO: Hoping we can get rid of WindowList so this method wouldn't be needed.
|
||||
int rebuildWindowList(int addIndex) {
|
||||
final int count = mChildren.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final WindowContainer wc = mChildren.get(i);
|
||||
addIndex = wc.rebuildWindowList(addIndex);
|
||||
}
|
||||
return addIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -606,6 +606,17 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
boolean mInputMethodTargetWaitingAnim;
|
||||
|
||||
WindowState mInputMethodWindow = null;
|
||||
// TODO: Remove with extreme prejudice! This list is maintained so that we can keep track of
|
||||
// dialogs that should go on top of the IME so they can be re-arranged in the window list any
|
||||
// time the IME changes position in the window list. Normally you would have a dialog be a child
|
||||
// window of the IME, but they don't share the same token since they are added by different
|
||||
// clients. This doesn't really affect what the user sees on screen since this dialogs have an
|
||||
// higher base layer than the IME window, but it will affect users of the window list that
|
||||
// expect the list to represent the order of things on-screen (e.g input service). This makes
|
||||
// the code for managing the window list hard to follow (see all the places it is used).
|
||||
// We can remove the use of this field when we automatically assign layers and z-order the
|
||||
// window list before it is used whenever window container order changes.
|
||||
final ArrayList<WindowState> mInputMethodDialogs = new ArrayList<>();
|
||||
|
||||
boolean mHardKeyboardAvailable;
|
||||
WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
|
||||
@@ -1366,16 +1377,19 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
|
||||
boolean imMayMove = true;
|
||||
|
||||
win.mToken.addWindow(win);
|
||||
if (type == TYPE_INPUT_METHOD) {
|
||||
win.mGivenInsetsPending = true;
|
||||
mInputMethodWindow = win;
|
||||
displayContent.computeImeTarget(true /* updateImeTarget */);
|
||||
win.mToken.addImeWindow(win);
|
||||
imMayMove = false;
|
||||
} else if (type == TYPE_INPUT_METHOD_DIALOG) {
|
||||
displayContent.computeImeTarget(true /* updateImeTarget */);
|
||||
mInputMethodDialogs.add(win);
|
||||
win.mToken.addWindow(win);
|
||||
displayContent.moveInputMethodDialogs(
|
||||
displayContent.findDesiredInputMethodWindowIndex(true));
|
||||
imMayMove = false;
|
||||
} else {
|
||||
win.mToken.addWindow(win);
|
||||
if (type == TYPE_WALLPAPER) {
|
||||
displayContent.mWallpaperController.clearLastWallpaperTimeoutTime();
|
||||
displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
|
||||
@@ -1448,7 +1462,7 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
}
|
||||
|
||||
if (imMayMove) {
|
||||
displayContent.computeImeTarget(true /* updateImeTarget */);
|
||||
displayContent.moveInputMethodWindowsIfNeeded(false);
|
||||
}
|
||||
|
||||
// Don't do layout here, the window must call
|
||||
@@ -1632,6 +1646,8 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
|
||||
if (mInputMethodWindow == win) {
|
||||
mInputMethodWindow = null;
|
||||
} else if (win.mAttrs.type == TYPE_INPUT_METHOD_DIALOG) {
|
||||
mInputMethodDialogs.remove(win);
|
||||
}
|
||||
|
||||
final WindowToken token = win.mToken;
|
||||
@@ -1661,11 +1677,13 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
|
||||
}
|
||||
|
||||
if (dc != null && !mWindowPlacerLocked.isInLayout()) {
|
||||
dc.assignWindowLayers(true /* setLayoutNeeded */);
|
||||
mWindowPlacerLocked.performSurfacePlacement();
|
||||
if (win.mAppToken != null) {
|
||||
win.mAppToken.updateReportedVisibilityLocked();
|
||||
if (dc != null && dc.removeFromWindowList(win)) {
|
||||
if (!mWindowPlacerLocked.isInLayout()) {
|
||||
dc.assignWindowLayers(true /* setLayoutNeeded */);
|
||||
mWindowPlacerLocked.performSurfacePlacement();
|
||||
if (win.mAppToken != null) {
|
||||
win.mAppToken.updateReportedVisibilityLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2046,15 +2064,14 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
// reassign them at this point if the IM window state gets shuffled
|
||||
boolean toBeDisplayed = (result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0;
|
||||
final DisplayContent dc = win.getDisplayContent();
|
||||
if (imMayMove) {
|
||||
dc.computeImeTarget(true /* updateImeTarget */);
|
||||
if (toBeDisplayed) {
|
||||
// Little hack here -- we -should- be able to rely on the function to return
|
||||
// true if the IME has moved and needs its layer recomputed. However, if the IME
|
||||
// was hidden and isn't actually moved in the list, its layer may be out of data
|
||||
// so we make sure to recompute it.
|
||||
dc.assignWindowLayers(false /* setLayoutNeeded */);
|
||||
}
|
||||
if (imMayMove && (dc.moveInputMethodWindowsIfNeeded(false) || toBeDisplayed)) {
|
||||
// Little hack here -- we -should- be able to rely on the function to return true if
|
||||
// the IME has moved and needs its layer recomputed. However, if the IME was hidden
|
||||
// and isn't actually moved in the list, its layer may be out of data so we make
|
||||
// sure to recompute it.
|
||||
// TODO: Probably not needed once the window list always has the right z-ordering
|
||||
// when the window hierarchy is updated.
|
||||
dc.assignWindowLayers(false /* setLayoutNeeded */);
|
||||
}
|
||||
|
||||
if (wallpaperMayMove) {
|
||||
@@ -3334,7 +3351,7 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
if (mAppTransition.isTransitionSet()) {
|
||||
task.setSendingToBottom(false);
|
||||
}
|
||||
displayContent.layoutAndAssignWindowLayersIfNeeded();
|
||||
displayContent.rebuildAppWindowsAndLayoutIfNeeded();
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(origId);
|
||||
@@ -3356,7 +3373,7 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
if (mAppTransition.isTransitionSet()) {
|
||||
task.setSendingToBottom(true);
|
||||
}
|
||||
stack.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
|
||||
stack.getDisplayContent().rebuildAppWindowsAndLayoutIfNeeded();
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(origId);
|
||||
@@ -4602,7 +4619,7 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return displayContent.screenshotApplications(appToken, width, height,
|
||||
return displayContent.screenshotApplications(appToken, displayId, width, height,
|
||||
includeFullDisplay, frameScale, config, wallpaperOnly);
|
||||
}
|
||||
|
||||
@@ -5164,7 +5181,7 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
|
||||
boolean result = true;
|
||||
|
||||
final ArrayList<WindowState> windows = new ArrayList();
|
||||
final WindowList windows = new WindowList();
|
||||
synchronized (mWindowMap) {
|
||||
mRoot.forAllWindows(w -> {
|
||||
windows.add(w);
|
||||
@@ -7295,7 +7312,7 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
changes |= FINISH_LAYOUT_REDO_LAYOUT;
|
||||
if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG_WM,
|
||||
"Wallpaper layer changed: assigning layers + relayout");
|
||||
dc.computeImeTarget(true /* updateImeTarget */);
|
||||
dc.moveInputMethodWindowsIfNeeded(true);
|
||||
mRoot.mWallpaperMayChange = true;
|
||||
// Since the window list has been rebuilt, focus might have to be recomputed since the
|
||||
// actual order of windows might have changed again.
|
||||
@@ -7388,25 +7405,10 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE);
|
||||
// TODO(multidisplay): Focused windows on default display only.
|
||||
final DisplayContent displayContent = getDefaultDisplayContentLocked();
|
||||
boolean imWindowChanged = false;
|
||||
if (mInputMethodWindow != null) {
|
||||
final WindowState prevTarget = mInputMethodTarget;
|
||||
final WindowState newTarget =
|
||||
displayContent.computeImeTarget(true /* updateImeTarget*/);
|
||||
|
||||
imWindowChanged = prevTarget != newTarget;
|
||||
|
||||
if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
|
||||
&& mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) {
|
||||
final int prevImeAnimLayer = mInputMethodWindow.mWinAnimator.mAnimLayer;
|
||||
displayContent.assignWindowLayers(false /* setLayoutNeeded */);
|
||||
imWindowChanged |=
|
||||
prevImeAnimLayer != mInputMethodWindow.mWinAnimator.mAnimLayer;
|
||||
}
|
||||
}
|
||||
|
||||
final boolean imWindowChanged = displayContent.moveInputMethodWindowsIfNeeded(
|
||||
mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
|
||||
&& mode != UPDATE_FOCUS_WILL_PLACE_SURFACES);
|
||||
if (imWindowChanged) {
|
||||
mWindowsChanged = true;
|
||||
displayContent.setLayoutNeeded();
|
||||
newFocus = mRoot.computeFocusedWindow();
|
||||
}
|
||||
@@ -7881,6 +7883,7 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
private void dumpTokensLocked(PrintWriter pw, boolean dumpAll) {
|
||||
pw.println("WINDOW MANAGER TOKENS (dumpsys window tokens)");
|
||||
mRoot.dumpTokens(pw, dumpAll);
|
||||
mRoot.mWallpaperController.dumpTokens(pw, " ", dumpAll);
|
||||
if (!mFinishedStarting.isEmpty()) {
|
||||
pw.println();
|
||||
pw.println(" Finishing start of application tokens:");
|
||||
@@ -7926,6 +7929,16 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
ArrayList<WindowState> windows) {
|
||||
mRoot.dumpWindowsNoHeader(pw, dumpAll, windows);
|
||||
|
||||
if (mInputMethodDialogs.size() > 0) {
|
||||
pw.println();
|
||||
pw.println(" Input method dialogs:");
|
||||
for (int i=mInputMethodDialogs.size()-1; i>=0; i--) {
|
||||
WindowState w = mInputMethodDialogs.get(i);
|
||||
if (windows == null || windows.contains(w)) {
|
||||
pw.print(" IM Dialog #"); pw.print(i); pw.print(": "); pw.println(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mPendingRemove.size() > 0) {
|
||||
pw.println();
|
||||
pw.println(" Remove pending for:");
|
||||
@@ -8084,7 +8097,7 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
|
||||
private boolean dumpWindows(PrintWriter pw, String name, String[] args, int opti,
|
||||
boolean dumpAll) {
|
||||
final ArrayList<WindowState> windows = new ArrayList();
|
||||
final WindowList windows = new WindowList();
|
||||
if ("apps".equals(name) || "visible".equals(name) || "visible-apps".equals(name)) {
|
||||
final boolean appsOnly = name.contains("apps");
|
||||
final boolean visibleOnly = name.contains("visible");
|
||||
|
||||
@@ -76,7 +76,6 @@ import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_
|
||||
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
|
||||
import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
|
||||
import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
|
||||
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
|
||||
import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
|
||||
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
|
||||
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
|
||||
@@ -116,7 +115,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIO
|
||||
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
|
||||
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
|
||||
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
|
||||
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
|
||||
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
|
||||
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
|
||||
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
|
||||
@@ -142,6 +140,9 @@ import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
|
||||
import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
|
||||
import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW;
|
||||
|
||||
class WindowList extends ArrayList<WindowState> {
|
||||
}
|
||||
|
||||
/** A window in the window manager. */
|
||||
class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState {
|
||||
static final String TAG = TAG_WITH_CLASS_NAME ? "WindowState" : TAG_WM;
|
||||
@@ -447,6 +448,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
|
||||
*/
|
||||
boolean mWindowRemovalAllowed;
|
||||
|
||||
/**
|
||||
* Temp for keeping track of windows that have been removed when
|
||||
* rebuilding window list.
|
||||
*/
|
||||
boolean mRebuilding;
|
||||
|
||||
// Input channel and input window handle used by the input dispatcher.
|
||||
final InputWindowHandle mInputWindowHandle;
|
||||
InputChannel mInputChannel;
|
||||
@@ -1714,7 +1721,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
|
||||
|
||||
final DisplayContent dc = getDisplayContent();
|
||||
if (mService.mInputMethodTarget == this) {
|
||||
dc.computeImeTarget(true /* updateImeTarget */);
|
||||
dc.moveInputMethodWindowsIfNeeded(false);
|
||||
}
|
||||
|
||||
final int type = mAttrs.type;
|
||||
@@ -1924,33 +1931,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
|
||||
return mLayer + specialAdjustment;
|
||||
}
|
||||
|
||||
boolean canBeImeTarget() {
|
||||
final int fl = mAttrs.flags & (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM);
|
||||
final int type = mAttrs.type;
|
||||
|
||||
if (fl != 0 && fl != (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM)
|
||||
&& type != TYPE_APPLICATION_STARTING) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (DEBUG_INPUT_METHOD) {
|
||||
Slog.i(TAG_WM, "isVisibleOrAdding " + this + ": " + isVisibleOrAdding());
|
||||
if (!isVisibleOrAdding()) {
|
||||
Slog.i(TAG_WM, " mSurfaceController=" + mWinAnimator.mSurfaceController
|
||||
+ " relayoutCalled=" + mRelayoutCalled
|
||||
+ " viewVis=" + mViewVisibility
|
||||
+ " policyVis=" + mPolicyVisibility
|
||||
+ " policyVisAfterAnim=" + mPolicyVisibilityAfterAnim
|
||||
+ " parentHidden=" + isParentWindowHidden()
|
||||
+ " exiting=" + mAnimatingExit + " destroying=" + mDestroying);
|
||||
if (mAppToken != null) {
|
||||
Slog.i(TAG_WM, " mAppToken.hiddenRequested=" + mAppToken.hiddenRequested);
|
||||
}
|
||||
}
|
||||
}
|
||||
return isVisibleOrAdding();
|
||||
}
|
||||
|
||||
void scheduleAnimationIfDimming() {
|
||||
final DisplayContent dc = getDisplayContent();
|
||||
if (dc == null) {
|
||||
@@ -2105,7 +2085,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
|
||||
return false;
|
||||
}
|
||||
|
||||
private void removeReplacedWindow() {
|
||||
void removeReplacedWindow() {
|
||||
if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replaced window: " + this);
|
||||
if (isDimming()) {
|
||||
transferDimToReplacement();
|
||||
@@ -2158,16 +2138,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void switchUser() {
|
||||
super.switchUser();
|
||||
if (isHiddenFromUserLocked()) {
|
||||
if (DEBUG_VISIBILITY) Slog.w(TAG_WM, "user changing, hiding " + this
|
||||
+ ", attrs=" + mAttrs.type + ", belonging to " + mOwnerUid);
|
||||
hideLw(false);
|
||||
}
|
||||
}
|
||||
|
||||
int getTouchableRegion(Region region, int flags) {
|
||||
final boolean modal = (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
|
||||
if (modal && mAppToken != null) {
|
||||
@@ -3529,6 +3499,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
|
||||
return mIsChildWindow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bottom child window in regards to z-order of this window or null if no children.
|
||||
*/
|
||||
WindowState getBottomChild() {
|
||||
// Child windows are z-ordered based on sub-layer using {@link #sWindowSubLayerComparator}
|
||||
// and the child with the lowest z-order will be at the head of the list.
|
||||
WindowState c = mChildren.peekFirst();
|
||||
return c == null ? null : c;
|
||||
}
|
||||
|
||||
boolean layoutInParentFrame() {
|
||||
return mIsChildWindow
|
||||
&& (mAttrs.privateFlags & PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME) != 0;
|
||||
@@ -3814,6 +3794,44 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
|
||||
return highestAnimLayer;
|
||||
}
|
||||
|
||||
@Override
|
||||
int rebuildWindowList(int addIndex) {
|
||||
return reAddWindow(addIndex);
|
||||
}
|
||||
|
||||
// TODO: come-up with a better name for this method that represents what it does.
|
||||
// Or, it is probably not going to matter anyways if we are successful in getting rid of
|
||||
// the WindowList concept.
|
||||
int reAddWindow(int index) {
|
||||
final DisplayContent dc = getDisplayContent();
|
||||
// Adding child windows relies on child windows being ordered by mSubLayer using
|
||||
// {@link #sWindowSubLayerComparator}.
|
||||
final int childCount = mChildren.size();
|
||||
boolean winAdded = false;
|
||||
for (int j = 0; j < childCount; j++) {
|
||||
final WindowState child = mChildren.get(j);
|
||||
if (!winAdded && child.mSubLayer >= 0) {
|
||||
if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM,
|
||||
"Re-adding child window at " + index + ": " + child);
|
||||
mRebuilding = false;
|
||||
dc.addToWindowList(this, index);
|
||||
index++;
|
||||
winAdded = true;
|
||||
}
|
||||
if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Re-adding window at " + index + ": " + child);
|
||||
child.mRebuilding = false;
|
||||
dc.addToWindowList(child, index);
|
||||
index++;
|
||||
}
|
||||
if (!winAdded) {
|
||||
if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Re-adding window at " + index + ": " + this);
|
||||
mRebuilding = false;
|
||||
dc.addToWindowList(this, index);
|
||||
index++;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
|
||||
if (mChildren.isEmpty()) {
|
||||
|
||||
@@ -239,8 +239,7 @@ class WindowSurfacePlacer {
|
||||
mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
|
||||
|
||||
final DisplayContent displayContent = mService.getDefaultDisplayContentLocked();
|
||||
// TODO: Don't believe this is really needed...
|
||||
//mService.mWindowsChanged = true;
|
||||
displayContent.rebuildAppWindowList();
|
||||
|
||||
mService.mRoot.mWallpaperMayChange = false;
|
||||
|
||||
@@ -357,8 +356,12 @@ class WindowSurfacePlacer {
|
||||
displayContent.setLayoutNeeded();
|
||||
|
||||
// TODO(multidisplay): IMEs are only supported on the default display.
|
||||
// TODO: Probably not needed once the window list always has the right z-ordering
|
||||
// when the window hierarchy is updated.
|
||||
final DisplayContent dc = mService.getDefaultDisplayContentLocked();
|
||||
dc.computeImeTarget(true /* updateImeTarget */);
|
||||
if (!dc.moveInputMethodWindowsIfNeeded(true)) {
|
||||
dc.assignWindowLayers(false /*setLayoutNeeded*/);
|
||||
}
|
||||
mService.updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES,
|
||||
true /*updateInputWindows*/);
|
||||
mService.mFocusMayChange = false;
|
||||
|
||||
@@ -168,6 +168,13 @@ class WindowToken extends WindowContainer<WindowState> {
|
||||
return highestAnimLayer;
|
||||
}
|
||||
|
||||
WindowState getTopWindow() {
|
||||
if (mChildren.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return (WindowState) mChildren.get(mChildren.size() - 1).getTop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the new window is considered greater than the existing window in terms of
|
||||
* z-order.
|
||||
@@ -182,16 +189,51 @@ class WindowToken extends WindowContainer<WindowState> {
|
||||
if (DEBUG_FOCUS) Slog.d(TAG_WM,
|
||||
"addWindow: win=" + win + " Callers=" + Debug.getCallers(5));
|
||||
|
||||
if (win.isChildWindow()) {
|
||||
// Child windows are added to their parent windows.
|
||||
if (!win.isChildWindow()) {
|
||||
if (asAppWindowToken() != null) {
|
||||
mDisplayContent.addAppWindowToWindowList(win);
|
||||
} else {
|
||||
mDisplayContent.addNonAppWindowToWindowList(win);
|
||||
}
|
||||
|
||||
if (!mChildren.contains(win)) {
|
||||
if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Adding " + win + " to " + this);
|
||||
addChild(win, mWindowComparator);
|
||||
}
|
||||
} else {
|
||||
mDisplayContent.addChildWindowToWindowList(win);
|
||||
}
|
||||
}
|
||||
|
||||
void addImeWindow(WindowState win) {
|
||||
int pos = mDisplayContent.findDesiredInputMethodWindowIndex(true);
|
||||
|
||||
if (pos < 0) {
|
||||
addWindow(win);
|
||||
mDisplayContent.moveInputMethodDialogs(pos);
|
||||
return;
|
||||
}
|
||||
|
||||
if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
|
||||
"Adding input method window " + win + " at " + pos);
|
||||
mDisplayContent.addToWindowList(win, pos);
|
||||
if (!mChildren.contains(win)) {
|
||||
if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Adding " + win + " to " + this);
|
||||
addChild(win, mWindowComparator);
|
||||
mService.mWindowsChanged = true;
|
||||
// TODO: Should we also be setting layout needed here and other places?
|
||||
addChild(win, null);
|
||||
}
|
||||
mDisplayContent.moveInputMethodDialogs(pos + 1);
|
||||
}
|
||||
|
||||
/** Return the first window in the token window list that isn't a starting window or null. */
|
||||
WindowState getFirstNonStartingWindow() {
|
||||
final int count = mChildren.size();
|
||||
// We only care about parent windows so no need to loop through child windows.
|
||||
for (int i = 0; i < count; i++) {
|
||||
final WindowState w = mChildren.get(i);
|
||||
if (w.mAttrs.type != TYPE_APPLICATION_STARTING) {
|
||||
return w;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Returns true if the token windows list is empty. */
|
||||
|
||||
@@ -116,6 +116,35 @@ public class WindowStateTests extends WindowTestsBase {
|
||||
assertFalse(win2.hasChild(randomWindow));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBottomChild() throws Exception {
|
||||
final WindowState parentWindow =
|
||||
createWindow(null, TYPE_APPLICATION, mWindowToken, "parentWindow");
|
||||
assertNull(parentWindow.getBottomChild());
|
||||
|
||||
final WindowState child1 =
|
||||
createWindow(parentWindow, TYPE_APPLICATION_PANEL, mWindowToken, "child1");
|
||||
assertEquals(child1, parentWindow.getBottomChild());
|
||||
|
||||
final WindowState child2 =
|
||||
createWindow(parentWindow, TYPE_APPLICATION_PANEL, mWindowToken, "child2");
|
||||
// Since child1 and child2 are at the same layer, then child2 is expect to be added on top
|
||||
// on child1
|
||||
assertEquals(child1, parentWindow.getBottomChild());
|
||||
|
||||
final WindowState child3 =
|
||||
createWindow(parentWindow, TYPE_APPLICATION_MEDIA_OVERLAY, mWindowToken, "child3");
|
||||
// Since child3 is a negative layer, we would expect it to be added below current children
|
||||
// with positive layers.
|
||||
assertEquals(child3, parentWindow.getBottomChild());
|
||||
|
||||
final WindowState child4 =
|
||||
createWindow(parentWindow, TYPE_APPLICATION_MEDIA_OVERLAY, mWindowToken, "child4");
|
||||
// We would also expect additional negative layers to be added below existing negative
|
||||
// layers.
|
||||
assertEquals(child4, parentWindow.getBottomChild());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetParentWindow() throws Exception {
|
||||
final WindowState parentWindow =
|
||||
|
||||
Reference in New Issue
Block a user