blob: 8e94109643131d8abe35110d06e4c1340c36a862 [file] [log] [blame]
/*
* Copyright (C) 2020 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.systemui.car.window;
import android.annotation.Nullable;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.systemui.car.navigationbar.CarNavigationBarController;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* This controller is responsible for the following:
* <p><ul>
* <li>Holds the global state for SystemUIOverlayWindow.
* <li>Allows {@link SystemUIOverlayWindowManager} to register {@link OverlayViewMediator}(s).
* <li>Enables {@link OverlayViewController)(s) to reveal/conceal themselves while respecting the
* global state of SystemUIOverlayWindow.
* </ul>
*/
@Singleton
public class OverlayViewGlobalStateController {
private static final boolean DEBUG = false;
private static final String TAG = OverlayViewGlobalStateController.class.getSimpleName();
private static final int UNKNOWN_Z_ORDER = -1;
private final SystemUIOverlayWindowController mSystemUIOverlayWindowController;
private final CarNavigationBarController mCarNavigationBarController;
private boolean mIsOccluded;
@VisibleForTesting
Map<OverlayViewController, Integer> mZOrderMap;
@VisibleForTesting
SortedMap<Integer, OverlayViewController> mZOrderVisibleSortedMap;
@VisibleForTesting
Set<OverlayViewController> mViewsHiddenForOcclusion;
@VisibleForTesting
OverlayViewController mHighestZOrder;
@Inject
public OverlayViewGlobalStateController(
CarNavigationBarController carNavigationBarController,
SystemUIOverlayWindowController systemUIOverlayWindowController) {
mSystemUIOverlayWindowController = systemUIOverlayWindowController;
mSystemUIOverlayWindowController.attach();
mCarNavigationBarController = carNavigationBarController;
mZOrderMap = new HashMap<>();
mZOrderVisibleSortedMap = new TreeMap<>();
mViewsHiddenForOcclusion = new HashSet<>();
}
/**
* Register {@link OverlayViewMediator} to use in SystemUIOverlayWindow.
*/
public void registerMediator(OverlayViewMediator overlayViewMediator) {
Log.d(TAG, "Registering content mediator: " + overlayViewMediator.getClass().getName());
overlayViewMediator.registerListeners();
overlayViewMediator.setupOverlayContentViewControllers();
}
/**
* Show content in Overlay Window using {@link OverlayPanelViewController}.
*
* This calls {@link OverlayViewGlobalStateController#showView(OverlayViewController, Runnable)}
* where the runnable is nullified since the actual showing of the panel is handled by the
* controller itself.
*/
public void showView(OverlayPanelViewController panelViewController) {
showView(panelViewController, /* show= */ null);
}
/**
* Show content in Overlay Window using {@link OverlayViewController}.
*/
public void showView(OverlayViewController viewController, @Nullable Runnable show) {
debugLog();
if (mIsOccluded && !viewController.shouldShowWhenOccluded()) {
mViewsHiddenForOcclusion.add(viewController);
return;
}
if (mZOrderVisibleSortedMap.isEmpty()) {
setWindowVisible(true);
}
if (!(viewController instanceof OverlayPanelViewController)) {
inflateView(viewController);
}
if (show != null) {
show.run();
}
updateInternalsWhenShowingView(viewController);
refreshNavigationBarVisibility();
Log.d(TAG, "Content shown: " + viewController.getClass().getName());
debugLog();
}
private void updateInternalsWhenShowingView(OverlayViewController viewController) {
int zOrder;
if (mZOrderMap.containsKey(viewController)) {
zOrder = mZOrderMap.get(viewController);
} else {
zOrder = mSystemUIOverlayWindowController.getBaseLayout().indexOfChild(
viewController.getLayout());
mZOrderMap.put(viewController, zOrder);
}
mZOrderVisibleSortedMap.put(zOrder, viewController);
refreshHighestZOrderWhenShowingView(viewController);
}
private void refreshHighestZOrderWhenShowingView(OverlayViewController viewController) {
if (mZOrderMap.getOrDefault(mHighestZOrder, UNKNOWN_Z_ORDER) < mZOrderMap.get(
viewController)) {
mHighestZOrder = viewController;
}
}
/**
* Hide content in Overlay Window using {@link OverlayPanelViewController}.
*
* This calls {@link OverlayViewGlobalStateController#hideView(OverlayViewController, Runnable)}
* where the runnable is nullified since the actual hiding of the panel is handled by the
* controller itself.
*/
public void hideView(OverlayPanelViewController panelViewController) {
hideView(panelViewController, /* hide= */ null);
}
/**
* Hide content in Overlay Window using {@link OverlayViewController}.
*/
public void hideView(OverlayViewController viewController, @Nullable Runnable hide) {
debugLog();
if (mIsOccluded && mViewsHiddenForOcclusion.contains(viewController)) {
mViewsHiddenForOcclusion.remove(viewController);
return;
}
if (!viewController.isInflated()) {
Log.d(TAG, "Content cannot be hidden since it isn't inflated: "
+ viewController.getClass().getName());
return;
}
if (!mZOrderMap.containsKey(viewController)) {
Log.d(TAG, "Content cannot be hidden since it has never been shown: "
+ viewController.getClass().getName());
return;
}
if (!mZOrderVisibleSortedMap.containsKey(mZOrderMap.get(viewController))) {
Log.d(TAG, "Content cannot be hidden since it isn't currently shown: "
+ viewController.getClass().getName());
return;
}
if (hide != null) {
hide.run();
}
mZOrderVisibleSortedMap.remove(mZOrderMap.get(viewController));
refreshHighestZOrderWhenHidingView(viewController);
refreshNavigationBarVisibility();
if (mZOrderVisibleSortedMap.isEmpty()) {
setWindowVisible(false);
}
Log.d(TAG, "Content hidden: " + viewController.getClass().getName());
debugLog();
}
private void refreshHighestZOrderWhenHidingView(OverlayViewController viewController) {
if (mZOrderVisibleSortedMap.isEmpty()) {
mHighestZOrder = null;
return;
}
if (!mHighestZOrder.equals(viewController)) {
return;
}
mHighestZOrder = mZOrderVisibleSortedMap.get(mZOrderVisibleSortedMap.lastKey());
}
private void refreshNavigationBarVisibility() {
if (mZOrderVisibleSortedMap.isEmpty() || mHighestZOrder.shouldShowNavigationBar()) {
mCarNavigationBarController.showBars();
} else {
mCarNavigationBarController.hideBars();
}
}
/** Returns {@code true} is the window is visible. */
public boolean isWindowVisible() {
return mSystemUIOverlayWindowController.isWindowVisible();
}
private void setWindowVisible(boolean visible) {
mSystemUIOverlayWindowController.setWindowVisible(visible);
}
/**
* Sets the {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM} flag of the
* sysui overlay window.
*/
public void setWindowNeedsInput(boolean needsInput) {
mSystemUIOverlayWindowController.setWindowNeedsInput(needsInput);
}
/** Returns {@code true} if the window is focusable. */
public boolean isWindowFocusable() {
return mSystemUIOverlayWindowController.isWindowFocusable();
}
/** Sets the focusable flag of the sysui overlawy window. */
public void setWindowFocusable(boolean focusable) {
mSystemUIOverlayWindowController.setWindowFocusable(focusable);
}
/** Inflates the view controlled by the given view controller. */
public void inflateView(OverlayViewController viewController) {
if (!viewController.isInflated()) {
viewController.inflate(mSystemUIOverlayWindowController.getBaseLayout());
}
}
/**
* Return {@code true} if OverlayWindow is in a state where HUNs should be displayed above it.
*/
public boolean shouldShowHUN() {
return mZOrderVisibleSortedMap.isEmpty() || mHighestZOrder.shouldShowHUN();
}
/**
* Set the OverlayViewWindow to be in occluded or unoccluded state. When OverlayViewWindow is
* occluded, all views mounted to it that are not configured to be shown during occlusion will
* be hidden.
*/
public void setOccluded(boolean occluded) {
if (occluded) {
// Hide views before setting mIsOccluded to true so the regular hideView logic is used,
// not the one used during occlusion.
hideViewsForOcclusion();
mIsOccluded = true;
} else {
mIsOccluded = false;
// show views after setting mIsOccluded to false so the regular showView logic is used,
// not the one used during occlusion.
showViewsHiddenForOcclusion();
}
}
private void hideViewsForOcclusion() {
HashSet<OverlayViewController> viewsCurrentlyShowing = new HashSet<>(
mZOrderVisibleSortedMap.values());
viewsCurrentlyShowing.forEach(overlayController -> {
if (!overlayController.shouldShowWhenOccluded()) {
hideView(overlayController, overlayController::hideInternal);
mViewsHiddenForOcclusion.add(overlayController);
}
});
}
private void showViewsHiddenForOcclusion() {
mViewsHiddenForOcclusion.forEach(overlayViewController -> {
showView(overlayViewController, overlayViewController::showInternal);
});
mViewsHiddenForOcclusion.clear();
}
private void debugLog() {
if (!DEBUG) {
return;
}
Log.d(TAG, "mHighestZOrder: " + mHighestZOrder);
Log.d(TAG, "mZOrderVisibleSortedMap.size(): " + mZOrderVisibleSortedMap.size());
Log.d(TAG, "mZOrderVisibleSortedMap: " + mZOrderVisibleSortedMap);
Log.d(TAG, "mZOrderMap.size(): " + mZOrderMap.size());
Log.d(TAG, "mZOrderMap: " + mZOrderMap);
Log.d(TAG, "mIsOccluded: " + mIsOccluded);
Log.d(TAG, "mViewsHiddenForOcclusion: " + mViewsHiddenForOcclusion);
Log.d(TAG, "mViewsHiddenForOcclusion.size(): " + mViewsHiddenForOcclusion.size());
}
}