| Index: chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarContainer.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarContainer.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarContainer.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..e078a9a5a81a52bef5c1841a03a83ed892d206bd
|
| --- /dev/null
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarContainer.java
|
| @@ -0,0 +1,522 @@
|
| +// Copyright (c) 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.chrome.browser.infobar;
|
| +
|
| +import android.animation.ObjectAnimator;
|
| +import android.app.Activity;
|
| +import android.graphics.Canvas;
|
| +import android.view.Gravity;
|
| +import android.view.MotionEvent;
|
| +import android.view.View;
|
| +import android.view.ViewGroup;
|
| +import android.widget.FrameLayout;
|
| +import android.widget.LinearLayout;
|
| +
|
| +import com.google.common.annotations.VisibleForTesting;
|
| +
|
| +import org.chromium.base.ApiCompatibilityUtils;
|
| +import org.chromium.base.CalledByNative;
|
| +import org.chromium.content.browser.DeviceUtils;
|
| +import org.chromium.ui.UiUtils;
|
| +
|
| +import java.util.ArrayDeque;
|
| +import java.util.ArrayList;
|
| +import java.util.Iterator;
|
| +import java.util.LinkedList;
|
| +
|
| +
|
| +/**
|
| + * A container for all the infobars of a specific tab.
|
| + * Note that infobars creation can be initiated from Java of from native code.
|
| + * When initiated from native code, special code is needed to keep the Java and native infobar in
|
| + * sync, see NativeInfoBar.
|
| + */
|
| +public class InfoBarContainer extends LinearLayout {
|
| + private static final String TAG = "InfoBarContainer";
|
| + private static final long REATTACH_FADE_IN_MS = 250;
|
| +
|
| + public interface InfoBarAnimationListener {
|
| + /**
|
| + * Notifies the subscriber when an animation is completed.
|
| + */
|
| + void notifyAnimationFinished(int animationType);
|
| + }
|
| +
|
| + private static class InfoBarTransitionInfo {
|
| + // InfoBar being animated.
|
| + public InfoBar target;
|
| +
|
| + // View to replace the current View shown by the ContentWrapperView.
|
| + public View toShow;
|
| +
|
| + // Which type of animation needs to be performed.
|
| + public int animationType;
|
| +
|
| + public InfoBarTransitionInfo(InfoBar bar, View view, int type) {
|
| + assert type >= AnimationHelper.ANIMATION_TYPE_SHOW;
|
| + assert type < AnimationHelper.ANIMATION_TYPE_BOUNDARY;
|
| +
|
| + target = bar;
|
| + toShow = view;
|
| + animationType = type;
|
| + }
|
| + }
|
| +
|
| + private InfoBarAnimationListener mAnimationListener;
|
| +
|
| + // Native InfoBarContainer pointer which will be set by nativeInit()
|
| + private int mNativeInfoBarContainer;
|
| +
|
| + private final Activity mActivity;
|
| +
|
| + private final AutoLoginDelegate mAutoLoginDelegate;
|
| +
|
| + // Whether the infobar are shown on top (below the location bar) or at the bottom of the screen.
|
| + private final boolean mInfoBarsOnTop;
|
| +
|
| + // The list of all infobars in this container, regardless of whether they've been shown yet.
|
| + private final ArrayList<InfoBar> mInfoBars = new ArrayList<InfoBar>();
|
| +
|
| + // We only animate changing infobars one at a time.
|
| + private final ArrayDeque<InfoBarTransitionInfo> mInfoBarTransitions;
|
| +
|
| + // Animation currently moving InfoBars around.
|
| + private AnimationHelper mAnimation;
|
| + private final FrameLayout mAnimationSizer;
|
| +
|
| + // True when this container has been emptied and its native counterpart has been destroyed.
|
| + private boolean mDestroyed = false;
|
| +
|
| + // The id of the tab associated with us. Set to TabBase.INVALID_TAB_ID if no tab is associated.
|
| + private int mTabId;
|
| +
|
| + // Parent view that contains us.
|
| + private ViewGroup mParentView;
|
| +
|
| + public InfoBarContainer(Activity activity, AutoLoginProcessor autoLoginProcessor,
|
| + int tabId, ViewGroup parentView, int nativeWebContents) {
|
| + super(activity);
|
| + setOrientation(LinearLayout.VERTICAL);
|
| + mAnimationListener = null;
|
| + mInfoBarTransitions = new ArrayDeque<InfoBarTransitionInfo>();
|
| +
|
| + mAutoLoginDelegate = new AutoLoginDelegate(autoLoginProcessor, activity);
|
| + mActivity = activity;
|
| + mTabId = tabId;
|
| + mParentView = parentView;
|
| +
|
| + mAnimationSizer = new FrameLayout(activity);
|
| + mAnimationSizer.setVisibility(INVISIBLE);
|
| +
|
| + // The tablet has the infobars below the location bar. On the phone they are at the bottom.
|
| + mInfoBarsOnTop = DeviceUtils.isTablet(activity);
|
| + setGravity(determineGravity());
|
| +
|
| + // Chromium's InfoBarContainer may add an InfoBar immediately during this initialization
|
| + // call, so make sure everything in the InfoBarContainer is completely ready beforehand.
|
| + mNativeInfoBarContainer = nativeInit(nativeWebContents, mAutoLoginDelegate);
|
| + }
|
| +
|
| + public void setAnimationListener(InfoBarAnimationListener listener) {
|
| + mAnimationListener = listener;
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + public InfoBarAnimationListener getAnimationListener() {
|
| + return mAnimationListener;
|
| + }
|
| +
|
| +
|
| + public boolean areInfoBarsOnTop() {
|
| + return mInfoBarsOnTop;
|
| + }
|
| +
|
| + @Override
|
| + public boolean onInterceptTouchEvent(MotionEvent ev) {
|
| + // Trap any attempts to fiddle with the Views while we're animating.
|
| + return mAnimation != null;
|
| + }
|
| +
|
| + @Override
|
| + public boolean onTouchEvent(MotionEvent event) {
|
| + // Consume all motion events so they do not reach the ContentView.
|
| + return true;
|
| + }
|
| +
|
| + private void addToParentView() {
|
| + if (mParentView != null && mParentView.indexOfChild(this) == -1) {
|
| + mParentView.addView(this, createLayoutParams());
|
| + }
|
| + }
|
| +
|
| + private int determineGravity() {
|
| + return mInfoBarsOnTop ? Gravity.TOP : Gravity.BOTTOM;
|
| + }
|
| +
|
| + private FrameLayout.LayoutParams createLayoutParams() {
|
| + return new FrameLayout.LayoutParams(
|
| + LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, determineGravity());
|
| + }
|
| +
|
| + private void removeFromParentView() {
|
| + if (getParent() != null) {
|
| + ((ViewGroup) getParent()).removeView(this);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Called when the parent {@link android.view.ViewGroup} has changed for
|
| + * this container.
|
| + */
|
| + public void onParentViewChanged(int tabId, ViewGroup parentView) {
|
| + mTabId = tabId;
|
| + mParentView = parentView;
|
| +
|
| + if (getParent() != null) {
|
| + removeFromParentView();
|
| + addToParentView();
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
|
| + if (mAnimation == null || child != mAnimation.getTarget()) {
|
| + return super.drawChild(canvas, child, drawingTime);
|
| + }
|
| + // When infobars are on top, the new infobar Z-order is greater than the previous infobar,
|
| + // which means it shows on top during the animation. We cannot change the Z-order in the
|
| + // linear layout, it is driven by the insertion index.
|
| + // So we simply clip the children to their bounds to make sure the new infobar does not
|
| + // paint over.
|
| + boolean retVal;
|
| + canvas.save();
|
| + canvas.clipRect(mAnimation.getTarget().getClippingRect());
|
| + retVal = super.drawChild(canvas, child, drawingTime);
|
| + canvas.restore();
|
| + return retVal;
|
| + }
|
| +
|
| + @Override
|
| + protected void onAttachedToWindow() {
|
| + super.onAttachedToWindow();
|
| + ObjectAnimator.ofFloat(this, "alpha", 0.f, 1.f).setDuration(REATTACH_FADE_IN_MS).start();
|
| + setVisibility(VISIBLE);
|
| + }
|
| +
|
| + @Override
|
| + protected void onDetachedFromWindow() {
|
| + super.onDetachedFromWindow();
|
| + setVisibility(INVISIBLE);
|
| + }
|
| +
|
| + public InfoBar findInfoBar(int nativeInfoBar) {
|
| + for (InfoBar infoBar : mInfoBars) {
|
| + if (infoBar.ownsNativeInfoBar(nativeInfoBar)) {
|
| + return infoBar;
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + /**
|
| + * Adds an InfoBar to the view hierarchy.
|
| + * @param infoBar InfoBar to add to the View hierarchy.
|
| + */
|
| + @CalledByNative
|
| + public void addInfoBar(InfoBar infoBar) {
|
| + assert !mDestroyed;
|
| + if (infoBar == null) {
|
| + return;
|
| + }
|
| + if (mInfoBars.contains(infoBar)) {
|
| + assert false : "Trying to add an info bar that has already been added.";
|
| + return;
|
| + }
|
| +
|
| + // We add the infobar immediately to mInfoBars but we wait for the animation to end to
|
| + // notify it's been added, as tests rely on this notification but expects the infobar view
|
| + // to be available when they get the notification.
|
| + mInfoBars.add(infoBar);
|
| + infoBar.setContext(mActivity);
|
| + infoBar.setInfoBarContainer(this);
|
| +
|
| + enqueueInfoBarAnimation(infoBar, null, AnimationHelper.ANIMATION_TYPE_SHOW);
|
| + }
|
| +
|
| + /**
|
| + * Returns the latest InfoBarTransitionInfo that deals with the given InfoBar.
|
| + * @param toFind InfoBar that we're looking for.
|
| + */
|
| + public InfoBarTransitionInfo findLastTransitionForInfoBar(InfoBar toFind) {
|
| + Iterator<InfoBarTransitionInfo> iterator = mInfoBarTransitions.descendingIterator();
|
| + while (iterator.hasNext()) {
|
| + InfoBarTransitionInfo info = iterator.next();
|
| + if (info.target == toFind) return info;
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + /**
|
| + * Animates swapping out the current View in the {@code infoBar} with {@code toShow} without
|
| + * destroying or dismissing the entire InfoBar.
|
| + * @param infoBar InfoBar that is having its content replaced.
|
| + * @param toShow View representing the InfoBar's new contents.
|
| + */
|
| + public void swapInfoBarViews(InfoBar infoBar, View toShow) {
|
| + assert !mDestroyed;
|
| +
|
| + if (!mInfoBars.contains(infoBar)) {
|
| + assert false : "Trying to swap an InfoBar that is not in this container.";
|
| + return;
|
| + }
|
| +
|
| + InfoBarTransitionInfo transition = findLastTransitionForInfoBar(infoBar);
|
| + if (transition != null && transition.toShow == toShow) {
|
| + assert false : "Tried to enqueue the same swap twice in a row.";
|
| + return;
|
| + }
|
| +
|
| + enqueueInfoBarAnimation(infoBar, toShow, AnimationHelper.ANIMATION_TYPE_SWAP);
|
| + }
|
| +
|
| + /**
|
| + * Removes an InfoBar from the view hierarchy.
|
| + * @param infoBar InfoBar to remove from the View hierarchy.
|
| + */
|
| + public void removeInfoBar(InfoBar infoBar) {
|
| + assert !mDestroyed;
|
| +
|
| + if (!mInfoBars.remove(infoBar)) {
|
| + assert false : "Trying to remove an InfoBar that is not in this container.";
|
| + return;
|
| + }
|
| +
|
| + // If an InfoBar is told to hide itself before it has a chance to be shown, don't bother
|
| + // with animating any of it.
|
| + boolean collapseAnimations = false;
|
| + ArrayDeque<InfoBarTransitionInfo> transitionCopy =
|
| + new ArrayDeque<InfoBarTransitionInfo>(mInfoBarTransitions);
|
| + for (InfoBarTransitionInfo info : transitionCopy) {
|
| + if (info.target == infoBar) {
|
| + if (info.animationType == AnimationHelper.ANIMATION_TYPE_SHOW) {
|
| + // We can assert that two attempts to show the same InfoBar won't be in the
|
| + // deque simultaneously because of the check in addInfoBar().
|
| + assert !collapseAnimations;
|
| + collapseAnimations = true;
|
| + }
|
| + if (collapseAnimations) {
|
| + mInfoBarTransitions.remove(info);
|
| + }
|
| + }
|
| + }
|
| +
|
| + if (!collapseAnimations) {
|
| + enqueueInfoBarAnimation(infoBar, null, AnimationHelper.ANIMATION_TYPE_HIDE);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Enqueue a new animation to run and kicks off the animation sequence.
|
| + */
|
| + private void enqueueInfoBarAnimation(InfoBar infoBar, View toShow, int animationType) {
|
| + InfoBarTransitionInfo info = new InfoBarTransitionInfo(infoBar, toShow, animationType);
|
| + mInfoBarTransitions.add(info);
|
| + processPendingInfoBars();
|
| + }
|
| +
|
| + @Override
|
| + protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
| + // Hide the infobars when the keyboard is showing.
|
| + boolean isShowing = (getVisibility() == View.VISIBLE);
|
| + if (UiUtils.isKeyboardShowing(mActivity, this)) {
|
| + if (isShowing) {
|
| + setVisibility(View.INVISIBLE);
|
| + }
|
| + } else {
|
| + if (!isShowing) {
|
| + setVisibility(View.VISIBLE);
|
| + }
|
| + }
|
| + super.onLayout(changed, l, t, r, b);
|
| + }
|
| +
|
| + /**
|
| + * @return True when this container has been emptied and its native counterpart has been
|
| + * destroyed.
|
| + */
|
| + public boolean hasBeenDestroyed() {
|
| + return mDestroyed;
|
| + }
|
| +
|
| + private void processPendingInfoBars() {
|
| + if (mAnimation != null || mInfoBarTransitions.isEmpty()) return;
|
| +
|
| + // Start animating what has to be animated.
|
| + InfoBarTransitionInfo info = mInfoBarTransitions.remove();
|
| + View toShow = info.toShow;
|
| + ContentWrapperView targetView;
|
| +
|
| + addToParentView();
|
| +
|
| + if (info.animationType == AnimationHelper.ANIMATION_TYPE_SHOW) {
|
| + targetView = info.target.getContentWrapper(true);
|
| + assert mInfoBars.contains(info.target);
|
| + toShow = targetView.detachCurrentView();
|
| + addView(targetView, mInfoBarsOnTop ? getChildCount() : 0,
|
| + new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
|
| + } else {
|
| + targetView = info.target.getContentWrapper(false);
|
| + }
|
| +
|
| + // Kick off the animation.
|
| + mAnimation = new AnimationHelper(this, targetView, info.target, toShow, info.animationType);
|
| + mAnimation.start();
|
| + }
|
| +
|
| + // Called by the tab when it has started loading a new page.
|
| + public void onPageStarted(String url) {
|
| + LinkedList<InfoBar> barsToRemove = new LinkedList<InfoBar>();
|
| +
|
| + for (InfoBar infoBar : mInfoBars) {
|
| + if (infoBar.shouldExpire(url)) {
|
| + barsToRemove.add(infoBar);
|
| + }
|
| + }
|
| +
|
| + for (InfoBar infoBar : barsToRemove) {
|
| + infoBar.dismiss();
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Returns the id of the tab we are associated with.
|
| + */
|
| + public int getTabId() {
|
| + return mTabId;
|
| + }
|
| +
|
| + public void destroy() {
|
| + mDestroyed = true;
|
| + removeAllViews();
|
| + if (mNativeInfoBarContainer != 0) {
|
| + nativeDestroy(mNativeInfoBarContainer);
|
| + }
|
| + mInfoBarTransitions.clear();
|
| + }
|
| +
|
| + /**
|
| + * @return all of the InfoBars held in this container.
|
| + */
|
| + @VisibleForTesting
|
| + public ArrayList<InfoBar> getInfoBars() {
|
| + return mInfoBars;
|
| + }
|
| +
|
| + /**
|
| + * Dismisses all {@link AutoLoginInfoBar}s in this {@link InfoBarContainer} that are for
|
| + * {@code accountName} and {@code authToken}. This also resets all {@link InfoBar}s that are
|
| + * for a different request.
|
| + * @param accountName The name of the account request is being accessed for.
|
| + * @param authToken The authentication token access is being requested for.
|
| + * @param success Whether or not the authentication attempt was successful.
|
| + * @param result The resulting token for the auto login request (ignored if {@code success} is
|
| + * {@code false}.
|
| + */
|
| + public void processAutoLogin(String accountName, String authToken, boolean success,
|
| + String result) {
|
| + mAutoLoginDelegate.dismissAutoLogins(accountName, authToken, success, result);
|
| + }
|
| +
|
| + /**
|
| + * Dismiss all auto logins infobars without processing any result.
|
| + */
|
| + public void dismissAutoLoginInfoBars() {
|
| + mAutoLoginDelegate.dismissAutoLogins("", "", false, "");
|
| + }
|
| +
|
| + public void prepareTransition(View toShow) {
|
| + if (toShow != null) {
|
| + // In order to animate the addition of the infobar, we need a layout first.
|
| + // Attach the child to invisible layout so that we can get measurements for it without
|
| + // moving everything in the real container.
|
| + ViewGroup parent = (ViewGroup) toShow.getParent();
|
| + if (parent != null) parent.removeView(toShow);
|
| +
|
| + assert mAnimationSizer.getParent() == null;
|
| + mParentView.addView(mAnimationSizer, createLayoutParams());
|
| + mAnimationSizer.addView(toShow, 0,
|
| + new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
|
| + mAnimationSizer.requestLayout();
|
| + }
|
| + }
|
| +
|
| + public void startTransition() {
|
| + if (mInfoBarsOnTop) {
|
| + // We need to clip this view to its bounds while it is animated because the layout's
|
| + // z-ordering puts it on top of other infobars as it's being animated.
|
| + ApiCompatibilityUtils.postInvalidateOnAnimation(this);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Finishes off whatever animation is running.
|
| + */
|
| + public void finishTransition() {
|
| + assert mAnimation != null;
|
| +
|
| + // If the InfoBar was hidden, get rid of its View entirely.
|
| + if (mAnimation.getAnimationType() == AnimationHelper.ANIMATION_TYPE_HIDE) {
|
| + removeView(mAnimation.getTarget());
|
| + }
|
| +
|
| + // Reset all translations and put everything where they need to be.
|
| + for (int i = 0; i < getChildCount(); ++i) {
|
| + View view = getChildAt(i);
|
| + view.setTranslationY(0);
|
| + }
|
| + requestLayout();
|
| +
|
| + // If there are no infobars shown, there is no need to keep the infobar container in the
|
| + // view hierarchy.
|
| + if (getChildCount() == 0) {
|
| + removeFromParentView();
|
| + }
|
| +
|
| + if (mAnimationSizer.getParent() != null) {
|
| + ((ViewGroup) mAnimationSizer.getParent()).removeView(mAnimationSizer);
|
| + }
|
| +
|
| + // Notify interested parties and move on to the next animation.
|
| + if (mAnimationListener != null) {
|
| + mAnimationListener.notifyAnimationFinished(mAnimation.getAnimationType());
|
| + }
|
| + mAnimation = null;
|
| + processPendingInfoBars();
|
| + }
|
| +
|
| + /**
|
| + * Searches a given view's child views for an instance of {@link InfoBarContainer}.
|
| + *
|
| + * @param parentView View to be searched for
|
| + * @return {@link InfoBarContainer} instance if it's one of the child views;
|
| + * otherwise {@code null}.
|
| + */
|
| + public static InfoBarContainer childViewOf(ViewGroup parentView) {
|
| + for (int i = 0; i < parentView.getChildCount(); i++) {
|
| + if (parentView.getChildAt(i) instanceof InfoBarContainer) {
|
| + return (InfoBarContainer) parentView.getChildAt(i);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + public int getNative() {
|
| + return mNativeInfoBarContainer;
|
| + }
|
| +
|
| + private native int nativeInit(int webContentsPtr, AutoLoginDelegate autoLoginDelegate);
|
| +
|
| + private native void nativeDestroy(int nativeInfoBarContainerAndroid);
|
| +}
|
|
|