1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.chrome.browser.browser_controls;
6 
7 import android.os.Handler;
8 import android.os.SystemClock;
9 
10 import androidx.annotation.VisibleForTesting;
11 
12 import org.chromium.base.CommandLine;
13 import org.chromium.base.supplier.ObservableSupplier;
14 import org.chromium.base.supplier.Supplier;
15 import org.chromium.chrome.browser.flags.ChromeSwitches;
16 import org.chromium.components.browser_ui.util.BrowserControlsVisibilityDelegate;
17 import org.chromium.content_public.common.BrowserControlsState;
18 import org.chromium.ui.util.TokenHolder;
19 
20 /**
21  * Determines the desired visibility of the browser controls based on the current state of the
22  * running activity.
23  */
24 public class BrowserStateBrowserControlsVisibilityDelegate
25         extends BrowserControlsVisibilityDelegate {
26     /** Minimum duration (in milliseconds) that the controls are shown when requested. */
27     @VisibleForTesting
28     static final long MINIMUM_SHOW_DURATION_MS = 3000;
29 
30     private static boolean sDisableOverridesForTesting;
31 
32     private final TokenHolder mTokenHolder;
33 
34     private final Handler mHandler = new Handler();
35 
36     /** Predicate that tells if we're in persistent fullscreen mode. */
37     private final Supplier<Boolean> mPersistentFullscreenMode;
38 
39     private long mCurrentShowingStartTime;
40 
41     /**
42      * Constructs a BrowserControlsVisibilityDelegate designed to deal with overrides driven by
43      * the browser UI (as opposed to the state of the tab).
44      *
45      * @param persistentFullscreenMode Predicate that tells if we're in persistent fullscreen mode.
46      */
BrowserStateBrowserControlsVisibilityDelegate( ObservableSupplier<Boolean> persistentFullscreenMode)47     public BrowserStateBrowserControlsVisibilityDelegate(
48             ObservableSupplier<Boolean> persistentFullscreenMode) {
49         super(BrowserControlsState.BOTH);
50         mTokenHolder = new TokenHolder(this::updateVisibilityConstraints);
51         mPersistentFullscreenMode = persistentFullscreenMode;
52         persistentFullscreenMode.addObserver((persistentMode) -> updateVisibilityConstraints());
53         updateVisibilityConstraints();
54     }
55 
ensureControlsVisibleForMinDuration()56     private void ensureControlsVisibleForMinDuration() {
57         // Do not lock the controls as visible. Such as in testing.
58         if (CommandLine.getInstance().hasSwitch(ChromeSwitches.DISABLE_MINIMUM_SHOW_DURATION)) {
59             return;
60         }
61         if (mHandler.hasMessages(0)) return; // Messages sent via post/postDelayed have what=0
62 
63         long currentShowingTime = SystemClock.uptimeMillis() - mCurrentShowingStartTime;
64         if (currentShowingTime >= MINIMUM_SHOW_DURATION_MS) return;
65 
66         final int temporaryToken = mTokenHolder.acquireToken();
67         mHandler.postDelayed(()
68                                      -> mTokenHolder.releaseToken(temporaryToken),
69                 MINIMUM_SHOW_DURATION_MS - currentShowingTime);
70     }
71 
72     /**
73      * Trigger a temporary showing of the browser controls.
74      */
showControlsTransient()75     public void showControlsTransient() {
76         if (!mTokenHolder.hasTokens()) mCurrentShowingStartTime = SystemClock.uptimeMillis();
77         ensureControlsVisibleForMinDuration();
78     }
79 
80     /**
81      * Trigger a permanent showing of the browser controls until requested otherwise.
82      *
83      * @return The token that determines whether the requester still needs persistent controls to
84      *         be present on the screen.
85      * @see #releasePersistentShowingToken(int)
86      */
showControlsPersistent()87     public int showControlsPersistent() {
88         if (!mTokenHolder.hasTokens()) mCurrentShowingStartTime = SystemClock.uptimeMillis();
89         return mTokenHolder.acquireToken();
90     }
91 
92     /**
93      * Same behavior as {@link #showControlsPersistent()} but also handles removing a previously
94      * requested token if necessary.
95      *
96      * @param oldToken The old fullscreen token to be cleared.
97      * @return The fullscreen token as defined in {@link #showControlsPersistent()}.
98      */
showControlsPersistentAndClearOldToken(int oldToken)99     public int showControlsPersistentAndClearOldToken(int oldToken) {
100         int newToken = showControlsPersistent();
101         mTokenHolder.releaseToken(oldToken);
102         return newToken;
103     }
104 
105     /**
106      * Notify the manager that the browser controls are no longer required for the given token.
107      *
108      * @param token The fullscreen token returned from {@link #showControlsPersistent()}.
109      */
releasePersistentShowingToken(int token)110     public void releasePersistentShowingToken(int token) {
111         if (mTokenHolder.containsOnly(token)) {
112             ensureControlsVisibleForMinDuration();
113         }
114         mTokenHolder.releaseToken(token);
115     }
116 
117     @BrowserControlsState
calculateVisibilityConstraints()118     private int calculateVisibilityConstraints() {
119         if (mPersistentFullscreenMode.get()) {
120             return BrowserControlsState.HIDDEN;
121         } else if (mTokenHolder.hasTokens() && !sDisableOverridesForTesting) {
122             return BrowserControlsState.SHOWN;
123         }
124         return BrowserControlsState.BOTH;
125     }
126 
updateVisibilityConstraints()127     private void updateVisibilityConstraints() {
128         set(calculateVisibilityConstraints());
129     }
130 
131     /**
132      * Disable any browser visibility overrides for testing.
133      */
disableForTesting()134     public static void disableForTesting() {
135         sDisableOverridesForTesting = true;
136     }
137 
138     /**
139      * Performs clean-up.
140      */
destroy()141     public void destroy() {
142         mHandler.removeCallbacksAndMessages(null);
143     }
144 }
145