summaryrefslogtreecommitdiff
path: root/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainerView.java
blob: 553608310f2683ae2c7f3bc6f1b604cd86449155 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
// Copyright 2013 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.weblayer_private;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import org.chromium.base.MathUtils;
import org.chromium.ui.display.DisplayAndroid;
import org.chromium.ui.display.DisplayUtil;

/**
 * The {@link View} for the {@link InfoBarContainer}.
 */
public class InfoBarContainerView extends SwipableOverlayView {
    /**
     * Observes container view changes.
     */
    public interface ContainerViewObserver extends InfoBarContainer.InfoBarAnimationListener {
        /**
         * Called when the height of shown content changed.
         * @param shownFraction The ratio of height of shown content to the height of the container
         *                      view.
         */
        void onShownRatioChanged(float shownFraction);
    }

    /** Top margin, including the toolbar and tabstrip height and 48dp of web contents. */
    private static final int TOP_MARGIN_PHONE_DP = 104;
    private static final int TOP_MARGIN_TABLET_DP = 144;

    /** Length of the animation to fade the InfoBarContainer back into View. */
    private static final long REATTACH_FADE_IN_MS = 250;

    /** Whether or not the InfoBarContainer is allowed to hide when the user scrolls. */
    private static boolean sIsAllowedToAutoHide = true;

    private final ContainerViewObserver mContainerViewObserver;
    private final InfoBarContainerLayout mLayout;

    /** Parent view that contains the InfoBarContainerLayout. */
    private ViewGroup mParentView;

    private TabImpl mTab;

    /** Animation used to snap the container to the nearest state if scroll direction changes. */
    private Animator mScrollDirectionChangeAnimation;

    /** Whether or not the current scroll is downward. */
    private boolean mIsScrollingDownward;

    /** Tracks the previous event's scroll offset to determine if a scroll is up or down. */
    private int mLastScrollOffsetY;

    /**
     * @param context The {@link Context} that this view is attached to.
     * @param containerViewObserver The {@link ContainerViewObserver} that gets notified on
     *                              container view changes.
     * @param isTablet Whether this view is displayed on tablet or not.
     */
    InfoBarContainerView(@NonNull Context context,
            @NonNull ContainerViewObserver containerViewObserver, TabImpl tab, boolean isTablet) {
        super(context, null);
        mTab = tab;
        mContainerViewObserver = containerViewObserver;

        // TODO(newt): move this workaround into the infobar views if/when they're scrollable.
        // Workaround for http://crbug.com/407149. See explanation in onMeasure() below.
        setVerticalScrollBarEnabled(false);

        updateLayoutParams(context, isTablet);

        Runnable makeContainerVisibleRunnable = () -> runUpEventAnimation(true);
        mLayout = new InfoBarContainerLayout(context, makeContainerVisibleRunnable,
                new InfoBarContainer.InfoBarAnimationListener() {
                    @Override
                    public void notifyAnimationFinished(int animationType) {
                        mContainerViewObserver.notifyAnimationFinished(animationType);
                    }

                    @Override
                    public void notifyAllAnimationsFinished(InfoBarUiItem frontInfoBar) {
                        mContainerViewObserver.notifyAllAnimationsFinished(frontInfoBar);
                    }
                });

        addView(mLayout,
                new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT,
                        Gravity.CENTER_HORIZONTAL));
    }

    void destroy() {
        removeFromParentView();
        mTab = null;
    }

    // SwipableOverlayView implementation.
    @Override
    @VisibleForTesting
    public boolean isAllowedToAutoHide() {
        return sIsAllowedToAutoHide;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (getVisibility() != View.GONE) {
            setVisibility(VISIBLE);
            setAlpha(0f);
            animate().alpha(1f).setDuration(REATTACH_FADE_IN_MS);
        }
    }

    @Override
    protected void runUpEventAnimation(boolean visible) {
        if (mScrollDirectionChangeAnimation != null) mScrollDirectionChangeAnimation.cancel();
        super.runUpEventAnimation(visible);
    }

    @Override
    protected boolean isIndependentlyAnimating() {
        return mScrollDirectionChangeAnimation != null;
    }

    // View implementation.
    @Override
    public void setTranslationY(float translationY) {
        int contentHeightDelta = mTab != null
                ? mTab.getBrowser().getViewController().getBottomContentHeightDelta()
                : 0;

        // Push the infobar container up by any delta caused by the bottom toolbar while ensuring
        // that it does not ascend beyond the top of the bottom toolbar nor descend beyond its own
        // height.
        float newTranslationY = MathUtils.clamp(
                translationY - contentHeightDelta, -contentHeightDelta, getHeight());

        super.setTranslationY(newTranslationY);

        float shownFraction = 0;
        if (getHeight() > 0) {
            shownFraction = contentHeightDelta > 0 ? 1f : 1f - (translationY / getHeight());
        }
        mContainerViewObserver.onShownRatioChanged(shownFraction);
    }

    /**
     * Sets whether the InfoBarContainer is allowed to auto-hide when the user scrolls the page.
     * Expected to be called when Touch Exploration is enabled.
     * @param isAllowed Whether auto-hiding is allowed.
     */
    public static void setIsAllowedToAutoHide(boolean isAllowed) {
        sIsAllowedToAutoHide = isAllowed;
    }

    /**
     * Notifies that an infobar's View ({@link InfoBar#getView}) has changed. If the infobar is
     * visible, a view swapping animation will be run.
     */
    void notifyInfoBarViewChanged() {
        mLayout.notifyInfoBarViewChanged();
    }

    /**
     * Sets the parent {@link ViewGroup} that contains the {@link InfoBarContainer}.
     */
    void setParentView(ViewGroup parent) {
        mParentView = parent;
        // Don't attach the container to the new parent if it is not previously attached.
        if (removeFromParentView()) addToParentView();
    }

    /**
     * Adds this class to the parent view {@link #mParentView}.
     */
    void addToParentView() {
        // If mTab is null, destroy() was called. This should not be added after destroyed.
        assert mTab != null;
        super.addToParentView(mParentView,
                mTab.getBrowser().getViewController().getDesiredInfoBarContainerViewIndex());
    }

    /**
     * Adds an {@link InfoBar} to the layout.
     * @param infoBar The {@link InfoBar} to be added.
     */
    void addInfoBar(InfoBar infoBar) {
        infoBar.createView();
        mLayout.addInfoBar(infoBar);
    }

    /**
     * Removes an {@link InfoBar} from the layout.
     * @param infoBar The {@link InfoBar} to be removed.
     */
    void removeInfoBar(InfoBar infoBar) {
        mLayout.removeInfoBar(infoBar);
    }

    /**
     * Hides or stops hiding this View.
     * @param isHidden Whether this View is should be hidden.
     */
    void setHidden(boolean isHidden) {
        setVisibility(isHidden ? View.GONE : View.VISIBLE);
    }

    /**
     * Run an animation when the scrolling direction of a gesture has changed (this does not mean
     * the gesture has ended).
     * @param visible Whether or not the view should be visible.
     */
    private void runDirectionChangeAnimation(boolean visible) {
        mScrollDirectionChangeAnimation = createVerticalSnapAnimation(visible);
        mScrollDirectionChangeAnimation.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mScrollDirectionChangeAnimation = null;
            }
        });
        mScrollDirectionChangeAnimation.start();
    }

    @Override
    // Ensure that this view's custom layout params are passed when adding it to its parent.
    public ViewGroup.MarginLayoutParams createLayoutParams() {
        return (ViewGroup.MarginLayoutParams) getLayoutParams();
    }

    private void updateLayoutParams(Context context, boolean isTablet) {
        RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
                LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
        int topMarginDp = isTablet ? TOP_MARGIN_TABLET_DP : TOP_MARGIN_PHONE_DP;
        lp.topMargin = DisplayUtil.dpToPx(DisplayAndroid.getNonMultiDisplay(context), topMarginDp);
        setLayoutParams(lp);
    }

    /**
     * Returns true if any animations are pending or in progress.
     */
    @VisibleForTesting
    public boolean isAnimating() {
        return mLayout.isAnimating();
    }
}