// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.compositor.bottombar;
import android.view.ViewGroup;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.StateChangeReason;
import org.chromium.ui.resources.dynamics.DynamicResourceLoader;
import java.util.Comparator;
import java.util.HashSet;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
/**
* Used to decide which panel should be showing on screen at any moment.
*/
public class OverlayPanelManager {
/**
* Priority of an OverlayPanel; used for deciding which panel will be shown when there are
* multiple candidates.
*/
public static enum PanelPriority {
LOW,
MEDIUM,
HIGH;
}
/** The initial size of the priority queue for suppressed panels. */
private static final int INITIAL_QUEUE_CAPACITY = 3;
/** A map of panels that this class is managing. */
private final Set<OverlayPanel> mPanelSet;
/** The panel that is currently being displayed. */
private OverlayPanel mActivePanel;
/**
* If a panel was being shown and another panel with higher priority was requested to show,
* the lower priority one is stored here.
*/
private Queue<OverlayPanel> mSuppressedPanels;
/** When a panel is suppressed, this is the panel waiting for the close animation to finish. */
private OverlayPanel mPendingPanel;
/** When a panel is suppressed, this the reason the pending panel is to be shown. */
private StateChangeReason mPendingReason;
/** This handles resource loading for each panels. */
private DynamicResourceLoader mDynamicResourceLoader;
/** This is the view group that all views related to the panel will be put into. */
private ViewGroup mContainerViewGroup;
/**
* Default constructor.
*/
public OverlayPanelManager() {
mSuppressedPanels = new PriorityQueue<>(INITIAL_QUEUE_CAPACITY,
new Comparator<OverlayPanel>() {
@Override
public int compare(OverlayPanel p1, OverlayPanel p2) {
// The head of the queue is the smallest element, so subtract p1's priority
// from p2's priority.
return p2.getPriority().ordinal() - p1.getPriority().ordinal();
}
});
mPanelSet = new HashSet<>();
}
/**
* Request that a panel with the specified ID be shown. This does not necessarily mean the
* panel will be shown.
* @param panel The panel to show.
* @param reason The reason the panel is going to be shown.
*/
public void requestPanelShow(OverlayPanel panel, StateChangeReason reason) {
if (panel == null || panel == mActivePanel) return;
if (mActivePanel == null) {
// If no panel is currently showing, simply show the requesting panel.
mActivePanel = panel;
// TODO(mdjones): peekPanel should not be exposed publicly since the manager
// controls if a panel should show or not.
mActivePanel.peekPanel(reason);
} else if (panel.getPriority().ordinal() > mActivePanel.getPriority().ordinal()) {
// If a panel with higher priority than the active one requests to be shown, suppress
// the active panel and show the requesting one. closePanel will trigger
// notifyPanelClosed.
mPendingPanel = panel;
mPendingReason = reason;
mActivePanel.closePanel(StateChangeReason.SUPPRESS, true);
} else if (panel.canBeSuppressed()) {
// If a panel was showing and the requesting panel has a lower priority, suppress it
// if possible.
if (!mSuppressedPanels.contains(panel)) mSuppressedPanels.add(panel);
}
}
/**
* Notify the manager that some other object hid the panel.
* NOTE(mdjones): It is possible that a panel other than the one currently showing was hidden.
* @param panel The panel that was closed.
*/
public void notifyPanelClosed(OverlayPanel panel, StateChangeReason reason) {
// TODO(mdjones): Close should behave like "requestShowPanel". The reason it currently does
// not is because closing will cancel animation for that panel. This method waits for the
// panel's "onClosed" event to fire, thus preserving the animation.
if (panel == null) return;
// If the reason to close was to suppress, only suppress the panel.
if (reason == StateChangeReason.SUPPRESS) {
if (mActivePanel == panel) {
if (mActivePanel.canBeSuppressed()) {
mSuppressedPanels.add(mActivePanel);
}
mActivePanel = mPendingPanel;
mActivePanel.peekPanel(mPendingReason);
mPendingPanel = null;
mPendingReason = StateChangeReason.UNKNOWN;
}
} else {
// Normal close panel flow.
if (panel == mActivePanel) {
mActivePanel = null;
if (!mSuppressedPanels.isEmpty()) {
mActivePanel = mSuppressedPanels.poll();
mActivePanel.peekPanel(StateChangeReason.UNSUPPRESS);
}
} else {
mSuppressedPanels.remove(panel);
}
}
}
/**
* Get the panel that has been determined to be active.
* @return The active OverlayPanel.
*/
@VisibleForTesting
public OverlayPanel getActivePanel() {
return mActivePanel;
}
/**
* @return The size of the suppressed panel queue.
*/
@VisibleForTesting
public int getSuppressedQueueSize() {
return mSuppressedPanels.size();
}
/**
* Destroy all panels owned by this manager.
*/
public void destroy() {
for (OverlayPanel p : mPanelSet) {
p.destroy();
}
mPanelSet.clear();
mActivePanel = null;
mSuppressedPanels.clear();
// Clear references to held resources.
mDynamicResourceLoader = null;
mContainerViewGroup = null;
}
/**
* Set the resource loader for all OverlayPanels.
* @param host The OverlayPanel host.
*/
public void setDynamicResourceLoader(DynamicResourceLoader loader) {
mDynamicResourceLoader = loader;
for (OverlayPanel p : mPanelSet) {
p.setDynamicResourceLoader(loader);
}
}
/**
* Set the ViewGroup for all panels.
* @param container The ViewGroup objects will be displayed in.
*/
public void setContainerView(ViewGroup container) {
mContainerViewGroup = container;
for (OverlayPanel p : mPanelSet) {
p.setContainerView(container);
}
}
/**
* Add a panel to the collection that is managed by this class. If any of the setters for this
* class were called before a panel was added, that panel will still get those resources.
* @param panel An OverlayPanel to be managed.
*/
public void registerPanel(OverlayPanel panel) {
// If any of the setters for this manager were called before some panel registration,
// make sure that panel gets the appropriate resources.
if (mDynamicResourceLoader != null) {
panel.setDynamicResourceLoader(mDynamicResourceLoader);
}
if (mContainerViewGroup != null) {
panel.setContainerView(mContainerViewGroup);
}
mPanelSet.add(panel);
}
}