summaryrefslogtreecommitdiff
path: root/chromium/weblayer/browser/java/org/chromium
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-12 14:27:29 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-13 09:35:20 +0000
commitc30a6232df03e1efbd9f3b226777b07e087a1122 (patch)
treee992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/weblayer/browser/java/org/chromium
parent7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff)
downloadqtwebengine-chromium-c30a6232df03e1efbd9f3b226777b07e087a1122.tar.gz
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/weblayer/browser/java/org/chromium')
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/AccessibilityUtil.java209
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/AutofillView.java3
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserControlsContainerView.java60
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserFragmentImpl.java4
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java40
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserViewController.java33
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/ConfirmInfoBar.java81
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentView.java519
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentViewRenderView.java57
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/CrashReporterControllerImpl.java1
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/DownloadImpl.java9
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/ExternalNavigationDelegateImpl.java17
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBar.java331
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarCompactLayout.java238
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainer.java486
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainerLayout.java852
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainerView.java257
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarUiItem.java69
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarWrapper.java44
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/IntentUtils.java48
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/MediaSessionManager.java140
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/MediaStreamManager.java23
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/MojoInterfaceRegistrar.java28
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/NavigationControllerImpl.java14
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/NewTabCallbackProxy.java6
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/PageInfoControllerDelegateImpl.java40
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java42
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/README.md36
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/SiteSettingsFragmentImpl.java39
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/SwipableOverlayView.java421
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java189
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateCompactInfoBar.java578
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateMenu.java75
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateMenuHelper.java321
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateOptions.java278
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateTabContent.java63
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateTabLayout.java240
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/UrlBarControllerImpl.java14
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerAccessibilityUtil.java2
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerFactoryImpl.java9
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java41
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationBuilder.java84
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationChannels.java7
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerSiteSettingsClient.java42
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/WebMessageReplyProxyImpl.java76
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/WebShareServiceFactory.java40
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IBrowser.aidl2
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationController.aidl3
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationControllerClient.aidl4
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IProfile.aidl6
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITab.aidl15
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITabClient.aidl7
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayer.aidl4
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayerClient.aidl2
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebMessageCallbackClient.aidl16
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebMessageReplyProxy.aidl9
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ScrollNotificationType.java18
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/SettingType.java7
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/WebLayerVersionConstants.java19
-rw-r--r--chromium/weblayer/browser/java/org/chromium/weblayer_private/test_interfaces/ITestWebLayer.aidl25
60 files changed, 5484 insertions, 859 deletions
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/AccessibilityUtil.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/AccessibilityUtil.java
deleted file mode 100644
index 5382c726ad7..00000000000
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/AccessibilityUtil.java
+++ /dev/null
@@ -1,209 +0,0 @@
-// Copyright 2014 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.
-
-// TODO(sky): this is a forked copy of that from src/chrome, refactor and share.
-
-package org.chromium.weblayer_private;
-
-import android.accessibilityservice.AccessibilityServiceInfo;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.os.Build;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
-import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import org.chromium.base.ContextUtils;
-import org.chromium.base.ObserverList;
-import org.chromium.base.TraceEvent;
-import org.chromium.base.task.PostTask;
-import org.chromium.content_public.browser.UiThreadTaskTraits;
-
-import java.util.List;
-
-/**
- * Exposes information about the current accessibility state.
- */
-public class AccessibilityUtil {
- /**
- * An observer to be notified of accessibility status changes.
- */
- public interface Observer {
- /**
- * @param enabled Whether a touch exploration or an accessibility service that performs can
- * perform gestures is enabled. Indicates that the UI must be fully navigable using
- * the accessibility view tree.
- */
- void onAccessibilityModeChanged(boolean enabled);
- }
-
- private Boolean mIsAccessibilityEnabled;
- private ObserverList<Observer> mObservers;
- private final class ModeChangeHandler
- implements AccessibilityStateChangeListener, TouchExplorationStateChangeListener {
- // AccessibilityStateChangeListener
-
- @Override
- public final void onAccessibilityStateChanged(boolean enabled) {
- updateIsAccessibilityEnabledAndNotify();
- }
-
- // TouchExplorationStateChangeListener
-
- @Override
- public void onTouchExplorationStateChanged(boolean enabled) {
- updateIsAccessibilityEnabledAndNotify();
- }
- }
-
- private ModeChangeHandler mModeChangeHandler;
-
- protected AccessibilityUtil() {}
-
- /**
- * Checks to see that this device has accessibility and touch exploration enabled.
- * @return Whether or not accessibility and touch exploration are enabled.
- */
- public boolean isAccessibilityEnabled() {
- if (mModeChangeHandler == null) registerModeChangeListeners();
- if (mIsAccessibilityEnabled != null) return mIsAccessibilityEnabled;
-
- TraceEvent.begin("AccessibilityManager::isAccessibilityEnabled");
-
- AccessibilityManager manager = getAccessibilityManager();
- boolean accessibilityEnabled =
- manager != null && manager.isEnabled() && manager.isTouchExplorationEnabled();
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && manager != null
- && manager.isEnabled() && !accessibilityEnabled) {
- List<AccessibilityServiceInfo> services = manager.getEnabledAccessibilityServiceList(
- AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
- for (AccessibilityServiceInfo service : services) {
- if (canPerformGestures(service)) {
- accessibilityEnabled = true;
- break;
- }
- }
- }
-
- mIsAccessibilityEnabled = accessibilityEnabled;
-
- TraceEvent.end("AccessibilityManager::isAccessibilityEnabled");
- return mIsAccessibilityEnabled;
- }
-
- /**
- * Add {@link Observer} object. The observer will be notified of the current accessibility
- * mode immediately.
- * @param observer Observer object monitoring a11y mode change.
- */
- public void addObserver(Observer observer) {
- getObservers().addObserver(observer);
-
- // Notify mode change to a new observer so things are initialized correctly when Chrome
- // has been re-started after closing due to the last tab being closed when homepage is
- // enabled. See crbug.com/541546.
- observer.onAccessibilityModeChanged(isAccessibilityEnabled());
- }
-
- /**
- * Remove {@link Observer} object.
- * @param observer Observer object monitoring a11y mode change.
- */
- public void removeObserver(Observer observer) {
- getObservers().removeObserver(observer);
- }
-
- /**
- * @return True if a hardware keyboard is detected.
- */
- public static boolean isHardwareKeyboardAttached(Configuration c) {
- return c.keyboard != Configuration.KEYBOARD_NOKEYS;
- }
-
- private AccessibilityManager getAccessibilityManager() {
- return (AccessibilityManager) ContextUtils.getApplicationContext().getSystemService(
- Context.ACCESSIBILITY_SERVICE);
- }
-
- private void registerModeChangeListeners() {
- assert mModeChangeHandler == null;
- mModeChangeHandler = new ModeChangeHandler();
- AccessibilityManager manager = getAccessibilityManager();
- manager.addAccessibilityStateChangeListener(mModeChangeHandler);
- manager.addTouchExplorationStateChangeListener(mModeChangeHandler);
- }
-
- /**
- * Removes all global state tracking observers/listeners as well as any observers added to this.
- * As this removes all observers, be very careful in calling. In general, only call when the
- * application is going to be destroyed.
- */
- protected void stopTrackingStateAndRemoveObservers() {
- if (mObservers != null) mObservers.clear();
- if (mModeChangeHandler == null) return;
- AccessibilityManager manager = getAccessibilityManager();
- manager.removeAccessibilityStateChangeListener(mModeChangeHandler);
- manager.removeTouchExplorationStateChangeListener(mModeChangeHandler);
- }
-
- /**
- * Forces recalculating the value of isAccessibilityEnabled(). If the value has changed observer
- * are notified.
- */
- protected void updateIsAccessibilityEnabledAndNotify() {
- boolean oldIsAccessibilityEnabled = isAccessibilityEnabled();
- // Setting to null forces the next call to isAccessibilityEnabled() to update the value.
- mIsAccessibilityEnabled = null;
- if (oldIsAccessibilityEnabled != isAccessibilityEnabled()) notifyModeChange();
- }
-
- private ObserverList<Observer> getObservers() {
- if (mObservers == null) mObservers = new ObserverList<>();
- return mObservers;
- }
-
- /**
- * Notify all the observers of the mode change.
- */
- private void notifyModeChange() {
- boolean enabled = isAccessibilityEnabled();
- for (Observer observer : getObservers()) {
- observer.onAccessibilityModeChanged(enabled);
- }
- }
-
- /**
- * Checks whether the given {@link AccessibilityServiceInfo} can perform gestures.
- * @param service The service to check.
- * @return Whether the {@code service} can perform gestures. On N+, this relies on the
- * capabilities the service can perform. On L & M, this looks specifically for
- * Switch Access.
- */
- private boolean canPerformGestures(AccessibilityServiceInfo service) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- return (service.getCapabilities()
- & AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES)
- != 0;
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- return service.getResolveInfo() != null
- && service.getResolveInfo().toString().contains("switchaccess");
- }
- return false;
- }
-
- /**
- * Set whether the device has accessibility enabled. Should be reset back to null after the test
- * has finished.
- * @param isEnabled whether the device has accessibility enabled.
- */
- @VisibleForTesting
- public void setAccessibilityEnabledForTesting(@Nullable Boolean isEnabled) {
- mIsAccessibilityEnabled = isEnabled;
- PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, this::notifyModeChange);
- }
-}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/AutofillView.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/AutofillView.java
index 1b2992d55b4..ca8217eae14 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/AutofillView.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/AutofillView.java
@@ -12,9 +12,12 @@ import android.view.ViewStructure;
import android.view.autofill.AutofillValue;
import android.widget.FrameLayout;
+import org.chromium.base.annotations.VerifiesOnO;
+
/**
* View which handles autofill support for a tab.
*/
+@VerifiesOnO
public class AutofillView extends FrameLayout {
private TabImpl mTab;
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserControlsContainerView.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserControlsContainerView.java
index d860d7cdef4..ce10368c02c 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserControlsContainerView.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserControlsContainerView.java
@@ -267,15 +267,32 @@ class BrowserControlsContainerView extends FrameLayout {
if (mView == null) return;
int width = right - left;
int height = bottom - top;
- if (height != mLastHeight || width != mLastWidth) {
- mLastWidth = width;
- mLastHeight = height;
- if (mLastWidth > 0 && mLastHeight > 0) {
- if (mViewResourceAdapter == null) {
- createAdapterAndLayer();
+ boolean heightChanged = height != mLastHeight;
+ if (!heightChanged && width == mLastWidth) return;
+
+ mLastWidth = width;
+ mLastHeight = height;
+ if (mLastWidth > 0 && mLastHeight > 0 && mViewResourceAdapter == null) {
+ createAdapterAndLayer();
+ } else if (mViewResourceAdapter != null) {
+ BrowserControlsContainerViewJni.get().setControlsSize(
+ mNativeBrowserControlsContainerView, mLastWidth, mLastHeight);
+ if (mWebContents != null) mWebContents.notifyBrowserControlsHeightChanged();
+ if (heightChanged) {
+ // When the height changes cc doesn't generate a new frame, which means this code
+ // must process the change now. If cc generated a new frame, it would likely be at
+ // the wrong size.
+ if (mControlsOffset == 0) {
+ // The controls are completely visible.
+ onOffsetsChanged(0, height);
} else {
- BrowserControlsContainerViewJni.get().setControlsSize(
- mNativeBrowserControlsContainerView, mLastWidth, mLastHeight);
+ // The controls are partially (and possibly completely) hidden. Snap to
+ // completely hidden.
+ if (mIsTop) {
+ onOffsetsChanged(-height, height);
+ } else {
+ onOffsetsChanged(height, 0);
+ }
}
}
}
@@ -333,7 +350,11 @@ class BrowserControlsContainerView extends FrameLayout {
private void finishScroll(int contentOffsetY) {
mInScroll = false;
setControlsOffset(0, contentOffsetY);
- mContentViewRenderView.postOnAnimation(() -> showControls());
+ if (BrowserControlsContainerViewJni.get().shouldDelayVisibilityChange()) {
+ mContentViewRenderView.postOnAnimation(() -> showControls());
+ } else {
+ showControls();
+ }
}
private void setControlsOffset(int controlsOffsetY, int contentOffsetY) {
@@ -350,16 +371,20 @@ class BrowserControlsContainerView extends FrameLayout {
}
if (mIsTop) {
BrowserControlsContainerViewJni.get().setTopControlsOffset(
- mNativeBrowserControlsContainerView, mControlsOffset, mContentOffset);
+ mNativeBrowserControlsContainerView, mContentOffset);
} else {
BrowserControlsContainerViewJni.get().setBottomControlsOffset(
- mNativeBrowserControlsContainerView, mControlsOffset);
+ mNativeBrowserControlsContainerView);
}
}
private void prepareForScroll() {
mInScroll = true;
- mContentViewRenderView.postOnAnimation(() -> hideControls());
+ if (BrowserControlsContainerViewJni.get().shouldDelayVisibilityChange()) {
+ mContentViewRenderView.postOnAnimation(() -> hideControls());
+ } else {
+ hideControls();
+ }
}
private void hideControls() {
@@ -371,6 +396,11 @@ class BrowserControlsContainerView extends FrameLayout {
}
@CalledByNative
+ private int getControlsOffset() {
+ return mControlsOffset;
+ }
+
+ @CalledByNative
private void didToggleFullscreenModeForTab(final boolean isFullscreen) {
// Delay hiding until after the animation. This comes from Chrome code.
if (mSystemUiFullscreenResizeRunnable != null) {
@@ -410,11 +440,11 @@ class BrowserControlsContainerView extends FrameLayout {
void deleteBrowserControlsContainerView(long nativeBrowserControlsContainerView);
void createControlsLayer(long nativeBrowserControlsContainerView, int id);
void deleteControlsLayer(long nativeBrowserControlsContainerView);
- void setTopControlsOffset(
- long nativeBrowserControlsContainerView, int controlsOffsetY, int contentOffsetY);
- void setBottomControlsOffset(long nativeBrowserControlsContainerView, int controlsOffsetY);
+ void setTopControlsOffset(long nativeBrowserControlsContainerView, int contentOffsetY);
+ void setBottomControlsOffset(long nativeBrowserControlsContainerView);
void setControlsSize(long nativeBrowserControlsContainerView, int width, int height);
void updateControlsResource(long nativeBrowserControlsContainerView);
void setWebContents(long nativeBrowserControlsContainerView, WebContents webContents);
+ boolean shouldDelayVisibilityChange();
}
}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserFragmentImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserFragmentImpl.java
index 33a8af8ce37..9ccb13e1704 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserFragmentImpl.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserFragmentImpl.java
@@ -4,6 +4,7 @@
package org.chromium.weblayer_private;
+import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
@@ -127,7 +128,8 @@ public class BrowserFragmentImpl extends RemoteFragmentImpl {
@Override
public void onStop() {
super.onStop();
- mBrowser.onFragmentStop();
+ Activity activity = getActivity();
+ mBrowser.onFragmentStop(activity != null && activity.getChangingConfigurations() != 0);
}
@Override
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java
index fc12eccb248..66433bde80a 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java
@@ -63,6 +63,7 @@ public class BrowserImpl extends IBrowser.Stub {
private final UrlBarControllerImpl mUrlBarController;
private boolean mFragmentStarted;
private boolean mFragmentResumed;
+ private boolean mFragmentStoppedForConfigurationChange;
// Cache the value instead of querying system every time.
private Boolean mPasswordEchoEnabled;
private Boolean mDarkThemeEnabled;
@@ -106,6 +107,8 @@ public class BrowserImpl extends IBrowser.Stub {
? savedInstanceState.getByteArray(SAVED_STATE_MINIMAL_PERSISTENCE_STATE_KEY)
: null;
+ windowAndroid.restoreInstanceState(savedInstanceState);
+
createAttachmentState(embedderAppContext, windowAndroid);
mNativeBrowser = BrowserImplJni.get().createBrowser(profile.getNativeProfile(), this);
mUrlBarController = new UrlBarControllerImpl(this, mNativeBrowser);
@@ -160,6 +163,10 @@ public class BrowserImpl extends IBrowser.Stub {
outState.putByteArray(SAVED_STATE_MINIMAL_PERSISTENCE_STATE_KEY,
BrowserImplJni.get().getMinimalPersistenceState(mNativeBrowser));
}
+
+ if (mWindowAndroid != null) {
+ mWindowAndroid.saveInstanceState(outState);
+ }
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
@@ -188,6 +195,13 @@ public class BrowserImpl extends IBrowser.Stub {
}
@Override
+ public TabImpl createTab() {
+ TabImpl tab = new TabImpl(mProfile, mWindowAndroid);
+ addTab(tab);
+ return tab;
+ }
+
+ @Override
public void setSupportsEmbedding(boolean enable, IObjectWrapper valueCallback) {
StrictModeWorkaround.apply();
getViewController().setSupportsEmbedding(enable,
@@ -230,7 +244,7 @@ public class BrowserImpl extends IBrowser.Stub {
}
@CalledByNative
- private void createTabForSessionRestore(long nativeTab) {
+ private void createJavaTabForNativeTab(long nativeTab) {
new TabImpl(mProfile, mWindowAndroid, nativeTab);
}
@@ -309,7 +323,7 @@ public class BrowserImpl extends IBrowser.Stub {
@CalledByNative
private void onActiveTabChanged(TabImpl tab) {
- mViewController.setActiveTab(tab);
+ if (mViewController != null) mViewController.setActiveTab(tab);
if (mInDestroy) return;
try {
if (mClient != null) {
@@ -388,10 +402,8 @@ public class BrowserImpl extends IBrowser.Stub {
updateAllTabsAndSetActive();
} else if (persistenceInfo.mPersistenceId == null
|| persistenceInfo.mPersistenceId.isEmpty()) {
- TabImpl tab = new TabImpl(mProfile, mWindowAndroid);
- addTab(tab);
- boolean set_active_result = setActiveTab(tab);
- assert set_active_result;
+ boolean setActiveResult = setActiveTab(createTab());
+ assert setActiveResult;
} // else case is session restore, which will asynchronously create tabs.
}
@@ -404,7 +416,6 @@ public class BrowserImpl extends IBrowser.Stub {
}
private void destroyTabImpl(TabImpl tab) {
- BrowserImplJni.get().removeTab(mNativeBrowser, tab.getNativeTab());
tab.destroy();
}
@@ -438,24 +449,31 @@ public class BrowserImpl extends IBrowser.Stub {
}
public void onFragmentStart() {
+ mFragmentStoppedForConfigurationChange = false;
mFragmentStarted = true;
BrowserImplJni.get().onFragmentStart(mNativeBrowser);
updateAllTabs();
checkPreferences();
}
- public void onFragmentStop() {
+ public void onFragmentStop(boolean forConfigurationChange) {
+ mFragmentStoppedForConfigurationChange = forConfigurationChange;
mFragmentStarted = false;
+ if (mFragmentStoppedForConfigurationChange) {
+ destroyAttachmentState();
+ }
updateAllTabs();
}
public void onFragmentResume() {
mFragmentResumed = true;
WebLayerAccessibilityUtil.get().onBrowserResumed();
+ BrowserImplJni.get().onFragmentResume(mNativeBrowser);
}
public void onFragmentPause() {
mFragmentResumed = false;
+ BrowserImplJni.get().onFragmentPause(mNativeBrowser);
}
public boolean isStarted() {
@@ -466,6 +484,10 @@ public class BrowserImpl extends IBrowser.Stub {
return mFragmentResumed;
}
+ public boolean isFragmentStoppedForConfigurationChange() {
+ return mFragmentStoppedForConfigurationChange;
+ }
+
private void destroyAttachmentState() {
if (mLocaleReceiver != null) {
mLocaleReceiver.destroy();
@@ -515,5 +537,7 @@ public class BrowserImpl extends IBrowser.Stub {
byte[] persistenceCryptoKey, byte[] minimalPersistenceState);
void webPreferencesChanged(long nativeBrowserImpl);
void onFragmentStart(long nativeBrowserImpl);
+ void onFragmentResume(long nativeBrowserImpl);
+ void onFragmentPause(long nativeBrowserImpl);
}
}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserViewController.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserViewController.java
index c9ae777bd63..436113adde3 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserViewController.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserViewController.java
@@ -17,6 +17,7 @@ import android.widget.RelativeLayout;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.components.browser_ui.modaldialog.AppModalPresenter;
+import org.chromium.components.embedder_support.view.ContentView;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;
@@ -79,7 +80,7 @@ public final class BrowserViewController
new BrowserControlsContainerView(context, mContentViewRenderView, this, false);
mBottomControlsContainerView.setId(View.generateViewId());
mContentView = ContentView.createContentView(
- context, mTopControlsContainerView.getEventOffsetHandler());
+ context, mTopControlsContainerView.getEventOffsetHandler(), null /* webContents */);
mContentViewRenderView.addView(mContentView,
new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.MATCH_PARENT));
@@ -125,6 +126,11 @@ public final class BrowserViewController
return mContentViewRenderView;
}
+ /** Returns the ViewGroup into which the InfoBarContainer should be parented. **/
+ public ViewGroup getInfoBarContainerParentView() {
+ return mContentViewRenderView;
+ }
+
public ViewGroup getContentView() {
return mContentView;
}
@@ -137,6 +143,14 @@ public final class BrowserViewController
return mAutofillView;
}
+ // Returns the index at which the infobar container view should be inserted.
+ public int getDesiredInfoBarContainerViewIndex() {
+ // Ensure that infobars are positioned behind WebContents overlays in z-order.
+ // TODO(blundell): Should infobars instead be hidden while a WebContents overlay is
+ // presented?
+ return mContentViewRenderView.indexOfChild(mWebContentsOverlayView) - 1;
+ }
+
public void setActiveTab(TabImpl tab) {
if (tab == mTab) return;
@@ -160,8 +174,8 @@ public final class BrowserViewController
new WebContentsGestureStateTracker(mContentView, webContents, this);
}
mAutofillView.setTab(mTab);
- mContentView.setTab(mTab);
+ mContentView.setWebContents(webContents);
mContentViewRenderView.setWebContents(webContents);
mTopControlsContainerView.setWebContents(webContents);
mBottomControlsContainerView.setWebContents(webContents);
@@ -184,6 +198,10 @@ public final class BrowserViewController
mBottomControlsContainerView.setView(view);
}
+ public int getBottomContentHeightDelta() {
+ return mBottomControlsContainerView.getContentHeightDelta();
+ }
+
public boolean compositorHasSurface() {
return mContentViewRenderView.hasSurface();
}
@@ -210,19 +228,24 @@ public final class BrowserViewController
}
@Override
- public void onDialogShown(PropertyModel model) {
+ public void onDialogAdded(PropertyModel model) {
onDialogVisibilityChanged(true);
}
@Override
- public void onDialogHidden(PropertyModel model) {
+ public void onLastDialogDismissed() {
onDialogVisibilityChanged(false);
}
private void onDialogVisibilityChanged(boolean showing) {
if (WebLayerFactoryImpl.getClientMajorVersion() < 82) return;
- if (mModalDialogManager.getCurrentType() == ModalDialogType.TAB) {
+ // ModalDialogManager.onLastDialogDismissed() may be called if |mTab| is currently null.
+ // This is because in some situations ModalDialogManager calls onLastDialogDismissed() even
+ // if there were no dialogs present and dismissDialog() is called. This matters as
+ // dismissDialog() may be called when |mTab| is null.
+ // TODO(sky): fix ModalDialogManager and remove mTab conditional.
+ if (mModalDialogManager.getCurrentType() == ModalDialogType.TAB && mTab != null) {
try {
mTab.getClient().onTabModalStateChanged(showing);
} catch (RemoteException e) {
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ConfirmInfoBar.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ConfirmInfoBar.java
new file mode 100644
index 00000000000..4b49a4e8ebf
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ConfirmInfoBar.java
@@ -0,0 +1,81 @@
+// Copyright 2020 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.graphics.Bitmap;
+
+import androidx.annotation.ColorRes;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.components.infobars.InfoBarLayout;
+
+/**
+ * An infobar that presents the user with several buttons.
+ *
+ * TODO(newt): merge this into InfoBar.java.
+ */
+public class ConfirmInfoBar extends InfoBar {
+ /** Text shown on the primary button, e.g. "OK". */
+ private final String mPrimaryButtonText;
+
+ /** Text shown on the secondary button, e.g. "Cancel".*/
+ private final String mSecondaryButtonText;
+
+ /** Text shown on the link, e.g. "Learn more". */
+ private final String mLinkText;
+
+ protected ConfirmInfoBar(int iconDrawableId, @ColorRes int iconTintId, Bitmap iconBitmap,
+ String message, String linkText, String primaryButtonText, String secondaryButtonText) {
+ super(iconDrawableId, iconTintId, message, iconBitmap);
+ mPrimaryButtonText = primaryButtonText;
+ mSecondaryButtonText = secondaryButtonText;
+ mLinkText = linkText;
+ }
+
+ @Override
+ public void createContent(InfoBarLayout layout) {
+ setButtons(layout, mPrimaryButtonText, mSecondaryButtonText);
+ if (mLinkText != null && !mLinkText.isEmpty()) layout.appendMessageLinkText(mLinkText);
+ }
+
+ /**
+ * If your custom infobar overrides this function, YOU'RE PROBABLY DOING SOMETHING WRONG.
+ *
+ * Adds buttons to the infobar. This should only be overridden in cases where an infobar
+ * requires adding something other than a button for its secondary View on the bottom row
+ * (almost never).
+ *
+ * @param primaryText Text to display on the primary button.
+ * @param secondaryText Text to display on the secondary button. May be null.
+ */
+ protected void setButtons(InfoBarLayout layout, String primaryText, String secondaryText) {
+ layout.setButtons(primaryText, secondaryText);
+ }
+
+ @Override
+ public void onButtonClicked(final boolean isPrimaryButton) {
+ int action = isPrimaryButton ? ActionType.OK : ActionType.CANCEL;
+ onButtonClicked(action);
+ }
+
+ /**
+ * Creates and begins the process for showing a ConfirmInfoBar.
+ * @param iconId ID corresponding to the icon that will be shown for the infobar.
+ * @param iconBitmap Bitmap to use if there is no equivalent Java resource for
+ * iconId.
+ * @param message Message to display to the user indicating what the infobar is for.
+ * @param linkText Link text to display in addition to the message.
+ * @param buttonOk String to display on the OK button.
+ * @param buttonCancel String to display on the Cancel button.
+ */
+ @CalledByNative
+ private static ConfirmInfoBar create(int iconId, Bitmap iconBitmap, String message,
+ String linkText, String buttonOk, String buttonCancel) {
+ ConfirmInfoBar infoBar = new ConfirmInfoBar(
+ iconId, 0, iconBitmap, message, linkText, buttonOk, buttonCancel);
+
+ return infoBar;
+ }
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentView.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentView.java
deleted file mode 100644
index 44accce66d8..00000000000
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentView.java
+++ /dev/null
@@ -1,519 +0,0 @@
-// Copyright 2012 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.content.Context;
-import android.content.res.Configuration;
-import android.graphics.Rect;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.view.DragEvent;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnSystemUiVisibilityChangeListener;
-import android.view.ViewGroup.OnHierarchyChangeListener;
-import android.view.ViewStructure;
-import android.view.accessibility.AccessibilityNodeProvider;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.widget.RelativeLayout;
-
-import org.chromium.base.ObserverList;
-import org.chromium.base.TraceEvent;
-import org.chromium.base.compat.ApiHelperForO;
-import org.chromium.content_public.browser.ImeAdapter;
-import org.chromium.content_public.browser.RenderCoordinates;
-import org.chromium.content_public.browser.SmartClipProvider;
-import org.chromium.content_public.browser.ViewEventSink;
-import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContentsAccessibility;
-import org.chromium.ui.base.EventForwarder;
-import org.chromium.ui.base.EventOffsetHandler;
-
-/**
- * The containing view for {@link WebContents} that exists in the Android UI hierarchy and exposes
- * the various {@link View} functionality to it.
- */
-public class ContentView extends RelativeLayout
- implements ViewEventSink.InternalAccessDelegate, SmartClipProvider,
- OnHierarchyChangeListener, OnSystemUiVisibilityChangeListener {
- private static final String TAG = "ContentView";
-
- // Default value to signal that the ContentView's size need not be overridden.
- public static final int DEFAULT_MEASURE_SPEC =
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
-
- private TabImpl mTab;
- private WebContents mWebContents;
- private boolean mIsObscuredForAccessibility;
- private final ObserverList<OnHierarchyChangeListener> mHierarchyChangeListeners =
- new ObserverList<>();
- private final ObserverList<OnSystemUiVisibilityChangeListener> mSystemUiChangeListeners =
- new ObserverList<>();
-
- /**
- * The desired size of this view in {@link MeasureSpec}. Set by the host
- * when it should be different from that of the parent.
- */
- private int mDesiredWidthMeasureSpec = DEFAULT_MEASURE_SPEC;
- private int mDesiredHeightMeasureSpec = DEFAULT_MEASURE_SPEC;
-
- private EventOffsetHandler mEventOffsetHandler;
-
- /**
- * Constructs a new ContentView for the appropriate Android version.
- * @param context The Context the view is running in, through which it can
- * access the current theme, resources, etc.
- * @param webContents The WebContents managing this content view.
- * @return an instance of a ContentView.
- */
- public static ContentView createContentView(
- Context context, EventOffsetHandler eventOffsetHandler) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- return new ContentViewApi23(context, eventOffsetHandler);
- }
- return new ContentView(context, eventOffsetHandler);
- }
-
- /**
- * Creates an instance of a ContentView.
- * @param context The Context the view is running in, through which it can
- * access the current theme, resources, etc.
- * @param webContents A pointer to the WebContents managing this content view.
- */
- ContentView(Context context, EventOffsetHandler eventOffsetHandler) {
- super(context, null, android.R.attr.webViewStyle);
-
- if (getScrollBarStyle() == View.SCROLLBARS_INSIDE_OVERLAY) {
- setHorizontalScrollBarEnabled(false);
- setVerticalScrollBarEnabled(false);
- }
-
- mEventOffsetHandler = eventOffsetHandler;
-
- setFocusable(true);
- setFocusableInTouchMode(true);
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- ApiHelperForO.setDefaultFocusHighlightEnabled(this, false);
- }
-
- setOnHierarchyChangeListener(this);
- setOnSystemUiVisibilityChangeListener(this);
- }
-
- protected WebContentsAccessibility getWebContentsAccessibility() {
- return mWebContents != null && !mWebContents.isDestroyed()
- ? WebContentsAccessibility.fromWebContents(mWebContents)
- : null;
- }
-
- protected TabImpl getTab() {
- return mTab;
- }
-
- public void setTab(TabImpl tab) {
- mTab = tab;
- boolean wasFocused = isFocused();
- boolean wasWindowFocused = hasWindowFocus();
- boolean wasAttached = isAttachedToWindow();
- boolean wasObscured = mIsObscuredForAccessibility;
- if (wasFocused) onFocusChanged(false, View.FOCUS_FORWARD, null);
- if (wasWindowFocused) onWindowFocusChanged(false);
- if (wasAttached) onDetachedFromWindow();
- if (wasObscured) setIsObscuredForAccessibility(false);
- mWebContents = mTab != null ? mTab.getWebContents() : null;
- if (wasFocused) onFocusChanged(true, View.FOCUS_FORWARD, null);
- if (wasWindowFocused) onWindowFocusChanged(true);
- if (wasAttached) onAttachedToWindow();
- if (wasObscured) setIsObscuredForAccessibility(true);
- }
-
- /**
- * Control whether WebContentsAccessibility will respond to accessibility requests.
- */
- public void setIsObscuredForAccessibility(boolean isObscured) {
- if (mIsObscuredForAccessibility == isObscured) return;
- mIsObscuredForAccessibility = isObscured;
- WebContentsAccessibility wcax = getWebContentsAccessibility();
- if (wcax == null) return;
- wcax.setObscuredByAnotherView(mIsObscuredForAccessibility);
- }
-
- @Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- WebContentsAccessibility wcax = getWebContentsAccessibility();
- return wcax != null && wcax.supportsAction(action)
- ? wcax.performAction(action, arguments)
- : super.performAccessibilityAction(action, arguments);
- }
-
- /**
- * Set the desired size of the view. The values are in {@link MeasureSpec}.
- * @param width The width of the content view.
- * @param height The height of the content view.
- */
- public void setDesiredMeasureSpec(int width, int height) {
- mDesiredWidthMeasureSpec = width;
- mDesiredHeightMeasureSpec = height;
- }
-
- @Override
- public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
- assert listener == this : "Use add/removeOnHierarchyChangeListener instead.";
- super.setOnHierarchyChangeListener(listener);
- }
-
- /**
- * Registers the given listener to receive state changes for the content view hierarchy.
- * @param listener Listener to receive view hierarchy state changes.
- */
- public void addOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
- mHierarchyChangeListeners.addObserver(listener);
- }
-
- /**
- * Unregisters the given listener from receiving state changes for the content view hierarchy.
- * @param listener Listener that doesn't want to receive view hierarchy state changes.
- */
- public void removeOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
- mHierarchyChangeListeners.removeObserver(listener);
- }
-
- @Override
- public void setOnSystemUiVisibilityChangeListener(OnSystemUiVisibilityChangeListener listener) {
- assert listener == this : "Use add/removeOnSystemUiVisibilityChangeListener instead.";
- super.setOnSystemUiVisibilityChangeListener(listener);
- }
-
- /**
- * Registers the given listener to receive system UI visibility state changes.
- * @param listener Listener to receive system UI visibility state changes.
- */
- public void addOnSystemUiVisibilityChangeListener(OnSystemUiVisibilityChangeListener listener) {
- mSystemUiChangeListeners.addObserver(listener);
- }
-
- /**
- * Unregisters the given listener from receiving system UI visibility state changes.
- * @param listener Listener that doesn't want to receive state changes.
- */
- public void removeOnSystemUiVisibilityChangeListener(
- OnSystemUiVisibilityChangeListener listener) {
- mSystemUiChangeListeners.removeObserver(listener);
- }
-
- // View.OnHierarchyChangeListener implementation
-
- @Override
- public void onChildViewRemoved(View parent, View child) {
- for (OnHierarchyChangeListener listener : mHierarchyChangeListeners) {
- listener.onChildViewRemoved(parent, child);
- }
- }
-
- @Override
- public void onChildViewAdded(View parent, View child) {
- for (OnHierarchyChangeListener listener : mHierarchyChangeListeners) {
- listener.onChildViewAdded(parent, child);
- }
- }
-
- // View.OnHierarchyChangeListener implementation
-
- @Override
- public void onSystemUiVisibilityChange(int visibility) {
- for (OnSystemUiVisibilityChangeListener listener : mSystemUiChangeListeners) {
- listener.onSystemUiVisibilityChange(visibility);
- }
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (mDesiredWidthMeasureSpec != DEFAULT_MEASURE_SPEC) {
- widthMeasureSpec = mDesiredWidthMeasureSpec;
- }
- if (mDesiredHeightMeasureSpec != DEFAULT_MEASURE_SPEC) {
- heightMeasureSpec = mDesiredHeightMeasureSpec;
- }
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
- public AccessibilityNodeProvider getAccessibilityNodeProvider() {
- WebContentsAccessibility wcax = getWebContentsAccessibility();
- AccessibilityNodeProvider provider =
- (wcax != null) ? wcax.getAccessibilityNodeProvider() : null;
- return (provider != null) ? provider : super.getAccessibilityNodeProvider();
- }
-
- // Needed by ViewEventSink.InternalAccessDelegate
- @Override
- public void onScrollChanged(int l, int t, int oldl, int oldt) {
- super.onScrollChanged(l, t, oldl, oldt);
- }
-
- @Override
- public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- // Calls may come while/after WebContents is destroyed. See https://crbug.com/821750#c8.
- if (mWebContents == null || mWebContents.isDestroyed()) return null;
- return ImeAdapter.fromWebContents(mWebContents).onCreateInputConnection(outAttrs);
- }
-
- @Override
- public boolean onCheckIsTextEditor() {
- if (mWebContents == null || mWebContents.isDestroyed()) return false;
- return ImeAdapter.fromWebContents(mWebContents).onCheckIsTextEditor();
- }
-
- @Override
- protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
- try {
- TraceEvent.begin("ContentView.onFocusChanged");
- super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
- if (mWebContents != null) {
- getViewEventSink().setHideKeyboardOnBlur(true);
- getViewEventSink().onViewFocusChanged(gainFocus);
- }
- } finally {
- TraceEvent.end("ContentView.onFocusChanged");
- }
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- super.onWindowFocusChanged(hasWindowFocus);
- if (mWebContents != null) {
- getViewEventSink().onWindowFocusChanged(hasWindowFocus);
- }
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- EventForwarder forwarder = getEventForwarder();
- return forwarder != null ? forwarder.onKeyUp(keyCode, event) : false;
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- if (!isFocused()) return super.dispatchKeyEvent(event);
- EventForwarder forwarder = getEventForwarder();
- return forwarder != null ? forwarder.dispatchKeyEvent(event) : false;
- }
-
- @Override
- public boolean onDragEvent(DragEvent event) {
- EventForwarder forwarder = getEventForwarder();
- return forwarder != null ? forwarder.onDragEvent(event, this) : false;
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent e) {
- boolean ret = super.onInterceptTouchEvent(e);
- mEventOffsetHandler.onInterceptTouchEvent(e);
- return ret;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- EventForwarder forwarder = getEventForwarder();
- boolean ret = forwarder != null ? forwarder.onTouchEvent(event) : false;
- mEventOffsetHandler.onTouchEvent(event);
- return ret;
- }
-
- @Override
- public boolean onInterceptHoverEvent(MotionEvent e) {
- mEventOffsetHandler.onInterceptHoverEvent(e);
- return super.onInterceptHoverEvent(e);
- }
-
- @Override
- public boolean dispatchDragEvent(DragEvent e) {
- mEventOffsetHandler.onPreDispatchDragEvent(e.getAction());
- boolean ret = super.dispatchDragEvent(e);
- mEventOffsetHandler.onPostDispatchDragEvent(e.getAction());
- return ret;
- }
-
- /**
- * Mouse move events are sent on hover enter, hover move and hover exit.
- * They are sent on hover exit because sometimes it acts as both a hover
- * move and hover exit.
- */
- @Override
- public boolean onHoverEvent(MotionEvent event) {
- EventForwarder forwarder = getEventForwarder();
- boolean consumed = forwarder != null ? forwarder.onHoverEvent(event) : false;
- WebContentsAccessibility wcax = getWebContentsAccessibility();
- if (wcax != null && !wcax.isTouchExplorationEnabled()) super.onHoverEvent(event);
- return consumed;
- }
-
- @Override
- public boolean onGenericMotionEvent(MotionEvent event) {
- EventForwarder forwarder = getEventForwarder();
- return forwarder != null ? forwarder.onGenericMotionEvent(event) : false;
- }
-
- private EventForwarder getEventForwarder() {
- return mWebContents != null ? mWebContents.getEventForwarder() : null;
- }
-
- private ViewEventSink getViewEventSink() {
- return mWebContents != null ? ViewEventSink.from(mWebContents) : null;
- }
-
- @Override
- public boolean performLongClick() {
- return false;
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- if (mWebContents != null) {
- getViewEventSink().onConfigurationChanged(newConfig);
- }
- super.onConfigurationChanged(newConfig);
- }
-
- /**
- * Currently the ContentView scrolling happens in the native side. In
- * the Java view system, it is always pinned at (0, 0). scrollBy() and scrollTo()
- * are overridden, so that View's mScrollX and mScrollY will be unchanged at
- * (0, 0). This is critical for drawing ContentView correctly.
- */
- @Override
- public void scrollBy(int x, int y) {
- EventForwarder forwarder = getEventForwarder();
- if (forwarder != null) forwarder.scrollBy(x, y);
- }
-
- @Override
- public void scrollTo(int x, int y) {
- EventForwarder forwarder = getEventForwarder();
- if (forwarder != null) forwarder.scrollTo(x, y);
- }
-
- @Override
- protected int computeHorizontalScrollExtent() {
- RenderCoordinates rc = getRenderCoordinates();
- return rc != null ? rc.getLastFrameViewportWidthPixInt() : 0;
- }
-
- @Override
- protected int computeHorizontalScrollOffset() {
- RenderCoordinates rc = getRenderCoordinates();
- return rc != null ? rc.getScrollXPixInt() : 0;
- }
-
- @Override
- protected int computeHorizontalScrollRange() {
- RenderCoordinates rc = getRenderCoordinates();
- return rc != null ? rc.getContentWidthPixInt() : 0;
- }
-
- @Override
- protected int computeVerticalScrollExtent() {
- RenderCoordinates rc = getRenderCoordinates();
- return rc != null ? rc.getLastFrameViewportHeightPixInt() : 0;
- }
-
- @Override
- protected int computeVerticalScrollOffset() {
- RenderCoordinates rc = getRenderCoordinates();
- return rc != null ? rc.getScrollYPixInt() : 0;
- }
-
- @Override
- protected int computeVerticalScrollRange() {
- RenderCoordinates rc = getRenderCoordinates();
- return rc != null ? rc.getContentHeightPixInt() : 0;
- }
-
- private RenderCoordinates getRenderCoordinates() {
- return mWebContents != null ? RenderCoordinates.fromWebContents(mWebContents) : null;
- }
-
- // End RelativeLayout overrides.
-
- @Override
- public boolean awakenScrollBars(int startDelay, boolean invalidate) {
- // For the default implementation of ContentView which draws the scrollBars on the native
- // side, calling this function may get us into a bad state where we keep drawing the
- // scrollBars, so disable it by always returning false.
- if (getScrollBarStyle() == View.SCROLLBARS_INSIDE_OVERLAY) return false;
- return super.awakenScrollBars(startDelay, invalidate);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- if (mWebContents != null) {
- getViewEventSink().onAttachedToWindow();
- }
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (mWebContents != null) {
- getViewEventSink().onDetachedFromWindow();
- }
- }
-
- // Implements SmartClipProvider
- @Override
- public void extractSmartClipData(int x, int y, int width, int height) {
- if (mWebContents != null) {
- mWebContents.requestSmartClipExtract(x, y, width, height);
- }
- }
-
- // Implements SmartClipProvider
- @Override
- public void setSmartClipResultHandler(final Handler resultHandler) {
- if (mWebContents != null) {
- mWebContents.setSmartClipResultHandler(resultHandler);
- }
- }
-
- ///////////////////////////////////////////////////////////////////////////////////////////////
- // Start Implementation of ViewEventSink.InternalAccessDelegate //
- ///////////////////////////////////////////////////////////////////////////////////////////////
-
- @Override
- public boolean super_onKeyUp(int keyCode, KeyEvent event) {
- return super.onKeyUp(keyCode, event);
- }
-
- @Override
- public boolean super_dispatchKeyEvent(KeyEvent event) {
- return super.dispatchKeyEvent(event);
- }
-
- @Override
- public boolean super_onGenericMotionEvent(MotionEvent event) {
- return super.onGenericMotionEvent(event);
- }
-
- ///////////////////////////////////////////////////////////////////////////////////////////////
- // End Implementation of ViewEventSink.InternalAccessDelegate //
- ///////////////////////////////////////////////////////////////////////////////////////////////
-
- private static class ContentViewApi23 extends ContentView {
- public ContentViewApi23(Context context, EventOffsetHandler eventOffsetHandler) {
- super(context, eventOffsetHandler);
- }
-
- @Override
- public void onProvideVirtualStructure(final ViewStructure structure) {
- WebContentsAccessibility wcax = getWebContentsAccessibility();
- if (wcax != null) wcax.onProvideVirtualStructure(structure, false);
- }
- }
-}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentViewRenderView.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentViewRenderView.java
index b74f5266590..ac0235eaeb2 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentViewRenderView.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentViewRenderView.java
@@ -86,6 +86,7 @@ public class ContentViewRenderView extends RelativeLayout {
int width, int height);
// |cacheBackBuffer| will delay destroying the EGLSurface until after the next swap.
void surfaceDestroyed(boolean cacheBackBuffer);
+ void surfaceRedrawNeededAsync(Runnable drawingFinished);
}
private final ArrayList<TrackedRunnable> mPendingRunnables = new ArrayList<>();
@@ -160,6 +161,11 @@ public class ContentViewRenderView extends RelativeLayout {
mNativeContentViewRenderView, cacheBackBuffer);
mCompositorHasSurface = false;
}
+
+ @Override
+ public void surfaceRedrawNeededAsync(Runnable drawingFinished) {
+ assert false; // NOTREACHED.
+ }
}
// Abstract differences between SurfaceView and TextureView behind this class.
@@ -220,6 +226,7 @@ public class ContentViewRenderView extends RelativeLayout {
private final TextureViewSurfaceTextureListener mSurfaceTextureListener;
private final ArrayList<ValueCallback<Boolean>> mModeCallbacks = new ArrayList<>();
+ private ArrayList<Runnable> mSurfaceRedrawNeededCallbacks;
public SurfaceData(@Mode int mode, FrameLayout parent, SurfaceEventListener listener,
int backgroundColor, Runnable evict) {
@@ -302,6 +309,7 @@ public class ContentViewRenderView extends RelativeLayout {
mListener.surfaceDestroyed(mCachedSurfaceNeedsEviction);
mNeedsOnSurfaceDestroyed = false;
}
+ runSurfaceRedrawNeededCallbacks();
if (mMode == MODE_SURFACE_VIEW) {
mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
@@ -403,6 +411,15 @@ public class ContentViewRenderView extends RelativeLayout {
return false;
}
+ public void runSurfaceRedrawNeededCallbacks() {
+ ArrayList<Runnable> callbacks = mSurfaceRedrawNeededCallbacks;
+ mSurfaceRedrawNeededCallbacks = null;
+ if (callbacks == null) return;
+ for (Runnable r : callbacks) {
+ r.run();
+ }
+ }
+
private void destroyPreviousData() {
if (mPrevSurfaceDataNeedsDestroy != null) {
mPrevSurfaceDataNeedsDestroy.destroy();
@@ -445,6 +462,22 @@ public class ContentViewRenderView extends RelativeLayout {
assert mNeedsOnSurfaceDestroyed;
mListener.surfaceDestroyed(cacheBackBuffer);
mNeedsOnSurfaceDestroyed = false;
+ runSurfaceRedrawNeededCallbacks();
+ }
+
+ @Override
+ public void surfaceRedrawNeededAsync(Runnable drawingFinished) {
+ if (mMarkedForDestroy) {
+ drawingFinished.run();
+ return;
+ }
+ assert mNativeContentViewRenderView != 0;
+ assert this == ContentViewRenderView.this.mCurrent;
+ if (mSurfaceRedrawNeededCallbacks == null) {
+ mSurfaceRedrawNeededCallbacks = new ArrayList<>();
+ }
+ mSurfaceRedrawNeededCallbacks.add(drawingFinished);
+ ContentViewRenderViewJni.get().setNeedsRedraw(mNativeContentViewRenderView);
}
private void runCallbacks() {
@@ -470,7 +503,7 @@ public class ContentViewRenderView extends RelativeLayout {
}
// Adapter for SurfaceHoolder.Callback.
- private static class SurfaceHolderCallback implements SurfaceHolder.Callback {
+ private static class SurfaceHolderCallback implements SurfaceHolder.Callback2 {
private final SurfaceEventListener mListener;
public SurfaceHolderCallback(SurfaceEventListener listener) {
@@ -491,6 +524,16 @@ public class ContentViewRenderView extends RelativeLayout {
public void surfaceDestroyed(SurfaceHolder holder) {
mListener.surfaceDestroyed(false /* cacheBackBuffer */);
}
+
+ @Override
+ public void surfaceRedrawNeeded(SurfaceHolder holder) {
+ // Intentionally not implemented.
+ }
+
+ @Override
+ public void surfaceRedrawNeededAsync(SurfaceHolder holder, Runnable drawingFinished) {
+ mListener.surfaceRedrawNeededAsync(drawingFinished);
+ }
}
// Adapter for TextureView.SurfaceTextureListener.
@@ -697,7 +740,9 @@ public class ContentViewRenderView extends RelativeLayout {
mWebContents = webContents;
if (webContents != null) {
- updateWebContentsSize();
+ if (getWidth() != 0 && getHeight() != 0) {
+ updateWebContentsSize();
+ }
ContentViewRenderViewJni.get().onPhysicalBackingSizeChanged(
mNativeContentViewRenderView, webContents, mPhysicalWidth, mPhysicalHeight);
}
@@ -719,6 +764,13 @@ public class ContentViewRenderView extends RelativeLayout {
return mCurrent.didSwapFrame();
}
+ @CalledByNative
+ private void didSwapBuffers(boolean sizeMatches) {
+ assert mCurrent != null;
+ if (!sizeMatches) return;
+ mCurrent.runSurfaceRedrawNeededCallbacks();
+ }
+
private void evictCachedSurface() {
if (mNativeContentViewRenderView == 0) return;
ContentViewRenderViewJni.get().evictCachedSurface(mNativeContentViewRenderView);
@@ -752,6 +804,7 @@ public class ContentViewRenderView extends RelativeLayout {
void surfaceDestroyed(long nativeContentViewRenderView, boolean cacheBackBuffer);
void surfaceChanged(long nativeContentViewRenderView, boolean canBeUsedWithSurfaceControl,
int format, int width, int height, Surface surface);
+ void setNeedsRedraw(long nativeContentViewRenderView);
void evictCachedSurface(long nativeContentViewRenderView);
ResourceManager getResourceManager(long nativeContentViewRenderView);
}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/CrashReporterControllerImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/CrashReporterControllerImpl.java
index 1f147c0467c..5318bcc20d0 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/CrashReporterControllerImpl.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/CrashReporterControllerImpl.java
@@ -204,6 +204,7 @@ public final class CrashReporterControllerImpl extends ICrashReporterController.
private String[] processNewMinidumpsOnBackgroundThread() {
Map<String, Map<String, String>> crashesInfoMap =
getCrashFileManager().importMinidumpsCrashKeys();
+ if (crashesInfoMap == null) return new String[0];
ArrayList<String> localIds = new ArrayList<>(crashesInfoMap.size());
for (Map.Entry<String, Map<String, String>> entry : crashesInfoMap.entrySet()) {
JSONObject crashKeysJson = new JSONObject(entry.getValue());
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/DownloadImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/DownloadImpl.java
index 694e8839498..b1046065bc3 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/DownloadImpl.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/DownloadImpl.java
@@ -24,7 +24,6 @@ import org.chromium.components.browser_ui.notifications.NotificationManagerProxy
import org.chromium.components.browser_ui.notifications.NotificationManagerProxyImpl;
import org.chromium.components.browser_ui.notifications.NotificationMetadata;
import org.chromium.components.browser_ui.notifications.PendingIntentProvider;
-import org.chromium.components.browser_ui.notifications.channels.ChannelsInitializer;
import org.chromium.components.browser_ui.util.DownloadUtils;
import org.chromium.weblayer_private.interfaces.APICallException;
import org.chromium.weblayer_private.interfaces.DownloadError;
@@ -342,18 +341,14 @@ public final class DownloadImpl extends IDownload.Stub {
PendingIntentProvider deletePendingIntent =
PendingIntentProvider.getBroadcast(context, mNotificationId, deleteIntent, 0);
- ChannelsInitializer channelsInitializer = new ChannelsInitializer(notificationManager,
- WebLayerNotificationChannels.getInstance(), context.getResources());
-
@DownloadState
int state = getState();
String channelId = state == DownloadState.COMPLETE
? WebLayerNotificationChannels.ChannelId.COMPLETED_DOWNLOADS
: WebLayerNotificationChannels.ChannelId.ACTIVE_DOWNLOADS;
- WebLayerNotificationBuilder builder =
- new WebLayerNotificationBuilder(context, channelId, channelsInitializer,
- new NotificationMetadata(0, NOTIFICATION_TAG, mNotificationId));
+ WebLayerNotificationBuilder builder = WebLayerNotificationBuilder.create(
+ channelId, new NotificationMetadata(0, NOTIFICATION_TAG, mNotificationId));
builder.setOngoing(true)
.setDeleteIntent(deletePendingIntent)
.setPriorityBeforeO(NotificationCompat.PRIORITY_DEFAULT);
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ExternalNavigationDelegateImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ExternalNavigationDelegateImpl.java
index 6cd383bebbf..253c35212d0 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ExternalNavigationDelegateImpl.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ExternalNavigationDelegateImpl.java
@@ -8,6 +8,8 @@ import android.app.Activity;
import android.content.Intent;
import android.content.pm.ResolveInfo;
+import androidx.annotation.Nullable;
+
import org.chromium.base.ContextUtils;
import org.chromium.base.PackageManagerUtils;
import org.chromium.components.embedder_support.util.UrlUtilities;
@@ -16,14 +18,18 @@ import org.chromium.components.external_intents.ExternalNavigationDelegate.Start
import org.chromium.components.external_intents.ExternalNavigationHandler;
import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResult;
import org.chromium.components.external_intents.ExternalNavigationParams;
+import org.chromium.components.webapk.lib.client.ChromeWebApkHostSignature;
+import org.chromium.components.webapk.lib.client.WebApkValidator;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.WindowAndroid;
+import org.chromium.url.Origin;
/**
* WebLayer's implementation of the {@link ExternalNavigationDelegate}.
*/
public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegate {
+ private static boolean sWebApkValidatorInitialized;
private final TabImpl mTab;
private boolean mTabDestroyed;
@@ -154,7 +160,8 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat
@Override
// This is relevant only if the intent ends up being handled by this app, which does not happen
// for WebLayer.
- public void maybeSetUserGesture(Intent intent) {}
+ public void maybeSetRequestMetadata(Intent intent, boolean hasUserGesture,
+ boolean isRendererInitiated, @Nullable Origin initiatorOrigin) {}
@Override
// This is relevant only if the intent ends up being handled by this app, which does not happen
@@ -205,8 +212,12 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat
@Override
public boolean isValidWebApk(String packageName) {
- // TODO(crbug.com/1063874): Determine whether to refine this.
- return false;
+ if (!sWebApkValidatorInitialized) {
+ WebApkValidator.init(ChromeWebApkHostSignature.EXPECTED_SIGNATURE,
+ ChromeWebApkHostSignature.PUBLIC_KEY);
+ sWebApkValidatorInitialized = true;
+ }
+ return WebApkValidator.isValidWebApk(ContextUtils.getApplicationContext(), packageName);
}
@Override
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBar.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBar.java
new file mode 100644
index 00000000000..69e4754d98b
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBar.java
@@ -0,0 +1,331 @@
+// Copyright 2020 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.content.Context;
+import android.graphics.Bitmap;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.ColorRes;
+import androidx.annotation.Nullable;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.chrome.browser.infobar.InfoBarIdentifier;
+import org.chromium.components.infobars.InfoBarInteractionHandler;
+import org.chromium.components.infobars.InfoBarLayout;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/**
+ * The base class for all InfoBar classes.
+ * Note that infobars expire by default when a new navigation occurs.
+ * Make sure to use setExpireOnNavigation(false) if you want an infobar to be sticky.
+ */
+@JNINamespace("weblayer")
+public abstract class InfoBar implements InfoBarInteractionHandler, InfoBarUiItem {
+ private static final String TAG = "InfoBar";
+
+ /**
+ * Interface for InfoBar to interact with its container.
+ */
+ public interface Container {
+ /**
+ * @return True if the infobar is in front.
+ */
+ boolean isFrontInfoBar(InfoBar infoBar);
+
+ /**
+ * Remove the infobar from its container.
+ * @param infoBar InfoBar to remove from the View hierarchy.
+ */
+ void removeInfoBar(InfoBar infoBar);
+
+ /**
+ * Notifies that an infobar's View ({@link InfoBar#getView}) has changed.
+ */
+ void notifyInfoBarViewChanged();
+
+ /**
+ * @return True if the container's destroy() method has been called.
+ */
+ boolean isDestroyed();
+ }
+
+ private final int mIconDrawableId;
+ private final Bitmap mIconBitmap;
+ private final @ColorRes int mIconTintId;
+ private final CharSequence mMessage;
+
+ private @Nullable Container mContainer;
+ private @Nullable View mView;
+ private @Nullable Context mContext;
+
+ private boolean mIsDismissed;
+ private boolean mControlsEnabled = true;
+
+ private @Nullable PropertyModel mModel;
+
+ // This points to the InfoBarAndroid class not any of its subclasses.
+ private long mNativeInfoBarPtr;
+
+ /**
+ * Constructor for regular infobars.
+ * @param iconDrawableId ID of the resource to use for the Icon. If 0, no icon will be shown.
+ * @param iconTintId The {@link ColorRes} used as tint for the {@code iconDrawableId}.
+ * @param message The message to show in the infobar.
+ * @param iconBitmap Icon to draw, in bitmap form. Used mainly for generated icons.
+ */
+ public InfoBar(
+ int iconDrawableId, @ColorRes int iconTintId, CharSequence message, Bitmap iconBitmap) {
+ mIconDrawableId = iconDrawableId;
+ mIconBitmap = iconBitmap;
+ mIconTintId = iconTintId;
+ mMessage = message;
+ }
+
+ /**
+ * Stores a pointer to the native-side counterpart of this InfoBar.
+ * @param nativeInfoBarPtr Pointer to the native InfoBarAndroid, not to its subclass.
+ */
+ @CalledByNative
+ private final void setNativeInfoBar(long nativeInfoBarPtr) {
+ mNativeInfoBarPtr = nativeInfoBarPtr;
+ }
+
+ @CalledByNative
+ protected void onNativeDestroyed() {
+ mNativeInfoBarPtr = 0;
+ }
+
+ /**
+ * Sets the Context used when creating the InfoBar.
+ */
+ public void setContext(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * @return The {@link Context} used to create the InfoBar. This will be null before the InfoBar
+ * is added to an {@link InfoBarContainer}, or after the InfoBar is closed.
+ */
+ @Nullable
+ protected Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Creates the View that represents the InfoBar.
+ * @return The View representing the InfoBar.
+ */
+ public final View createView() {
+ assert mContext != null;
+
+ if (usesCompactLayout()) {
+ InfoBarCompactLayout layout = new InfoBarCompactLayout(
+ mContext, this, mIconDrawableId, mIconTintId, mIconBitmap);
+ createCompactLayoutContent(layout);
+ mView = layout;
+ } else {
+ InfoBarLayout layout = new InfoBarLayout(
+ mContext, this, mIconDrawableId, mIconTintId, mIconBitmap, mMessage);
+ createContent(layout);
+ layout.onContentCreated();
+ mView = layout;
+ }
+
+ return mView;
+ }
+
+ /**
+ * @return The model for this infobar if one was created.
+ */
+ @Nullable
+ PropertyModel getModel() {
+ return mModel;
+ }
+
+ /**
+ * If this returns true, the infobar contents will be replaced with a one-line layout.
+ * When overriding this, also override {@link #getAccessibilityMessage}.
+ */
+ protected boolean usesCompactLayout() {
+ return false;
+ }
+
+ /**
+ * Prepares the InfoBar for display and adds InfoBar-specific controls to the layout.
+ * @param layout Layout containing all of the controls.
+ */
+ protected void createContent(InfoBarLayout layout) {}
+
+ /**
+ * Prepares and inserts views into an {@link InfoBarCompactLayout}.
+ * {@link #usesCompactLayout} must return 'true' for this function to be called.
+ * @param layout Layout to plug views into.
+ */
+ protected void createCompactLayoutContent(InfoBarCompactLayout layout) {}
+
+ /**
+ * Replaces the View currently shown in the infobar with the given View. Triggers the swap
+ * animation via the InfoBarContainer.
+ */
+ protected void replaceView(View newView) {
+ mView = newView;
+ mContainer.notifyInfoBarViewChanged();
+ }
+
+ /**
+ * Returns the View shown in this infobar. Only valid after createView() has been called.
+ */
+ @Override
+ public View getView() {
+ return mView;
+ }
+
+ /**
+ * Returns the accessibility message to announce when this infobar is first shown.
+ * Override this if the InfoBar doesn't have {@link R.id.infobar_message}. It is usually the
+ * case when it is in CompactLayout.
+ */
+ protected CharSequence getAccessibilityMessage(CharSequence defaultTitle) {
+ return defaultTitle == null ? "" : defaultTitle;
+ }
+
+ @Override
+ public CharSequence getAccessibilityText() {
+ if (mView == null) return "";
+
+ CharSequence title = null;
+ TextView messageView = (TextView) mView.findViewById(R.id.infobar_message);
+ if (messageView != null) {
+ title = messageView.getText();
+ }
+ title = getAccessibilityMessage(title);
+ if (title.length() > 0) {
+ title = title + " ";
+ }
+ // TODO(crbug/773717): Avoid string concatenation due to i18n.
+ return title + mContext.getString(R.string.weblayer_bottom_bar_screen_position);
+ }
+
+ @Override
+ public int getPriority() {
+ return InfoBarPriority.PAGE_TRIGGERED;
+ }
+
+ @Override
+ @InfoBarIdentifier
+ public int getInfoBarIdentifier() {
+ if (mNativeInfoBarPtr == 0) return InfoBarIdentifier.INVALID;
+ return InfoBarJni.get().getInfoBarIdentifier(mNativeInfoBarPtr, InfoBar.this);
+ }
+
+ /**
+ * @return whether the infobar actually needed closing.
+ */
+ @CalledByNative
+ private boolean closeInfoBar() {
+ if (!mIsDismissed) {
+ mIsDismissed = true;
+ if (!mContainer.isDestroyed()) {
+ // If the container was destroyed, it's already been emptied of all its infobars.
+ onStartedHiding();
+ mContainer.removeInfoBar(this);
+ }
+ mContainer = null;
+ mView = null;
+ mContext = null;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @return If the infobar is the front infobar (i.e. visible and not hidden behind other
+ * infobars).
+ */
+ public boolean isFrontInfoBar() {
+ return mContainer.isFrontInfoBar(this);
+ }
+
+ /**
+ * Called just before the Java infobar has begun hiding. Give the chance to clean up any child
+ * UI that may remain open.
+ */
+ protected void onStartedHiding() {}
+
+ /**
+ * Returns pointer to native InfoBarAndroid instance.
+ * TODO(crbug/1056346): The function is used in subclasses typically to get Tab reference. When
+ * Tab is modularized, replace this function with the one that returns Tab reference.
+ */
+ protected long getNativeInfoBarPtr() {
+ return mNativeInfoBarPtr;
+ }
+
+ /**
+ * Sets the Container that displays the InfoBar.
+ */
+ public void setContainer(Container container) {
+ mContainer = container;
+ }
+
+ /**
+ * @return Whether or not this InfoBar is already dismissed (i.e. closed).
+ */
+ protected boolean isDismissed() {
+ return mIsDismissed;
+ }
+
+ @Override
+ public boolean areControlsEnabled() {
+ return mControlsEnabled;
+ }
+
+ @Override
+ public void setControlsEnabled(boolean state) {
+ mControlsEnabled = state;
+ }
+
+ @Override
+ public void onClick() {
+ setControlsEnabled(false);
+ }
+
+ @Override
+ public void onButtonClicked(boolean isPrimaryButton) {}
+
+ @Override
+ public void onLinkClicked() {
+ if (mNativeInfoBarPtr != 0) InfoBarJni.get().onLinkClicked(mNativeInfoBarPtr, InfoBar.this);
+ }
+
+ /**
+ * Performs some action related to the button being clicked.
+ * @param action The type of action defined in {@link ActionType} in this class.
+ */
+ protected void onButtonClicked(@ActionType int action) {
+ if (mNativeInfoBarPtr != 0) {
+ InfoBarJni.get().onButtonClicked(mNativeInfoBarPtr, InfoBar.this, action);
+ }
+ }
+
+ @Override
+ public void onCloseButtonClicked() {
+ if (mNativeInfoBarPtr != 0 && !mIsDismissed) {
+ InfoBarJni.get().onCloseButtonClicked(mNativeInfoBarPtr, InfoBar.this);
+ }
+ }
+
+ @NativeMethods
+ interface Natives {
+ int getInfoBarIdentifier(long nativeInfoBarAndroid, InfoBar caller);
+ void onLinkClicked(long nativeInfoBarAndroid, InfoBar caller);
+ void onButtonClicked(long nativeInfoBarAndroid, InfoBar caller, int action);
+ void onCloseButtonClicked(long nativeInfoBarAndroid, InfoBar caller);
+ }
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarCompactLayout.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarCompactLayout.java
new file mode 100644
index 00000000000..c2303eaea6b
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarCompactLayout.java
@@ -0,0 +1,238 @@
+// Copyright 2017 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.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.method.LinkMovementMethod;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.ColorRes;
+import androidx.annotation.StringRes;
+import androidx.appcompat.content.res.AppCompatResources;
+
+import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.base.Callback;
+import org.chromium.components.infobars.InfoBarInteractionHandler;
+import org.chromium.components.infobars.InfoBarLayout;
+import org.chromium.components.infobars.InfoBarMessageView;
+import org.chromium.ui.text.NoUnderlineClickableSpan;
+import org.chromium.ui.widget.ChromeImageButton;
+
+/**
+ * Lays out controls along a line, sandwiched between an (optional) icon and close button.
+ * This should only be used by the {@link InfoBar} class, and is created when the InfoBar subclass
+ * declares itself to be using a compact layout via {@link InfoBar#usesCompactLayout}.
+ */
+public class InfoBarCompactLayout extends LinearLayout implements View.OnClickListener {
+ private final InfoBarInteractionHandler mInfoBar;
+ private final int mCompactInfoBarSize;
+ private final int mIconWidth;
+ private final View mCloseButton;
+
+ /**
+ * Constructs a compat layout for the specified infobar.
+ * @param context The context used to render.
+ * @param infoBar {@link InfoBarInteractionHandler} that listens to events.
+ * @param iconResourceId Resource ID of the icon to use for the infobar.
+ * @param iconTintId The {@link ColorRes} used as tint for {@code iconResourceId}.
+ * @param iconBitmap Bitmap for the icon to use, if {@code iconResourceId} is not set.
+ */
+ // TODO(crbug/1056346): ctor is made public to allow access from InfoBar. Once
+ // InfoBar is modularized, restore access to package private.
+ public InfoBarCompactLayout(Context context, InfoBarInteractionHandler infoBar,
+ int iconResourceId, @ColorRes int iconTintId, Bitmap iconBitmap) {
+ super(context);
+ mInfoBar = infoBar;
+ mCompactInfoBarSize =
+ context.getResources().getDimensionPixelOffset(R.dimen.infobar_compact_size);
+ mIconWidth = context.getResources().getDimensionPixelOffset(R.dimen.infobar_big_icon_size);
+
+ setOrientation(LinearLayout.HORIZONTAL);
+ setGravity(Gravity.CENTER_VERTICAL);
+
+ prepareIcon(iconResourceId, iconTintId, iconBitmap);
+ mCloseButton = prepareCloseButton();
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (view.getId() == R.id.infobar_close_button) {
+ mInfoBar.onCloseButtonClicked();
+ } else {
+ assert false;
+ }
+ }
+
+ /**
+ * Inserts a view before the close button.
+ * @param view View to insert.
+ * @param weight Weight to assign to it.
+ */
+ // TODO(crbug/1056346): addContent is made public to allow access from InfoBar. Once
+ // InfoBar is modularized, restore access to protected.
+ public void addContent(View view, float weight) {
+ LinearLayout.LayoutParams params;
+ if (weight <= 0.0f) {
+ params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, mCompactInfoBarSize);
+ } else {
+ params = new LinearLayout.LayoutParams(0, LayoutParams.WRAP_CONTENT, weight);
+ }
+ view.setMinimumHeight(mCompactInfoBarSize);
+ params.gravity = Gravity.BOTTOM;
+ addView(view, indexOfChild(mCloseButton), params);
+ }
+
+ /**
+ * Adds an icon to the start of the infobar, if the infobar requires one.
+ * @param iconResourceId Resource ID of the icon to use.
+ * @param iconTintId The {@link ColorRes} used as tint for {@code iconResourceId}.
+ * @param iconBitmap Raw {@link Bitmap} to use instead of a resource.
+ */
+ private void prepareIcon(int iconResourceId, @ColorRes int iconTintId, Bitmap iconBitmap) {
+ ImageView iconView =
+ InfoBarLayout.createIconView(getContext(), iconResourceId, iconTintId, iconBitmap);
+ if (iconView != null) {
+ LinearLayout.LayoutParams iconParams =
+ new LinearLayout.LayoutParams(mIconWidth, mCompactInfoBarSize);
+ addView(iconView, iconParams);
+ }
+ }
+
+ /**
+ * Creates a close button that can be inserted into an infobar.
+ * NOTE: This was forked from //chrome's InfoBarLayout.java, as WebLayer supports only compact
+ * infobars and does not have a corresponding InfoBarLayout.java.
+ * @param context Context to grab resources from.
+ * @return {@link ImageButton} that represents a close button.
+ */
+ static ImageButton createCloseButton(Context context) {
+ final ColorStateList tint =
+ AppCompatResources.getColorStateList(context, R.color.default_icon_color);
+ TypedArray a =
+ context.obtainStyledAttributes(new int[] {android.R.attr.selectableItemBackground});
+ Drawable closeButtonBackground = a.getDrawable(0);
+ a.recycle();
+
+ ChromeImageButton closeButton = new ChromeImageButton(context);
+ closeButton.setId(R.id.infobar_close_button);
+ closeButton.setImageResource(R.drawable.btn_close);
+ ApiCompatibilityUtils.setImageTintList(closeButton, tint);
+ closeButton.setBackground(closeButtonBackground);
+ closeButton.setContentDescription(context.getString(R.string.weblayer_infobar_close));
+ closeButton.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+
+ return closeButton;
+ }
+
+ /** Adds a close button to the end of the infobar. */
+ private View prepareCloseButton() {
+ ImageButton closeButton = createCloseButton(getContext());
+ closeButton.setOnClickListener(this);
+ LinearLayout.LayoutParams closeParams =
+ new LinearLayout.LayoutParams(mCompactInfoBarSize, mCompactInfoBarSize);
+ addView(closeButton, closeParams);
+ return closeButton;
+ }
+
+ /**
+ * Helps building a standard message to display in a compact InfoBar. The message can feature
+ * a link to perform and action from this infobar.
+ */
+ public static class MessageBuilder {
+ private final InfoBarCompactLayout mLayout;
+ private CharSequence mMessage;
+ private CharSequence mLink;
+
+ /** @param layout The layout we are building a message view for. */
+ public MessageBuilder(InfoBarCompactLayout layout) {
+ mLayout = layout;
+ }
+
+ public MessageBuilder withText(CharSequence message) {
+ assert mMessage == null;
+ mMessage = message;
+
+ return this;
+ }
+
+ public MessageBuilder withText(@StringRes int messageResId) {
+ assert mMessage == null;
+ mMessage = mLayout.getResources().getString(messageResId);
+
+ return this;
+ }
+
+ /** Appends a link after the main message, its displayed text being the specified string. */
+ public MessageBuilder withLink(CharSequence label, Callback<View> onTapCallback) {
+ assert mLink == null;
+
+ final Resources resources = mLayout.getResources();
+ SpannableString link = new SpannableString(label);
+ link.setSpan(new NoUnderlineClickableSpan(resources, onTapCallback), 0, label.length(),
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ mLink = link;
+
+ return this;
+ }
+
+ /**
+ * Appends a link after the main message, its displayed text being constructed from the
+ * given resource ID.
+ */
+ public MessageBuilder withLink(@StringRes int textResId, Callback<View> onTapCallback) {
+ final Resources resources = mLayout.getResources();
+ String label = resources.getString(textResId);
+ return withLink(label, onTapCallback);
+ }
+
+ /** Finalizes the message view as set up in the builder and inserts it into the layout. */
+ public void buildAndInsert() {
+ mLayout.addContent(build(), 1f);
+ }
+
+ /**
+ * Finalizes the message view as set up in the builder. The caller is responsible for adding
+ * it to the parent layout.
+ */
+ public View build() {
+ // TODO(dgn): Should be able to handle ReaderMode and Survey infobars but they have non
+ // standard interaction models (no button/link, whole bar is a button) or style (large
+ // rather than default text). Revisit after snowflake review.
+
+ assert mMessage != null;
+
+ final int messagePadding = mLayout.getResources().getDimensionPixelOffset(
+ R.dimen.infobar_compact_message_vertical_padding);
+
+ SpannableStringBuilder builder = new SpannableStringBuilder();
+ builder.append(mMessage);
+ if (mLink != null) builder.append(" ").append(mLink);
+
+ TextView prompt = new InfoBarMessageView(mLayout.getContext());
+ ApiCompatibilityUtils.setTextAppearance(
+ prompt, R.style.TextAppearance_TextMedium_Primary);
+ prompt.setText(builder);
+ prompt.setGravity(Gravity.CENTER_VERTICAL);
+ prompt.setPadding(0, messagePadding, 0, messagePadding);
+
+ if (mLink != null) prompt.setMovementMethod(LinkMovementMethod.getInstance());
+
+ return prompt;
+ }
+ }
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainer.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainer.java
new file mode 100644
index 00000000000..15037971602
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainer.java
@@ -0,0 +1,486 @@
+// 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.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import org.chromium.base.ObserverList;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.chrome.browser.infobar.InfoBarIdentifier;
+import org.chromium.content_public.browser.NavigationHandle;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.content_public.browser.WebContentsObserver;
+import org.chromium.ui.KeyboardVisibilityDelegate.KeyboardVisibilityListener;
+import org.chromium.ui.util.AccessibilityUtil;
+
+import java.util.ArrayList;
+
+/**
+ * A container for all the infobars of a specific tab.
+ * Note that infobars creation can be initiated from Java or from native code.
+ * When initiated from native code, special code is needed to keep the Java and native infobar in
+ * sync, see NativeInfoBar.
+ */
+@JNINamespace("weblayer")
+public class InfoBarContainer implements KeyboardVisibilityListener, InfoBar.Container {
+ private static final String TAG = "InfoBarContainer";
+
+ // Number of instances that have not been destroyed.
+ private static int sInstanceCount;
+
+ // InfoBarContainer's handling of accessibility is a global toggle, and thus a static observer
+ // suffices. However, observing accessibility events has the wrinkle that all accessibility
+ // observers are removed when there are no more Browsers and are not re-added if a new Browser
+ // is subsequently created. To handle this wrinkle, |sAccessibilityObserver| is added as an
+ // observer whenever the number of non-destroyed InfoBarContainers becomes non-zero and removed
+ // whenever that number flips to zero.
+ private static final AccessibilityUtil.Observer sAccessibilityObserver;
+ static {
+ sAccessibilityObserver = (enabled) -> setIsAllowedToAutoHide(!enabled);
+ }
+
+ /**
+ * A listener for the InfoBar animations.
+ */
+ public interface InfoBarAnimationListener {
+ public static final int ANIMATION_TYPE_SHOW = 0;
+ public static final int ANIMATION_TYPE_SWAP = 1;
+ public static final int ANIMATION_TYPE_HIDE = 2;
+
+ /**
+ * Notifies the subscriber when an animation is completed.
+ */
+ void notifyAnimationFinished(int animationType);
+
+ /**
+ * Notifies the subscriber when all animations are finished.
+ * @param frontInfoBar The frontmost infobar or {@code null} if none are showing.
+ */
+ void notifyAllAnimationsFinished(InfoBarUiItem frontInfoBar);
+ }
+
+ /**
+ * An observer that is notified of changes to a {@link InfoBarContainer} object.
+ */
+ public interface InfoBarContainerObserver {
+ /**
+ * Called when an {@link InfoBar} is about to be added (before the animation).
+ * @param container The notifying {@link InfoBarContainer}
+ * @param infoBar An {@link InfoBar} being added
+ * @param isFirst Whether the infobar container was empty
+ */
+ void onAddInfoBar(InfoBarContainer container, InfoBar infoBar, boolean isFirst);
+
+ /**
+ * Called when an {@link InfoBar} is about to be removed (before the animation).
+ * @param container The notifying {@link InfoBarContainer}
+ * @param infoBar An {@link InfoBar} being removed
+ * @param isLast Whether the infobar container is going to be empty
+ */
+ void onRemoveInfoBar(InfoBarContainer container, InfoBar infoBar, boolean isLast);
+
+ /**
+ * Called when the InfobarContainer is attached to the window.
+ * @param hasInfobars True if infobar container has infobars to show.
+ */
+ void onInfoBarContainerAttachedToWindow(boolean hasInfobars);
+
+ /**
+ * A notification that the shown ratio of the infobar container has changed.
+ * @param container The notifying {@link InfoBarContainer}
+ * @param shownRatio The shown ratio of the infobar container.
+ */
+ void onInfoBarContainerShownRatioChanged(InfoBarContainer container, float shownRatio);
+ }
+
+ /**
+ * Resets the visibility of the InfoBarContainer when the user navigates, following Chrome's
+ * behavior. In particular in Chrome some features hide the infobar container. This hiding is
+ * always on a per-URL basis that should be undone on navigation. While no feature in WebLayer
+ * yet does this, we put this * defensive behavior in place so that any such added features
+ * don't end up inadvertently hiding the infobar container "forever" in a given tab.
+ */
+ private final WebContentsObserver mWebContentsObserver = new WebContentsObserver() {
+ @Override
+ public void didFinishNavigation(NavigationHandle navigation) {
+ if (navigation.hasCommitted() && navigation.isInMainFrame()) {
+ setHidden(false);
+ }
+ }
+ };
+
+ public void onTabDidGainActive() {
+ initializeContainerView(mTab.getBrowser().getContext());
+ updateWebContents();
+ mInfoBarContainerView.addToParentView();
+ }
+
+ public void onTabDidLoseActive() {
+ mInfoBarContainerView.removeFromParentView();
+ destroyContainerView();
+ }
+
+ /** The list of all InfoBars in this container, regardless of whether they've been shown yet. */
+ private final ArrayList<InfoBar> mInfoBars = new ArrayList<>();
+
+ private final ObserverList<InfoBarContainerObserver> mObservers = new ObserverList<>();
+ private final ObserverList<InfoBarAnimationListener> mAnimationListeners = new ObserverList<>();
+
+ private final InfoBarContainerView.ContainerViewObserver mContainerViewObserver =
+ new InfoBarContainerView.ContainerViewObserver() {
+ @Override
+ public void notifyAnimationFinished(int animationType) {
+ for (InfoBarAnimationListener listener : mAnimationListeners) {
+ listener.notifyAnimationFinished(animationType);
+ }
+ }
+
+ @Override
+ public void notifyAllAnimationsFinished(InfoBarUiItem frontInfoBar) {
+ for (InfoBarAnimationListener listener : mAnimationListeners) {
+ listener.notifyAllAnimationsFinished(frontInfoBar);
+ }
+ }
+
+ @Override
+ public void onShownRatioChanged(float shownFraction) {
+ for (InfoBarContainer.InfoBarContainerObserver observer : mObservers) {
+ observer.onInfoBarContainerShownRatioChanged(
+ InfoBarContainer.this, shownFraction);
+ }
+ }
+ };
+
+ /** The tab that hosts this infobar container. */
+ private final TabImpl mTab;
+
+ /** Native InfoBarContainer pointer which will be set by InfoBarContainerJni.get().init(). */
+ private long mNativeInfoBarContainer;
+
+ /** True when this container has been emptied and its native counterpart has been destroyed. */
+ private boolean mDestroyed;
+
+ /** Whether or not this View should be hidden. */
+ private boolean mIsHidden;
+
+ /**
+ * The view for this {@link InfoBarContainer}. It will be null when the {@link Tab} is detached
+ * from a {@link ChromeActivity}.
+ */
+ private @Nullable InfoBarContainerView mInfoBarContainerView;
+
+ InfoBarContainer(TabImpl tab) {
+ if (++sInstanceCount == 1) {
+ WebLayerAccessibilityUtil.get().addObserver(sAccessibilityObserver);
+ }
+
+ mTab = tab;
+ mTab.getWebContents().addObserver(mWebContentsObserver);
+
+ // Chromium's InfoBarContainer may add an InfoBar immediately during this initialization
+ // call, so make sure everything in the InfoBarContainer is completely ready beforehand.
+ mNativeInfoBarContainer = InfoBarContainerJni.get().init(InfoBarContainer.this);
+ }
+
+ /**
+ * Adds an {@link InfoBarContainerObserver}.
+ * @param observer The {@link InfoBarContainerObserver} to add.
+ */
+ public void addObserver(InfoBarContainerObserver observer) {
+ mObservers.addObserver(observer);
+ }
+
+ /**
+ * Removes a {@link InfoBarContainerObserver}.
+ * @param observer The {@link InfoBarContainerObserver} to remove.
+ */
+ public void removeObserver(InfoBarContainerObserver observer) {
+ mObservers.removeObserver(observer);
+ }
+
+ /**
+ * Sets the parent {@link ViewGroup} that contains the {@link InfoBarContainer}.
+ */
+ public void setParentView(ViewGroup parent) {
+ assert mTab.getBrowser().getActiveTab() == mTab;
+ if (mInfoBarContainerView != null) mInfoBarContainerView.setParentView(parent);
+ }
+
+ @VisibleForTesting
+ public void addAnimationListener(InfoBarAnimationListener listener) {
+ mAnimationListeners.addObserver(listener);
+ }
+
+ /**
+ * Removes the passed in {@link InfoBarAnimationListener} from the {@link InfoBarContainer}.
+ */
+ public void removeAnimationListener(InfoBarAnimationListener listener) {
+ mAnimationListeners.removeObserver(listener);
+ }
+
+ /**
+ * Adds an InfoBar to the view hierarchy.
+ * @param infoBar InfoBar to add to the View hierarchy.
+ */
+ @CalledByNative
+ private 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;
+ }
+
+ infoBar.setContext(mInfoBarContainerView.getContext());
+ infoBar.setContainer(this);
+
+ // We notify observers immediately (before the animation starts).
+ for (InfoBarContainerObserver observer : mObservers) {
+ observer.onAddInfoBar(this, infoBar, mInfoBars.isEmpty());
+ }
+
+ assert mInfoBarContainerView != null : "The container view is null when adding an InfoBar";
+
+ // 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);
+
+ mInfoBarContainerView.addInfoBar(infoBar);
+ }
+
+ @VisibleForTesting
+ public View getViewForTesting() {
+ return mInfoBarContainerView;
+ }
+
+ /**
+ * Adds an InfoBar to the view hierarchy.
+ * @param infoBar InfoBar to add to the View hierarchy.
+ */
+ @VisibleForTesting
+ public void addInfoBarForTesting(InfoBar infoBar) {
+ addInfoBar(infoBar);
+ }
+
+ @Override
+ public void notifyInfoBarViewChanged() {
+ assert !mDestroyed;
+ if (mInfoBarContainerView != null) mInfoBarContainerView.notifyInfoBarViewChanged();
+ }
+
+ /**
+ * Sets the visibility for the {@link InfoBarContainerView}.
+ * @param visibility One of {@link View#GONE}, {@link View#INVISIBLE}, or {@link View#VISIBLE}.
+ */
+ public void setVisibility(int visibility) {
+ if (mInfoBarContainerView != null) mInfoBarContainerView.setVisibility(visibility);
+ }
+
+ /**
+ * @return The visibility of the {@link InfoBarContainerView}.
+ */
+ public int getVisibility() {
+ return mInfoBarContainerView != null ? mInfoBarContainerView.getVisibility() : View.GONE;
+ }
+
+ @Override
+ 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;
+ }
+
+ // Notify observers immediately, before any animations begin.
+ for (InfoBarContainerObserver observer : mObservers) {
+ observer.onRemoveInfoBar(this, infoBar, mInfoBars.isEmpty());
+ }
+
+ assert mInfoBarContainerView
+ != null : "The container view is null when removing an InfoBar.";
+ mInfoBarContainerView.removeInfoBar(infoBar);
+ }
+
+ @Override
+ public boolean isDestroyed() {
+ return mDestroyed;
+ }
+
+ public void destroy() {
+ mTab.getWebContents().removeObserver(mWebContentsObserver);
+
+ if (--sInstanceCount == 0) {
+ WebLayerAccessibilityUtil.get().removeObserver(sAccessibilityObserver);
+ }
+
+ if (mInfoBarContainerView != null) destroyContainerView();
+ if (mNativeInfoBarContainer != 0) {
+ InfoBarContainerJni.get().destroy(mNativeInfoBarContainer, InfoBarContainer.this);
+ mNativeInfoBarContainer = 0;
+ }
+ mDestroyed = true;
+ }
+
+ /**
+ * @return all of the InfoBars held in this container.
+ */
+ @VisibleForTesting
+ public ArrayList<InfoBar> getInfoBarsForTesting() {
+ return mInfoBars;
+ }
+
+ /**
+ * @return True if the container has any InfoBars.
+ */
+ @CalledByNative
+ public boolean hasInfoBars() {
+ return !mInfoBars.isEmpty();
+ }
+
+ /**
+ * @return InfoBarIdentifier of the InfoBar which is currently at the top of the infobar stack,
+ * or InfoBarIdentifier.INVALID if there are no infobars.
+ */
+ @CalledByNative
+ private @InfoBarIdentifier int getTopInfoBarIdentifier() {
+ if (!hasInfoBars()) return InfoBarIdentifier.INVALID;
+ return mInfoBars.get(0).getInfoBarIdentifier();
+ }
+
+ /**
+ * Hides or stops hiding this View.
+ *
+ * @param isHidden Whether this View is should be hidden.
+ */
+ public void setHidden(boolean isHidden) {
+ mIsHidden = isHidden;
+ if (mInfoBarContainerView == null) return;
+ mInfoBarContainerView.setHidden(isHidden);
+ }
+
+ /**
+ * 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.
+ */
+ private static void setIsAllowedToAutoHide(boolean isAllowed) {
+ InfoBarContainerView.setIsAllowedToAutoHide(isAllowed);
+ }
+
+ // KeyboardVisibilityListener implementation.
+ @Override
+ public void keyboardVisibilityChanged(boolean isKeyboardShowing) {
+ assert mInfoBarContainerView != null;
+ boolean isShowing = (mInfoBarContainerView.getVisibility() == View.VISIBLE);
+ if (isKeyboardShowing) {
+ if (isShowing) {
+ mInfoBarContainerView.setVisibility(View.INVISIBLE);
+ }
+ } else {
+ if (!isShowing && !mIsHidden) {
+ mInfoBarContainerView.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+
+ private void updateWebContents() {
+ // When the tab is detached, we don't update the InfoBarContainer web content so that it
+ // stays null until the tab is attached to some ChromeActivity.
+ if (mInfoBarContainerView == null) return;
+ WebContents webContents = mTab.getWebContents();
+
+ if (webContents != null && webContents != mInfoBarContainerView.getWebContents()) {
+ mInfoBarContainerView.setWebContents(webContents);
+ if (mNativeInfoBarContainer != 0) {
+ InfoBarContainerJni.get().setWebContents(
+ mNativeInfoBarContainer, InfoBarContainer.this, webContents);
+ }
+ }
+ }
+
+ private void initializeContainerView(Context chromeActivity) {
+ assert chromeActivity
+ != null
+ : "ChromeActivity should not be null when initializing InfoBarContainerView";
+ mInfoBarContainerView = new InfoBarContainerView(chromeActivity, mContainerViewObserver,
+ mTab, /*isTablet=*/!mTab.getBrowser().isWindowOnSmallDevice());
+
+ mInfoBarContainerView.addOnAttachStateChangeListener(
+ new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ for (InfoBarContainer.InfoBarContainerObserver observer : mObservers) {
+ observer.onInfoBarContainerAttachedToWindow(!mInfoBars.isEmpty());
+ }
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {}
+ });
+
+ mInfoBarContainerView.setHidden(mIsHidden);
+ setParentView(mTab.getBrowser().getViewController().getInfoBarContainerParentView());
+
+ mTab.getBrowser().getWindowAndroid().getKeyboardDelegate().addKeyboardVisibilityListener(
+ this);
+ }
+
+ private void destroyContainerView() {
+ if (mInfoBarContainerView != null) {
+ mInfoBarContainerView.setWebContents(null);
+ if (mNativeInfoBarContainer != 0) {
+ InfoBarContainerJni.get().setWebContents(
+ mNativeInfoBarContainer, InfoBarContainer.this, null);
+ }
+ mInfoBarContainerView.destroy();
+ mInfoBarContainerView = null;
+ }
+
+ mTab.getBrowser().getWindowAndroid().getKeyboardDelegate().removeKeyboardVisibilityListener(
+ this);
+ }
+
+ @Override
+ public boolean isFrontInfoBar(InfoBar infoBar) {
+ if (mInfoBars.isEmpty()) return false;
+ return mInfoBars.get(0) == infoBar;
+ }
+
+ /**
+ * Returns true if any animations are pending or in progress.
+ */
+ @VisibleForTesting
+ public boolean isAnimating() {
+ assert mInfoBarContainerView != null;
+ return mInfoBarContainerView.isAnimating();
+ }
+
+ /**
+ * @return The {@link InfoBarContainerView} this class holds.
+ */
+ @VisibleForTesting
+ public InfoBarContainerView getContainerViewForTesting() {
+ return mInfoBarContainerView;
+ }
+
+ @NativeMethods
+ interface Natives {
+ long init(InfoBarContainer caller);
+ void setWebContents(long nativeInfoBarContainerAndroid, InfoBarContainer caller,
+ WebContents webContents);
+ void destroy(long nativeInfoBarContainerAndroid, InfoBarContainer caller);
+ }
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainerLayout.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainerLayout.java
new file mode 100644
index 00000000000..4f91d6f437d
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainerLayout.java
@@ -0,0 +1,852 @@
+// 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.weblayer_private;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.Resources;
+import android.text.TextUtils;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import org.chromium.ui.widget.OptimizedFrameLayout;
+import org.chromium.weblayer_private.InfoBarContainer.InfoBarAnimationListener;
+
+import java.util.ArrayList;
+
+/**
+ * Layout that displays infobars in a stack. Handles all the animations when adding or removing
+ * infobars and when swapping infobar contents.
+ *
+ * The first infobar to be added is visible at the front of the stack. Later infobars peek up just
+ * enough behind the front infobar to signal their existence; their contents aren't visible at all.
+ * The stack has a max depth of three infobars. If additional infobars are added beyond this, they
+ * won't be visible at all until infobars in front of them are dismissed.
+ *
+ * Animation details:
+ * - Newly added infobars slide up from the bottom and then their contents fade in.
+ * - Disappearing infobars slide down and away. The remaining infobars, if any, resize to the
+ * new front infobar's size, then the content of the new front infobar fades in.
+ * - When swapping the front infobar's content, the old content fades out, the infobar resizes to
+ * the new content's size, then the new content fades in.
+ * - Only a single animation happens at a time. If several infobars are added and/or removed in
+ * quick succession, the animations will be queued and run sequentially.
+ *
+ * Note: this class depends only on Android view code; it intentionally does not depend on any other
+ * infobar code. This is an explicit design decision and should remain this way.
+ *
+ * TODO(newt): what happens when detached from window? Do animations run? Do animations jump to end
+ * values? Should they jump to end values? Does requestLayout() get called when detached
+ * from window? Probably not; it probably just gets called later when reattached.
+ *
+ * TODO(newt): use hardware acceleration? See
+ * http://blog.danlew.net/2015/10/20/using-hardware-layers-to-improve-animation-performance/
+ * and http://developer.android.com/guide/topics/graphics/hardware-accel.html#layers
+ *
+ * TODO(newt): handle tall infobars on small devices. Use a ScrollView inside the InfoBarWrapper?
+ * Make sure InfoBarContainerLayout doesn't extend into tabstrip on tablet.
+ *
+ * TODO(newt): Disable key events during animations, perhaps by overriding dispatchKeyEvent().
+ * Or can we just call setEnabled() false on the infobar wrapper? Will this cause the buttons
+ * visual state to change (i.e. to turn gray)?
+ *
+ * TODO(newt): finalize animation timings and interpolators.
+ */
+public class InfoBarContainerLayout extends OptimizedFrameLayout {
+ /**
+ * Creates an empty InfoBarContainerLayout.
+ */
+ InfoBarContainerLayout(Context context, Runnable makeContainerVisibleRunnable,
+ InfoBarAnimationListener animationListener) {
+ super(context, null);
+ Resources res = context.getResources();
+ mBackInfobarHeight = res.getDimensionPixelSize(R.dimen.infobar_peeking_height);
+ mFloatingBehavior = new FloatingBehavior(this);
+ mAnimationListener = animationListener;
+ mMakeContainerVisibleRunnable = makeContainerVisibleRunnable;
+ }
+
+ /**
+ * Adds an infobar to the container. The infobar appearing animation will happen after the
+ * current animation, if any, finishes.
+ */
+ void addInfoBar(InfoBarUiItem item) {
+ mItems.add(findInsertIndex(item), item);
+ processPendingAnimations();
+ }
+
+ /**
+ * Finds the appropriate index in the infobar stack for inserting this item.
+ * @param item The infobar to be inserted.
+ */
+ private int findInsertIndex(InfoBarUiItem item) {
+ for (int i = 0; i < mItems.size(); ++i) {
+ if (item.getPriority() < mItems.get(i).getPriority()) {
+ return i;
+ }
+ }
+
+ return mItems.size();
+ }
+
+ /**
+ * Removes an infobar from the container. The infobar will be animated off the screen if it's
+ * currently visible.
+ */
+ void removeInfoBar(InfoBarUiItem item) {
+ mItems.remove(item);
+ processPendingAnimations();
+ }
+
+ /**
+ * Notifies that an infobar's View ({@link InfoBarUiItem#getView}) has changed. If the infobar
+ * is visible in the front of the stack, the infobar will fade out the old contents, resize,
+ * then fade in the new contents.
+ */
+ void notifyInfoBarViewChanged() {
+ processPendingAnimations();
+ }
+
+ /**
+ * Returns true if any animations are pending or in progress.
+ */
+ boolean isAnimating() {
+ return mAnimation != null;
+ }
+
+ /////////////////////////////////////////
+ // Implementation details
+ /////////////////////////////////////////
+
+ /** The maximum number of infobars visible at any time. */
+ private static final int MAX_STACK_DEPTH = 3;
+
+ // Animation durations.
+ private static final int DURATION_SLIDE_UP_MS = 250;
+ private static final int DURATION_SLIDE_DOWN_MS = 250;
+ private static final int DURATION_FADE_MS = 100;
+ private static final int DURATION_FADE_OUT_MS = 200;
+
+ /**
+ * Base class for animations inside the InfoBarContainerLayout.
+ *
+ * Provides a standardized way to prepare for, run, and clean up after animations. Each subclass
+ * should implement prepareAnimation(), createAnimator(), and onAnimationEnd() as needed.
+ */
+ private abstract class InfoBarAnimation {
+ private Animator mAnimator;
+
+ final boolean isStarted() {
+ return mAnimator != null;
+ }
+
+ final void start() {
+ Animator.AnimatorListener listener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ InfoBarAnimation.this.onAnimationEnd();
+ mAnimation = null;
+ mAnimationListener.notifyAnimationFinished(getAnimationType());
+ processPendingAnimations();
+ }
+ };
+
+ mAnimator = createAnimator();
+ mAnimator.addListener(listener);
+ mAnimator.start();
+ }
+
+ /**
+ * Returns an animator that animates an InfoBarWrapper's y-translation from its current
+ * value to endValue and updates the side shadow positions on each frame.
+ */
+ ValueAnimator createTranslationYAnimator(final InfoBarWrapper wrapper, float endValue) {
+ ValueAnimator animator = ValueAnimator.ofFloat(wrapper.getTranslationY(), endValue);
+ animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ wrapper.setTranslationY((float) animation.getAnimatedValue());
+ mFloatingBehavior.updateShadowPosition();
+ }
+ });
+ return animator;
+ }
+
+ /**
+ * Called before the animation begins. This is the time to add views to the hierarchy and
+ * adjust layout parameters.
+ */
+ void prepareAnimation() {}
+
+ /**
+ * Called to create an Animator which will control the animation. Called after
+ * prepareAnimation() and after a subsequent layout has happened.
+ */
+ abstract Animator createAnimator();
+
+ /**
+ * Called after the animation completes. This is the time to do post-animation cleanup, such
+ * as removing views from the hierarchy.
+ */
+ void onAnimationEnd() {}
+
+ /**
+ * Returns the InfoBarAnimationListener.ANIMATION_TYPE_* constant that corresponds to this
+ * type of animation (showing, swapping, etc).
+ */
+ abstract int getAnimationType();
+ }
+
+ /**
+ * The animation to show the first infobar. The infobar slides up from the bottom; then its
+ * content fades in.
+ */
+ private class FirstInfoBarAppearingAnimation extends InfoBarAnimation {
+ private InfoBarUiItem mFrontItem;
+ private InfoBarWrapper mFrontWrapper;
+ private View mFrontContents;
+
+ FirstInfoBarAppearingAnimation(InfoBarUiItem frontItem) {
+ mFrontItem = frontItem;
+ }
+
+ @Override
+ void prepareAnimation() {
+ mFrontContents = mFrontItem.getView();
+ mFrontWrapper = new InfoBarWrapper(getContext(), mFrontItem);
+ mFrontWrapper.addView(mFrontContents);
+ addWrapper(mFrontWrapper);
+ }
+
+ @Override
+ Animator createAnimator() {
+ mFrontWrapper.setTranslationY(mFrontWrapper.getHeight());
+ mFrontContents.setAlpha(0f);
+
+ AnimatorSet animator = new AnimatorSet();
+ animator.playSequentially(
+ createTranslationYAnimator(mFrontWrapper, 0f).setDuration(DURATION_SLIDE_UP_MS),
+ ObjectAnimator.ofFloat(mFrontContents, View.ALPHA, 1f)
+ .setDuration(DURATION_FADE_MS));
+ return animator;
+ }
+
+ @Override
+ void onAnimationEnd() {
+ announceForAccessibility(mFrontItem.getAccessibilityText());
+ }
+
+ @Override
+ int getAnimationType() {
+ return InfoBarAnimationListener.ANIMATION_TYPE_SHOW;
+ }
+ }
+
+ /**
+ * The animation to show the a new front-most infobar in front of existing visible infobars. The
+ * infobar slides up from the bottom; then its content fades in. The previously visible infobars
+ * will be resized simulatenously to the new desired size.
+ */
+ private class FrontInfoBarAppearingAnimation extends InfoBarAnimation {
+ private InfoBarUiItem mFrontItem;
+ private InfoBarWrapper mFrontWrapper;
+ private InfoBarWrapper mOldFrontWrapper;
+ private View mFrontContents;
+
+ FrontInfoBarAppearingAnimation(InfoBarUiItem frontItem) {
+ mFrontItem = frontItem;
+ }
+
+ @Override
+ void prepareAnimation() {
+ mOldFrontWrapper = mInfoBarWrappers.get(0);
+
+ mFrontContents = mFrontItem.getView();
+ mFrontWrapper = new InfoBarWrapper(getContext(), mFrontItem);
+ mFrontWrapper.addView(mFrontContents);
+ addWrapperToFront(mFrontWrapper);
+ }
+
+ @Override
+ Animator createAnimator() {
+ // After adding the new wrapper, the new front item's view, and the old front item's
+ // view are both in their wrappers, and the height of the stack as determined by
+ // FrameLayout will take both into account. This means the height of the container will
+ // be larger than it needs to be, if the previous old front item is larger than the sum
+ // of the new front item and mBackInfobarHeight.
+ //
+ // First work out how much the container will grow or shrink by.
+ int heightDelta =
+ mFrontWrapper.getHeight() + mBackInfobarHeight - mOldFrontWrapper.getHeight();
+
+ // Now work out where to animate the new front item to / from.
+ int newFrontStart = mFrontWrapper.getHeight();
+ int newFrontEnd = 0;
+ if (heightDelta < 0) {
+ // If the container is shrinking, this won't be reflected in the layout just yet.
+ // The layout will have extra space in it for the previous front infobar, which the
+ // animation of the new front infobar has to take into account.
+ newFrontStart -= heightDelta;
+ newFrontEnd -= heightDelta;
+ }
+ mFrontWrapper.setTranslationY(newFrontStart);
+ mFrontContents.setAlpha(0f);
+
+ // Since we are adding the infobar to the top of the stack, make the container fully
+ // visible since it could be at hidden or partially hidden state.
+ mMakeContainerVisibleRunnable.run();
+
+ AnimatorSet animator = new AnimatorSet();
+ animator.play(createTranslationYAnimator(mFrontWrapper, newFrontEnd)
+ .setDuration(DURATION_SLIDE_UP_MS));
+
+ // If the container is shrinking, the back infobars need to animate down (from 0 to the
+ // positive delta). Otherwise they have to animate up (from the negative delta to 0).
+ int backStart = Math.max(0, heightDelta);
+ int backEnd = Math.max(-heightDelta, 0);
+ for (int i = 1; i < mInfoBarWrappers.size(); i++) {
+ mInfoBarWrappers.get(i).setTranslationY(backStart);
+ animator.play(createTranslationYAnimator(mInfoBarWrappers.get(i), backEnd)
+ .setDuration(DURATION_SLIDE_UP_MS));
+ }
+
+ animator.play(ObjectAnimator.ofFloat(mFrontContents, View.ALPHA, 1f)
+ .setDuration(DURATION_FADE_MS))
+ .after(DURATION_SLIDE_UP_MS);
+
+ return animator;
+ }
+
+ @Override
+ void onAnimationEnd() {
+ // Remove the old front wrappers view so it won't affect the height of the container any
+ // more.
+ mOldFrontWrapper.removeAllViews();
+
+ // Now set any Y offsets to 0 as there is no need to account for the old front wrapper
+ // making the container higher than it should be.
+ for (int i = 0; i < mInfoBarWrappers.size(); i++) {
+ mInfoBarWrappers.get(i).setTranslationY(0);
+ }
+ updateLayoutParams();
+ announceForAccessibility(mFrontItem.getAccessibilityText());
+ }
+
+ @Override
+ int getAnimationType() {
+ return InfoBarAnimationListener.ANIMATION_TYPE_SHOW;
+ }
+ }
+
+ /**
+ * The animation to show a back infobar. The infobar slides up behind the existing infobars, so
+ * its top edge peeks out just a bit.
+ */
+ private class BackInfoBarAppearingAnimation extends InfoBarAnimation {
+ private InfoBarWrapper mAppearingWrapper;
+
+ BackInfoBarAppearingAnimation(InfoBarUiItem appearingItem) {
+ mAppearingWrapper = new InfoBarWrapper(getContext(), appearingItem);
+ }
+
+ @Override
+ void prepareAnimation() {
+ addWrapper(mAppearingWrapper);
+ }
+
+ @Override
+ Animator createAnimator() {
+ mAppearingWrapper.setTranslationY(mAppearingWrapper.getHeight());
+ return createTranslationYAnimator(mAppearingWrapper, 0f)
+ .setDuration(DURATION_SLIDE_UP_MS);
+ }
+
+ @Override
+ public void onAnimationEnd() {
+ mAppearingWrapper.removeView(mAppearingWrapper.getItem().getView());
+ }
+
+ @Override
+ int getAnimationType() {
+ return InfoBarAnimationListener.ANIMATION_TYPE_SHOW;
+ }
+ }
+
+ /**
+ * The animation to hide the front infobar and reveal the second-to-front infobar. The front
+ * infobar slides down and off the screen. The back infobar(s) will adjust to the size of the
+ * new front infobar, and then the new front infobar's contents will fade in.
+ */
+ private class FrontInfoBarDisappearingAndRevealingAnimation extends InfoBarAnimation {
+ private InfoBarWrapper mOldFrontWrapper;
+ private InfoBarWrapper mNewFrontWrapper;
+ private View mNewFrontContents;
+
+ @Override
+ void prepareAnimation() {
+ mOldFrontWrapper = mInfoBarWrappers.get(0);
+ mNewFrontWrapper = mInfoBarWrappers.get(1);
+ mNewFrontContents = mNewFrontWrapper.getItem().getView();
+ mNewFrontWrapper.addView(mNewFrontContents);
+ }
+
+ @Override
+ Animator createAnimator() {
+ // The amount by which mNewFrontWrapper will grow (negative value indicates shrinking).
+ int deltaHeight = (mNewFrontWrapper.getHeight() - mBackInfobarHeight)
+ - mOldFrontWrapper.getHeight();
+ int startTranslationY = Math.max(deltaHeight, 0);
+ int endTranslationY = Math.max(-deltaHeight, 0);
+
+ // Slide the front infobar down and away.
+ AnimatorSet animator = new AnimatorSet();
+ mOldFrontWrapper.setTranslationY(startTranslationY);
+ animator.play(createTranslationYAnimator(
+ mOldFrontWrapper, startTranslationY + mOldFrontWrapper.getHeight())
+ .setDuration(DURATION_SLIDE_UP_MS));
+
+ // Slide the other infobars to their new positions.
+ // Note: animator.play() causes these animations to run simultaneously.
+ for (int i = 1; i < mInfoBarWrappers.size(); i++) {
+ mInfoBarWrappers.get(i).setTranslationY(startTranslationY);
+ animator.play(createTranslationYAnimator(mInfoBarWrappers.get(i), endTranslationY)
+ .setDuration(DURATION_SLIDE_UP_MS));
+ }
+
+ mNewFrontContents.setAlpha(0f);
+ animator.play(ObjectAnimator.ofFloat(mNewFrontContents, View.ALPHA, 1f)
+ .setDuration(DURATION_FADE_MS))
+ .after(DURATION_SLIDE_UP_MS);
+
+ return animator;
+ }
+
+ @Override
+ void onAnimationEnd() {
+ mOldFrontWrapper.removeAllViews();
+ removeWrapper(mOldFrontWrapper);
+ for (int i = 0; i < mInfoBarWrappers.size(); i++) {
+ mInfoBarWrappers.get(i).setTranslationY(0);
+ }
+ announceForAccessibility(mNewFrontWrapper.getItem().getAccessibilityText());
+ }
+
+ @Override
+ int getAnimationType() {
+ return InfoBarAnimationListener.ANIMATION_TYPE_HIDE;
+ }
+ }
+
+ /**
+ * The animation to hide the backmost infobar, or the front infobar if there's only one infobar.
+ * The infobar simply slides down out of the container.
+ */
+ private class InfoBarDisappearingAnimation extends InfoBarAnimation {
+ private InfoBarWrapper mDisappearingWrapper;
+
+ @Override
+ void prepareAnimation() {
+ mDisappearingWrapper = mInfoBarWrappers.get(mInfoBarWrappers.size() - 1);
+ }
+
+ @Override
+ Animator createAnimator() {
+ return createTranslationYAnimator(
+ mDisappearingWrapper, mDisappearingWrapper.getHeight())
+ .setDuration(DURATION_SLIDE_DOWN_MS);
+ }
+
+ @Override
+ void onAnimationEnd() {
+ mDisappearingWrapper.removeAllViews();
+ removeWrapper(mDisappearingWrapper);
+ }
+
+ @Override
+ int getAnimationType() {
+ return InfoBarAnimationListener.ANIMATION_TYPE_HIDE;
+ }
+ }
+
+ /**
+ * The animation to swap the contents of the front infobar. The current contents fade out,
+ * then the infobar resizes to fit the new contents, then the new contents fade in.
+ */
+ private class FrontInfoBarSwapContentsAnimation extends InfoBarAnimation {
+ private InfoBarWrapper mFrontWrapper;
+ private View mOldContents;
+ private View mNewContents;
+
+ @Override
+ void prepareAnimation() {
+ mFrontWrapper = mInfoBarWrappers.get(0);
+ mOldContents = mFrontWrapper.getChildAt(0);
+ mNewContents = mFrontWrapper.getItem().getView();
+ mFrontWrapper.addView(mNewContents);
+ }
+
+ @Override
+ Animator createAnimator() {
+ int deltaHeight = mNewContents.getHeight() - mOldContents.getHeight();
+ InfoBarContainerLayout.this.setTranslationY(Math.max(0, deltaHeight));
+ mNewContents.setAlpha(0f);
+
+ AnimatorSet animator = new AnimatorSet();
+ animator.playSequentially(ObjectAnimator.ofFloat(mOldContents, View.ALPHA, 0f)
+ .setDuration(DURATION_FADE_OUT_MS),
+ ObjectAnimator
+ .ofFloat(InfoBarContainerLayout.this, View.TRANSLATION_Y,
+ Math.max(0, -deltaHeight))
+ .setDuration(DURATION_SLIDE_UP_MS),
+ ObjectAnimator.ofFloat(mNewContents, View.ALPHA, 1f)
+ .setDuration(DURATION_FADE_OUT_MS));
+ return animator;
+ }
+
+ @Override
+ void onAnimationEnd() {
+ mFrontWrapper.removeViewAt(0);
+ InfoBarContainerLayout.this.setTranslationY(0f);
+ mFrontWrapper.getItem().setControlsEnabled(true);
+ announceForAccessibility(mFrontWrapper.getItem().getAccessibilityText());
+ }
+
+ @Override
+ int getAnimationType() {
+ return InfoBarAnimationListener.ANIMATION_TYPE_SWAP;
+ }
+ }
+
+ /**
+ * Controls whether infobars fill the full available width, or whether they "float" in the
+ * middle of the available space. The latter case happens if the available space is wider than
+ * the max width allowed for infobars.
+ *
+ * Also handles the shadows on the sides of the infobars in floating mode. The side shadows are
+ * separate views -- rather than being part of each InfoBarWrapper -- to avoid a double-shadow
+ * effect, which would happen during animations when two InfoBarWrappers overlap each other.
+ */
+ private static class FloatingBehavior {
+ /** The InfoBarContainerLayout. */
+ private FrameLayout mLayout;
+
+ /**
+ * The max width of the infobars. If the available space is wider than this, the infobars
+ * will switch to floating mode.
+ */
+ private final int mMaxWidth;
+
+ /** The width of the left and right shadows. */
+ private final int mShadowWidth;
+
+ /** Whether the layout is currently floating. */
+ private boolean mIsFloating;
+
+ /** The shadows that appear on the sides of the infobars in floating mode. */
+ private View mLeftShadowView;
+ private View mRightShadowView;
+
+ FloatingBehavior(FrameLayout layout) {
+ mLayout = layout;
+ Resources res = mLayout.getContext().getResources();
+ mMaxWidth = res.getDimensionPixelSize(R.dimen.infobar_max_width);
+ mShadowWidth = res.getDimensionPixelSize(R.dimen.infobar_shadow_width);
+ }
+
+ /**
+ * This should be called in onMeasure() before super.onMeasure(). The return value is a new
+ * widthMeasureSpec that should be passed to super.onMeasure().
+ */
+ int beforeOnMeasure(int widthMeasureSpec) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ boolean isFloating = width > mMaxWidth;
+ if (isFloating != mIsFloating) {
+ mIsFloating = isFloating;
+ onIsFloatingChanged();
+ }
+
+ if (isFloating) {
+ int mode = MeasureSpec.getMode(widthMeasureSpec);
+ width = Math.min(width, mMaxWidth + 2 * mShadowWidth);
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, mode);
+ }
+ return widthMeasureSpec;
+ }
+
+ /**
+ * This should be called in onMeasure() after super.onMeasure().
+ */
+ void afterOnMeasure(int measuredHeight) {
+ if (!mIsFloating) return;
+ // Measure side shadows to match the parent view's height.
+ int widthSpec = MeasureSpec.makeMeasureSpec(mShadowWidth, MeasureSpec.EXACTLY);
+ int heightSpec = MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY);
+ mLeftShadowView.measure(widthSpec, heightSpec);
+ mRightShadowView.measure(widthSpec, heightSpec);
+ }
+
+ /**
+ * This should be called whenever the Y-position of an infobar changes.
+ */
+ void updateShadowPosition() {
+ if (!mIsFloating) return;
+ float minY = mLayout.getHeight();
+ int childCount = mLayout.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = mLayout.getChildAt(i);
+ if (child != mLeftShadowView && child != mRightShadowView) {
+ minY = Math.min(minY, child.getY());
+ }
+ }
+ mLeftShadowView.setY(minY);
+ mRightShadowView.setY(minY);
+ }
+
+ private void onIsFloatingChanged() {
+ if (mIsFloating) {
+ initShadowViews();
+ mLayout.setPadding(mShadowWidth, 0, mShadowWidth, 0);
+ mLayout.setClipToPadding(false);
+ mLayout.addView(mLeftShadowView);
+ mLayout.addView(mRightShadowView);
+ } else {
+ mLayout.setPadding(0, 0, 0, 0);
+ mLayout.removeView(mLeftShadowView);
+ mLayout.removeView(mRightShadowView);
+ }
+ }
+
+ @SuppressLint("RtlHardcoded")
+ private void initShadowViews() {
+ if (mLeftShadowView != null) return;
+
+ mLeftShadowView = new View(mLayout.getContext());
+ mLeftShadowView.setBackgroundResource(R.drawable.infobar_shadow_left);
+ LayoutParams leftLp = new FrameLayout.LayoutParams(0, 0, Gravity.LEFT);
+ leftLp.leftMargin = -mShadowWidth;
+ mLeftShadowView.setLayoutParams(leftLp);
+
+ mRightShadowView = new View(mLayout.getContext());
+ mRightShadowView.setBackgroundResource(R.drawable.infobar_shadow_left);
+ LayoutParams rightLp = new FrameLayout.LayoutParams(0, 0, Gravity.RIGHT);
+ rightLp.rightMargin = -mShadowWidth;
+ mRightShadowView.setScaleX(-1f);
+ mRightShadowView.setLayoutParams(rightLp);
+ }
+ }
+
+ /**
+ * The height of back infobars, i.e. the distance between the top of the front infobar and the
+ * top of the next infobar back.
+ */
+ private final int mBackInfobarHeight;
+
+ /**
+ * All the Items, in front to back order.
+ * This list is updated immediately when addInfoBar(), removeInfoBar(), and swapInfoBar() are
+ * called; so during animations, it does *not* match the currently visible views.
+ */
+ private final ArrayList<InfoBarUiItem> mItems = new ArrayList<>();
+
+ /**
+ * The currently visible InfoBarWrappers, in front to back order.
+ */
+ private final ArrayList<InfoBarWrapper> mInfoBarWrappers = new ArrayList<>();
+
+ /** A observer that is notified when animations finish. */
+ private final InfoBarAnimationListener mAnimationListener;
+
+ /** The current animation, or null if no animation is happening currently. */
+ private InfoBarAnimation mAnimation;
+
+ private FloatingBehavior mFloatingBehavior;
+
+ /** The runnable to make infobar container fully visible. */
+ private Runnable mMakeContainerVisibleRunnable;
+
+ /**
+ * Determines whether any animations need to run in order to make the visible views match the
+ * current list of Items in mItems. If so, kicks off the next animation that's needed.
+ */
+ private void processPendingAnimations() {
+ // If an animation is running, wait until it finishes before beginning the next animation.
+ if (mAnimation != null) return;
+
+ // The steps below are ordered to minimize movement during animations. In particular,
+ // removals happen before additions or swaps, and changes are made to back infobars before
+ // front infobars.
+
+ // First, remove any infobars that are no longer in mItems, if any. Check the back infobars
+ // before the front.
+ for (int i = mInfoBarWrappers.size() - 1; i >= 0; i--) {
+ InfoBarUiItem visibleItem = mInfoBarWrappers.get(i).getItem();
+ if (!mItems.contains(visibleItem)) {
+ if (i == 0 && mInfoBarWrappers.size() >= 2) {
+ // Remove the front infobar and reveal the second-to-front infobar.
+ runAnimation(new FrontInfoBarDisappearingAndRevealingAnimation());
+ return;
+
+ } else {
+ // Move the infobar to the very back if it's not already there.
+ InfoBarWrapper wrapper = mInfoBarWrappers.get(i);
+ if (i != mInfoBarWrappers.size() - 1) {
+ removeWrapper(wrapper);
+ addWrapper(wrapper);
+ }
+
+ // Remove the backmost infobar (which may be the front infobar).
+ runAnimation(new InfoBarDisappearingAnimation());
+ return;
+ }
+ }
+ }
+
+ // Second, run swap animation on front infobar if needed.
+ if (!mInfoBarWrappers.isEmpty()) {
+ InfoBarUiItem frontItem = mInfoBarWrappers.get(0).getItem();
+ View frontContents = mInfoBarWrappers.get(0).getChildAt(0);
+ if (frontContents != frontItem.getView()) {
+ runAnimation(new FrontInfoBarSwapContentsAnimation());
+ return;
+ }
+ }
+
+ // Third, check if we should add any infobars in front of visible infobars. This can happen
+ // if an infobar has been inserted into mItems, in front of the currently visible item. To
+ // detect this the items at the beginning of mItems are compared against the first item in
+ // mInfoBarWrappers.
+ if (!mInfoBarWrappers.isEmpty()) {
+ // Find the infobar with the highest index that isn't currently being shown.
+ InfoBarUiItem currentVisibleItem = mInfoBarWrappers.get(0).getItem();
+ InfoBarUiItem itemToInsert = null;
+ for (int checkIndex = 0; checkIndex < mItems.size(); checkIndex++) {
+ if (mItems.get(checkIndex) == currentVisibleItem) {
+ // There are no remaining infobars that can possibly override the
+ // currently displayed one.
+ break;
+ } else {
+ // Found an infobar that isn't being displayed yet. Track it so that
+ // it can be animated in.
+ itemToInsert = mItems.get(checkIndex);
+ }
+ }
+ if (itemToInsert != null) {
+ runAnimation(new FrontInfoBarAppearingAnimation(itemToInsert));
+ return;
+ }
+ }
+
+ // Fourth, check if we should add any infobars at the back.
+ int desiredChildCount = Math.min(mItems.size(), MAX_STACK_DEPTH);
+ if (mInfoBarWrappers.size() < desiredChildCount) {
+ InfoBarUiItem itemToShow = mItems.get(mInfoBarWrappers.size());
+ runAnimation(mInfoBarWrappers.isEmpty()
+ ? new FirstInfoBarAppearingAnimation(itemToShow)
+ : new BackInfoBarAppearingAnimation(itemToShow));
+ return;
+ }
+
+ // Fifth, now that we've stabilized, let listeners know that we have no more animations.
+ InfoBarUiItem frontItem =
+ mInfoBarWrappers.size() > 0 ? mInfoBarWrappers.get(0).getItem() : null;
+ mAnimationListener.notifyAllAnimationsFinished(frontItem);
+ }
+
+ private void runAnimation(InfoBarAnimation animation) {
+ mAnimation = animation;
+ mAnimation.prepareAnimation();
+ if (isLayoutRequested()) {
+ // onLayout() will call mAnimation.start().
+ } else {
+ mAnimation.start();
+ }
+ }
+
+ private void addWrapper(InfoBarWrapper wrapper) {
+ addView(wrapper, 0, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+ mInfoBarWrappers.add(wrapper);
+ updateLayoutParams();
+ }
+
+ private void addWrapperToFront(InfoBarWrapper wrapper) {
+ addView(wrapper, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+ mInfoBarWrappers.add(0, wrapper);
+ updateLayoutParams();
+ }
+
+ private void removeWrapper(InfoBarWrapper wrapper) {
+ removeView(wrapper);
+ mInfoBarWrappers.remove(wrapper);
+ updateLayoutParams();
+ }
+
+ private void updateLayoutParams() {
+ // Stagger the top margins so the back infobars peek out a bit.
+ int childCount = mInfoBarWrappers.size();
+ for (int i = 0; i < childCount; i++) {
+ View child = mInfoBarWrappers.get(i);
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ lp.topMargin = (childCount - 1 - i) * mBackInfobarHeight;
+ child.setLayoutParams(lp);
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ widthMeasureSpec = mFloatingBehavior.beforeOnMeasure(widthMeasureSpec);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ mFloatingBehavior.afterOnMeasure(getMeasuredHeight());
+ }
+
+ @Override
+ public void announceForAccessibility(CharSequence text) {
+ if (TextUtils.isEmpty(text)) return;
+ super.announceForAccessibility(text);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ mFloatingBehavior.updateShadowPosition();
+
+ // Animations start after a layout has completed, at which point all views are guaranteed
+ // to have valid sizes and positions.
+ if (mAnimation != null && !mAnimation.isStarted()) {
+ mAnimation.start();
+ }
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // Trap any attempts to fiddle with the infobars while we're animating.
+ return super.onInterceptTouchEvent(ev) || mAnimation != null
+ || (!mInfoBarWrappers.isEmpty()
+ && !mInfoBarWrappers.get(0).getItem().areControlsEnabled());
+ }
+
+ @Override
+ @SuppressLint("ClickableViewAccessibility")
+ public boolean onTouchEvent(MotionEvent event) {
+ super.onTouchEvent(event);
+ // Consume all touch events so they do not reach the ContentView.
+ return true;
+ }
+
+ @Override
+ public boolean onHoverEvent(MotionEvent event) {
+ super.onHoverEvent(event);
+ // Consume all hover events so they do not reach the ContentView. In touch exploration mode,
+ // this prevents the user from interacting with the part of the ContentView behind the
+ // infobars. http://crbug.com/430701
+ return true;
+ }
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainerView.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainerView.java
new file mode 100644
index 00000000000..553608310f2
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarContainerView.java
@@ -0,0 +1,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();
+ }
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarUiItem.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarUiItem.java
new file mode 100644
index 00000000000..5a653d069c3
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarUiItem.java
@@ -0,0 +1,69 @@
+// Copyright 2020 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.view.View;
+
+import androidx.annotation.IntDef;
+
+import org.chromium.chrome.browser.infobar.InfoBarIdentifier;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An interface for items that can be added to an InfoBarContainerLayout.
+ */
+public interface InfoBarUiItem {
+ // The infobar priority.
+ @IntDef({InfoBarPriority.CRITICAL, InfoBarPriority.USER_TRIGGERED,
+ InfoBarPriority.PAGE_TRIGGERED, InfoBarPriority.BACKGROUND})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface InfoBarPriority {
+ int CRITICAL = 0;
+ int USER_TRIGGERED = 1;
+ int PAGE_TRIGGERED = 2;
+ int BACKGROUND = 3;
+ }
+
+ /**
+ * Returns the View that represents this infobar. This should have no background or borders;
+ * a background and shadow will be added by a wrapper view.
+ */
+ View getView();
+
+ /**
+ * Returns whether controls for this View should be clickable. If false, all input events on
+ * this item will be ignored.
+ */
+ boolean areControlsEnabled();
+
+ /**
+ * Sets whether or not controls for this View should be clickable. This does not affect the
+ * visual state of the infobar.
+ * @param state If false, all input events on this Item will be ignored.
+ */
+ void setControlsEnabled(boolean state);
+
+ /**
+ * Returns the accessibility text to announce when this infobar is first shown.
+ */
+ CharSequence getAccessibilityText();
+
+ /**
+ * Returns the priority of an infobar. High priority infobar is shown in front of low
+ * priority infobar. If infobars have the same priorities, the most recently added one
+ * is shown behind previous ones.
+ *
+ */
+ int getPriority();
+
+ /**
+ * Returns the type of infobar, as best as can be determined at this time. See
+ * components/infobars/core/infobar_delegate.h.
+ */
+ @InfoBarIdentifier
+ int getInfoBarIdentifier();
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarWrapper.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarWrapper.java
new file mode 100644
index 00000000000..8a574254a96
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/InfoBarWrapper.java
@@ -0,0 +1,44 @@
+// Copyright 2016 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.content.Context;
+import android.content.res.Resources;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.FrameLayout;
+
+/**
+ * Layout that holds an infobar's contents and provides a background color and a top shadow.
+ */
+class InfoBarWrapper extends FrameLayout {
+ private final InfoBarUiItem mItem;
+
+ /**
+ * Constructor for inflating from Java.
+ */
+ InfoBarWrapper(Context context, InfoBarUiItem item) {
+ super(context);
+ mItem = item;
+ Resources res = context.getResources();
+ int peekingHeight = res.getDimensionPixelSize(R.dimen.infobar_peeking_height);
+ int shadowHeight = res.getDimensionPixelSize(R.dimen.infobar_shadow_height);
+ setMinimumHeight(peekingHeight + shadowHeight);
+
+ // setBackgroundResource() changes the padding, so call setPadding() second.
+ setBackgroundResource(R.drawable.weblayer_infobar_wrapper_bg);
+ setPadding(0, shadowHeight, 0, 0);
+ }
+
+ InfoBarUiItem getItem() {
+ return mItem;
+ }
+
+ @Override
+ public void onViewAdded(View child) {
+ child.setLayoutParams(new LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, Gravity.TOP));
+ }
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/IntentUtils.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/IntentUtils.java
new file mode 100644
index 00000000000..5d2dfec5c69
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/IntentUtils.java
@@ -0,0 +1,48 @@
+// Copyright 2020 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.content.Intent;
+import android.os.RemoteException;
+import android.util.AndroidRuntimeException;
+
+/** A utility class for creating and handling common intents. */
+public class IntentUtils {
+ private static final String sExtraTabId = "TAB_ID";
+ private static final String sActivateTabAction =
+ "org.chromium.weblayer.intent_utils.ACTIVATE_TAB";
+
+ /**
+ * Handles an intent generated by this class.
+ * @return true if the intent was handled, or false if the intent wasn't generated by this
+ * class.
+ */
+ public static boolean handleIntent(Intent intent) {
+ if (!intent.getAction().equals(sActivateTabAction)) return false;
+
+ int tabId = intent.getIntExtra(sExtraTabId, -1);
+ TabImpl tab = TabImpl.getTabById(tabId);
+ if (tab == null) return true;
+
+ try {
+ tab.getClient().bringTabToFront();
+ } catch (RemoteException e) {
+ throw new AndroidRuntimeException(e);
+ }
+ return true;
+ }
+
+ /**
+ * Creates an intent to bring a tab to the foreground.
+ * This intent should also bring the app to the foreground.
+ * @param tabId the identifier for the tab.
+ */
+ public static Intent createBringTabToFrontIntent(int tabId) {
+ Intent intent = WebLayerImpl.createIntent();
+ intent.putExtra(sExtraTabId, tabId);
+ intent.setAction(sActivateTabAction);
+ return intent;
+ }
+};
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/MediaSessionManager.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/MediaSessionManager.java
new file mode 100644
index 00000000000..a36125ace70
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/MediaSessionManager.java
@@ -0,0 +1,140 @@
+// Copyright 2020 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.annotation.SuppressLint;
+import android.app.Service;
+import android.content.Intent;
+import android.support.v4.media.session.MediaSessionCompat;
+
+import org.chromium.components.browser_ui.media.MediaNotificationController;
+import org.chromium.components.browser_ui.media.MediaNotificationInfo;
+import org.chromium.components.browser_ui.media.MediaSessionHelper;
+import org.chromium.components.browser_ui.notifications.ChromeNotification;
+import org.chromium.components.browser_ui.notifications.ChromeNotificationBuilder;
+import org.chromium.components.browser_ui.notifications.ForegroundServiceUtils;
+import org.chromium.components.browser_ui.notifications.NotificationMetadata;
+
+/**
+ * A glue class for MediaSession.
+ * This class defines delegates that provide WebLayer-specific behavior to shared MediaSession code.
+ * It also manages the lifetime of {@link MediaNotificationController} and the {@link Service}
+ * associated with the notification.
+ */
+class MediaSessionManager {
+ // This is a singleton because there's only at most one MediaSession active at a time.
+ @SuppressLint("StaticFieldLeak")
+ static MediaNotificationController sController;
+
+ private static int sNotificationId = 0;
+
+ static void serviceStarted(Service service, Intent intent) {
+ if (sController != null && sController.processIntent(service, intent)) return;
+
+ // The service has been started with startForegroundService() but the
+ // notification hasn't been shown. See similar logic in {@link
+ // ChromeMediaNotificationControllerDelegate}.
+ MediaNotificationController.finishStartingForegroundServiceOnO(
+ service, createChromeNotificationBuilder().buildChromeNotification());
+ // Call stopForeground to guarantee Android unset the foreground bit.
+ ForegroundServiceUtils.getInstance().stopForeground(
+ service, Service.STOP_FOREGROUND_REMOVE);
+ service.stopSelf();
+ }
+
+ static void serviceDestroyed() {
+ if (sController != null) sController.onServiceDestroyed();
+ sController = null;
+ }
+
+ static MediaSessionHelper.Delegate createMediaSessionHelperDelegate(int tabId) {
+ return new MediaSessionHelper.Delegate() {
+ @Override
+ public Intent createBringTabToFrontIntent() {
+ return IntentUtils.createBringTabToFrontIntent(tabId);
+ }
+
+ @Override
+ public boolean fetchLargeFaviconImage() {
+ // TODO(crbug.com/1076463): WebLayer doesn't support favicons.
+ return false;
+ }
+
+ @Override
+ public MediaNotificationInfo.Builder createMediaNotificationInfoBuilder() {
+ ensureNotificationId();
+ return new MediaNotificationInfo.Builder().setInstanceId(tabId).setId(
+ sNotificationId);
+ }
+
+ @Override
+ public void showMediaNotification(MediaNotificationInfo notificationInfo) {
+ assert notificationInfo.id == sNotificationId;
+ if (sController == null) {
+ sController = new MediaNotificationController(
+ new WebLayerMediaNotificationControllerDelegate());
+ }
+ sController.mThrottler.queueNotification(notificationInfo);
+ }
+
+ @Override
+ public void hideMediaNotification() {
+ if (sController != null) sController.hideNotification(tabId);
+ }
+
+ @Override
+ public void activateAndroidMediaSession() {
+ if (sController != null) sController.activateAndroidMediaSession(tabId);
+ }
+ };
+ }
+
+ private static class WebLayerMediaNotificationControllerDelegate
+ implements MediaNotificationController.Delegate {
+ @Override
+ public Intent createServiceIntent() {
+ return WebLayerImpl.createMediaSessionServiceIntent();
+ }
+
+ @Override
+ public String getAppName() {
+ return WebLayerImpl.getClientApplicationName();
+ }
+
+ @Override
+ public String getNotificationGroupName() {
+ return "org.chromium.weblayer.MediaSession";
+ }
+
+ @Override
+ public ChromeNotificationBuilder createChromeNotificationBuilder() {
+ return MediaSessionManager.createChromeNotificationBuilder();
+ }
+
+ @Override
+ public void onMediaSessionUpdated(MediaSessionCompat session) {
+ // This is only relevant when casting.
+ }
+
+ @Override
+ public void logNotificationShown(ChromeNotification notification) {}
+ }
+
+ private static ChromeNotificationBuilder createChromeNotificationBuilder() {
+ ensureNotificationId();
+
+ // Only the null tag will work as expected, because {@link Service#startForeground()} only
+ // takes an ID and no tag. If we pass a tag here, then the notification that's used to
+ // display a paused state (no foreground service) will not be identified as the same one
+ // that's used with the foreground service.
+ return WebLayerNotificationBuilder.create(
+ WebLayerNotificationChannels.ChannelId.MEDIA_PLAYBACK,
+ new NotificationMetadata(0, null /*notificationTag*/, sNotificationId));
+ }
+
+ private static void ensureNotificationId() {
+ if (sNotificationId == 0) sNotificationId = WebLayerImpl.getMediaSessionNotificationId();
+ }
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/MediaStreamManager.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/MediaStreamManager.java
index 446af44ea47..90925f10df5 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/MediaStreamManager.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/MediaStreamManager.java
@@ -21,7 +21,6 @@ import org.chromium.components.browser_ui.notifications.NotificationManagerProxy
import org.chromium.components.browser_ui.notifications.NotificationManagerProxyImpl;
import org.chromium.components.browser_ui.notifications.NotificationMetadata;
import org.chromium.components.browser_ui.notifications.PendingIntentProvider;
-import org.chromium.components.browser_ui.notifications.channels.ChannelsInitializer;
import org.chromium.components.webrtc.MediaCaptureNotificationUtil;
import org.chromium.components.webrtc.MediaCaptureNotificationUtil.MediaType;
import org.chromium.content_public.browser.WebContents;
@@ -65,6 +64,7 @@ public class MediaStreamManager {
/**
* @return a string that prefixes all intents that can be handled by {@link forwardIntent}.
+ * @Deprecated in M85+, this class does not handle intents. Remove in M88.
*/
public static String getIntentPrefix() {
return WEBRTC_PREFIX;
@@ -73,6 +73,7 @@ public class MediaStreamManager {
/**
* Handles an intent coming from a media streaming notification.
* @param intent the intent which was previously posted via {@link update}.
+ * @Deprecated in M85+, this class does not handle intents. Remove in M88.
*/
public static void forwardIntent(Intent intent) {
assert intent.getAction().equals(ACTIVATE_TAB_INTENT);
@@ -208,28 +209,28 @@ public class MediaStreamManager {
}
Context appContext = ContextUtils.getApplicationContext();
- Intent intent = WebLayerImpl.createIntent();
- intent.putExtra(EXTRA_TAB_ID, mNotificationId);
- intent.setAction(ACTIVATE_TAB_INTENT);
+ Intent intent = null;
+ if (WebLayerFactoryImpl.getClientMajorVersion() >= 85) {
+ intent = IntentUtils.createBringTabToFrontIntent(mNotificationId);
+ } else {
+ intent = WebLayerImpl.createIntent();
+ intent.putExtra(EXTRA_TAB_ID, mNotificationId);
+ intent.setAction(ACTIVATE_TAB_INTENT);
+ }
PendingIntentProvider contentIntent =
PendingIntentProvider.getBroadcast(appContext, mNotificationId, intent, 0);
int mediaType = audio && video ? MediaType.AUDIO_AND_VIDEO
: audio ? MediaType.AUDIO_ONLY : MediaType.VIDEO_ONLY;
- NotificationManagerProxy notificationManagerProxy = getNotificationManager();
- ChannelsInitializer channelsInitializer = new ChannelsInitializer(notificationManagerProxy,
- WebLayerNotificationChannels.getInstance(), appContext.getResources());
-
// TODO(crbug/1076098): don't pass a URL in incognito.
ChromeNotification notification = MediaCaptureNotificationUtil.createNotification(
- new WebLayerNotificationBuilder(appContext,
+ WebLayerNotificationBuilder.create(
WebLayerNotificationChannels.ChannelId.WEBRTC_CAM_AND_MIC,
- channelsInitializer,
new NotificationMetadata(0, AV_STREAM_TAG, mNotificationId)),
mediaType, mTab.getWebContents().getVisibleUrl().getSpec(),
WebLayerImpl.getClientApplicationName(), contentIntent, null /*stopIntent*/);
- notificationManagerProxy.notify(notification);
+ getNotificationManager().notify(notification);
updateActiveNotifications(true);
notifyClient(audio, video);
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/MojoInterfaceRegistrar.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/MojoInterfaceRegistrar.java
new file mode 100644
index 00000000000..d124792cd0d
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/MojoInterfaceRegistrar.java
@@ -0,0 +1,28 @@
+// Copyright 2020 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 org.chromium.base.annotations.CalledByNative;
+import org.chromium.content_public.browser.InterfaceRegistrar;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.services.service_manager.InterfaceRegistry;
+import org.chromium.webshare.mojom.ShareService;
+
+/**
+ * Registers Java implementations of mojo interfaces.
+ */
+class MojoInterfaceRegistrar {
+ @CalledByNative
+ private static void registerMojoInterfaces() {
+ InterfaceRegistrar.Registry.addWebContentsRegistrar(new WebContentsInterfaceRegistrar());
+ }
+
+ private static class WebContentsInterfaceRegistrar implements InterfaceRegistrar<WebContents> {
+ @Override
+ public void registerInterfaces(InterfaceRegistry registry, final WebContents webContents) {
+ registry.addInterface(ShareService.MANAGER, new WebShareServiceFactory(webContents));
+ }
+ }
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/NavigationControllerImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/NavigationControllerImpl.java
index c44b3b66031..3957ed46557 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/NavigationControllerImpl.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/NavigationControllerImpl.java
@@ -113,6 +113,13 @@ public final class NavigationControllerImpl extends INavigationController.Stub {
mNativeNavigationController, index);
}
+ @Override
+ public boolean isNavigationEntrySkippable(int index) {
+ StrictModeWorkaround.apply();
+ return NavigationControllerImplJni.get().isNavigationEntrySkippable(
+ mNativeNavigationController, index);
+ }
+
@CalledByNative
private NavigationImpl createNavigation(long nativeNavigationImpl) {
return new NavigationImpl(mNavigationControllerClient, nativeNavigationImpl);
@@ -159,6 +166,12 @@ public final class NavigationControllerImpl extends INavigationController.Stub {
mNavigationControllerClient.onFirstContentfulPaint();
}
+ @CalledByNative
+ private void onOldPageNoLongerRendered(String uri) throws RemoteException {
+ if (WebLayerFactoryImpl.getClientMajorVersion() < 85) return;
+ mNavigationControllerClient.onOldPageNoLongerRendered(uri);
+ }
+
@NativeMethods
interface Natives {
void setNavigationControllerImpl(
@@ -178,5 +191,6 @@ public final class NavigationControllerImpl extends INavigationController.Stub {
int getNavigationListCurrentIndex(long nativeNavigationControllerImpl);
String getNavigationEntryDisplayUri(long nativeNavigationControllerImpl, int index);
String getNavigationEntryTitle(long nativeNavigationControllerImpl, int index);
+ boolean isNavigationEntrySkippable(long nativeNavigationControllerImpl, int index);
}
}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/NewTabCallbackProxy.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/NewTabCallbackProxy.java
index c5d665b7c08..25645acdc76 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/NewTabCallbackProxy.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/NewTabCallbackProxy.java
@@ -48,12 +48,10 @@ public final class NewTabCallbackProxy {
}
@CalledByNative
- public void onNewTab(long nativeTab, @ImplNewTabType int mode) throws RemoteException {
+ public void onNewTab(TabImpl tab, @ImplNewTabType int mode) throws RemoteException {
// This class should only be created while the tab is attached to a fragment.
assert mTab.getBrowser() != null;
- TabImpl tab =
- new TabImpl(mTab.getProfile(), mTab.getBrowser().getWindowAndroid(), nativeTab);
- mTab.getBrowser().addTab(tab);
+ assert mTab.getBrowser().equals(tab.getBrowser());
mTab.getClient().onNewTab(tab.getId(), mode);
}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/PageInfoControllerDelegateImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/PageInfoControllerDelegateImpl.java
index 687be42fe1a..314b6b7ff29 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/PageInfoControllerDelegateImpl.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/PageInfoControllerDelegateImpl.java
@@ -7,10 +7,15 @@ package org.chromium.weblayer_private;
import android.content.Context;
import android.content.Intent;
+import androidx.annotation.NonNull;
+
import org.chromium.base.StrictModeContext;
import org.chromium.base.supplier.Supplier;
+import org.chromium.components.content_settings.CookieControlsBridge;
+import org.chromium.components.content_settings.CookieControlsObserver;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.components.page_info.PageInfoControllerDelegate;
+import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.url.GURL;
import org.chromium.weblayer_private.interfaces.SiteSettingsIntentHelper;
@@ -20,18 +25,27 @@ import org.chromium.weblayer_private.interfaces.SiteSettingsIntentHelper;
*/
public class PageInfoControllerDelegateImpl extends PageInfoControllerDelegate {
private final Context mContext;
+ private final WebContents mWebContents;
private final String mProfileName;
- public PageInfoControllerDelegateImpl(Context context, String profileName, GURL url,
- Supplier<ModalDialogManager> modalDialogManager) {
+ static PageInfoControllerDelegateImpl create(WebContents webContents) {
+ TabImpl tab = TabImpl.fromWebContents(webContents);
+ assert tab != null;
+ return new PageInfoControllerDelegateImpl(tab.getBrowser().getContext(), webContents,
+ tab.getProfile(), tab.getBrowser().getWindowAndroid()::getModalDialogManager);
+ }
+
+ private PageInfoControllerDelegateImpl(Context context, WebContents webContents,
+ ProfileImpl profile, Supplier<ModalDialogManager> modalDialogManager) {
super(modalDialogManager, new AutocompleteSchemeClassifierImpl(),
/** vrHandler= */ null,
/** isSiteSettingsAvailable= */
- UrlConstants.HTTP_SCHEME.equals(url.getScheme())
- || UrlConstants.HTTPS_SCHEME.equals(url.getScheme()),
- /** cookieControlsShown= */ false);
+ isHttpOrHttps(webContents.getVisibleUrl()),
+ /** cookieControlsShown= */
+ CookieControlsBridge.isCookieControlsEnabled(profile));
mContext = context;
- mProfileName = profileName;
+ mWebContents = webContents;
+ mProfileName = profile.getName();
}
/**
@@ -47,4 +61,18 @@ public class PageInfoControllerDelegateImpl extends PageInfoControllerDelegate {
mContext.startActivity(intent);
}
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @NonNull
+ public CookieControlsBridge createCookieControlsBridge(CookieControlsObserver observer) {
+ return new CookieControlsBridge(observer, mWebContents, null);
+ }
+
+ private static boolean isHttpOrHttps(GURL url) {
+ String scheme = url.getScheme();
+ return UrlConstants.HTTP_SCHEME.equals(scheme) || UrlConstants.HTTPS_SCHEME.equals(scheme);
+ }
}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java
index ba5cc8c56dd..c359dc3513d 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java
@@ -5,6 +5,7 @@
package org.chromium.weblayer_private;
import android.content.Intent;
+import android.text.TextUtils;
import android.webkit.ValueCallback;
import androidx.annotation.NonNull;
@@ -25,7 +26,10 @@ import org.chromium.weblayer_private.interfaces.SettingType;
import org.chromium.weblayer_private.interfaces.StrictModeWorkaround;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Implementation of IProfile.
@@ -168,6 +172,40 @@ public final class ProfileImpl extends IProfile.Stub implements BrowserContextHa
return mCookieManager;
}
+ @Override
+ public void getBrowserPersistenceIds(@NonNull IObjectWrapper callback) {
+ StrictModeWorkaround.apply();
+ checkNotDestroyed();
+ ValueCallback<Set<String>> valueCallback =
+ (ValueCallback<Set<String>>) ObjectWrapper.unwrap(callback, ValueCallback.class);
+ Callback<String[]> baseCallback = (String[] result) -> {
+ valueCallback.onReceiveValue(new HashSet<String>(Arrays.asList(result)));
+ };
+ ProfileImplJni.get().getBrowserPersistenceIds(mNativeProfile, baseCallback);
+ }
+
+ @Override
+ public void removeBrowserPersistenceStorage(String[] ids, @NonNull IObjectWrapper callback) {
+ StrictModeWorkaround.apply();
+ checkNotDestroyed();
+ ValueCallback<Boolean> valueCallback =
+ (ValueCallback<Boolean>) ObjectWrapper.unwrap(callback, ValueCallback.class);
+ Callback<Boolean> baseCallback = valueCallback::onReceiveValue;
+ for (String id : ids) {
+ if (TextUtils.isEmpty(id)) {
+ throw new IllegalArgumentException("id must be non-null and non-empty");
+ }
+ }
+ ProfileImplJni.get().removeBrowserPersistenceStorage(mNativeProfile, ids, baseCallback);
+ }
+
+ @Override
+ public void prepareForPossibleCrossOriginNavigation() {
+ StrictModeWorkaround.apply();
+ checkNotDestroyed();
+ ProfileImplJni.get().prepareForPossibleCrossOriginNavigation(mNativeProfile);
+ }
+
void checkNotDestroyed() {
if (!mBeingDeleted) return;
throw new IllegalArgumentException("Profile being destroyed: " + mName);
@@ -232,5 +270,9 @@ public final class ProfileImpl extends IProfile.Stub implements BrowserContextHa
void ensureBrowserContextInitialized(long nativeProfileImpl);
void setBooleanSetting(long nativeProfileImpl, int type, boolean value);
boolean getBooleanSetting(long nativeProfileImpl, int type);
+ void getBrowserPersistenceIds(long nativeProfileImpl, Callback<String[]> callback);
+ void removeBrowserPersistenceStorage(
+ long nativeProfileImpl, String[] ids, Callback<Boolean> callback);
+ void prepareForPossibleCrossOriginNavigation(long nativeProfileImpl);
}
}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/README.md b/chromium/weblayer/browser/java/org/chromium/weblayer_private/README.md
new file mode 100644
index 00000000000..35e0aedea89
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/README.md
@@ -0,0 +1,36 @@
+# Which Context should I use?
+
+The code in this directory references different types of contexts. Please read about what each
+represents before deciding which one you should use.
+
+## Embedder's Activity Context
+
+The fragment that WebLayer is loaded in holds a reference to the activity that it is currently
+attached to. This is what's referred to by [`mEmbedderActivityContext`][link1] in BrowserImpl and
+BrowserFragmentImpl. It should be used to reference anything associated with the activity. For
+instance, embedder-specific resources, like Color resources which are resolved according to the
+theme of the embedding activity.
+
+[link1]: https://source.chromium.org/chromium/chromium/src/+/6c336f4d55231595c038756f58a9e61d416a9c8f:weblayer/browser/java/org/chromium/weblayer_private/BrowserFragmentImpl.java;bpv=1;bpt=1
+
+## WebLayer's Activity Context
+
+WebLayer has a lot of resources of its own which need to be accessed by the implementation code. We
+thus wrap the embedder's activity context so that resource and assert look-ups against the wrapped
+context go to the WebView or WebLayer support APK and not the embedder's APK. This wrapped Context
+is what's returned by [`BrowserImpl.getContext()`][link2]. Use this when referencing WebLayer specific
+resources. This is expected to be the most common use case.
+
+[link2]: https://source.chromium.org/chromium/chromium/src/+/master:weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java?q=f:browserimpl%20getContext&ss=chromium%2Fchromium%2Fsrc
+
+## Embedder's Application Context
+
+Occasionally, we need the embedder's application context, as opposed to its activity context. For
+instance, fetching the current locale which applies to the entire application.
+Similar to WebLayer's Activity Context, this context is also wrapped in our implementation so we can
+reference WebLayer-specific resources. This is what's returned by
+[`ContextUtils.getApplicationContext()`][link3].
+It shouldn't be downcast to Application (or any subclass thereof) since it's wrapped in a
+ContextWrapper.
+
+[link3]: https://source.chromium.org/chromium/chromium/src/+/master:base/android/java/src/org/chromium/base/ContextUtils.java?q=f:base%2FContextUtils%20getApplicationContext()&ss=chromium%2Fchromium%2Fsrc
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/SiteSettingsFragmentImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/SiteSettingsFragmentImpl.java
index d30bc58aed9..072df069fb9 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/SiteSettingsFragmentImpl.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/SiteSettingsFragmentImpl.java
@@ -12,15 +12,18 @@ import android.os.Handler;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
import android.view.Window;
+import androidx.appcompat.app.AppCompatDelegate;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentController;
import androidx.fragment.app.FragmentHostCallback;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
+import org.chromium.components.browser_ui.settings.SettingsUtils;
import org.chromium.components.browser_ui.site_settings.SingleCategorySettings;
import org.chromium.components.browser_ui.site_settings.SingleWebsiteSettings;
import org.chromium.components.browser_ui.site_settings.SiteSettings;
@@ -59,6 +62,7 @@ public class SiteSettingsFragmentImpl extends RemoteFragmentImpl {
// resource IDs.
private Context mContext;
+ private boolean mStarted;
private FragmentController mFragmentController;
/**
@@ -78,6 +82,12 @@ public class SiteSettingsFragmentImpl extends RemoteFragmentImpl {
private PassthroughFragmentActivity(SiteSettingsFragmentImpl fragmentImpl) {
mFragmentImpl = fragmentImpl;
attachBaseContext(mFragmentImpl.getWebLayerContext());
+ // This class doesn't extend AppCompatActivity, so some appcompat functionality doesn't
+ // get initialized, which leads to some appcompat widgets (like switches) rendering
+ // incorrectly. There are some resource issues with having this class extend
+ // AppCompatActivity, but until we sort those out, creating an AppCompatDelegate will
+ // perform the necessary initialization.
+ AppCompatDelegate.create(this, null);
}
@Override
@@ -182,8 +192,9 @@ public class SiteSettingsFragmentImpl extends RemoteFragmentImpl {
@Override
public LayoutInflater onGetLayoutInflater() {
- return (LayoutInflater) mFragmentImpl.getWebLayerContext().getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
+ Context context = mFragmentImpl.getWebLayerContext();
+ return ((LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
+ .cloneInContext(context);
}
@Override
@@ -271,6 +282,24 @@ public class SiteSettingsFragmentImpl extends RemoteFragmentImpl {
throw new RuntimeException("Failed to create Site Settings Fragment", e);
}
}
+
+ root.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ // Add the shadow scroll listener here once the View is attached to the Window.
+ SiteSettingsPreferenceFragment preferenceFragment =
+ (SiteSettingsPreferenceFragment) mFragmentController
+ .getSupportFragmentManager()
+ .findFragmentByTag(FRAGMENT_TAG);
+ ViewGroup listView = preferenceFragment.getListView();
+ listView.getViewTreeObserver().addOnScrollChangedListener(
+ SettingsUtils.getShowShadowOnScrollListener(
+ listView, view.findViewById(R.id.shadow)));
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {}
+ });
return root;
}
@@ -298,7 +327,11 @@ public class SiteSettingsFragmentImpl extends RemoteFragmentImpl {
@Override
public void onStart() {
super.onStart();
- mFragmentController.dispatchActivityCreated();
+
+ if (!mStarted) {
+ mStarted = true;
+ mFragmentController.dispatchActivityCreated();
+ }
mFragmentController.noteStateNotSaved();
mFragmentController.execPendingActions();
mFragmentController.dispatchStart();
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/SwipableOverlayView.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/SwipableOverlayView.java
new file mode 100644
index 00000000000..7f46f8afcd2
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/SwipableOverlayView.java
@@ -0,0 +1,421 @@
+// Copyright 2014 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.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Region;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
+
+import androidx.annotation.IntDef;
+
+import org.chromium.base.MathUtils;
+import org.chromium.content_public.browser.GestureListenerManager;
+import org.chromium.content_public.browser.GestureStateListenerWithScroll;
+import org.chromium.content_public.browser.WebContents;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * View that slides up from the bottom of the page and slides away as the user scrolls the page.
+ * Meant to be tacked onto the {@link org.chromium.content_public.browser.WebContents}'s view and
+ * alerted when either the page scroll position or viewport size changes.
+ *
+ * GENERAL BEHAVIOR
+ * This View is brought onto the screen by sliding upwards from the bottom of the screen. Afterward
+ * the View slides onto and off of the screen vertically as the user scrolls upwards or
+ * downwards on the page.
+ *
+ * As the scroll offset or the viewport height are updated via a scroll or fling, the difference
+ * from the initial value is used to determine the View's Y-translation. If a gesture is stopped,
+ * the View will be snapped back into the center of the screen or entirely off of the screen, based
+ * on how much of the View is visible, or where the user is currently located on the page.
+ */
+public abstract class SwipableOverlayView extends FrameLayout {
+ private static final float FULL_THRESHOLD = 0.5f;
+ private static final float VERTICAL_FLING_SHOW_THRESHOLD = 0.2f;
+ private static final float VERTICAL_FLING_HIDE_THRESHOLD = 0.9f;
+
+ @IntDef({Gesture.NONE, Gesture.SCROLLING, Gesture.FLINGING})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface Gesture {
+ int NONE = 0;
+ int SCROLLING = 1;
+ int FLINGING = 2;
+ }
+
+ private static final long ANIMATION_DURATION_MS = 250;
+
+ /** Detects when the user is dragging the WebContents. */
+ private final GestureStateListenerWithScroll mGestureStateListener;
+
+ /** Listens for changes in the layout. */
+ private final View.OnLayoutChangeListener mLayoutChangeListener;
+
+ /** Interpolator used for the animation. */
+ private final Interpolator mInterpolator;
+
+ /** Tracks whether the user is scrolling or flinging. */
+ private @Gesture int mGestureState;
+
+ /** Animation currently being used to translate the View. */
+ private Animator mCurrentAnimation;
+
+ /** Used to determine when the layout has changed and the Viewport must be updated. */
+ private int mParentHeight;
+
+ /** Offset from the top of the page when the current gesture was first started. */
+ private int mInitialOffsetY;
+
+ /** How tall the View is, including its margins. */
+ private int mTotalHeight;
+
+ /** Whether or not the View ever been fully displayed. */
+ private boolean mIsBeingDisplayedForFirstTime;
+
+ /** The WebContents to which the overlay is added. */
+ private WebContents mWebContents;
+
+ /**
+ * Creates a SwipableOverlayView.
+ * @param context Context for acquiring resources.
+ * @param attrs Attributes from the XML layout inflation.
+ */
+ public SwipableOverlayView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mGestureStateListener = createGestureStateListener();
+ mGestureState = Gesture.NONE;
+ mLayoutChangeListener = createLayoutChangeListener();
+ mInterpolator = new DecelerateInterpolator(1.0f);
+
+ // We make this view 'draw' to provide a placeholder for its animations.
+ setWillNotDraw(false);
+ }
+
+ /**
+ * Set the given WebContents for scrolling changes.
+ */
+ public void setWebContents(WebContents webContents) {
+ if (mWebContents != null) {
+ GestureListenerManager.fromWebContents(mWebContents)
+ .removeListener(mGestureStateListener);
+ }
+
+ mWebContents = webContents;
+ // See comment in onLayout() as to why the listener is only attached if mTotalHeight is > 0.
+ if (mWebContents != null && mTotalHeight > 0) {
+ GestureListenerManager.fromWebContents(mWebContents).addListener(mGestureStateListener);
+ }
+ }
+
+ public WebContents getWebContents() {
+ return mWebContents;
+ }
+
+ protected void addToParentView(ViewGroup parentView, int index) {
+ if (parentView == null) return;
+ if (getParent() == null) {
+ parentView.addView(this, index, createLayoutParams());
+
+ // Listen for the layout to know when to animate the View coming onto the screen.
+ addOnLayoutChangeListener(mLayoutChangeListener);
+ }
+ }
+
+ /**
+ * Removes the SwipableOverlayView from its parent and stops monitoring the WebContents.
+ * @return Whether the View was removed from its parent.
+ */
+ public boolean removeFromParentView() {
+ if (getParent() == null) return false;
+
+ ((ViewGroup) getParent()).removeView(this);
+ removeOnLayoutChangeListener(mLayoutChangeListener);
+ return true;
+ }
+
+ /**
+ * Creates a set of LayoutParams that makes the View hug the bottom of the screen. Override it
+ * for other types of behavior.
+ * @return LayoutParams for use when adding the View to its parent.
+ */
+ public ViewGroup.MarginLayoutParams createLayoutParams() {
+ return new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT,
+ Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (!isAllowedToAutoHide()) setTranslationY(0.0f);
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+ if (!isAllowedToAutoHide()) setTranslationY(0.0f);
+ }
+
+ /**
+ * See {@link #android.view.ViewGroup.onLayout(boolean, int, int, int, int)}.
+ */
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ // Update the viewport height when the parent View's height changes (e.g. after rotation).
+ int currentParentHeight = getParent() == null ? 0 : ((View) getParent()).getHeight();
+ if (mParentHeight != currentParentHeight) {
+ mParentHeight = currentParentHeight;
+ mGestureState = Gesture.NONE;
+ if (mCurrentAnimation != null) mCurrentAnimation.end();
+ }
+
+ // Update the known effective height of the View.
+ MarginLayoutParams params = (MarginLayoutParams) getLayoutParams();
+ mTotalHeight = getMeasuredHeight() + params.topMargin + params.bottomMargin;
+
+ // Adding a listener to GestureListenerManager results in extra IPCs on every frame, which
+ // is very costly. Only attach the listener if needed.
+ if (mWebContents != null) {
+ if (mTotalHeight > 0) {
+ GestureListenerManager.fromWebContents(mWebContents)
+ .addListener(mGestureStateListener);
+ } else {
+ GestureListenerManager.fromWebContents(mWebContents)
+ .removeListener(mGestureStateListener);
+ }
+ }
+
+ super.onLayout(changed, l, t, r, b);
+ }
+
+ /**
+ * Creates a listener than monitors the WebContents for scrolls and flings.
+ * The listener updates the location of this View to account for the user's gestures.
+ * @return GestureStateListenerWithScroll to send to the WebContents.
+ */
+ private GestureStateListenerWithScroll createGestureStateListener() {
+ return new GestureStateListenerWithScroll() {
+ /** Tracks the previous event's scroll offset to determine if a scroll is up or down. */
+ private int mLastScrollOffsetY;
+
+ /** Location of the View when the current gesture was first started. */
+ private float mInitialTranslationY;
+
+ /** The initial extent of the scroll when triggered. */
+ private float mInitialExtentY;
+
+ @Override
+ public void onFlingStartGesture(int scrollOffsetY, int scrollExtentY) {
+ if (!isAllowedToAutoHide() || !cancelCurrentAnimation()) return;
+ resetInternalScrollState(scrollOffsetY, scrollExtentY);
+ mGestureState = Gesture.FLINGING;
+ }
+
+ @Override
+ public void onFlingEndGesture(int scrollOffsetY, int scrollExtentY) {
+ if (mGestureState != Gesture.FLINGING) return;
+ mGestureState = Gesture.NONE;
+
+ updateTranslation(scrollOffsetY, scrollExtentY);
+
+ boolean isScrollingDownward = scrollOffsetY > mLastScrollOffsetY;
+
+ boolean isVisibleInitially = mInitialTranslationY < mTotalHeight;
+ float percentageVisible = 1.0f - (getTranslationY() / mTotalHeight);
+ float visibilityThreshold = isVisibleInitially ? VERTICAL_FLING_HIDE_THRESHOLD
+ : VERTICAL_FLING_SHOW_THRESHOLD;
+ boolean isVisibleEnough = percentageVisible > visibilityThreshold;
+ boolean isNearTopOfPage = scrollOffsetY < (mTotalHeight * FULL_THRESHOLD);
+
+ boolean show = (!isScrollingDownward && isVisibleEnough) || isNearTopOfPage;
+
+ runUpEventAnimation(show);
+ }
+
+ @Override
+ public void onScrollStarted(int scrollOffsetY, int scrollExtentY) {
+ if (!isAllowedToAutoHide() || !cancelCurrentAnimation()) return;
+ resetInternalScrollState(scrollOffsetY, scrollExtentY);
+ mLastScrollOffsetY = scrollOffsetY;
+ mGestureState = Gesture.SCROLLING;
+ }
+
+ @Override
+ public void onScrollEnded(int scrollOffsetY, int scrollExtentY) {
+ if (mGestureState != Gesture.SCROLLING) return;
+ mGestureState = Gesture.NONE;
+
+ updateTranslation(scrollOffsetY, scrollExtentY);
+
+ runUpEventAnimation(shouldSnapToVisibleState(scrollOffsetY));
+ }
+
+ @Override
+ public void onScrollOffsetOrExtentChanged(int scrollOffsetY, int scrollExtentY) {
+ mLastScrollOffsetY = scrollOffsetY;
+
+ if (!shouldConsumeScroll(scrollOffsetY, scrollExtentY)) {
+ resetInternalScrollState(scrollOffsetY, scrollExtentY);
+ return;
+ }
+
+ // This function is called for both fling and scrolls.
+ if (mGestureState == Gesture.NONE || !cancelCurrentAnimation()
+ || isIndependentlyAnimating()) {
+ return;
+ }
+
+ updateTranslation(scrollOffsetY, scrollExtentY);
+ }
+
+ private void updateTranslation(int scrollOffsetY, int scrollExtentY) {
+ float scrollDiff =
+ (scrollOffsetY - mInitialOffsetY) + (scrollExtentY - mInitialExtentY);
+ float translation =
+ MathUtils.clamp(mInitialTranslationY + scrollDiff, mTotalHeight, 0);
+
+ // If the container has reached the completely shown position, reset the initial
+ // scroll so any movement will start hiding it again.
+ if (translation <= 0f) resetInternalScrollState(scrollOffsetY, scrollExtentY);
+
+ setTranslationY(translation);
+ }
+
+ /**
+ * Resets the internal values that a scroll or fling will base its calculations off of.
+ */
+ private void resetInternalScrollState(int scrollOffsetY, int scrollExtentY) {
+ mInitialOffsetY = scrollOffsetY;
+ mInitialExtentY = scrollExtentY;
+ mInitialTranslationY = getTranslationY();
+ }
+ };
+ }
+
+ /**
+ * @param scrollOffsetY The current scroll offset on the Y axis.
+ * @param scrollExtentY The current scroll extent on the Y axis.
+ * @return Whether or not the scroll should be consumed by the view.
+ */
+ protected boolean shouldConsumeScroll(int scrollOffsetY, int scrollExtentY) {
+ return true;
+ }
+
+ /**
+ * @param scrollOffsetY The current scroll offset on the Y axis.
+ * @return Whether the view should snap to a visible state.
+ */
+ protected boolean shouldSnapToVisibleState(int scrollOffsetY) {
+ boolean isNearTopOfPage = scrollOffsetY < (mTotalHeight * FULL_THRESHOLD);
+ boolean isVisibleEnough = getTranslationY() < mTotalHeight * FULL_THRESHOLD;
+ return isNearTopOfPage || isVisibleEnough;
+ }
+
+ /**
+ * @return Whether or not the view is animating independent of the user's scroll position.
+ */
+ protected boolean isIndependentlyAnimating() {
+ return false;
+ }
+
+ /**
+ * Creates a listener that is used only to animate the View coming onto the screen.
+ * @return The SimpleOnGestureListener that will monitor the View.
+ */
+ private View.OnLayoutChangeListener createLayoutChangeListener() {
+ return new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ removeOnLayoutChangeListener(mLayoutChangeListener);
+
+ // Animate the View coming in from the bottom of the screen.
+ setTranslationY(mTotalHeight);
+ mIsBeingDisplayedForFirstTime = true;
+ runUpEventAnimation(true);
+ }
+ };
+ }
+
+ /**
+ * Create an animation that snaps the View into position vertically.
+ * @param visible If true, snaps the View to the bottom-center of the screen. If false,
+ * translates the View below the bottom-center of the screen so that it is
+ * effectively invisible.
+ * @return An animator with the snap animation.
+ */
+ protected Animator createVerticalSnapAnimation(boolean visible) {
+ float targetTranslationY = visible ? 0.0f : mTotalHeight;
+ float yDifference = Math.abs(targetTranslationY - getTranslationY()) / mTotalHeight;
+ long duration = Math.max(0, (long) (ANIMATION_DURATION_MS * yDifference));
+
+ Animator animator = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, targetTranslationY);
+ animator.setDuration(duration);
+ animator.setInterpolator(mInterpolator);
+
+ return animator;
+ }
+
+ /**
+ * Run an animation when a gesture has ended (an 'up' motion event).
+ * @param visible Whether or not the view should be visible.
+ */
+ protected void runUpEventAnimation(boolean visible) {
+ if (mCurrentAnimation != null) mCurrentAnimation.cancel();
+ mCurrentAnimation = createVerticalSnapAnimation(visible);
+ mCurrentAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mGestureState = Gesture.NONE;
+ mCurrentAnimation = null;
+ mIsBeingDisplayedForFirstTime = false;
+ }
+ });
+ mCurrentAnimation.start();
+ }
+
+ /**
+ * Cancels the current animation, unless the View is coming onto the screen for the first time.
+ * @return True if the animation was canceled or wasn't running, false otherwise.
+ */
+ private boolean cancelCurrentAnimation() {
+ if (mIsBeingDisplayedForFirstTime) return false;
+ if (mCurrentAnimation != null) mCurrentAnimation.cancel();
+ return true;
+ }
+
+ /**
+ * @return Whether the SwipableOverlayView is allowed to hide itself on scroll.
+ */
+ protected boolean isAllowedToAutoHide() {
+ return true;
+ }
+
+ /**
+ * Override gatherTransparentRegion to make this view's layout a placeholder for its
+ * animations. This is only called during layout, so it doesn't really make sense to apply
+ * post-layout properties like it does by default. Together with setWillNotDraw(false),
+ * this ensures no child animation within this view's layout will be clipped by a SurfaceView.
+ */
+ @Override
+ public boolean gatherTransparentRegion(Region region) {
+ float translationY = getTranslationY();
+ setTranslationY(0);
+ boolean result = super.gatherTransparentRegion(region);
+ // Restoring TranslationY invalidates this view unnecessarily. However, this function
+ // is called as part of layout, which implies a full redraw is about to occur anyway.
+ setTranslationY(translationY);
+ return result;
+ }
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java
index 38d6cf9557c..382bfb7c605 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java
@@ -18,13 +18,16 @@ import android.view.ViewStructure;
import android.view.autofill.AutofillValue;
import android.webkit.ValueCallback;
+import androidx.annotation.VisibleForTesting;
+
import org.chromium.base.Callback;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.components.autofill.AutofillActionModeCallback;
import org.chromium.components.autofill.AutofillProvider;
-import org.chromium.components.autofill.AutofillProviderImpl;
+import org.chromium.components.browser_ui.http_auth.LoginPrompt;
+import org.chromium.components.browser_ui.media.MediaSessionHelper;
import org.chromium.components.browser_ui.util.BrowserControlsVisibilityDelegate;
import org.chromium.components.browser_ui.util.ComposedBrowserControlsVisibilityDelegate;
import org.chromium.components.embedder_support.contextmenu.ContextMenuParams;
@@ -55,7 +58,9 @@ import org.chromium.weblayer_private.interfaces.INavigationControllerClient;
import org.chromium.weblayer_private.interfaces.IObjectWrapper;
import org.chromium.weblayer_private.interfaces.ITab;
import org.chromium.weblayer_private.interfaces.ITabClient;
+import org.chromium.weblayer_private.interfaces.IWebMessageCallbackClient;
import org.chromium.weblayer_private.interfaces.ObjectWrapper;
+import org.chromium.weblayer_private.interfaces.ScrollNotificationType;
import org.chromium.weblayer_private.interfaces.StrictModeWorkaround;
import java.util.ArrayList;
@@ -67,7 +72,7 @@ import java.util.Map;
* Implementation of ITab.
*/
@JNINamespace("weblayer")
-public final class TabImpl extends ITab.Stub {
+public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer {
private static int sNextId = 1;
// Map from id to TabImpl.
private static final Map<Integer, TabImpl> sTabMap = new HashMap<Integer, TabImpl>();
@@ -83,6 +88,7 @@ public final class TabImpl extends ITab.Stub {
private TabViewAndroidDelegate mViewAndroidDelegate;
// BrowserImpl this TabImpl is in. This is only null during creation.
private BrowserImpl mBrowser;
+ private LoginPrompt mLoginPrompt;
/**
* The AutofillProvider that integrates with system-level autofill. This is null until
* updateFromBrowser() is invoked.
@@ -107,10 +113,12 @@ public final class TabImpl extends ITab.Stub {
private boolean mWaitingForMatchRects;
private InterceptNavigationDelegateClientImpl mInterceptNavigationDelegateClient;
private InterceptNavigationDelegateImpl mInterceptNavigationDelegate;
+ private InfoBarContainer mInfoBarContainer;
+ private MediaSessionHelper mMediaSessionHelper;
private boolean mPostContainerViewInitDone;
- private AccessibilityUtil.Observer mAccessibilityObserver;
+ private WebLayerAccessibilityUtil.Observer mAccessibilityObserver;
private static class InternalAccessDelegateImpl
implements ViewEventSink.InternalAccessDelegate {
@@ -165,6 +173,37 @@ public final class TabImpl extends ITab.Stub {
viewController.onBottomControlsChanged(bottomControlsOffsetY);
}
}
+
+ @Override
+ public void onBackgroundColorChanged(int color) {
+ if (WebLayerFactoryImpl.getClientMajorVersion() >= 85) {
+ try {
+ mClient.onBackgroundColorChanged(color);
+ } catch (RemoteException e) {
+ throw new APICallException(e);
+ }
+ }
+ }
+
+ @Override
+ protected void onVerticalScrollDirectionChanged(
+ boolean directionUp, float currentScrollRatio) {
+ if (WebLayerFactoryImpl.getClientMajorVersion() >= 85) {
+ try {
+ mClient.onScrollNotification(directionUp
+ ? ScrollNotificationType.DIRECTION_CHANGED_UP
+ : ScrollNotificationType.DIRECTION_CHANGED_DOWN,
+ currentScrollRatio);
+ } catch (RemoteException e) {
+ throw new APICallException(e);
+ }
+ }
+ }
+ }
+
+ public static TabImpl fromWebContents(WebContents webContents) {
+ if (webContents == null || webContents.isDestroyed()) return null;
+ return TabImplJni.get().fromWebContents(webContents);
}
public static TabImpl getTabById(int tabId) {
@@ -223,12 +262,21 @@ public final class TabImpl extends ITab.Stub {
mInterceptNavigationDelegateClient.initializeWithDelegate(mInterceptNavigationDelegate);
sTabMap.put(mId, this);
+ mInfoBarContainer = new InfoBarContainer(this);
mAccessibilityObserver = (boolean enabled) -> {
setBrowserControlsVisibilityConstraint(ImplControlsVisibilityReason.ACCESSIBILITY,
enabled ? BrowserControlsState.SHOWN : BrowserControlsState.BOTH);
};
// addObserver() calls to observer when added.
WebLayerAccessibilityUtil.get().addObserver(mAccessibilityObserver);
+
+ // MediaSession only works if the client is new enough. Sadly, passing
+ // kDisableMediaSessionAPI does not fully disable the API, so a check is also necessary
+ // before installing this observer.
+ if (WebLayerFactoryImpl.getClientMajorVersion() >= 85) {
+ mMediaSessionHelper = new MediaSessionHelper(
+ mWebContents, MediaSessionManager.createMediaSessionHelperDelegate(mId));
+ }
}
private void doInitAfterSettingContainerView() {
@@ -280,7 +328,7 @@ public final class TabImpl extends ITab.Stub {
// Set up |mAutofillProvider| to operate in the new Context. It's safe to assume
// the context won't change unless it is first nulled out, since the fragment
// must be detached before it can be reattached to a new Context.
- mAutofillProvider = new AutofillProviderImpl(
+ mAutofillProvider = new AutofillProvider(
mBrowser.getContext(), mBrowser.getAutofillView(), "WebLayer");
TabImplJni.get().onAutofillProviderChanged(mNativeTab, mAutofillProvider);
}
@@ -333,17 +381,27 @@ public final class TabImpl extends ITab.Stub {
assert mBrowser != null;
TabImplJni.get().setBrowserControlsContainerViews(
mNativeTab, topControlsContainerViewHandle, bottomControlsContainerViewHandle);
+ mInfoBarContainer.onTabDidGainActive();
updateWebContentsVisibility();
- mWebContents.onShow();
}
/**
* Called when this TabImpl is no longer the active TabImpl.
*/
public void onDidLoseActive() {
+ if (mAutofillProvider != null) {
+ mAutofillProvider.hidePopup();
+ }
+
hideFindInPageUiAndNotifyClient();
- mWebContents.onHide();
updateWebContentsVisibility();
+
+ // This method is called as part of the final phase of TabImpl destruction, at which
+ // point mInfoBarContainer has already been destroyed.
+ if (mInfoBarContainer != null) {
+ mInfoBarContainer.onTabDidLoseActive();
+ }
+
TabImplJni.get().setBrowserControlsContainerViews(mNativeTab, 0, 0);
}
@@ -351,7 +409,8 @@ public final class TabImpl extends ITab.Stub {
* Returns whether this Tab is visible.
*/
public boolean isVisible() {
- return (mBrowser.getActiveTab() == this && mBrowser.isStarted());
+ return (mBrowser.getActiveTab() == this
+ && (mBrowser.isStarted() || mBrowser.isFragmentStoppedForConfigurationChange()));
}
private void updateWebContentsVisibility() {
@@ -379,10 +438,17 @@ public final class TabImpl extends ITab.Stub {
return mWebContents;
}
- long getNativeTab() {
+ // Public for tests.
+ @VisibleForTesting
+ public long getNativeTab() {
return mNativeTab;
}
+ @VisibleForTesting
+ public InfoBarContainer getInfoBarContainerForTesting() {
+ return mInfoBarContainer;
+ }
+
@Override
public NavigationControllerImpl createNavigationController(INavigationControllerClient client) {
StrictModeWorkaround.apply();
@@ -522,6 +588,7 @@ public final class TabImpl extends ITab.Stub {
@Override
public boolean dismissTransientUi() {
+ StrictModeWorkaround.apply();
BrowserViewController viewController = getViewController();
if (viewController != null && viewController.dismissTabModalOverlay()) return true;
@@ -541,10 +608,34 @@ public final class TabImpl extends ITab.Stub {
@Override
public String getGuid() {
+ StrictModeWorkaround.apply();
return TabImplJni.get().getGuid(mNativeTab);
}
@Override
+ public boolean setData(Map data) {
+ StrictModeWorkaround.apply();
+ String[] flattenedMap = new String[data.size() * 2];
+ int i = 0;
+ for (Map.Entry<String, String> entry : ((Map<String, String>) data).entrySet()) {
+ flattenedMap[i++] = entry.getKey();
+ flattenedMap[i++] = entry.getValue();
+ }
+ return TabImplJni.get().setData(mNativeTab, flattenedMap);
+ }
+
+ @Override
+ public Map getData() {
+ StrictModeWorkaround.apply();
+ String[] data = TabImplJni.get().getData(mNativeTab);
+ Map<String, String> map = new HashMap<>();
+ for (int i = 0; i < data.length; i += 2) {
+ map.put(data[i], data[i + 1]);
+ }
+ return map;
+ }
+
+ @Override
public void captureScreenShot(float scale, IObjectWrapper valueCallback) {
StrictModeWorkaround.apply();
ValueCallback<Pair<Bitmap, Integer>> unwrappedCallback =
@@ -553,6 +644,18 @@ public final class TabImpl extends ITab.Stub {
TabImplJni.get().captureScreenShot(mNativeTab, scale, unwrappedCallback);
}
+ @Override
+ public boolean canTranslate() {
+ StrictModeWorkaround.apply();
+ return TabImplJni.get().canTranslate(mNativeTab);
+ }
+
+ @Override
+ public void showTranslateUi() {
+ StrictModeWorkaround.apply();
+ TabImplJni.get().showTranslateUi(mNativeTab);
+ }
+
@CalledByNative
private static void runCaptureScreenShotCallback(
ValueCallback<Pair<Bitmap, Integer>> callback, Bitmap bitmap, int errorCode) {
@@ -634,6 +737,53 @@ public final class TabImpl extends ITab.Stub {
getBrowser().destroyTab(this);
}
+ @CalledByNative
+ private void showHttpAuthPrompt(String host, String url) {
+ mLoginPrompt = new LoginPrompt(mBrowser.getContext(), host, url, this);
+ mLoginPrompt.show();
+ }
+
+ @CalledByNative
+ private void closeHttpAuthPrompt() {
+ mLoginPrompt = null;
+ }
+
+ @Override
+ public void cancel() {
+ TabImplJni.get().cancelHttpAuth(mNativeTab);
+ }
+
+ @Override
+ public void proceed(String username, String password) {
+ TabImplJni.get().setHttpAuth(mNativeTab, username, password);
+ }
+
+ @Override
+ public void registerWebMessageCallback(
+ String jsObjectName, List<String> allowedOrigins, IWebMessageCallbackClient client) {
+ if (jsObjectName.isEmpty()) {
+ throw new IllegalArgumentException("JS object name must not be empty");
+ }
+ if (allowedOrigins.isEmpty()) {
+ throw new IllegalArgumentException("At least one origin must be specified");
+ }
+ for (String origin : allowedOrigins) {
+ if (TextUtils.isEmpty(origin)) {
+ throw new IllegalArgumentException("Origin must not be non-empty");
+ }
+ }
+ String registerError = TabImplJni.get().registerWebMessageCallback(mNativeTab, jsObjectName,
+ allowedOrigins.toArray(new String[allowedOrigins.size()]), client);
+ if (!TextUtils.isEmpty(registerError)) {
+ throw new IllegalArgumentException(registerError);
+ }
+ }
+
+ @Override
+ public void unregisterWebMessageCallback(String jsObjectName) {
+ TabImplJni.get().unregisterWebMessageCallback(mNativeTab, jsObjectName);
+ }
+
public void destroy() {
// Ensure that this method isn't called twice.
assert mInterceptNavigationDelegate != null;
@@ -668,6 +818,9 @@ public final class TabImpl extends ITab.Stub {
mInterceptNavigationDelegateClient.destroy();
mInterceptNavigationDelegate = null;
+ mInfoBarContainer.destroy();
+ mInfoBarContainer = null;
+
mMediaStreamManager.destroy();
mMediaStreamManager = null;
@@ -734,6 +887,11 @@ public final class TabImpl extends ITab.Stub {
onBrowserControlsStateUpdated(mBrowserControlsVisibility.get());
}
+ @VisibleForTesting
+ public boolean canBrowserControlsScrollForTesting() {
+ return mBrowserControlsVisibility.get() == BrowserControlsState.BOTH;
+ }
+
private void onBrowserControlsStateUpdated(int state) {
// If something has overridden the FIP's SHOWN constraint, cancel FIP. This causes FIP to
// dismiss when entering fullscreen.
@@ -770,8 +928,14 @@ public final class TabImpl extends ITab.Stub {
return (mBrowser.getActiveTab() == this) ? mBrowser.getViewController() : null;
}
+ @VisibleForTesting
+ public boolean canInfoBarContainerScrollForTesting() {
+ return mInfoBarContainer.getContainerViewForTesting().isAllowedToAutoHide();
+ }
+
@NativeMethods
interface Natives {
+ TabImpl fromWebContents(WebContents webContents);
long createTab(long profile, TabImpl caller);
void setJavaImpl(long nativeTabImpl, TabImpl impl);
void onAutofillProviderChanged(long nativeTabImpl, AutofillProvider autofillProvider);
@@ -786,6 +950,15 @@ public final class TabImpl extends ITab.Stub {
String getGuid(long nativeTabImpl);
void captureScreenShot(long nativeTabImpl, float scale,
ValueCallback<Pair<Bitmap, Integer>> valueCallback);
+ boolean setData(long nativeTabImpl, String[] data);
+ String[] getData(long nativeTabImpl);
boolean isRendererControllingBrowserControlsOffsets(long nativeTabImpl);
+ void setHttpAuth(long nativeTabImpl, String username, String password);
+ void cancelHttpAuth(long nativeTabImpl);
+ String registerWebMessageCallback(long nativeTabImpl, String jsObjectName,
+ String[] allowedOrigins, IWebMessageCallbackClient client);
+ void unregisterWebMessageCallback(long nativeTabImpl, String jsObjectName);
+ boolean canTranslate(long nativeTabImpl);
+ void showTranslateUi(long nativeTabImpl);
}
}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateCompactInfoBar.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateCompactInfoBar.java
new file mode 100644
index 00000000000..a97315e6fa4
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateCompactInfoBar.java
@@ -0,0 +1,578 @@
+// Copyright 2017 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.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLayoutChangeListener;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+import androidx.core.content.ContextCompat;
+
+import com.google.android.material.tabs.TabLayout;
+
+import org.chromium.base.StrictModeContext;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.ui.widget.Toast;
+
+/**
+ * Java version of the compact translate infobar.
+ */
+@JNINamespace("weblayer")
+public class TranslateCompactInfoBar extends InfoBar
+ implements TabLayout.OnTabSelectedListener, TranslateMenuHelper.TranslateMenuListener {
+ public static final int TRANSLATING_INFOBAR = 1;
+ public static final int AFTER_TRANSLATING_INFOBAR = 2;
+
+ private static final int SOURCE_TAB_INDEX = 0;
+ private static final int TARGET_TAB_INDEX = 1;
+
+ // Action ID for Snackbar.
+ // Actions performed by clicking on on the overflow menu.
+ public static final int ACTION_OVERFLOW_ALWAYS_TRANSLATE = 0;
+ public static final int ACTION_OVERFLOW_NEVER_SITE = 1;
+ public static final int ACTION_OVERFLOW_NEVER_LANGUAGE = 2;
+ // Actions triggered automatically. (when translation or denied count reaches the threshold.)
+ public static final int ACTION_AUTO_ALWAYS_TRANSLATE = 3;
+ public static final int ACTION_AUTO_NEVER_LANGUAGE = 4;
+
+ private final int mInitialStep;
+ private final int mDefaultTextColor;
+ private final TranslateOptions mOptions;
+
+ private long mNativeTranslateInfoBarPtr;
+ private TranslateTabLayout mTabLayout;
+
+ // Metric to track the total number of translations in a page, including reverts to original.
+ private int mTotalTranslationCount;
+
+ // Histogram names for logging metrics.
+ private static final String INFOBAR_HISTOGRAM_TRANSLATE_LANGUAGE =
+ "Translate.CompactInfobar.Language.Translate";
+ private static final String INFOBAR_HISTOGRAM_MORE_LANGUAGES_LANGUAGE =
+ "Translate.CompactInfobar.Language.MoreLanguages";
+ private static final String INFOBAR_HISTOGRAM_PAGE_NOT_IN_LANGUAGE =
+ "Translate.CompactInfobar.Language.PageNotIn";
+ private static final String INFOBAR_HISTOGRAM_ALWAYS_TRANSLATE_LANGUAGE =
+ "Translate.CompactInfobar.Language.AlwaysTranslate";
+ private static final String INFOBAR_HISTOGRAM_NEVER_TRANSLATE_LANGUAGE =
+ "Translate.CompactInfobar.Language.NeverTranslate";
+ private static final String INFOBAR_HISTOGRAM = "Translate.CompactInfobar.Event";
+ private static final String INFOBAR_HISTOGRAM_TRANSLATION_COUNT =
+ "Translate.CompactInfobar.TranslationsPerPage";
+
+ /**
+ * This is used to back a UMA histogram, so it should be treated as
+ * append-only. The values should not be changed or reused, and
+ * INFOBAR_HISTOGRAM_BOUNDARY should be the last.
+ */
+ private static final int INFOBAR_IMPRESSION = 0;
+ private static final int INFOBAR_TARGET_TAB_TRANSLATE = 1;
+ private static final int INFOBAR_DECLINE = 2;
+ private static final int INFOBAR_OPTIONS = 3;
+ private static final int INFOBAR_MORE_LANGUAGES = 4;
+ private static final int INFOBAR_MORE_LANGUAGES_TRANSLATE = 5;
+ private static final int INFOBAR_PAGE_NOT_IN = 6;
+ private static final int INFOBAR_ALWAYS_TRANSLATE = 7;
+ private static final int INFOBAR_NEVER_TRANSLATE = 8;
+ private static final int INFOBAR_NEVER_TRANSLATE_SITE = 9;
+ private static final int INFOBAR_SCROLL_HIDE = 10;
+ private static final int INFOBAR_SCROLL_SHOW = 11;
+ private static final int INFOBAR_REVERT = 12;
+ private static final int INFOBAR_SNACKBAR_ALWAYS_TRANSLATE_IMPRESSION = 13;
+ private static final int INFOBAR_SNACKBAR_NEVER_TRANSLATE_IMPRESSION = 14;
+ private static final int INFOBAR_SNACKBAR_NEVER_TRANSLATE_SITE_IMPRESSION = 15;
+ private static final int INFOBAR_SNACKBAR_CANCEL_ALWAYS = 16;
+ private static final int INFOBAR_SNACKBAR_CANCEL_NEVER_SITE = 17;
+ private static final int INFOBAR_SNACKBAR_CANCEL_NEVER = 18;
+ private static final int INFOBAR_ALWAYS_TRANSLATE_UNDO = 19;
+ private static final int INFOBAR_CLOSE_DEPRECATED = 20;
+ private static final int INFOBAR_SNACKBAR_AUTO_ALWAYS_IMPRESSION = 21;
+ private static final int INFOBAR_SNACKBAR_AUTO_NEVER_IMPRESSION = 22;
+ private static final int INFOBAR_SNACKBAR_CANCEL_AUTO_ALWAYS = 23;
+ private static final int INFOBAR_SNACKBAR_CANCEL_AUTO_NEVER = 24;
+ private static final int INFOBAR_HISTOGRAM_BOUNDARY = 25;
+
+ // Need 2 instances of TranslateMenuHelper to prevent a race condition bug which happens when
+ // showing language menu after dismissing overflow menu.
+ private TranslateMenuHelper mOverflowMenuHelper;
+ private TranslateMenuHelper mLanguageMenuHelper;
+
+ private ImageButton mMenuButton;
+ private InfoBarCompactLayout mParent;
+
+ private boolean mMenuExpanded;
+ private boolean mIsFirstLayout = true;
+ private boolean mUserInteracted;
+
+ @CalledByNative
+ private static InfoBar create(TabImpl tab, int initialStep, String sourceLanguageCode,
+ String targetLanguageCode, boolean alwaysTranslate, boolean triggeredFromMenu,
+ String[] languages, String[] languageCodes, int[] hashCodes, int tabTextColor) {
+ recordInfobarAction(INFOBAR_IMPRESSION);
+ return new TranslateCompactInfoBar(initialStep, sourceLanguageCode, targetLanguageCode,
+ alwaysTranslate, triggeredFromMenu, languages, languageCodes, hashCodes,
+ tabTextColor);
+ }
+
+ TranslateCompactInfoBar(int initialStep, String sourceLanguageCode, String targetLanguageCode,
+ boolean alwaysTranslate, boolean triggeredFromMenu, String[] languages,
+ String[] languageCodes, int[] hashCodes, int tabTextColor) {
+ super(R.drawable.infobar_translate_compact, 0, null, null);
+
+ mInitialStep = initialStep;
+ mDefaultTextColor = tabTextColor;
+ mOptions = TranslateOptions.create(sourceLanguageCode, targetLanguageCode, languages,
+ languageCodes, alwaysTranslate, triggeredFromMenu, hashCodes);
+ }
+
+ @Override
+ protected boolean usesCompactLayout() {
+ return true;
+ }
+
+ @Override
+ protected void createCompactLayoutContent(InfoBarCompactLayout parent) {
+ LinearLayout content;
+ // LayoutInflater may trigger accessing the disk.
+ try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
+ content = (LinearLayout) LayoutInflater.from(getContext())
+ .inflate(R.layout.weblayer_infobar_translate_compact_content, parent,
+ false);
+ }
+
+ // When parent tab is being switched out (view detached), dismiss all menus and snackbars.
+ content.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View view) {}
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {
+ dismissMenusAndSnackbars();
+ }
+ });
+
+ mTabLayout =
+ (TranslateTabLayout) content.findViewById(R.id.weblayer_translate_infobar_tabs);
+ if (mDefaultTextColor > 0) {
+ mTabLayout.setTabTextColors(
+ ContextCompat.getColor(getContext(), R.color.default_text_color),
+ ContextCompat.getColor(
+ getContext(), R.color.weblayer_tab_layout_selected_tab_color));
+ }
+ mTabLayout.addTabs(mOptions.sourceLanguageName(), mOptions.targetLanguageName());
+
+ if (mInitialStep == TRANSLATING_INFOBAR) {
+ // Set translating status in the beginning for pages translated automatically.
+ mTabLayout.getTabAt(TARGET_TAB_INDEX).select();
+ mTabLayout.showProgressBarOnTab(TARGET_TAB_INDEX);
+ mUserInteracted = true;
+ } else if (mInitialStep == AFTER_TRANSLATING_INFOBAR) {
+ // Focus on target tab since we are after translation.
+ mTabLayout.getTabAt(TARGET_TAB_INDEX).select();
+ }
+
+ mTabLayout.addOnTabSelectedListener(this);
+
+ // Dismiss all menus and end scrolling animation when there is layout changed.
+ mTabLayout.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) {
+ // Dismiss all menus to prevent menu misplacement.
+ dismissMenus();
+
+ if (mIsFirstLayout) {
+ // Scrolls to the end to make sure the target language tab is visible when
+ // language tabs is too long.
+ mTabLayout.startScrollingAnimationIfNeeded();
+ mIsFirstLayout = false;
+ return;
+ }
+
+ // End scrolling animation when layout changed.
+ mTabLayout.endScrollingAnimationIfPlaying();
+ }
+ }
+ });
+
+ mMenuButton = content.findViewById(R.id.weblayer_translate_infobar_menu_button);
+ mMenuButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mTabLayout.endScrollingAnimationIfPlaying();
+ recordInfobarAction(INFOBAR_OPTIONS);
+ initMenuHelper(TranslateMenu.MENU_OVERFLOW);
+ mOverflowMenuHelper.show(TranslateMenu.MENU_OVERFLOW, getParentWidth());
+ mMenuExpanded = true;
+ }
+ });
+
+ parent.addContent(content, 1.0f);
+ mParent = parent;
+ }
+
+ private void initMenuHelper(int menuType) {
+ boolean isIncognito = TranslateCompactInfoBarJni.get().isIncognito(
+ mNativeTranslateInfoBarPtr, TranslateCompactInfoBar.this);
+ switch (menuType) {
+ case TranslateMenu.MENU_OVERFLOW:
+ if (mOverflowMenuHelper == null) {
+ mOverflowMenuHelper = new TranslateMenuHelper(
+ getContext(), mMenuButton, mOptions, this, isIncognito);
+ }
+ return;
+ case TranslateMenu.MENU_TARGET_LANGUAGE:
+ case TranslateMenu.MENU_SOURCE_LANGUAGE:
+ if (mLanguageMenuHelper == null) {
+ mLanguageMenuHelper = new TranslateMenuHelper(
+ getContext(), mMenuButton, mOptions, this, isIncognito);
+ }
+ return;
+ default:
+ assert false : "Unsupported Menu Item Id";
+ }
+ }
+
+ private void startTranslating(int tabPosition) {
+ if (TARGET_TAB_INDEX == tabPosition) {
+ // Already on the target tab.
+ mTabLayout.showProgressBarOnTab(TARGET_TAB_INDEX);
+ onButtonClicked(ActionType.TRANSLATE);
+ mUserInteracted = true;
+ } else {
+ mTabLayout.getTabAt(TARGET_TAB_INDEX).select();
+ }
+ }
+
+ @CalledByNative
+ private void onPageTranslated(int errorType) {
+ incrementAndRecordTranslationsPerPageCount();
+ if (mTabLayout != null) {
+ mTabLayout.hideProgressBar();
+ if (errorType != 0) {
+ Toast.makeText(getContext(), R.string.translate_infobar_error, Toast.LENGTH_SHORT)
+ .show();
+ // Disable OnTabSelectedListener then revert selection.
+ mTabLayout.removeOnTabSelectedListener(this);
+ mTabLayout.getTabAt(SOURCE_TAB_INDEX).select();
+ // Add OnTabSelectedListener back.
+ mTabLayout.addOnTabSelectedListener(this);
+ }
+ }
+ }
+
+ @CalledByNative
+ private void setNativePtr(long nativePtr) {
+ mNativeTranslateInfoBarPtr = nativePtr;
+ }
+
+ @CalledByNative
+ private void setAutoAlwaysTranslate() {
+ createAndShowSnackbar(ACTION_AUTO_ALWAYS_TRANSLATE);
+ }
+
+ @Override
+ protected void onNativeDestroyed() {
+ mNativeTranslateInfoBarPtr = 0;
+ super.onNativeDestroyed();
+ }
+
+ private void closeInfobar(boolean explicitly) {
+ if (isDismissed()) return;
+
+ if (!mUserInteracted) {
+ recordInfobarAction(INFOBAR_DECLINE);
+ }
+
+ // NOTE: In Chrome there is a check for whether auto "never translate" should be triggered
+ // via a snackbar here. However, WebLayer does not have snackbars and thus does not have
+ // this check as there would be no way to inform the user of the functionality being
+ // triggered. The user of course has the option of choosing "never translate" from the
+ // overflow menu.
+
+ // This line will dismiss this infobar.
+ super.onCloseButtonClicked();
+ }
+
+ @Override
+ public void onCloseButtonClicked() {
+ mTabLayout.endScrollingAnimationIfPlaying();
+ closeInfobar(true);
+ }
+
+ @Override
+ public void onTabSelected(TabLayout.Tab tab) {
+ switch (tab.getPosition()) {
+ case SOURCE_TAB_INDEX:
+ incrementAndRecordTranslationsPerPageCount();
+ recordInfobarAction(INFOBAR_REVERT);
+ onButtonClicked(ActionType.TRANSLATE_SHOW_ORIGINAL);
+ return;
+ case TARGET_TAB_INDEX:
+ recordInfobarAction(INFOBAR_TARGET_TAB_TRANSLATE);
+ recordInfobarLanguageData(
+ INFOBAR_HISTOGRAM_TRANSLATE_LANGUAGE, mOptions.targetLanguageCode());
+ startTranslating(TARGET_TAB_INDEX);
+ return;
+ default:
+ assert false : "Unexpected Tab Index";
+ }
+ }
+
+ @Override
+ public void onTabUnselected(TabLayout.Tab tab) {}
+
+ @Override
+ public void onTabReselected(TabLayout.Tab tab) {}
+
+ @Override
+ public void onOverflowMenuItemClicked(int itemId) {
+ switch (itemId) {
+ case TranslateMenu.ID_OVERFLOW_MORE_LANGUAGE:
+ recordInfobarAction(INFOBAR_MORE_LANGUAGES);
+ initMenuHelper(TranslateMenu.MENU_TARGET_LANGUAGE);
+ mLanguageMenuHelper.show(TranslateMenu.MENU_TARGET_LANGUAGE, getParentWidth());
+ return;
+ case TranslateMenu.ID_OVERFLOW_ALWAYS_TRANSLATE:
+ // Only show snackbar when "Always Translate" is enabled.
+ if (!mOptions.getTranslateState(TranslateOptions.Type.ALWAYS_LANGUAGE)) {
+ recordInfobarAction(INFOBAR_ALWAYS_TRANSLATE);
+ recordInfobarLanguageData(INFOBAR_HISTOGRAM_ALWAYS_TRANSLATE_LANGUAGE,
+ mOptions.sourceLanguageCode());
+ createAndShowSnackbar(ACTION_OVERFLOW_ALWAYS_TRANSLATE);
+ } else {
+ recordInfobarAction(INFOBAR_ALWAYS_TRANSLATE_UNDO);
+ handleTranslateOptionPostSnackbar(ACTION_OVERFLOW_ALWAYS_TRANSLATE);
+ }
+ return;
+ case TranslateMenu.ID_OVERFLOW_NEVER_LANGUAGE:
+ recordInfobarAction(INFOBAR_NEVER_TRANSLATE);
+ recordInfobarLanguageData(
+ INFOBAR_HISTOGRAM_NEVER_TRANSLATE_LANGUAGE, mOptions.sourceLanguageCode());
+ createAndShowSnackbar(ACTION_OVERFLOW_NEVER_LANGUAGE);
+ return;
+ case TranslateMenu.ID_OVERFLOW_NEVER_SITE:
+ recordInfobarAction(INFOBAR_NEVER_TRANSLATE_SITE);
+ createAndShowSnackbar(ACTION_OVERFLOW_NEVER_SITE);
+ return;
+ case TranslateMenu.ID_OVERFLOW_NOT_THIS_LANGUAGE:
+ recordInfobarAction(INFOBAR_PAGE_NOT_IN);
+ initMenuHelper(TranslateMenu.MENU_SOURCE_LANGUAGE);
+ mLanguageMenuHelper.show(TranslateMenu.MENU_SOURCE_LANGUAGE, getParentWidth());
+ return;
+ default:
+ assert false : "Unexpected overflow menu code";
+ }
+ }
+
+ @Override
+ public void onTargetMenuItemClicked(String code) {
+ // Reset target code in both UI and native.
+ if (mNativeTranslateInfoBarPtr != 0 && mOptions.setTargetLanguage(code)) {
+ recordInfobarAction(INFOBAR_MORE_LANGUAGES_TRANSLATE);
+ recordInfobarLanguageData(
+ INFOBAR_HISTOGRAM_MORE_LANGUAGES_LANGUAGE, mOptions.targetLanguageCode());
+ TranslateCompactInfoBarJni.get().applyStringTranslateOption(mNativeTranslateInfoBarPtr,
+ TranslateCompactInfoBar.this, TranslateOption.TARGET_CODE, code);
+ // Adjust UI.
+ mTabLayout.replaceTabTitle(TARGET_TAB_INDEX, mOptions.getRepresentationFromCode(code));
+ startTranslating(mTabLayout.getSelectedTabPosition());
+ }
+ }
+
+ @Override
+ public void onSourceMenuItemClicked(String code) {
+ // Reset source code in both UI and native.
+ if (mNativeTranslateInfoBarPtr != 0 && mOptions.setSourceLanguage(code)) {
+ recordInfobarLanguageData(
+ INFOBAR_HISTOGRAM_PAGE_NOT_IN_LANGUAGE, mOptions.sourceLanguageCode());
+ TranslateCompactInfoBarJni.get().applyStringTranslateOption(mNativeTranslateInfoBarPtr,
+ TranslateCompactInfoBar.this, TranslateOption.SOURCE_CODE, code);
+ // Adjust UI.
+ mTabLayout.replaceTabTitle(SOURCE_TAB_INDEX, mOptions.getRepresentationFromCode(code));
+ startTranslating(mTabLayout.getSelectedTabPosition());
+ }
+ }
+
+ // Dismiss all overflow menus that remains open.
+ // This is called when infobar started hiding or layout changed.
+ private void dismissMenus() {
+ if (mOverflowMenuHelper != null) mOverflowMenuHelper.dismiss();
+ if (mLanguageMenuHelper != null) mLanguageMenuHelper.dismiss();
+ }
+
+ // Dismiss all overflow menus and snackbars that belong to this infobar and remain open.
+ private void dismissMenusAndSnackbars() {
+ dismissMenus();
+ }
+
+ @Override
+ protected void onStartedHiding() {
+ dismissMenusAndSnackbars();
+ }
+
+ @Override
+ protected CharSequence getAccessibilityMessage(CharSequence defaultMessage) {
+ return getContext().getString(R.string.translate_button);
+ }
+
+ /**
+ * Returns true if overflow menu is showing. This is only used for automation testing.
+ */
+ public boolean isShowingOverflowMenuForTesting() {
+ if (mOverflowMenuHelper == null) return false;
+ return mOverflowMenuHelper.isShowing();
+ }
+
+ /**
+ * Returns true if language menu is showing. This is only used for automation testing.
+ */
+ public boolean isShowingLanguageMenuForTesting() {
+ if (mLanguageMenuHelper == null) return false;
+ return mLanguageMenuHelper.isShowing();
+ }
+
+ /**
+ * Returns true if the tab at the given |tabIndex| is selected. This is only used for automation
+ * testing.
+ */
+ private boolean isTabSelectedForTesting(int tabIndex) {
+ return mTabLayout.getTabAt(tabIndex).isSelected();
+ }
+
+ /**
+ * Returns true if the target tab is selected. This is only used for automation testing.
+ */
+ public boolean isSourceTabSelectedForTesting() {
+ return this.isTabSelectedForTesting(SOURCE_TAB_INDEX);
+ }
+
+ /**
+ * Returns true if the target tab is selected. This is only used for automation testing.
+ */
+ public boolean isTargetTabSelectedForTesting() {
+ return this.isTabSelectedForTesting(TARGET_TAB_INDEX);
+ }
+
+ private void createAndShowSnackbar(int actionId) {
+ // NOTE: WebLayer doesn't have snackbars, so the relevant action is just taken directly.
+ // TODO(blundell): If WebLayer ends up staying with this implementation long-term, update
+ // the nomenclature of this file to avoid any references to snackbars.
+ handleTranslateOptionPostSnackbar(actionId);
+ }
+
+ private void handleTranslateOptionPostSnackbar(int actionId) {
+ // Quit if native is destroyed.
+ if (mNativeTranslateInfoBarPtr == 0) return;
+
+ switch (actionId) {
+ case ACTION_OVERFLOW_ALWAYS_TRANSLATE:
+ toggleAlwaysTranslate();
+ // Start translating if always translate is selected and if page is not already
+ // translated to the target language.
+ if (mOptions.getTranslateState(TranslateOptions.Type.ALWAYS_LANGUAGE)
+ && mTabLayout.getSelectedTabPosition() == SOURCE_TAB_INDEX) {
+ startTranslating(mTabLayout.getSelectedTabPosition());
+ }
+ return;
+ case ACTION_AUTO_ALWAYS_TRANSLATE:
+ toggleAlwaysTranslate();
+ return;
+ case ACTION_OVERFLOW_NEVER_LANGUAGE:
+ case ACTION_AUTO_NEVER_LANGUAGE:
+ mUserInteracted = true;
+ mOptions.toggleNeverTranslateLanguageState(
+ !mOptions.getTranslateState(TranslateOptions.Type.NEVER_LANGUAGE));
+ TranslateCompactInfoBarJni.get().applyBoolTranslateOption(
+ mNativeTranslateInfoBarPtr, TranslateCompactInfoBar.this,
+ TranslateOption.NEVER_TRANSLATE,
+ mOptions.getTranslateState(TranslateOptions.Type.NEVER_LANGUAGE));
+ return;
+ case ACTION_OVERFLOW_NEVER_SITE:
+ mUserInteracted = true;
+ mOptions.toggleNeverTranslateDomainState(
+ !mOptions.getTranslateState(TranslateOptions.Type.NEVER_DOMAIN));
+ TranslateCompactInfoBarJni.get().applyBoolTranslateOption(
+ mNativeTranslateInfoBarPtr, TranslateCompactInfoBar.this,
+ TranslateOption.NEVER_TRANSLATE_SITE,
+ mOptions.getTranslateState(TranslateOptions.Type.NEVER_DOMAIN));
+ return;
+ default:
+ assert false : "Unsupported Menu Item Id, in handle post snackbar";
+ }
+ }
+
+ private void toggleAlwaysTranslate() {
+ mOptions.toggleAlwaysTranslateLanguageState(
+ !mOptions.getTranslateState(TranslateOptions.Type.ALWAYS_LANGUAGE));
+ TranslateCompactInfoBarJni.get().applyBoolTranslateOption(mNativeTranslateInfoBarPtr,
+ TranslateCompactInfoBar.this, TranslateOption.ALWAYS_TRANSLATE,
+ mOptions.getTranslateState(TranslateOptions.Type.ALWAYS_LANGUAGE));
+ }
+
+ private static void recordInfobarAction(int action) {
+ RecordHistogram.recordEnumeratedHistogram(
+ INFOBAR_HISTOGRAM, action, INFOBAR_HISTOGRAM_BOUNDARY);
+ }
+
+ private void recordInfobarLanguageData(String histogram, String langCode) {
+ Integer hashCode = mOptions.getUMAHashCodeFromCode(langCode);
+ if (hashCode != null) {
+ RecordHistogram.recordSparseHistogram(histogram, hashCode);
+ }
+ }
+
+ private void incrementAndRecordTranslationsPerPageCount() {
+ RecordHistogram.recordCountHistogram(
+ INFOBAR_HISTOGRAM_TRANSLATION_COUNT, ++mTotalTranslationCount);
+ }
+
+ // Return the width of parent in pixels. Return 0 if there is no parent.
+ private int getParentWidth() {
+ return mParent != null ? mParent.getWidth() : 0;
+ }
+
+ @CalledByNative
+ // Selects the tab corresponding to |actionType| to simulate the user pressing on this tab.
+ private void selectTabForTesting(int actionType) {
+ if (actionType == ActionType.TRANSLATE) {
+ mTabLayout.getTabAt(TARGET_TAB_INDEX).select();
+ } else if (actionType == ActionType.TRANSLATE_SHOW_ORIGINAL) {
+ mTabLayout.getTabAt(SOURCE_TAB_INDEX).select();
+ } else {
+ assert false;
+ }
+ }
+
+ @CalledByNative
+ // Simulates a click of the overflow menu item for "never translate this language."
+ private void clickNeverTranslateLanguageMenuItemForTesting() {
+ onOverflowMenuItemClicked(TranslateMenu.ID_OVERFLOW_NEVER_LANGUAGE);
+ }
+
+ @CalledByNative
+ // Simulates a click of the overflow menu item for "never translate this site."
+ private void clickNeverTranslateSiteMenuItemForTesting() {
+ onOverflowMenuItemClicked(TranslateMenu.ID_OVERFLOW_NEVER_SITE);
+ }
+
+ @NativeMethods
+ interface Natives {
+ void applyStringTranslateOption(long nativeTranslateCompactInfoBar,
+ TranslateCompactInfoBar caller, int option, String value);
+ void applyBoolTranslateOption(long nativeTranslateCompactInfoBar,
+ TranslateCompactInfoBar caller, int option, boolean value);
+ boolean shouldAutoNeverTranslate(long nativeTranslateCompactInfoBar,
+ TranslateCompactInfoBar caller, boolean menuExpanded);
+ boolean isIncognito(long nativeTranslateCompactInfoBar, TranslateCompactInfoBar caller);
+ }
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateMenu.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateMenu.java
new file mode 100644
index 00000000000..cfb1a06c2f5
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateMenu.java
@@ -0,0 +1,75 @@
+// Copyright 2017 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 java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Translate menu config and its item entity definition.
+ */
+public final class TranslateMenu {
+ /**
+ * The menu item entity.
+ */
+ static final class MenuItem {
+ public final int mType;
+ public final int mId;
+ public final String mCode;
+ public final boolean mWithDivider;
+
+ MenuItem(int itemType, int itemId, boolean withDivider) {
+ this(itemType, itemId, EMPTY_STRING, withDivider);
+ }
+
+ MenuItem(int itemType, int itemId, String code) {
+ this(itemType, itemId, code, false);
+ }
+
+ MenuItem(int itemType, int itemId, String code, boolean withDivider) {
+ mType = itemType;
+ mId = itemId;
+ mCode = code;
+ mWithDivider = withDivider;
+ }
+ }
+
+ public static final String EMPTY_STRING = "";
+
+ // Menu type config.
+ public static final int MENU_OVERFLOW = 0;
+ public static final int MENU_TARGET_LANGUAGE = 1;
+ public static final int MENU_SOURCE_LANGUAGE = 2;
+
+ // Menu item type config.
+ public static final int ITEM_LANGUAGE = 0;
+ public static final int ITEM_CHECKBOX_OPTION = 1;
+ public static final int MENU_ITEM_TYPE_COUNT = 2;
+
+ // Menu Item ID config for MENU_OVERFLOW.
+ public static final int ID_OVERFLOW_MORE_LANGUAGE = 0;
+ public static final int ID_OVERFLOW_ALWAYS_TRANSLATE = 1;
+ public static final int ID_OVERFLOW_NEVER_SITE = 2;
+ public static final int ID_OVERFLOW_NEVER_LANGUAGE = 3;
+ public static final int ID_OVERFLOW_NOT_THIS_LANGUAGE = 4;
+
+ /**
+ * Build overflow menu item list.
+ */
+ static List<MenuItem> getOverflowMenu(boolean isIncognito) {
+ List<MenuItem> menu = new ArrayList<MenuItem>();
+ menu.add(new MenuItem(ITEM_CHECKBOX_OPTION, ID_OVERFLOW_MORE_LANGUAGE, true));
+ if (!isIncognito) {
+ // "Always translate" does nothing in incognito mode, so just hide it.
+ menu.add(new MenuItem(ITEM_CHECKBOX_OPTION, ID_OVERFLOW_ALWAYS_TRANSLATE, false));
+ }
+ menu.add(new MenuItem(ITEM_CHECKBOX_OPTION, ID_OVERFLOW_NEVER_LANGUAGE, false));
+ menu.add(new MenuItem(ITEM_CHECKBOX_OPTION, ID_OVERFLOW_NEVER_SITE, false));
+ menu.add(new MenuItem(ITEM_CHECKBOX_OPTION, ID_OVERFLOW_NOT_THIS_LANGUAGE, false));
+ return menu;
+ }
+
+ private TranslateMenu() {}
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateMenuHelper.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateMenuHelper.java
new file mode 100644
index 00000000000..16454817172
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateMenuHelper.java
@@ -0,0 +1,321 @@
+// Copyright 2017 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.content.Context;
+import android.graphics.Rect;
+import android.os.Build;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListPopupWindow;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import androidx.core.content.ContextCompat;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A Helper class for managing the Translate Overflow Menu.
+ */
+public class TranslateMenuHelper implements AdapterView.OnItemClickListener {
+ private final TranslateMenuListener mMenuListener;
+ private final TranslateOptions mOptions;
+
+ private ContextThemeWrapper mContextWrapper;
+ private TranslateMenuAdapter mAdapter;
+ private View mAnchorView;
+ private ListPopupWindow mPopup;
+ private boolean mIsIncognito;
+
+ /**
+ * Interface for receiving the click event of menu item.
+ */
+ public interface TranslateMenuListener {
+ void onOverflowMenuItemClicked(int itemId);
+ void onTargetMenuItemClicked(String code);
+ void onSourceMenuItemClicked(String code);
+ }
+
+ public TranslateMenuHelper(Context context, View anchorView, TranslateOptions options,
+ TranslateMenuListener itemListener, boolean isIncognito) {
+ mContextWrapper = new ContextThemeWrapper(context, R.style.OverflowMenuThemeOverlay);
+ mAnchorView = anchorView;
+ mOptions = options;
+ mMenuListener = itemListener;
+ mIsIncognito = isIncognito;
+ }
+
+ /**
+ * Build translate menu by menu type.
+ */
+ private List<TranslateMenu.MenuItem> getMenuList(int menuType) {
+ List<TranslateMenu.MenuItem> menuList = new ArrayList<TranslateMenu.MenuItem>();
+ if (menuType == TranslateMenu.MENU_OVERFLOW) {
+ // TODO(googleo): Add language short list above static menu after its data is ready.
+ menuList.addAll(TranslateMenu.getOverflowMenu(mIsIncognito));
+ } else {
+ for (int i = 0; i < mOptions.allLanguages().size(); ++i) {
+ String code = mOptions.allLanguages().get(i).mLanguageCode;
+ // Avoid source language in the source language list.
+ if (menuType == TranslateMenu.MENU_SOURCE_LANGUAGE
+ && code.equals(mOptions.sourceLanguageCode())) {
+ continue;
+ }
+ // Avoid target language in the target language list.
+ if (menuType == TranslateMenu.MENU_TARGET_LANGUAGE
+ && code.equals(mOptions.targetLanguageCode())) {
+ continue;
+ }
+ menuList.add(new TranslateMenu.MenuItem(TranslateMenu.ITEM_LANGUAGE, i, code));
+ }
+ }
+ return menuList;
+ }
+
+ /**
+ * Show the overflow menu.
+ * @param menuType The type of overflow menu to show.
+ * @param maxwidth Maximum width of menu. Set to 0 when not specified.
+ */
+ public void show(int menuType, int maxWidth) {
+ if (mPopup == null) {
+ mPopup = new ListPopupWindow(mContextWrapper, null, android.R.attr.popupMenuStyle);
+ mPopup.setModal(true);
+ mPopup.setAnchorView(mAnchorView);
+ mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+
+ // Need to explicitly set the background here. Relying on it being set in the style
+ // caused an incorrectly drawn background.
+ // TODO(martiw): We might need a new menu background here.
+ mPopup.setBackgroundDrawable(
+ ContextCompat.getDrawable(mContextWrapper, R.drawable.popup_bg_tinted));
+
+ mPopup.setOnItemClickListener(this);
+
+ // The menu must be shifted down by the height of the anchor view in order to be
+ // displayed over and above it.
+ int anchorHeight = mAnchorView.getHeight();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ // Setting a positive offset here shifts the menu down.
+ mPopup.setVerticalOffset(anchorHeight);
+ } else {
+ // The framework's PopupWindow positioning changed between N and M. Setting
+ // a negative offset here shifts the menu down rather than up.
+ mPopup.setVerticalOffset(-anchorHeight);
+ }
+
+ mAdapter = new TranslateMenuAdapter(menuType);
+ mPopup.setAdapter(mAdapter);
+ } else {
+ mAdapter.refreshMenu(menuType);
+ }
+
+ if (menuType == TranslateMenu.MENU_OVERFLOW) {
+ // Use measured width when it is a overflow menu.
+ Rect bgPadding = new Rect();
+ mPopup.getBackground().getPadding(bgPadding);
+ int measuredWidth = measureMenuWidth(mAdapter) + bgPadding.left + bgPadding.right;
+ mPopup.setWidth((maxWidth > 0 && measuredWidth > maxWidth) ? maxWidth : measuredWidth);
+ } else {
+ // Use fixed width otherwise.
+ int popupWidth = mContextWrapper.getResources().getDimensionPixelSize(
+ R.dimen.weblayer_infobar_translate_menu_width);
+ mPopup.setWidth(popupWidth);
+ }
+
+ // When layout is RTL, set the horizontal offset to align the menu with the left side of the
+ // screen.
+ if (mAnchorView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+ int[] tempLocation = new int[2];
+ mAnchorView.getLocationOnScreen(tempLocation);
+ mPopup.setHorizontalOffset(-tempLocation[0]);
+ }
+
+ if (!mPopup.isShowing()) {
+ mPopup.show();
+ mPopup.getListView().setItemsCanFocus(true);
+ }
+ }
+
+ private int measureMenuWidth(TranslateMenuAdapter adapter) {
+ final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
+ final int count = adapter.getCount();
+ int width = 0;
+ int itemType = 0;
+ View itemView = null;
+ for (int i = 0; i < count; i++) {
+ final int positionType = adapter.getItemViewType(i);
+ if (positionType != itemType) {
+ itemType = positionType;
+ itemView = null;
+ }
+ itemView = adapter.getView(i, itemView, null);
+ itemView.measure(widthMeasureSpec, heightMeasureSpec);
+ width = Math.max(width, itemView.getMeasuredWidth());
+ }
+ return width;
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ dismiss();
+
+ TranslateMenu.MenuItem item = mAdapter.getItem(position);
+ switch (mAdapter.mMenuType) {
+ case TranslateMenu.MENU_OVERFLOW:
+ mMenuListener.onOverflowMenuItemClicked(item.mId);
+ return;
+ case TranslateMenu.MENU_TARGET_LANGUAGE:
+ mMenuListener.onTargetMenuItemClicked(item.mCode);
+ return;
+ case TranslateMenu.MENU_SOURCE_LANGUAGE:
+ mMenuListener.onSourceMenuItemClicked(item.mCode);
+ return;
+ default:
+ assert false : "Unsupported Menu Item Id";
+ }
+ }
+
+ /**
+ * Dismisses the translate option menu.
+ */
+ public void dismiss() {
+ if (isShowing()) {
+ mPopup.dismiss();
+ }
+ }
+
+ /**
+ * @return Whether the menu is currently showing.
+ */
+ public boolean isShowing() {
+ if (mPopup == null) {
+ return false;
+ }
+ return mPopup.isShowing();
+ }
+
+ /**
+ * The provides the views of the menu items and dividers.
+ */
+ private final class TranslateMenuAdapter extends ArrayAdapter<TranslateMenu.MenuItem> {
+ private final LayoutInflater mInflater;
+ private int mMenuType;
+
+ public TranslateMenuAdapter(int menuType) {
+ super(mContextWrapper, R.layout.weblayer_translate_menu_item, getMenuList(menuType));
+ mInflater = LayoutInflater.from(mContextWrapper);
+ mMenuType = menuType;
+ }
+
+ private void refreshMenu(int menuType) {
+ // MENU_OVERFLOW is static and it should not reload.
+ if (menuType == TranslateMenu.MENU_OVERFLOW) return;
+
+ clear();
+
+ mMenuType = menuType;
+ addAll(getMenuList(menuType));
+ notifyDataSetChanged();
+ }
+
+ private String getItemViewText(TranslateMenu.MenuItem item) {
+ if (mMenuType == TranslateMenu.MENU_OVERFLOW) {
+ // Overflow menu items are manually defined one by one.
+ String source = mOptions.sourceLanguageName();
+ switch (item.mId) {
+ case TranslateMenu.ID_OVERFLOW_ALWAYS_TRANSLATE:
+ return mContextWrapper.getString(
+ R.string.translate_option_always_translate, source);
+ case TranslateMenu.ID_OVERFLOW_MORE_LANGUAGE:
+ return mContextWrapper.getString(R.string.translate_option_more_language);
+ case TranslateMenu.ID_OVERFLOW_NEVER_SITE:
+ return mContextWrapper.getString(R.string.translate_never_translate_site);
+ case TranslateMenu.ID_OVERFLOW_NEVER_LANGUAGE:
+ return mContextWrapper.getString(
+ R.string.translate_option_never_translate, source);
+ case TranslateMenu.ID_OVERFLOW_NOT_THIS_LANGUAGE:
+ return mContextWrapper.getString(
+ R.string.translate_option_not_source_language, source);
+ default:
+ assert false : "Unexpected Overflow Item Id";
+ }
+ } else {
+ // Get source and target language menu items text by language code.
+ return mOptions.getRepresentationFromCode(item.mCode);
+ }
+ return "";
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return getItem(position).mType;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return TranslateMenu.MENU_ITEM_TYPE_COUNT;
+ }
+
+ private View getItemView(
+ View menuItemView, int position, ViewGroup parent, int resourceId) {
+ if (menuItemView == null) {
+ menuItemView = mInflater.inflate(resourceId, parent, false);
+ }
+ ((TextView) menuItemView.findViewById(R.id.weblayer_menu_item_text))
+ .setText(getItemViewText(getItem(position)));
+ return menuItemView;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View menuItemView = convertView;
+ switch (getItemViewType(position)) {
+ case TranslateMenu.ITEM_CHECKBOX_OPTION:
+ menuItemView = getItemView(menuItemView, position, parent,
+ R.layout.weblayer_translate_menu_item_checked);
+
+ ImageView checkboxIcon =
+ menuItemView.findViewById(R.id.weblayer_menu_item_icon);
+ if (getItem(position).mId == TranslateMenu.ID_OVERFLOW_ALWAYS_TRANSLATE
+ && mOptions.getTranslateState(TranslateOptions.Type.ALWAYS_LANGUAGE)) {
+ checkboxIcon.setVisibility(View.VISIBLE);
+ } else if (getItem(position).mId == TranslateMenu.ID_OVERFLOW_NEVER_LANGUAGE
+ && mOptions.getTranslateState(TranslateOptions.Type.NEVER_LANGUAGE)) {
+ checkboxIcon.setVisibility(View.VISIBLE);
+ } else if (getItem(position).mId == TranslateMenu.ID_OVERFLOW_NEVER_SITE
+ && mOptions.getTranslateState(TranslateOptions.Type.NEVER_DOMAIN)) {
+ checkboxIcon.setVisibility(View.VISIBLE);
+ } else {
+ checkboxIcon.setVisibility(View.INVISIBLE);
+ }
+
+ View divider =
+ (View) menuItemView.findViewById(R.id.weblayer_menu_item_divider);
+ if (getItem(position).mWithDivider) {
+ divider.setVisibility(View.VISIBLE);
+ }
+ break;
+ case TranslateMenu.ITEM_LANGUAGE:
+ menuItemView = getItemView(
+ menuItemView, position, parent, R.layout.weblayer_translate_menu_item);
+ break;
+ default:
+ assert false : "Unexpected MenuItem type";
+ }
+ return menuItemView;
+ }
+ }
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateOptions.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateOptions.java
new file mode 100644
index 00000000000..ba38d4e26f6
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateOptions.java
@@ -0,0 +1,278 @@
+// 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.text.TextUtils;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A class that keeps the state of the different translation options and
+ * languages.
+ */
+public class TranslateOptions {
+ /**
+ * A container for Language Code and it's translated representation and it's native UMA
+ * specific hashcode.
+ * For example for Spanish when viewed from a French locale, this will contain es, Espagnol,
+ * 114573335
+ **/
+ public static class TranslateLanguageData {
+ public final String mLanguageCode;
+ public final String mLanguageRepresentation;
+ public final Integer mLanguageUMAHashCode;
+
+ public TranslateLanguageData(
+ String languageCode, String languageRepresentation, Integer uMAhashCode) {
+ assert languageCode != null;
+ assert languageRepresentation != null;
+ mLanguageCode = languageCode;
+ mLanguageRepresentation = languageRepresentation;
+ mLanguageUMAHashCode = uMAhashCode;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof TranslateLanguageData)) return false;
+ TranslateLanguageData other = (TranslateLanguageData) obj;
+ return this.mLanguageCode.equals(other.mLanguageCode)
+ && this.mLanguageRepresentation.equals(other.mLanguageRepresentation)
+ && this.mLanguageUMAHashCode.equals(other.mLanguageUMAHashCode);
+ }
+
+ @Override
+ public int hashCode() {
+ return (mLanguageCode + mLanguageRepresentation).hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "mLanguageCode:" + mLanguageCode + " - mlanguageRepresentation "
+ + mLanguageRepresentation + " - mLanguageUMAHashCode " + mLanguageUMAHashCode;
+ }
+ }
+
+ // Values must be numerated from 0 and can't have gaps
+ // (they're used for indexing mOptions).
+ @IntDef({Type.NEVER_LANGUAGE, Type.NEVER_DOMAIN, Type.ALWAYS_LANGUAGE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Type {
+ int NEVER_LANGUAGE = 0;
+ int NEVER_DOMAIN = 1;
+ int ALWAYS_LANGUAGE = 2;
+
+ int NUM_ENTRIES = 3;
+ }
+
+ private String mSourceLanguageCode;
+ private String mTargetLanguageCode;
+
+ private final ArrayList<TranslateLanguageData> mAllLanguages;
+
+ // language code to translated language name map
+ // Conceptually final
+ private Map<String, String> mCodeToRepresentation;
+
+ // Language code to its UMA hashcode representation.
+ private Map<String, Integer> mCodeToUMAHashCode;
+
+ // Will reflect the state before the object was ever modified
+ private final boolean[] mOriginalOptions;
+
+ private final String mOriginalSourceLanguageCode;
+ private final String mOriginalTargetLanguageCode;
+ private final boolean mTriggeredFromMenu;
+
+ private final boolean[] mOptions;
+
+ private TranslateOptions(String sourceLanguageCode, String targetLanguageCode,
+ ArrayList<TranslateLanguageData> allLanguages, boolean neverLanguage,
+ boolean neverDomain, boolean alwaysLanguage, boolean triggeredFromMenu,
+ boolean[] originalOptions) {
+ assert Type.NUM_ENTRIES == 3;
+ mOptions = new boolean[Type.NUM_ENTRIES];
+ mOptions[Type.NEVER_LANGUAGE] = neverLanguage;
+ mOptions[Type.NEVER_DOMAIN] = neverDomain;
+ mOptions[Type.ALWAYS_LANGUAGE] = alwaysLanguage;
+
+ mOriginalOptions = originalOptions == null ? mOptions.clone() : originalOptions.clone();
+
+ mSourceLanguageCode = sourceLanguageCode;
+ mTargetLanguageCode = targetLanguageCode;
+ mOriginalSourceLanguageCode = mSourceLanguageCode;
+ mOriginalTargetLanguageCode = mTargetLanguageCode;
+ mTriggeredFromMenu = triggeredFromMenu;
+
+ mAllLanguages = allLanguages;
+ mCodeToRepresentation = new HashMap<String, String>();
+ mCodeToUMAHashCode = new HashMap<String, Integer>();
+ for (TranslateLanguageData language : allLanguages) {
+ mCodeToRepresentation.put(language.mLanguageCode, language.mLanguageRepresentation);
+ mCodeToUMAHashCode.put(language.mLanguageCode, language.mLanguageUMAHashCode);
+ }
+ }
+
+ /**
+ * Creates a TranslateOptions by the given data.
+ */
+ public static TranslateOptions create(String sourceLanguageCode, String targetLanguageCode,
+ String[] languages, String[] codes, boolean alwaysTranslate, boolean triggeredFromMenu,
+ int[] hashCodes) {
+ assert languages.length == codes.length;
+
+ ArrayList<TranslateLanguageData> languageList = new ArrayList<TranslateLanguageData>();
+ for (int i = 0; i < languages.length; ++i) {
+ Integer hashCode = hashCodes != null ? Integer.valueOf(hashCodes[i]) : null;
+ languageList.add(new TranslateLanguageData(codes[i], languages[i], hashCode));
+ }
+ return new TranslateOptions(sourceLanguageCode, targetLanguageCode, languageList, false,
+ false, alwaysTranslate, triggeredFromMenu, null);
+ }
+
+ /**
+ * Returns a copy of the current instance.
+ */
+ TranslateOptions copy() {
+ return new TranslateOptions(mSourceLanguageCode, mTargetLanguageCode, mAllLanguages,
+ mOptions[Type.NEVER_LANGUAGE], mOptions[Type.NEVER_DOMAIN],
+ mOptions[Type.ALWAYS_LANGUAGE], mTriggeredFromMenu, mOriginalOptions);
+ }
+
+ public String sourceLanguageName() {
+ return getRepresentationFromCode(mSourceLanguageCode);
+ }
+
+ public String targetLanguageName() {
+ return getRepresentationFromCode(mTargetLanguageCode);
+ }
+
+ public String sourceLanguageCode() {
+ return mSourceLanguageCode;
+ }
+
+ public String targetLanguageCode() {
+ return mTargetLanguageCode;
+ }
+
+ public boolean triggeredFromMenu() {
+ return mTriggeredFromMenu;
+ }
+
+ public boolean optionsChanged() {
+ return (!mSourceLanguageCode.equals(mOriginalSourceLanguageCode))
+ || (!mTargetLanguageCode.equals(mOriginalTargetLanguageCode))
+ || (mOptions[Type.NEVER_LANGUAGE] != mOriginalOptions[Type.NEVER_LANGUAGE])
+ || (mOptions[Type.NEVER_DOMAIN] != mOriginalOptions[Type.NEVER_DOMAIN])
+ || (mOptions[Type.ALWAYS_LANGUAGE] != mOriginalOptions[Type.ALWAYS_LANGUAGE]);
+ }
+
+ public List<TranslateLanguageData> allLanguages() {
+ return mAllLanguages;
+ }
+
+ public boolean getTranslateState(@Type int type) {
+ return mOptions[type];
+ }
+
+ public boolean setSourceLanguage(String languageCode) {
+ boolean canSet = canSetLanguage(languageCode, mTargetLanguageCode);
+ if (canSet) mSourceLanguageCode = languageCode;
+ return canSet;
+ }
+
+ public boolean setTargetLanguage(String languageCode) {
+ boolean canSet = canSetLanguage(mSourceLanguageCode, languageCode);
+ if (canSet) mTargetLanguageCode = languageCode;
+ return canSet;
+ }
+
+ /**
+ * Sets the new state of never translate domain.
+ *
+ * @return true if the toggling was possible
+ */
+ public void toggleNeverTranslateDomainState(boolean value) {
+ mOptions[Type.NEVER_DOMAIN] = value;
+ }
+
+ /**
+ * Sets the new state of never translate language.
+ *
+ * @return true if the toggling was possible
+ */
+ public boolean toggleNeverTranslateLanguageState(boolean value) {
+ // Do not toggle if we are activating NeverLanguage but AlwaysTranslate
+ // for a language pair with the same source language is already active.
+ if (mOptions[Type.ALWAYS_LANGUAGE] && value) return false;
+ mOptions[Type.NEVER_LANGUAGE] = value;
+ return true;
+ }
+
+ /**
+ * Sets the new state of never translate a language pair.
+ *
+ * @return true if the toggling was possible
+ */
+ public boolean toggleAlwaysTranslateLanguageState(boolean value) {
+ // Do not toggle if we are activating AlwaysLanguage but NeverLanguage is active already.
+ if (mOptions[Type.NEVER_LANGUAGE] && value) return false;
+ mOptions[Type.ALWAYS_LANGUAGE] = value;
+ return true;
+ }
+
+ /**
+ * Gets the language's translated representation from a given language code.
+ * @param languageCode ISO code for the language
+ * @return The translated representation of the language, or "" if not found.
+ */
+ public String getRepresentationFromCode(String languageCode) {
+ return isValidLanguageCode(languageCode) ? mCodeToRepresentation.get(languageCode) : "";
+ }
+
+ /**
+ * Gets the language's UMA hashcode representation from a given language code.
+ * @param languageCode ISO code for the language
+ * @return The UMA hashcode representation of the language, or null if not found.
+ */
+ public Integer getUMAHashCodeFromCode(String languageCode) {
+ return isValidLanguageUMAHashCode(languageCode) ? mCodeToUMAHashCode.get(languageCode)
+ : null;
+ }
+
+ private boolean isValidLanguageCode(String languageCode) {
+ return !TextUtils.isEmpty(languageCode) && mCodeToRepresentation.containsKey(languageCode);
+ }
+
+ private boolean isValidLanguageUMAHashCode(String languageCode) {
+ return !TextUtils.isEmpty(languageCode) && mCodeToUMAHashCode.containsKey(languageCode);
+ }
+
+ private boolean canSetLanguage(String sourceCode, String targetCode) {
+ return isValidLanguageCode(sourceCode) && isValidLanguageCode(targetCode);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append(sourceLanguageCode())
+ .append(" -> ")
+ .append(targetLanguageCode())
+ .append(" - ")
+ .append("Never Language:")
+ .append(mOptions[Type.NEVER_LANGUAGE])
+ .append(" Always Language:")
+ .append(mOptions[Type.ALWAYS_LANGUAGE])
+ .append(" Never Domain:")
+ .append(mOptions[Type.NEVER_DOMAIN])
+ .toString();
+ }
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateTabContent.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateTabContent.java
new file mode 100644
index 00000000000..4cde0b46193
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateTabContent.java
@@ -0,0 +1,63 @@
+// Copyright 2017 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.content.Context;
+import android.content.res.ColorStateList;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+/**
+ * The content of the tab shown in the TranslateTabLayout.
+ */
+public class TranslateTabContent extends FrameLayout {
+ private TextView mTextView;
+ private ProgressBar mProgressBar;
+
+ /**
+ * Constructor for inflating from XML.
+ */
+ public TranslateTabContent(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mTextView = (TextView) findViewById(R.id.weblayer_translate_infobar_tab_text);
+ mProgressBar = (ProgressBar) findViewById(R.id.weblayer_translate_infobar_tab_progressbar);
+ }
+
+ /**
+ * Sets the text color for all the states (normal, selected, focused) to be this color.
+ * @param colors The color state list of the title text.
+ */
+ public void setTextColor(ColorStateList colors) {
+ mTextView.setTextColor(colors);
+ }
+
+ /**
+ * Set the title text for this tab.
+ * @param tabTitle The new title string.
+ */
+ public void setText(CharSequence tabTitle) {
+ mTextView.setText(tabTitle);
+ }
+
+ /** Hide progress bar and show text. */
+ public void hideProgressBar() {
+ mProgressBar.setVisibility(View.INVISIBLE);
+ mTextView.setVisibility(View.VISIBLE);
+ }
+
+ /** Show progress bar and hide text. */
+ public void showProgressBar() {
+ mTextView.setVisibility(View.INVISIBLE);
+ mProgressBar.setVisibility(View.VISIBLE);
+ }
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateTabLayout.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateTabLayout.java
new file mode 100644
index 00000000000..37c27f37cc6
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TranslateTabLayout.java
@@ -0,0 +1,240 @@
+// Copyright 2017 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.animation.ObjectAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+
+import androidx.annotation.NonNull;
+
+import com.google.android.material.tabs.TabLayout;
+
+import org.chromium.base.StrictModeContext;
+import org.chromium.components.browser_ui.widget.animation.Interpolators;
+
+/**
+ * TabLayout shown in the TranslateCompactInfoBar.
+ */
+public class TranslateTabLayout extends TabLayout {
+ /** The tab in which a spinning progress bar is showing. */
+ private Tab mTabShowingProgressBar;
+
+ /** The amount of waiting time before starting the scrolling animation. */
+ private static final long START_POSITION_WAIT_DURATION_MS = 1000;
+
+ /** The amount of time it takes to scroll to the end during the scrolling animation. */
+ private static final long SCROLL_DURATION_MS = 300;
+
+ /** We define the keyframes of the scrolling animation in this object. */
+ ObjectAnimator mScrollToEndAnimator;
+
+ /** Start padding of a Tab. Used for width calculation only. Will not be applied to views. */
+ private int mTabPaddingStart;
+
+ /** End padding of a Tab. Used for width calculation only. Will not be applied to views. */
+ private int mTabPaddingEnd;
+
+ /**
+ * Constructor for inflating from XML.
+ */
+ @SuppressLint("CustomViewStyleable") // TODO(crbug.com/807725): Remove and fix.
+ public TranslateTabLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.TabLayout, 0, R.style.Widget_Design_TabLayout);
+ mTabPaddingStart = mTabPaddingEnd =
+ a.getDimensionPixelSize(R.styleable.TabLayout_tabPadding, 0);
+ mTabPaddingStart =
+ a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingStart, mTabPaddingStart);
+ mTabPaddingEnd =
+ a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingEnd, mTabPaddingEnd);
+ }
+
+ /**
+ * Add new Tabs with title strings.
+ * @param titles Titles of the tabs to be added.
+ */
+ public void addTabs(CharSequence... titles) {
+ for (CharSequence title : titles) {
+ addTabWithTitle(title);
+ }
+ }
+
+ /**
+ * Add a new Tab with the title string.
+ * @param tabTitle Title string of the new tab.
+ */
+ public void addTabWithTitle(CharSequence tabTitle) {
+ TranslateTabContent tabContent;
+ // LayoutInflater may trigger accessing the disk.
+ try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
+ tabContent =
+ (TranslateTabContent) LayoutInflater.from(getContext())
+ .inflate(R.layout.weblayer_infobar_translate_tab_content, this, false);
+ }
+ // Set text color using tabLayout's ColorStateList. So that the title text will change
+ // color when selected and unselected.
+ tabContent.setTextColor(getTabTextColors());
+ tabContent.setText(tabTitle);
+
+ Tab tab = newTab();
+ tab.setCustomView(tabContent);
+ tab.setContentDescription(tabTitle);
+ super.addTab(tab);
+ }
+
+ /**
+ * Replace the title string of a tab.
+ * @param tabPos The position of the tab to modify.
+ * @param tabTitle The new title string.
+ */
+ public void replaceTabTitle(int tabPos, CharSequence tabTitle) {
+ if (tabPos < 0 || tabPos >= getTabCount()) {
+ return;
+ }
+ Tab tab = getTabAt(tabPos);
+ ((TranslateTabContent) tab.getCustomView()).setText(tabTitle);
+ tab.setContentDescription(tabTitle);
+ }
+
+ /**
+ * Show the spinning progress bar on a specified tab.
+ * @param tabPos The position of the tab to show the progress bar.
+ */
+ public void showProgressBarOnTab(int tabPos) {
+ if (tabPos < 0 || tabPos >= getTabCount() || mTabShowingProgressBar != null) {
+ return;
+ }
+ mTabShowingProgressBar = getTabAt(tabPos);
+
+ // TODO(martiw) See if we need to setContentDescription as "Translating" here.
+
+ if (tabIsSupported(mTabShowingProgressBar)) {
+ ((TranslateTabContent) mTabShowingProgressBar.getCustomView()).showProgressBar();
+ }
+ }
+
+ /**
+ * Hide the spinning progress bar in the tabs.
+ */
+ public void hideProgressBar() {
+ if (mTabShowingProgressBar == null) return;
+
+ if (tabIsSupported(mTabShowingProgressBar)) {
+ ((TranslateTabContent) mTabShowingProgressBar.getCustomView()).hideProgressBar();
+ }
+
+ mTabShowingProgressBar = null;
+ }
+
+ // Overridden to block children's touch event when showing progress bar.
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // Allow touches to propagate to children only if the layout can be interacted with.
+ if (mTabShowingProgressBar != null) {
+ return true;
+ }
+ endScrollingAnimationIfPlaying();
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ /** Check if the tab is supported in TranslateTabLayout. */
+ private boolean tabIsSupported(Tab tab) {
+ return (tab.getCustomView() instanceof TranslateTabContent);
+ }
+
+ // Overridden to make sure only supported Tabs can be added.
+ @Override
+ public void addTab(@NonNull Tab tab, int position, boolean setSelected) {
+ if (!tabIsSupported(tab)) {
+ throw new IllegalArgumentException();
+ }
+ super.addTab(tab, position, setSelected);
+ }
+
+ // Overrided to make sure only supported Tabs can be added.
+ @Override
+ public void addTab(@NonNull Tab tab, boolean setSelected) {
+ if (!tabIsSupported(tab)) {
+ throw new IllegalArgumentException();
+ }
+ super.addTab(tab, setSelected);
+ }
+
+ /**
+ * Calculate and return the width of a specified tab. Tab doesn't provide a means of getting
+ * the width so we need to calculate the width by summing up the tab paddings and content width.
+ * @param position Tab position.
+ * @return Tab's width in pixels.
+ */
+ private int getTabWidth(int position) {
+ if (getTabAt(position) == null) return 0;
+ return getTabAt(position).getCustomView().getWidth() + mTabPaddingStart + mTabPaddingEnd;
+ }
+
+ /**
+ * Calculate the total width of all tabs and return it.
+ * @return Total width of all tabs in pixels.
+ */
+ private int getTabsTotalWidth() {
+ int totalWidth = 0;
+ for (int i = 0; i < getTabCount(); i++) {
+ totalWidth += getTabWidth(i);
+ }
+ return totalWidth;
+ }
+
+ /**
+ * Calculate the maximum scroll distance (by subtracting layout width from total width of tabs)
+ * and return it.
+ * @return Maximum scroll distance in pixels.
+ */
+ private int maxScrollDistance() {
+ int scrollDistance = getTabsTotalWidth() - getWidth();
+ return scrollDistance > 0 ? scrollDistance : 0;
+ }
+
+ /**
+ * Perform the scrolling animation if this tablayout has any scrollable distance.
+ */
+ // TODO(crbug.com/900912): Figure out whether setScrollX is actually available.
+ @SuppressLint("ObjectAnimatorBinding")
+ public void startScrollingAnimationIfNeeded() {
+ int maxScrollDistance = maxScrollDistance();
+ if (maxScrollDistance == 0) {
+ return;
+ }
+ // The steps of the scrolling animation:
+ // 1. wait for START_POSITION_WAIT_DURATION_MS.
+ // 2. scroll to the end in SCROLL_DURATION_MS.
+ mScrollToEndAnimator = ObjectAnimator.ofInt(this, "scrollX",
+ getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : maxScrollDistance);
+ mScrollToEndAnimator.setStartDelay(START_POSITION_WAIT_DURATION_MS);
+ mScrollToEndAnimator.setDuration(SCROLL_DURATION_MS);
+ mScrollToEndAnimator.setInterpolator(Interpolators.DECELERATE_INTERPOLATOR);
+ mScrollToEndAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mScrollToEndAnimator = null;
+ }
+ });
+ mScrollToEndAnimator.start();
+ }
+
+ /**
+ * End the scrolling animation if it is playing.
+ */
+ public void endScrollingAnimationIfPlaying() {
+ if (mScrollToEndAnimator != null) mScrollToEndAnimator.end();
+ }
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/UrlBarControllerImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/UrlBarControllerImpl.java
index 31a6a5484b2..4f3d7b439f1 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/UrlBarControllerImpl.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/UrlBarControllerImpl.java
@@ -28,6 +28,7 @@ import org.chromium.components.omnibox.SecurityButtonAnimationDelegate;
import org.chromium.components.omnibox.SecurityStatusIcon;
import org.chromium.components.page_info.PageInfoController;
import org.chromium.components.page_info.PermissionParamsListBuilderDelegate;
+import org.chromium.content_public.browser.WebContents;
import org.chromium.weblayer_private.interfaces.IObjectWrapper;
import org.chromium.weblayer_private.interfaces.IUrlBarController;
import org.chromium.weblayer_private.interfaces.ObjectWrapper;
@@ -159,20 +160,19 @@ public class UrlBarControllerImpl extends IUrlBarController.Stub {
ContextCompat.getColor(embedderContext, mUrlIconColor)));
}
- mSecurityButton.setOnClickListener(v -> { showPageInfoUi(v); });
if (mShowPageInfoWhenUrlTextClicked) {
- mUrlTextView.setOnClickListener(v -> { showPageInfoUi(v); });
+ setOnClickListener(v -> { showPageInfoUi(v); });
+ } else {
+ mSecurityButton.setOnClickListener(v -> { showPageInfoUi(v); });
}
}
private void showPageInfoUi(View v) {
+ WebContents webContents = mBrowserImpl.getActiveTab().getWebContents();
PageInfoController.show(mBrowserImpl.getWindowAndroid().getActivity().get(),
- mBrowserImpl.getActiveTab().getWebContents(),
+ webContents,
/* contentPublisher= */ null, PageInfoController.OpenedFromSource.TOOLBAR,
- new PageInfoControllerDelegateImpl(mBrowserImpl.getContext(),
- mBrowserImpl.getProfile().getName(),
- mBrowserImpl.getActiveTab().getWebContents().getVisibleUrl(),
- mBrowserImpl.getWindowAndroid()::getModalDialogManager),
+ PageInfoControllerDelegateImpl.create(webContents),
new PermissionParamsListBuilderDelegate(mBrowserImpl.getProfile()));
}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerAccessibilityUtil.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerAccessibilityUtil.java
index 9054076d7b4..6cce5a8b27d 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerAccessibilityUtil.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerAccessibilityUtil.java
@@ -4,6 +4,8 @@
package org.chromium.weblayer_private;
+import org.chromium.ui.util.AccessibilityUtil;
+
/**
* Exposes information about the current accessibility state.
*/
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerFactoryImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerFactoryImpl.java
index ad83409a452..9e79ff718ec 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerFactoryImpl.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerFactoryImpl.java
@@ -12,6 +12,7 @@ import org.chromium.components.version_info.VersionConstants;
import org.chromium.weblayer_private.interfaces.IWebLayer;
import org.chromium.weblayer_private.interfaces.IWebLayerFactory;
import org.chromium.weblayer_private.interfaces.StrictModeWorkaround;
+import org.chromium.weblayer_private.interfaces.WebLayerVersionConstants;
/**
* Factory used to create WebLayer as well as verify compatibility.
@@ -49,7 +50,13 @@ public final class WebLayerFactoryImpl extends IWebLayerFactory.Stub {
@Override
public boolean isClientSupported() {
StrictModeWorkaround.apply();
- return Math.abs(sClientMajorVersion - getImplementationMajorVersion()) <= 4;
+ int implMajorVersion = getImplementationMajorVersion();
+ // While the client always calls this method, the most recently shipped product gets to
+ // decide compatibility. If we instead let the implementation always decide, then we would
+ // not be able to change the allowed skew of older implementations, even if the client could
+ // support it.
+ if (sClientMajorVersion > implMajorVersion) return true;
+ return implMajorVersion - sClientMajorVersion <= WebLayerVersionConstants.MAX_SKEW;
}
/**
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java
index 7150b1d5ca7..c9223c62146 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java
@@ -4,6 +4,7 @@
package org.chromium.weblayer_private;
+import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -321,6 +322,9 @@ public final class WebLayerImpl extends IWebLayer.Stub {
public void onReceivedBroadcast(IObjectWrapper appContextWrapper, Intent intent) {
StrictModeWorkaround.apply();
Context context = ObjectWrapper.unwrap(appContextWrapper, Context.class);
+
+ if (IntentUtils.handleIntent(intent)) return;
+
if (intent.getAction().startsWith(DownloadImpl.getIntentPrefix())) {
DownloadImpl.forwardIntent(context, intent, mProfileManager);
} else if (intent.getAction().startsWith(MediaStreamManager.getIntentPrefix())) {
@@ -329,6 +333,19 @@ public final class WebLayerImpl extends IWebLayer.Stub {
}
@Override
+ public void onMediaSessionServiceStarted(IObjectWrapper sessionService, Intent intent) {
+ StrictModeWorkaround.apply();
+ MediaSessionManager.serviceStarted(
+ ObjectWrapper.unwrap(sessionService, Service.class), intent);
+ }
+
+ @Override
+ public void onMediaSessionServiceDestroyed() {
+ StrictModeWorkaround.apply();
+ MediaSessionManager.serviceDestroyed();
+ }
+
+ @Override
public void enumerateAllProfileNames(IObjectWrapper valueCallback) {
StrictModeWorkaround.apply();
final ValueCallback<String[]> callback =
@@ -380,6 +397,30 @@ public final class WebLayerImpl extends IWebLayer.Stub {
}
}
+ public static Intent createMediaSessionServiceIntent() {
+ if (sClient == null) {
+ throw new IllegalStateException("WebLayer should have been initialized already.");
+ }
+
+ try {
+ return sClient.createMediaSessionServiceIntent();
+ } catch (RemoteException e) {
+ throw new APICallException(e);
+ }
+ }
+
+ public static int getMediaSessionNotificationId() {
+ if (sClient == null) {
+ throw new IllegalStateException("WebLayer should have been initialized already.");
+ }
+
+ try {
+ return sClient.getMediaSessionNotificationId();
+ } catch (RemoteException e) {
+ throw new APICallException(e);
+ }
+ }
+
public static String getClientApplicationName() {
Context context = ContextUtils.getApplicationContext();
return new StringBuilder()
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationBuilder.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationBuilder.java
index f591a671aa2..50984bb6403 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationBuilder.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationBuilder.java
@@ -4,19 +4,37 @@
package org.chromium.weblayer_private;
+import android.annotation.TargetApi;
+import android.app.Notification;
+import android.app.PendingIntent;
import android.content.Context;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.webkit.WebViewFactory;
+import androidx.annotation.NonNull;
+
+import org.chromium.base.ContextUtils;
import org.chromium.components.browser_ui.notifications.ChromeNotificationBuilder;
import org.chromium.components.browser_ui.notifications.NotificationBuilder;
+import org.chromium.components.browser_ui.notifications.NotificationManagerProxyImpl;
import org.chromium.components.browser_ui.notifications.NotificationMetadata;
import org.chromium.components.browser_ui.notifications.channels.ChannelsInitializer;
/** A notification builder for WebLayer which has extra logic to make icons work correctly. */
final class WebLayerNotificationBuilder extends NotificationBuilder {
- public WebLayerNotificationBuilder(Context context, String channelId,
+ /** Creates a notification builder. */
+ public static WebLayerNotificationBuilder create(
+ @WebLayerNotificationChannels.ChannelId String channelId,
+ @NonNull NotificationMetadata metadata) {
+ Context appContext = ContextUtils.getApplicationContext();
+ ChannelsInitializer initializer =
+ new ChannelsInitializer(new NotificationManagerProxyImpl(appContext),
+ WebLayerNotificationChannels.getInstance(), appContext.getResources());
+ return new WebLayerNotificationBuilder(appContext, channelId, initializer, metadata);
+ }
+
+ private WebLayerNotificationBuilder(Context context, String channelId,
ChannelsInitializer channelsInitializer, NotificationMetadata metadata) {
super(context, channelId, channelsInitializer, metadata);
}
@@ -25,17 +43,67 @@ final class WebLayerNotificationBuilder extends NotificationBuilder {
public ChromeNotificationBuilder setSmallIcon(int icon) {
if (WebLayerImpl.isAndroidResource(icon)) {
super.setSmallIcon(icon);
- return this;
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ super.setSmallIcon(createIcon(icon));
+ } else {
+ // Some fallback is required, or the notification won't appear.
+ super.setSmallIcon(getFallbackAndroidResource(icon));
}
+ return this;
+ }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- super.setSmallIcon(
- Icon.createWithResource(WebViewFactory.getLoadedPackageInfo().packageName,
- WebLayerImpl.getResourceIdForSystemUi(icon)));
+ @Override
+ @SuppressWarnings("deprecation")
+ public ChromeNotificationBuilder addAction(int icon, CharSequence title, PendingIntent intent) {
+ if (WebLayerImpl.isAndroidResource(icon)) {
+ super.addAction(icon, title, intent);
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ super.addAction(
+ new Notification.Action.Builder(createIcon(icon), title, intent).build());
} else {
- // Some fallback is required, or the notification won't appear.
- super.setSmallIcon(android.R.drawable.radiobutton_on_background);
+ super.addAction(getFallbackAndroidResource(icon), title, intent);
}
return this;
}
+
+ @TargetApi(Build.VERSION_CODES.M)
+ private Icon createIcon(int resId) {
+ return Icon.createWithResource(WebViewFactory.getLoadedPackageInfo().packageName,
+ WebLayerImpl.getResourceIdForSystemUi(resId));
+ }
+
+ /**
+ * Finds a reasonable replacement for the given app-defined resource from among stock android
+ * resources. This is useful when {@link Icon} is not available.
+ */
+ private int getFallbackAndroidResource(int appResourceId) {
+ if (appResourceId == R.drawable.ic_play_arrow_white_36dp) {
+ return android.R.drawable.ic_media_play;
+ }
+ if (appResourceId == R.drawable.ic_pause_white_36dp) {
+ return android.R.drawable.ic_media_pause;
+ }
+ if (appResourceId == R.drawable.ic_stop_white_36dp) {
+ // There's no ic_media_stop. This standin is at least a square. In practice this
+ // shouldn't ever come up as stop is only used in (Chrome) cast notifications.
+ return android.R.drawable.checkbox_off_background;
+ }
+ if (appResourceId == R.drawable.ic_skip_previous_white_36dp) {
+ return android.R.drawable.ic_media_previous;
+ }
+ if (appResourceId == R.drawable.ic_skip_next_white_36dp) {
+ return android.R.drawable.ic_media_next;
+ }
+ if (appResourceId == R.drawable.ic_fast_forward_white_36dp) {
+ return android.R.drawable.ic_media_ff;
+ }
+ if (appResourceId == R.drawable.ic_fast_rewind_white_36dp) {
+ return android.R.drawable.ic_media_rew;
+ }
+ if (appResourceId == R.drawable.audio_playing) {
+ return android.R.drawable.ic_lock_silent_mode_off;
+ }
+
+ return android.R.drawable.radiobutton_on_background;
+ }
}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationChannels.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationChannels.java
index 264421f20eb..5967d90d6ec 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationChannels.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationChannels.java
@@ -51,12 +51,13 @@ class WebLayerNotificationChannels extends ChannelDefinitions {
* channel, remove the ID from this StringDef, remove its entry from Predefined Channels.MAP,
* and add it to the return value of {@link #getLegacyChannelIds()}.
*/
- @StringDef({ChannelId.ACTIVE_DOWNLOADS, ChannelId.COMPLETED_DOWNLOADS,
+ @StringDef({ChannelId.ACTIVE_DOWNLOADS, ChannelId.COMPLETED_DOWNLOADS, ChannelId.MEDIA_PLAYBACK,
ChannelId.WEBRTC_CAM_AND_MIC})
@Retention(RetentionPolicy.SOURCE)
public @interface ChannelId {
String ACTIVE_DOWNLOADS = "org.chromium.weblayer.active_downloads";
String COMPLETED_DOWNLOADS = "org.chromium.weblayer.completed_downloads";
+ String MEDIA_PLAYBACK = "org.chromium.weblayer.media_playback";
String WEBRTC_CAM_AND_MIC = "org.chromium.weblayer.webrtc_cam_and_mic";
}
@@ -81,6 +82,10 @@ class WebLayerNotificationChannels extends ChannelDefinitions {
PredefinedChannel.create(ChannelId.COMPLETED_DOWNLOADS,
R.string.notification_category_completed_downloads,
NotificationManager.IMPORTANCE_LOW, ChannelGroupId.WEBLAYER));
+ map.put(ChannelId.MEDIA_PLAYBACK,
+ PredefinedChannel.create(ChannelId.MEDIA_PLAYBACK,
+ R.string.notification_category_media_playback,
+ NotificationManager.IMPORTANCE_LOW, ChannelGroupId.WEBLAYER));
map.put(ChannelId.WEBRTC_CAM_AND_MIC,
PredefinedChannel.create(ChannelId.WEBRTC_CAM_AND_MIC,
R.string.notification_category_webrtc_cam_and_mic,
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerSiteSettingsClient.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerSiteSettingsClient.java
index d047261bcb4..cabbef5f87a 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerSiteSettingsClient.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerSiteSettingsClient.java
@@ -11,8 +11,6 @@ import androidx.annotation.Nullable;
import androidx.preference.Preference;
import org.chromium.base.Callback;
-import org.chromium.base.annotations.JNINamespace;
-import org.chromium.base.annotations.NativeMethods;
import org.chromium.components.browser_ui.settings.ManagedPreferenceDelegate;
import org.chromium.components.browser_ui.site_settings.SiteSettingsCategory.Type;
import org.chromium.components.browser_ui.site_settings.SiteSettingsClient;
@@ -28,7 +26,6 @@ import java.util.Set;
/**
* A SiteSettingsClient instance that contains WebLayer-specific Site Settings logic.
*/
-@JNINamespace("weblayer")
public class WebLayerSiteSettingsClient
implements SiteSettingsClient, ManagedPreferenceDelegate, SiteSettingsHelpClient,
SiteSettingsPrefClient, WebappSettingsClient {
@@ -75,8 +72,8 @@ public class WebLayerSiteSettingsClient
public boolean isCategoryVisible(@Type int type) {
return type == Type.ALL_SITES || type == Type.AUTOMATIC_DOWNLOADS || type == Type.CAMERA
|| type == Type.COOKIES || type == Type.DEVICE_LOCATION || type == Type.JAVASCRIPT
- || type == Type.MICROPHONE || type == Type.PROTECTED_MEDIA || type == Type.SOUND
- || type == Type.USE_STORAGE;
+ || type == Type.MICROPHONE || type == Type.POPUPS || type == Type.PROTECTED_MEDIA
+ || type == Type.SOUND || type == Type.USE_STORAGE;
}
@Override
@@ -122,32 +119,6 @@ public class WebLayerSiteSettingsClient
public void launchProtectedContentHelpAndFeedbackActivity(Activity currentActivity) {}
// SiteSettingsPrefClient implementation:
- // TODO(crbug.com/1071603): Once PrefServiceBridge is componentized we can get rid of the JNI
- // methods here and call PrefServiceBridge directly.
-
- @Override
- public boolean getBlockThirdPartyCookies() {
- return WebLayerSiteSettingsClientJni.get().getBlockThirdPartyCookies(mBrowserContextHandle);
- }
- @Override
- public void setBlockThirdPartyCookies(boolean newValue) {
- WebLayerSiteSettingsClientJni.get().setBlockThirdPartyCookies(
- mBrowserContextHandle, newValue);
- }
- @Override
- public boolean isBlockThirdPartyCookiesManaged() {
- // WebLayer doesn't support managed prefs.
- return false;
- }
-
- @Override
- public int getCookieControlsMode() {
- return WebLayerSiteSettingsClientJni.get().getCookieControlsMode(mBrowserContextHandle);
- }
- @Override
- public void setCookieControlsMode(int newValue) {
- WebLayerSiteSettingsClientJni.get().setCookieControlsMode(mBrowserContextHandle, newValue);
- }
// The quiet notification UI is a Chrome-specific feature for now.
@Override
@@ -187,13 +158,4 @@ public class WebLayerSiteSettingsClient
public String getNotificationDelegatePackageNameForOrigin(Origin origin) {
return null;
}
-
- @NativeMethods
- interface Natives {
- boolean getBlockThirdPartyCookies(BrowserContextHandle browserContextHandle);
- void setBlockThirdPartyCookies(BrowserContextHandle browserContextHandle, boolean newValue);
-
- int getCookieControlsMode(BrowserContextHandle browserContextHandle);
- void setCookieControlsMode(BrowserContextHandle browserContextHandle, int newValue);
- }
}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebMessageReplyProxyImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebMessageReplyProxyImpl.java
new file mode 100644
index 00000000000..d639a42cf3a
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebMessageReplyProxyImpl.java
@@ -0,0 +1,76 @@
+// Copyright 2020 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.os.RemoteException;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.weblayer_private.interfaces.APICallException;
+import org.chromium.weblayer_private.interfaces.IWebMessageCallbackClient;
+import org.chromium.weblayer_private.interfaces.IWebMessageReplyProxy;
+
+/**
+ * WebMessageReplyProxyImpl is responsible for both sending and receiving WebMessages.
+ */
+@JNINamespace("weblayer")
+public final class WebMessageReplyProxyImpl extends IWebMessageReplyProxy.Stub {
+ private long mNativeWebMessageReplyProxyImpl;
+ private final IWebMessageCallbackClient mClient;
+ // Unique id (scoped to the call to Tab.registerWebMessageCallback()) for this proxy. This is
+ // sent over AIDL.
+ private final int mId;
+
+ private WebMessageReplyProxyImpl(long nativeWebMessageReplyProxyImpl, int id,
+ IWebMessageCallbackClient client, boolean isMainFrame, String sourceOrigin) {
+ mNativeWebMessageReplyProxyImpl = nativeWebMessageReplyProxyImpl;
+ mClient = client;
+ mId = id;
+ try {
+ client.onNewReplyProxy(this, mId, isMainFrame, sourceOrigin);
+ } catch (RemoteException e) {
+ throw new APICallException(e);
+ }
+ }
+
+ @CalledByNative
+ private static WebMessageReplyProxyImpl create(long nativeWebMessageReplyProxyImpl, int id,
+ IWebMessageCallbackClient client, boolean isMainFrame, String sourceOrigin) {
+ return new WebMessageReplyProxyImpl(
+ nativeWebMessageReplyProxyImpl, id, client, isMainFrame, sourceOrigin);
+ }
+
+ @CalledByNative
+ private void onNativeDestroyed() {
+ mNativeWebMessageReplyProxyImpl = 0;
+ try {
+ mClient.onReplyProxyDestroyed(mId);
+ } catch (RemoteException e) {
+ throw new APICallException(e);
+ }
+ }
+
+ @CalledByNative
+ private void onPostMessage(String message) {
+ try {
+ mClient.onPostMessage(mId, message);
+ } catch (RemoteException e) {
+ throw new APICallException(e);
+ }
+ }
+
+ @Override
+ public void postMessage(String message) {
+ if (mNativeWebMessageReplyProxyImpl != 0) {
+ WebMessageReplyProxyImplJni.get().postMessage(mNativeWebMessageReplyProxyImpl, message);
+ }
+ }
+
+ @NativeMethods
+ interface Natives {
+ void postMessage(long nativeWebMessageReplyProxyImpl, String message);
+ }
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebShareServiceFactory.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebShareServiceFactory.java
new file mode 100644
index 00000000000..2005e53d2fd
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebShareServiceFactory.java
@@ -0,0 +1,40 @@
+// Copyright 2020 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 org.chromium.components.browser_ui.share.ShareHelper;
+import org.chromium.components.browser_ui.share.ShareParams;
+import org.chromium.components.browser_ui.webshare.ShareServiceImpl;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.services.service_manager.InterfaceFactory;
+import org.chromium.webshare.mojom.ShareService;
+
+/**
+ * Factory that creates instances of ShareService.
+ */
+public class WebShareServiceFactory implements InterfaceFactory<ShareService> {
+ private final WebContents mWebContents;
+
+ public WebShareServiceFactory(WebContents webContents) {
+ mWebContents = webContents;
+ }
+
+ @Override
+ public ShareService createImpl() {
+ ShareServiceImpl.WebShareDelegate delegate = new ShareServiceImpl.WebShareDelegate() {
+ @Override
+ public boolean canShare() {
+ return mWebContents.getTopLevelNativeWindow().getActivity() != null;
+ }
+
+ @Override
+ public void share(ShareParams params) {
+ ShareHelper.shareWithUi(params);
+ }
+ };
+
+ return new ShareServiceImpl(mWebContents, delegate);
+ }
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IBrowser.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IBrowser.aidl
index 8093e226baa..ed9a123fe9c 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IBrowser.aidl
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IBrowser.aidl
@@ -34,4 +34,6 @@ interface IBrowser {
IUrlBarController getUrlBarController() = 9;
void setBottomView(in IObjectWrapper view) = 10;
+
+ ITab createTab() = 11;
}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationController.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationController.aidl
index 3a21e9b518b..653b9a6af12 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationController.aidl
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationController.aidl
@@ -33,4 +33,7 @@ interface INavigationController {
// Added in 82, removed in 83.
// void replace(in String uri) = 12;
+
+ // Added in 85.
+ boolean isNavigationEntrySkippable(int index) = 13;
}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationControllerClient.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationControllerClient.aidl
index 60132f87c71..73432f8bd19 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationControllerClient.aidl
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationControllerClient.aidl
@@ -6,6 +6,7 @@ package org.chromium.weblayer_private.interfaces;
import org.chromium.weblayer_private.interfaces.IClientNavigation;
import org.chromium.weblayer_private.interfaces.INavigation;
+import org.chromium.weblayer_private.interfaces.IObjectWrapper;
/**
* Interface used by NavigationController to inform the client of changes. This largely duplicates
@@ -29,4 +30,7 @@ interface INavigationControllerClient {
void loadProgressChanged(double progress) = 7;
void onFirstContentfulPaint() = 8;
+
+ // Added in M85.
+ void onOldPageNoLongerRendered(in String uri) = 9;
}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IProfile.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IProfile.aidl
index 75966ad04a4..6ec60700f85 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IProfile.aidl
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IProfile.aidl
@@ -30,4 +30,10 @@ interface IProfile {
// Added in Version 84.
void setBooleanSetting(int type, boolean value) = 7;
boolean getBooleanSetting(int type) = 8;
+
+ // Added in Version 85.
+ void getBrowserPersistenceIds(in IObjectWrapper resultCallback) = 9;
+ void removeBrowserPersistenceStorage(in String[] ids,
+ in IObjectWrapper resultCallback) = 10;
+ void prepareForPossibleCrossOriginNavigation() = 11;
}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITab.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITab.aidl
index b396292d4da..c029b9a6c18 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITab.aidl
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITab.aidl
@@ -4,6 +4,8 @@
package org.chromium.weblayer_private.interfaces;
+import java.util.List;
+
import org.chromium.weblayer_private.interfaces.IDownloadCallbackClient;
import org.chromium.weblayer_private.interfaces.IErrorPageCallbackClient;
import org.chromium.weblayer_private.interfaces.IFindInPageCallbackClient;
@@ -13,6 +15,7 @@ import org.chromium.weblayer_private.interfaces.INavigationController;
import org.chromium.weblayer_private.interfaces.INavigationControllerClient;
import org.chromium.weblayer_private.interfaces.IObjectWrapper;
import org.chromium.weblayer_private.interfaces.ITabClient;
+import org.chromium.weblayer_private.interfaces.IWebMessageCallbackClient;
interface ITab {
void setClient(in ITabClient client) = 0;
@@ -51,4 +54,16 @@ interface ITab {
// Added in 84
void captureScreenShot(in float scale, in IObjectWrapper resultCallback) = 16;
+
+ // Added in 85
+ boolean setData(in Map data) = 17;
+
+ // Added in 85
+ Map getData() = 18;
+ void registerWebMessageCallback(in String jsObjectName,
+ in List<String> allowedOrigins,
+ in IWebMessageCallbackClient client) = 19;
+ void unregisterWebMessageCallback(in String jsObjectName) = 20;
+ boolean canTranslate() = 21;
+ void showTranslateUi() = 22;
}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITabClient.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITabClient.aidl
index 7313c2c8cec..12b6c4cfeda 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITabClient.aidl
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITabClient.aidl
@@ -36,4 +36,11 @@ interface ITabClient {
// Added in M84.
void onTabDestroyed() = 8;
+
+ // Added in M85.
+ void onBackgroundColorChanged(in int color) = 9;
+
+ // Added in M85
+ void onScrollNotification(
+ in int notificationType, in float currentScrollRatio) = 10;
}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayer.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayer.aidl
index cdaa4ebb3e3..7c8af14c1f6 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayer.aidl
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayer.aidl
@@ -96,4 +96,8 @@ interface IWebLayer {
ISiteSettingsFragment createSiteSettingsFragmentImpl(
in IRemoteFragmentClient remoteFragmentClient,
in IObjectWrapper fragmentArgs) = 16;
+
+ // Added in Version 85.
+ void onMediaSessionServiceStarted(in IObjectWrapper sessionService, in Intent intent) = 17;
+ void onMediaSessionServiceDestroyed() = 18;
}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayerClient.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayerClient.aidl
index 9857b597c48..e152bef181b 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayerClient.aidl
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayerClient.aidl
@@ -8,4 +8,6 @@ import android.content.Intent;
interface IWebLayerClient {
Intent createIntent() = 0;
+ Intent createMediaSessionServiceIntent() = 1;
+ int getMediaSessionNotificationId() = 2;
}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebMessageCallbackClient.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebMessageCallbackClient.aidl
new file mode 100644
index 00000000000..91ce8b87153
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebMessageCallbackClient.aidl
@@ -0,0 +1,16 @@
+// Copyright 2020 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.interfaces;
+
+import org.chromium.weblayer_private.interfaces.IWebMessageReplyProxy;
+
+interface IWebMessageCallbackClient {
+ void onNewReplyProxy(in IWebMessageReplyProxy proxy,
+ in int proxyId,
+ in boolean isMainFrame,
+ in String sourceOrigin) = 0;
+ void onPostMessage(in int proxyId, in String message) = 1;
+ void onReplyProxyDestroyed(in int proxyId) = 2;
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebMessageReplyProxy.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebMessageReplyProxy.aidl
new file mode 100644
index 00000000000..208c78d53ef
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebMessageReplyProxy.aidl
@@ -0,0 +1,9 @@
+// Copyright 2020 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.interfaces;
+
+interface IWebMessageReplyProxy {
+ void postMessage(in String message) = 0;
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ScrollNotificationType.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ScrollNotificationType.java
new file mode 100644
index 00000000000..424442ffed3
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ScrollNotificationType.java
@@ -0,0 +1,18 @@
+// Copyright 2020 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.interfaces;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@IntDef({ScrollNotificationType.DIRECTION_CHANGED_UP,
+ ScrollNotificationType.DIRECTION_CHANGED_DOWN})
+@Retention(RetentionPolicy.SOURCE)
+public @interface ScrollNotificationType {
+ int DIRECTION_CHANGED_UP = 0;
+ int DIRECTION_CHANGED_DOWN = 1;
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/SettingType.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/SettingType.java
index b6fd35e49e5..1b37b08d9b3 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/SettingType.java
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/SettingType.java
@@ -9,8 +9,13 @@ import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-@IntDef({SettingType.BASIC_SAFE_BROWSING_ENABLED})
+@IntDef({SettingType.BASIC_SAFE_BROWSING_ENABLED, SettingType.UKM_ENABLED,
+ SettingType.EXTENDED_REPORTING_SAFE_BROWSING_ENABLED,
+ SettingType.REAL_TIME_SAFE_BROWSING_ENABLED})
@Retention(RetentionPolicy.SOURCE)
public @interface SettingType {
int BASIC_SAFE_BROWSING_ENABLED = 0;
+ int UKM_ENABLED = 1;
+ int EXTENDED_REPORTING_SAFE_BROWSING_ENABLED = 2;
+ int REAL_TIME_SAFE_BROWSING_ENABLED = 3;
}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/WebLayerVersionConstants.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/WebLayerVersionConstants.java
new file mode 100644
index 00000000000..a76b13c3ba8
--- /dev/null
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/WebLayerVersionConstants.java
@@ -0,0 +1,19 @@
+// Copyright 2020 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.interfaces;
+
+/**
+ * Versioning related constants.
+ */
+public interface WebLayerVersionConstants {
+ /**
+ * Maximum allowed version skew. If the skew is greater than this, the implementation and client
+ * are not considered compatible, and WebLayer is unusable. The skew is the absolute value of
+ * the difference between the client major version and the implementation major version.
+ *
+ * @see WebLayer#isAvailable()
+ */
+ int MAX_SKEW = 4;
+}
diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/test_interfaces/ITestWebLayer.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/test_interfaces/ITestWebLayer.aidl
index 3664df20f44..ac1cd24f40f 100644
--- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/test_interfaces/ITestWebLayer.aidl
+++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/test_interfaces/ITestWebLayer.aidl
@@ -4,6 +4,9 @@
package org.chromium.weblayer_private.test_interfaces;
+import org.chromium.weblayer_private.interfaces.IObjectWrapper;
+import org.chromium.weblayer_private.interfaces.ITab;
+
interface ITestWebLayer {
// Force network connectivity state.
boolean isNetworkChangeAutoDetectOn() = 1;
@@ -19,4 +22,26 @@ interface ITestWebLayer {
// Forces the system location setting to enabled.
void setSystemLocationSettingEnabled(boolean enabled) = 6;
+
+ // See comments in TestWebLayer for details.
+ void waitForBrowserControlsMetadataState(in ITab tab,
+ in int top,
+ in int bottom,
+ in IObjectWrapper runnable) = 7;
+
+ void setAccessibilityEnabled(in boolean enabled) = 8;
+
+ boolean canBrowserControlsScroll(in ITab tab) = 9;
+
+ // Creates and shows a test infobar in |tab|, calling |runnable| when the addition (including
+ // animations) is complete.
+ void addInfoBar(in ITab tab, in IObjectWrapper runnable) = 10;
+
+ // Gets the infobar container view associated with |tab|.
+ IObjectWrapper getInfoBarContainerView(in ITab tab) = 11;
+
+ void setIgnoreMissingKeyForTranslateManager(in boolean ignore) = 12;
+ void forceNetworkConnectivityState(in boolean networkAvailable) = 13;
+
+ boolean canInfoBarContainerScroll(in ITab tab) = 14;
}