Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2288)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarContainer.java

Issue 24109002: [InfoBar] Upstram basic infobar flow for Android. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@upstream_infobar_full
Patch Set: Fix License header in two more files Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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);
+}

Powered by Google App Engine
This is Rietveld 408576698