/*
|
* Copyright (C) 2018 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
package com.android.server.wm;
|
|
import static android.view.InsetsState.TYPE_IME;
|
import static android.view.InsetsState.TYPE_NAVIGATION_BAR;
|
import static android.view.InsetsState.TYPE_TOP_BAR;
|
import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
|
import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
|
import static android.view.ViewRootImpl.sNewInsetsMode;
|
|
import android.annotation.NonNull;
|
import android.annotation.Nullable;
|
import android.util.ArrayMap;
|
import android.util.ArraySet;
|
import android.util.SparseArray;
|
import android.view.InsetsSource;
|
import android.view.InsetsSourceControl;
|
import android.view.InsetsState;
|
import android.view.ViewRootImpl;
|
|
import java.io.PrintWriter;
|
import java.util.ArrayList;
|
import java.util.function.Consumer;
|
|
/**
|
* Manages global window inset state in the system represented by {@link InsetsState}.
|
*/
|
class InsetsStateController {
|
|
private final InsetsState mLastState = new InsetsState();
|
private final InsetsState mState = new InsetsState();
|
private final DisplayContent mDisplayContent;
|
|
private final ArrayMap<Integer, InsetsSourceProvider> mControllers = new ArrayMap<>();
|
private final ArrayMap<WindowState, ArrayList<Integer>> mWinControlTypeMap = new ArrayMap<>();
|
private final SparseArray<WindowState> mTypeWinControlMap = new SparseArray<>();
|
private final ArraySet<WindowState> mPendingControlChanged = new ArraySet<>();
|
|
private final Consumer<WindowState> mDispatchInsetsChanged = w -> {
|
if (w.isVisible()) {
|
w.notifyInsetsChanged();
|
}
|
};
|
|
InsetsStateController(DisplayContent displayContent) {
|
mDisplayContent = displayContent;
|
}
|
|
/**
|
* When dispatching window state to the client, we'll need to exclude the source that represents
|
* the window that is being dispatched.
|
*
|
* @param target The client we dispatch the state to.
|
* @return The state stripped of the necessary information.
|
*/
|
InsetsState getInsetsForDispatch(WindowState target) {
|
final InsetsSourceProvider provider = target.getInsetProvider();
|
if (provider == null) {
|
return mState;
|
}
|
|
final InsetsState state = new InsetsState();
|
state.set(mState);
|
final int type = provider.getSource().getType();
|
state.removeSource(type);
|
|
// Navigation bar doesn't get influenced by anything else
|
if (type == TYPE_NAVIGATION_BAR) {
|
state.removeSource(TYPE_IME);
|
state.removeSource(TYPE_TOP_BAR);
|
}
|
return state;
|
}
|
|
@Nullable InsetsSourceControl[] getControlsForDispatch(WindowState target) {
|
ArrayList<Integer> controlled = mWinControlTypeMap.get(target);
|
if (controlled == null) {
|
return null;
|
}
|
final int size = controlled.size();
|
final InsetsSourceControl[] result = new InsetsSourceControl[size];
|
for (int i = 0; i < size; i++) {
|
result[i] = mControllers.get(controlled.get(i)).getControl();
|
}
|
return result;
|
}
|
|
/**
|
* @return The provider of a specific type.
|
*/
|
InsetsSourceProvider getSourceProvider(int type) {
|
return mControllers.computeIfAbsent(type,
|
key -> new InsetsSourceProvider(mState.getSource(key), this, mDisplayContent));
|
}
|
|
/**
|
* Called when a layout pass has occurred.
|
*/
|
void onPostLayout() {
|
mState.setDisplayFrame(mDisplayContent.getBounds());
|
for (int i = mControllers.size() - 1; i>= 0; i--) {
|
mControllers.valueAt(i).onPostLayout();
|
}
|
if (!mLastState.equals(mState)) {
|
mLastState.set(mState, true /* copySources */);
|
notifyInsetsChanged();
|
}
|
}
|
|
void onInsetsModified(WindowState windowState, InsetsState state) {
|
boolean changed = false;
|
for (int i = state.getSourcesCount() - 1; i >= 0; i--) {
|
final InsetsSource source = state.sourceAt(i);
|
final InsetsSourceProvider provider = mControllers.get(source.getType());
|
if (provider == null) {
|
continue;
|
}
|
changed |= provider.onInsetsModified(windowState, source);
|
}
|
if (changed) {
|
notifyInsetsChanged();
|
}
|
}
|
|
void onImeTargetChanged(@Nullable WindowState imeTarget) {
|
onControlChanged(TYPE_IME, imeTarget);
|
notifyPendingInsetsControlChanged();
|
}
|
|
/**
|
* Called when the top opaque fullscreen window that is able to control the system bars changes.
|
*
|
* @param controllingWindow The window that is now able to control the system bars appearance
|
* and visibility.
|
*/
|
void onBarControllingWindowChanged(@Nullable WindowState controllingWindow) {
|
// TODO: Apply policy that determines whether controllingWindow is able to control system
|
// bars
|
|
// TODO: Depending on the form factor, mapping is different
|
onControlChanged(TYPE_TOP_BAR, controllingWindow);
|
onControlChanged(TYPE_NAVIGATION_BAR, controllingWindow);
|
notifyPendingInsetsControlChanged();
|
}
|
|
void notifyControlRevoked(@NonNull WindowState previousControllingWin,
|
InsetsSourceProvider provider) {
|
removeFromControlMaps(previousControllingWin, provider.getSource().getType());
|
}
|
|
private void onControlChanged(int type, @Nullable WindowState win) {
|
final WindowState previous = mTypeWinControlMap.get(type);
|
if (win == previous) {
|
return;
|
}
|
final InsetsSourceProvider controller = getSourceProvider(type);
|
if (controller == null) {
|
return;
|
}
|
if (!controller.isControllable()) {
|
return;
|
}
|
controller.updateControlForTarget(win, false /* force */);
|
if (previous != null) {
|
removeFromControlMaps(previous, type);
|
mPendingControlChanged.add(previous);
|
}
|
if (win != null) {
|
addToControlMaps(win, type);
|
mPendingControlChanged.add(win);
|
}
|
}
|
|
private void removeFromControlMaps(@NonNull WindowState win, int type) {
|
final ArrayList<Integer> array = mWinControlTypeMap.get(win);
|
if (array == null) {
|
return;
|
}
|
array.remove((Integer) type);
|
if (array.isEmpty()) {
|
mWinControlTypeMap.remove(win);
|
}
|
mTypeWinControlMap.remove(type);
|
}
|
|
private void addToControlMaps(@NonNull WindowState win, int type) {
|
final ArrayList<Integer> array = mWinControlTypeMap.computeIfAbsent(win,
|
key -> new ArrayList<>());
|
array.add(type);
|
mTypeWinControlMap.put(type, win);
|
}
|
|
void notifyControlChanged(WindowState target) {
|
mPendingControlChanged.add(target);
|
notifyPendingInsetsControlChanged();
|
}
|
|
private void notifyPendingInsetsControlChanged() {
|
if (mPendingControlChanged.isEmpty()) {
|
return;
|
}
|
mDisplayContent.mWmService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
|
for (int i = mPendingControlChanged.size() - 1; i >= 0; i--) {
|
final WindowState controllingWin = mPendingControlChanged.valueAt(i);
|
controllingWin.notifyInsetsControlChanged();
|
}
|
mPendingControlChanged.clear();
|
});
|
}
|
|
private void notifyInsetsChanged() {
|
mDisplayContent.forAllWindows(mDispatchInsetsChanged, true /* traverseTopToBottom */);
|
}
|
|
void dump(String prefix, PrintWriter pw) {
|
pw.println(prefix + "WindowInsetsStateController");
|
mState.dump(prefix + " ", pw);
|
pw.println(prefix + " " + "Control map:");
|
for (int i = mTypeWinControlMap.size() - 1; i >= 0; i--) {
|
pw.print(prefix + " ");
|
pw.println(InsetsState.typeToString(mTypeWinControlMap.keyAt(i)) + " -> "
|
+ mTypeWinControlMap.valueAt(i));
|
}
|
}
|
}
|