Commit 82b7a051 authored by Friedrich Horschig's avatar Friedrich Horschig Committed by Commit Bot

[Android] Move keyboard delegate into ActivityWindow

Before this CL, several places (esp. in browser/) would override the
instance of the keyboard delegate in ui/. This caused an inconsistent,
hard to track state of the KeyboardVisibilityDelegate.

With this CL, the ui/ takes control of setting the delegate and specific
browser-side implementations are called by using the appropriate factory
methods. For that, the KeyboardVisibilityDelegate (brief: KVD) uses
sublasses with very clearly split responsibilities:

KeyboardVisibilityDelegate (ui/; public interface for all clients)
<-- ActivityKVD (ui/; propagates visibility changes to listeners)
  <-- SingleWindowKVD (browser/; prevents multi-window use)
    <-- ChromeKVD (browser/ separates extensions/native)
      <-- FakeKeyboard (javatests/; mocks native keyboard)

The CL actually has no functional changes, it performs these moves:
 - WindowAndroid Listeners moved into KeyboardVisibilityDelegate
 - AcivityWindowAndroid callbacks moved into ActivityKVD
 - ProcessInitializationHandler delegate became SingleWindowKVD
 - ManualFillingTestHelper's FakeKeyboard became a new, public class

Functional changes follow with https://crrev.com/c/1286426 and mainly
affect the ChromeKVD.

Purpose of isAndroidSoftKeyboardShowing(...) and
hideAndroidSoftKeyboardKeyboard(...):
 - Template methods to simplify mocking the calls to InputMethodManager
 - Ensures access to keyboard without extensions in subclasses (e.g. Chrome KVD)
 - prevents use of @CallSuper

Change-Id: I06cf50f48625d2d883efc8cd66a3d062204e51b2
Reviewed-on: https://chromium-review.googlesource.com/c/1286649
Commit-Queue: Friedrich Horschig [EDT] <fhorschig@chromium.org>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Cr-Commit-Position: refs/heads/master@{#602293}
parent 64380b10
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser;
import android.app.Activity;
import android.content.Context;
import android.support.annotation.Nullable;
import android.view.View;
import org.chromium.chrome.browser.init.SingleWindowKeyboardVisibilityDelegate;
import java.lang.ref.WeakReference;
/**
* A {@link SingleWindowKeyboardVisibilityDelegate} that considers UI elements of a
* {@link ChromeActivity} which amend or replace the keyboard.
*/
public class ChromeKeyboardVisibilityDelegate extends SingleWindowKeyboardVisibilityDelegate {
/**
* Creates a new visibility delegate.
* @param activity A {@link WeakReference} to a {@link ChromeActivity}.
*/
public ChromeKeyboardVisibilityDelegate(WeakReference<Activity> activity) {
super(activity);
assert activity.get() instanceof ChromeActivity;
}
@Override
public @Nullable ChromeActivity getActivity() {
return (ChromeActivity) super.getActivity();
}
/**
* Hide only Android's soft keyboard. Keeps eventual keyboard replacements and extensions
* untouched. Usually, you will want to call {@link #hideKeyboard(View)}.
* @param view A focused {@link View}.
* @return True if the keyboard was visible before this call.
*/
public boolean hideSoftKeyboardOnly(View view) {
return hideAndroidSoftKeyboard(view);
}
/**
* Returns whether Android soft keyboard is showing and ignores all extensions/replacements.
* Usually, you will want to call {@link #isKeyboardShowing(Context, View)}.
* @param context A {@link Context} instance.
* @param view A {@link View}.
* @return Returns true if Android's soft keyboard is visible. Ignores extensions/replacements.
*/
public boolean isSoftKeyboardShowing(Context context, View view) {
return isAndroidSoftKeyboardShowing(context, view);
}
}
\ No newline at end of file
...@@ -7,19 +7,33 @@ package org.chromium.chrome.browser; ...@@ -7,19 +7,33 @@ package org.chromium.chrome.browser;
import android.app.Activity; import android.app.Activity;
import android.view.View; import android.view.View;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.infobar.InfoBarIdentifier; import org.chromium.chrome.browser.infobar.InfoBarIdentifier;
import org.chromium.chrome.browser.infobar.SimpleConfirmInfoBarBuilder; import org.chromium.chrome.browser.infobar.SimpleConfirmInfoBarBuilder;
import org.chromium.chrome.browser.metrics.WebApkUma; import org.chromium.chrome.browser.metrics.WebApkUma;
import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.webapps.WebApkActivity; import org.chromium.chrome.browser.webapps.WebApkActivity;
import org.chromium.ui.base.ActivityAndroidPermissionDelegate; import org.chromium.ui.base.ActivityAndroidPermissionDelegate;
import org.chromium.ui.base.ActivityKeyboardVisibilityDelegate;
import org.chromium.ui.base.ActivityWindowAndroid; import org.chromium.ui.base.ActivityWindowAndroid;
import java.lang.ref.WeakReference;
/** /**
* The window that has access to the main activity and is able to create and receive intents, * The window that has access to the main activity and is able to create and receive intents,
* and show error messages. * and show error messages.
*/ */
public class ChromeWindow extends ActivityWindowAndroid { public class ChromeWindow extends ActivityWindowAndroid {
/**
* Interface allowing to inject a different keyboard delegate for testing.
*/
@VisibleForTesting
public interface KeyboardVisibilityDelegateFactory {
ActivityKeyboardVisibilityDelegate create(WeakReference<Activity> activity);
}
private static KeyboardVisibilityDelegateFactory sKeyboardVisibilityDelegateFactory =
ChromeKeyboardVisibilityDelegate::new;
/** /**
* Creates Chrome specific ActivityWindowAndroid. * Creates Chrome specific ActivityWindowAndroid.
* @param activity The activity that owns the ChromeWindow. * @param activity The activity that owns the ChromeWindow.
...@@ -53,6 +67,11 @@ public class ChromeWindow extends ActivityWindowAndroid { ...@@ -53,6 +67,11 @@ public class ChromeWindow extends ActivityWindowAndroid {
}; };
} }
@Override
protected ActivityKeyboardVisibilityDelegate createKeyboardVisibilityDelegate() {
return sKeyboardVisibilityDelegateFactory.create(getActivity());
}
/** /**
* Shows an infobar error message overriding the WindowAndroid implementation. * Shows an infobar error message overriding the WindowAndroid implementation.
*/ */
...@@ -71,4 +90,15 @@ public class ChromeWindow extends ActivityWindowAndroid { ...@@ -71,4 +90,15 @@ public class ChromeWindow extends ActivityWindowAndroid {
super.showCallbackNonExistentError(error); super.showCallbackNonExistentError(error);
} }
} }
@VisibleForTesting
public static void setKeyboardVisibilityDelegateFactory(
KeyboardVisibilityDelegateFactory factory) {
sKeyboardVisibilityDelegateFactory = factory;
}
@VisibleForTesting
public static void resetKeyboardVisibilityDelegateFactory() {
setKeyboardVisibilityDelegateFactory(ChromeKeyboardVisibilityDelegate::new);
}
} }
...@@ -13,6 +13,7 @@ import org.chromium.base.Supplier; ...@@ -13,6 +13,7 @@ import org.chromium.base.Supplier;
import org.chromium.base.VisibleForTesting; import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.ChromeActivity; import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeFeatureList; import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.ChromeKeyboardVisibilityDelegate;
import org.chromium.chrome.browser.InsetObserverView; import org.chromium.chrome.browser.InsetObserverView;
import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Action; import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Action;
import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Provider; import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Provider;
...@@ -31,6 +32,7 @@ import org.chromium.chrome.browser.tabmodel.TabModel; ...@@ -31,6 +32,7 @@ import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelObserver; import org.chromium.chrome.browser.tabmodel.TabModelObserver;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabModelObserver; import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabModelObserver;
import org.chromium.ui.DropdownPopupWindow; import org.chromium.ui.DropdownPopupWindow;
import org.chromium.ui.KeyboardVisibilityDelegate;
import org.chromium.ui.base.WindowAndroid; import org.chromium.ui.base.WindowAndroid;
import java.util.HashMap; import java.util.HashMap;
...@@ -43,7 +45,7 @@ import java.util.Map; ...@@ -43,7 +45,7 @@ import java.util.Map;
class ManualFillingMediator class ManualFillingMediator
extends EmptyTabObserver implements KeyboardAccessoryCoordinator.VisibilityDelegate { extends EmptyTabObserver implements KeyboardAccessoryCoordinator.VisibilityDelegate {
private WindowAndroid mWindowAndroid; private WindowAndroid mWindowAndroid;
private final WindowAndroid.KeyboardVisibilityListener mVisibilityListener = private final KeyboardVisibilityDelegate.KeyboardVisibilityListener mVisibilityListener =
this::onKeyboardVisibilityChanged; this::onKeyboardVisibilityChanged;
private Supplier<InsetObserverView> mInsetObserverViewSupplier; private Supplier<InsetObserverView> mInsetObserverViewSupplier;
private boolean mShouldShow = false; private boolean mShouldShow = false;
...@@ -155,7 +157,7 @@ class ManualFillingMediator ...@@ -155,7 +157,7 @@ class ManualFillingMediator
setInsetObserverViewSupplier(mActivity::getInsetObserverView); setInsetObserverViewSupplier(mActivity::getInsetObserverView);
LayoutManager manager = getLayoutManager(); LayoutManager manager = getLayoutManager();
if (manager != null) manager.addSceneChangeObserver(mTabSwitcherObserver); if (manager != null) manager.addSceneChangeObserver(mTabSwitcherObserver);
windowAndroid.addKeyboardVisibilityListener(mVisibilityListener); windowAndroid.getKeyboardDelegate().addKeyboardVisibilityListener(mVisibilityListener);
mTabModelObserver = new TabModelSelectorTabModelObserver(mActivity.getTabModelSelector()) { mTabModelObserver = new TabModelSelectorTabModelObserver(mActivity.getTabModelSelector()) {
@Override @Override
public void didSelectTab(Tab tab, @TabModel.TabSelectionType int type, int lastId) { public void didSelectTab(Tab tab, @TabModel.TabSelectionType int type, int lastId) {
...@@ -224,7 +226,7 @@ class ManualFillingMediator ...@@ -224,7 +226,7 @@ class ManualFillingMediator
void destroy() { void destroy() {
if (!isInitialized()) return; if (!isInitialized()) return;
pause(); pause();
mWindowAndroid.removeKeyboardVisibilityListener(mVisibilityListener); getKeyboard().removeKeyboardVisibilityListener(mVisibilityListener);
LayoutManager manager = getLayoutManager(); LayoutManager manager = getLayoutManager();
if (manager != null) manager.removeSceneChangeObserver(mTabSwitcherObserver); if (manager != null) manager.removeSceneChangeObserver(mTabSwitcherObserver);
mWindowAndroid = null; mWindowAndroid = null;
...@@ -245,7 +247,7 @@ class ManualFillingMediator ...@@ -245,7 +247,7 @@ class ManualFillingMediator
pause(); pause();
ViewGroup contentView = getContentView(); ViewGroup contentView = getContentView();
if (contentView != null) { if (contentView != null) {
mWindowAndroid.getKeyboardDelegate().hideKeyboard(getContentView()); getKeyboard().hideSoftKeyboardOnly(getContentView());
} }
} }
...@@ -257,7 +259,7 @@ class ManualFillingMediator ...@@ -257,7 +259,7 @@ class ManualFillingMediator
if (!isInitialized() || !mKeyboardAccessory.hasContents() || mShouldShow) return; if (!isInitialized() || !mKeyboardAccessory.hasContents() || mShouldShow) return;
mShouldShow = true; mShouldShow = true;
ViewGroup contentView = getContentView(); ViewGroup contentView = getContentView();
if (mWindowAndroid.getKeyboardDelegate().isKeyboardShowing(mActivity, contentView)) { if (getKeyboard().isSoftKeyboardShowing(mActivity, contentView)) {
displayKeyboardAccessory(); displayKeyboardAccessory();
} }
} }
...@@ -309,14 +311,14 @@ class ManualFillingMediator ...@@ -309,14 +311,14 @@ class ManualFillingMediator
View rootView = contentView.getRootView(); View rootView = contentView.getRootView();
if (rootView == null) return; if (rootView == null) return;
mAccessorySheet.setHeight(calculateAccessorySheetHeight(rootView)); mAccessorySheet.setHeight(calculateAccessorySheetHeight(rootView));
mWindowAndroid.getKeyboardDelegate().hideKeyboard(contentView); getKeyboard().hideSoftKeyboardOnly(contentView);
} }
@Override @Override
public void onCloseAccessorySheet() { public void onCloseAccessorySheet() {
ViewGroup contentView = getContentView(); ViewGroup contentView = getContentView();
if (contentView == null || mActivity == null) return; // The tab was cleaned up already. if (contentView == null || mActivity == null) return; // The tab was cleaned up already.
if (mWindowAndroid.getKeyboardDelegate().isKeyboardShowing(mActivity, contentView)) { if (getKeyboard().isSoftKeyboardShowing(mActivity, contentView)) {
return; // If the keyboard is showing or is starting to show, the sheet closes gently. return; // If the keyboard is showing or is starting to show, the sheet closes gently.
} }
mActivity.getFullscreenManager().setBottomControlsHeight(mPreviousControlHeight); mActivity.getFullscreenManager().setBottomControlsHeight(mPreviousControlHeight);
...@@ -339,7 +341,7 @@ class ManualFillingMediator ...@@ -339,7 +341,7 @@ class ManualFillingMediator
assert mActivity != null : "ManualFillingMediator needs initialization."; assert mActivity != null : "ManualFillingMediator needs initialization.";
mKeyboardExtensionSizeManager.setKeyboardExtensionHeight(calculateAccessoryBarHeight()); mKeyboardExtensionSizeManager.setKeyboardExtensionHeight(calculateAccessoryBarHeight());
if (mActivity.getCurrentFocus() != null) { if (mActivity.getCurrentFocus() != null) {
mWindowAndroid.getKeyboardDelegate().showKeyboard(mActivity.getCurrentFocus()); getKeyboard().showKeyboard(mActivity.getCurrentFocus());
} }
} }
...@@ -386,6 +388,11 @@ class ManualFillingMediator ...@@ -386,6 +388,11 @@ class ManualFillingMediator
return compositorViewHolder.getLayoutManager(); return compositorViewHolder.getLayoutManager();
} }
private ChromeKeyboardVisibilityDelegate getKeyboard() {
KeyboardVisibilityDelegate delegate = mWindowAndroid.getKeyboardDelegate();
return (ChromeKeyboardVisibilityDelegate) delegate;
}
private AccessoryState getOrCreateAccessoryState(Tab tab) { private AccessoryState getOrCreateAccessoryState(Tab tab) {
assert tab != null : "Accessory state was requested without providing a non-null tab!"; assert tab != null : "Accessory state was requested without providing a non-null tab!";
AccessoryState state = mModel.get(tab); AccessoryState state = mModel.get(tab);
...@@ -418,7 +425,7 @@ class ManualFillingMediator ...@@ -418,7 +425,7 @@ class ManualFillingMediator
// Without known inset (which is keyboard + bottom soft keys), use the keyboard height. // Without known inset (which is keyboard + bottom soft keys), use the keyboard height.
return Math.max(mActivity.getResources().getDimensionPixelSize( return Math.max(mActivity.getResources().getDimensionPixelSize(
org.chromium.chrome.R.dimen.keyboard_accessory_suggestion_height), org.chromium.chrome.R.dimen.keyboard_accessory_suggestion_height),
mWindowAndroid.getKeyboardDelegate().calculateKeyboardHeight(mActivity, rootView)); getKeyboard().calculateKeyboardHeight(rootView));
} }
private @Px int calculateAccessoryBarHeight() { private @Px int calculateAccessoryBarHeight() {
......
...@@ -5,13 +5,11 @@ ...@@ -5,13 +5,11 @@
package org.chromium.chrome.browser.init; package org.chromium.chrome.browser.init;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Build; import android.os.Build;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.annotation.WorkerThread; import android.support.annotation.WorkerThread;
import android.view.View;
import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.InputMethodSubtype;
...@@ -55,7 +53,6 @@ import org.chromium.chrome.browser.media.MediaCaptureNotificationService; ...@@ -55,7 +53,6 @@ import org.chromium.chrome.browser.media.MediaCaptureNotificationService;
import org.chromium.chrome.browser.media.MediaViewerUtils; import org.chromium.chrome.browser.media.MediaViewerUtils;
import org.chromium.chrome.browser.metrics.LaunchMetrics; import org.chromium.chrome.browser.metrics.LaunchMetrics;
import org.chromium.chrome.browser.metrics.PackageMetrics; import org.chromium.chrome.browser.metrics.PackageMetrics;
import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings; import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
import org.chromium.chrome.browser.notifications.channels.ChannelsUpdater; import org.chromium.chrome.browser.notifications.channels.ChannelsUpdater;
import org.chromium.chrome.browser.ntp.NewTabPage; import org.chromium.chrome.browser.ntp.NewTabPage;
...@@ -82,7 +79,6 @@ import org.chromium.content_public.common.ContentSwitches; ...@@ -82,7 +79,6 @@ import org.chromium.content_public.common.ContentSwitches;
import org.chromium.printing.PrintDocumentAdapterWrapper; import org.chromium.printing.PrintDocumentAdapterWrapper;
import org.chromium.printing.PrintingControllerImpl; import org.chromium.printing.PrintingControllerImpl;
import org.chromium.ui.ContactsPickerListener; import org.chromium.ui.ContactsPickerListener;
import org.chromium.ui.KeyboardVisibilityDelegate;
import org.chromium.ui.PhotoPickerListener; import org.chromium.ui.PhotoPickerListener;
import org.chromium.ui.UiUtils; import org.chromium.ui.UiUtils;
import org.chromium.ui.base.SelectFileDialog; import org.chromium.ui.base.SelectFileDialog;
...@@ -152,24 +148,6 @@ public class ProcessInitializationHandler { ...@@ -152,24 +148,6 @@ public class ProcessInitializationHandler {
protected void handlePreNativeInitialization() { protected void handlePreNativeInitialization() {
Context application = ContextUtils.getApplicationContext(); Context application = ContextUtils.getApplicationContext();
KeyboardVisibilityDelegate.setInstance(new KeyboardVisibilityDelegate() {
@Override
public boolean isKeyboardShowing(Context context, View view) {
Activity activity = null;
if (context instanceof Activity) {
activity = (Activity) context;
} else if (view != null && view.getContext() instanceof Activity) {
activity = (Activity) view.getContext();
}
if (activity != null
&& MultiWindowUtils.getInstance().isLegacyMultiWindow(activity)) {
return false; // For multi-window mode we do not track keyboard visibility.
}
return super.isKeyboardShowing(context, view);
}
});
// Initialize the AccountManagerFacade with the correct AccountManagerDelegate. Must be done // Initialize the AccountManagerFacade with the correct AccountManagerDelegate. Must be done
// only once and before AccountMangerHelper.get(...) is called to avoid using the // only once and before AccountMangerHelper.get(...) is called to avoid using the
// default AccountManagerDelegate. // default AccountManagerDelegate.
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.init;
import android.app.Activity;
import android.content.Context;
import android.view.View;
import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
import org.chromium.ui.base.ActivityKeyboardVisibilityDelegate;
import org.chromium.ui.base.WindowAndroid;
import java.lang.ref.WeakReference;
/**
* A {@link ActivityKeyboardVisibilityDelegate} that prevents the multi-window functionality from
* triggering the layout-based keyboard detection.
*/
public class SingleWindowKeyboardVisibilityDelegate extends ActivityKeyboardVisibilityDelegate {
public SingleWindowKeyboardVisibilityDelegate(WeakReference<Activity> activity) {
super(activity);
}
@Override
public boolean isKeyboardShowing(Context context, View view) {
Activity activity = WindowAndroid.activityFromContext(context);
if (activity == null && view != null && view.getContext() instanceof Activity) {
activity = (Activity) view.getContext();
}
if (activity != null && MultiWindowUtils.getInstance().isLegacyMultiWindow(activity)) {
return false; // For multi-window mode we do not track keyboard visibility.
}
return super.isKeyboardShowing(context, view);
}
}
\ No newline at end of file
...@@ -25,6 +25,7 @@ import org.chromium.chrome.browser.WindowDelegate; ...@@ -25,6 +25,7 @@ import org.chromium.chrome.browser.WindowDelegate;
import org.chromium.chrome.browser.customtabs.CustomTabsConnection; import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
import org.chromium.chrome.browser.document.ChromeLauncherActivity; import org.chromium.chrome.browser.document.ChromeLauncherActivity;
import org.chromium.chrome.browser.init.AsyncInitializationActivity; import org.chromium.chrome.browser.init.AsyncInitializationActivity;
import org.chromium.chrome.browser.init.SingleWindowKeyboardVisibilityDelegate;
import org.chromium.chrome.browser.locale.LocaleManager; import org.chromium.chrome.browser.locale.LocaleManager;
import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteController; import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteController;
import org.chromium.chrome.browser.snackbar.SnackbarManager; import org.chromium.chrome.browser.snackbar.SnackbarManager;
...@@ -37,6 +38,7 @@ import org.chromium.chrome.browser.util.IntentUtils; ...@@ -37,6 +38,7 @@ import org.chromium.chrome.browser.util.IntentUtils;
import org.chromium.components.url_formatter.UrlFormatter; import org.chromium.components.url_formatter.UrlFormatter;
import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.common.ContentUrlConstants; import org.chromium.content_public.common.ContentUrlConstants;
import org.chromium.ui.base.ActivityKeyboardVisibilityDelegate;
import org.chromium.ui.base.ActivityWindowAndroid; import org.chromium.ui.base.ActivityWindowAndroid;
/** Queries the user's default search engine and shows autocomplete suggestions. */ /** Queries the user's default search engine and shows autocomplete suggestions. */
...@@ -115,7 +117,12 @@ public class SearchActivity extends AsyncInitializationActivity ...@@ -115,7 +117,12 @@ public class SearchActivity extends AsyncInitializationActivity
@Override @Override
protected ActivityWindowAndroid createWindowAndroid() { protected ActivityWindowAndroid createWindowAndroid() {
return new ActivityWindowAndroid(this); return new ActivityWindowAndroid(this) {
@Override
protected ActivityKeyboardVisibilityDelegate createKeyboardVisibilityDelegate() {
return new SingleWindowKeyboardVisibilityDelegate(getActivity());
}
};
} }
@Override @Override
......
...@@ -22,8 +22,8 @@ import org.chromium.chrome.browser.toolbar.ToolbarButtonSlotData.ToolbarButtonDa ...@@ -22,8 +22,8 @@ import org.chromium.chrome.browser.toolbar.ToolbarButtonSlotData.ToolbarButtonDa
import org.chromium.chrome.browser.widget.textbubble.TextBubble; import org.chromium.chrome.browser.widget.textbubble.TextBubble;
import org.chromium.components.feature_engagement.FeatureConstants; import org.chromium.components.feature_engagement.FeatureConstants;
import org.chromium.components.feature_engagement.Tracker; import org.chromium.components.feature_engagement.Tracker;
import org.chromium.ui.KeyboardVisibilityDelegate;
import org.chromium.ui.base.WindowAndroid; import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.base.WindowAndroid.KeyboardVisibilityListener;
import org.chromium.ui.resources.ResourceManager; import org.chromium.ui.resources.ResourceManager;
import org.chromium.ui.widget.ViewRectProvider; import org.chromium.ui.widget.ViewRectProvider;
...@@ -32,9 +32,9 @@ import org.chromium.ui.widget.ViewRectProvider; ...@@ -32,9 +32,9 @@ import org.chromium.ui.widget.ViewRectProvider;
* coordinators, running most of the business logic associated with the bottom toolbar, and updating * coordinators, running most of the business logic associated with the bottom toolbar, and updating
* the model accordingly. * the model accordingly.
*/ */
class BottomToolbarMediator implements FullscreenListener, KeyboardVisibilityListener, class BottomToolbarMediator
OverlayPanelManagerObserver, OverviewModeObserver, implements FullscreenListener, KeyboardVisibilityDelegate.KeyboardVisibilityListener,
SceneChangeObserver { OverlayPanelManagerObserver, OverviewModeObserver, SceneChangeObserver {
/** The amount of time to show the Duet help bubble for. */ /** The amount of time to show the Duet help bubble for. */
private static final int DUET_IPH_BUBBLE_SHOW_DURATION_MS = 6000; private static final int DUET_IPH_BUBBLE_SHOW_DURATION_MS = 6000;
...@@ -106,7 +106,9 @@ class BottomToolbarMediator implements FullscreenListener, KeyboardVisibilityLis ...@@ -106,7 +106,9 @@ class BottomToolbarMediator implements FullscreenListener, KeyboardVisibilityLis
void destroy() { void destroy() {
mFullscreenManager.removeListener(this); mFullscreenManager.removeListener(this);
if (mOverviewModeBehavior != null) mOverviewModeBehavior.removeOverviewModeObserver(this); if (mOverviewModeBehavior != null) mOverviewModeBehavior.removeOverviewModeObserver(this);
if (mWindowAndroid != null) mWindowAndroid.removeKeyboardVisibilityListener(this); if (mWindowAndroid != null) {
mWindowAndroid.getKeyboardDelegate().removeKeyboardVisibilityListener(this);
}
if (mModel.get(BottomToolbarModel.LAYOUT_MANAGER) != null) { if (mModel.get(BottomToolbarModel.LAYOUT_MANAGER) != null) {
LayoutManager manager = mModel.get(BottomToolbarModel.LAYOUT_MANAGER); LayoutManager manager = mModel.get(BottomToolbarModel.LAYOUT_MANAGER);
manager.getOverlayPanelManager().removeObserver(this); manager.getOverlayPanelManager().removeObserver(this);
...@@ -229,7 +231,7 @@ class BottomToolbarMediator implements FullscreenListener, KeyboardVisibilityLis ...@@ -229,7 +231,7 @@ class BottomToolbarMediator implements FullscreenListener, KeyboardVisibilityLis
assert mWindowAndroid == null : "#setWindowAndroid should only be called once per toolbar."; assert mWindowAndroid == null : "#setWindowAndroid should only be called once per toolbar.";
// Watch for keyboard events so we can hide the bottom toolbar when the keyboard is showing. // Watch for keyboard events so we can hide the bottom toolbar when the keyboard is showing.
mWindowAndroid = windowAndroid; mWindowAndroid = windowAndroid;
mWindowAndroid.addKeyboardVisibilityListener(this); mWindowAndroid.getKeyboardDelegate().addKeyboardVisibilityListener(this);
} }
void setTabSwitcherButtonData( void setTabSwitcherButtonData(
......
...@@ -31,6 +31,7 @@ chrome_java_sources = [ ...@@ -31,6 +31,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/ChromeBackupWatcher.java", "java/src/org/chromium/chrome/browser/ChromeBackupWatcher.java",
"java/src/org/chromium/chrome/browser/ChromeFeatureList.java", "java/src/org/chromium/chrome/browser/ChromeFeatureList.java",
"java/src/org/chromium/chrome/browser/ChromeHttpAuthHandler.java", "java/src/org/chromium/chrome/browser/ChromeHttpAuthHandler.java",
"java/src/org/chromium/chrome/browser/ChromeKeyboardVisibilityDelegate.java",
"java/src/org/chromium/chrome/browser/ChromeStrictMode.java", "java/src/org/chromium/chrome/browser/ChromeStrictMode.java",
"java/src/org/chromium/chrome/browser/ChromeStringConstants.java", "java/src/org/chromium/chrome/browser/ChromeStringConstants.java",
"java/src/org/chromium/chrome/browser/ChromeSwitches.java", "java/src/org/chromium/chrome/browser/ChromeSwitches.java",
...@@ -770,6 +771,7 @@ chrome_java_sources = [ ...@@ -770,6 +771,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/init/NativeInitializationController.java", "java/src/org/chromium/chrome/browser/init/NativeInitializationController.java",
"java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java", "java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java",
"java/src/org/chromium/chrome/browser/init/ServiceManagerStartupUtils.java", "java/src/org/chromium/chrome/browser/init/ServiceManagerStartupUtils.java",
"java/src/org/chromium/chrome/browser/init/SingleWindowKeyboardVisibilityDelegate.java",
"java/src/org/chromium/chrome/browser/installedapp/InstalledAppProviderFactory.java", "java/src/org/chromium/chrome/browser/installedapp/InstalledAppProviderFactory.java",
"java/src/org/chromium/chrome/browser/installedapp/InstalledAppProviderImpl.java", "java/src/org/chromium/chrome/browser/installedapp/InstalledAppProviderImpl.java",
"java/src/org/chromium/chrome/browser/installedapp/PackageHash.java", "java/src/org/chromium/chrome/browser/installedapp/PackageHash.java",
...@@ -1793,6 +1795,7 @@ chrome_test_java_sources = [ ...@@ -1793,6 +1795,7 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/autofill/AutofillTestHelper.java", "javatests/src/org/chromium/chrome/browser/autofill/AutofillTestHelper.java",
"javatests/src/org/chromium/chrome/browser/autofill/PersonalDataManagerTest.java", "javatests/src/org/chromium/chrome/browser/autofill/PersonalDataManagerTest.java",
"javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetViewTest.java", "javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetViewTest.java",
"javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/FakeKeyboard.java",
"javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryViewTest.java", "javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryViewTest.java",
"javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingIntegrationTest.java", "javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingIntegrationTest.java",
"javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingTestHelper.java", "javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingTestHelper.java",
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.autofill.keyboard_accessory;
import static org.chromium.base.ThreadUtils.runOnUiThreadBlocking;
import android.app.Activity;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import org.chromium.chrome.browser.ChromeKeyboardVisibilityDelegate;
import org.chromium.chrome.browser.ChromeWindow;
import org.chromium.chrome.browser.InsetObserverView;
import java.lang.ref.WeakReference;
/**
* This class allows to mock the {@link org.chromium.ui.KeyboardVisibilityDelegate} in any given
* {@link org.chromium.chrome.test.ChromeActivityTestRule} which allows to write tests relying on
* keyboard without having to deal with the soft keyboard. To use it, inject its constructor as
* factory into the {@link org.chromium.chrome.browser.ChromeWindow} before launching an activity.
* To reset, call {@link ChromeWindow#resetKeyboardVisibilityDelegateFactory()}.
* <pre>E.g.{@code
* // To force a keyboard open.
* ChromeWindow.setKeyboardVisibilityDelegateFactory(FakeKeyboard::new);
* aTestRule.startMainActivityOnBlankPage();
* ChromeWindow.resetKeyboardVisibilityDelegateFactory();
* aTestRule.getKeyboardDelegate().showKeyboard(); // No delay/waiting necessary.
* }</pre>
*/
public class FakeKeyboard extends ChromeKeyboardVisibilityDelegate {
private static final int KEYBOARD_HEIGHT_DP = 234;
private boolean mIsShowing;
public FakeKeyboard(WeakReference<Activity> activity) {
super(activity);
}
private int getStaticKeyboardHeight() {
return (int) getActivity().getResources().getDisplayMetrics().density * KEYBOARD_HEIGHT_DP;
}
@Override
protected boolean isAndroidSoftKeyboardShowing(Context context, View view) {
return mIsShowing;
}
@Override
public void showKeyboard(View view) {
boolean keyboardWasVisible = isKeyboardShowing(getActivity(), view);
mIsShowing = true;
runOnUiThreadBlocking(() -> {
if (!keyboardWasVisible) notifyListeners(mIsShowing);
// Pretend a layout change for components listening to the activity directly:
View contentView = getActivity().findViewById(android.R.id.content);
ViewGroup.LayoutParams p = contentView.getLayoutParams();
p.height = p.height - getStaticKeyboardHeight();
contentView.setLayoutParams(p);
});
}
@Override
protected boolean hideAndroidSoftKeyboard(View view) {
boolean keyboardWasVisible = isKeyboardShowing(getActivity(), view);
mIsShowing = false;
runOnUiThreadBlocking(() -> {
if (keyboardWasVisible) notifyListeners(mIsShowing);
// Pretend a layout change for components listening to the activity directly:
View contentView = getActivity().findViewById(android.R.id.content);
ViewGroup.LayoutParams p = contentView.getLayoutParams();
p.height = p.height + getStaticKeyboardHeight();
contentView.setLayoutParams(p);
});
return keyboardWasVisible;
}
@Override
public int calculateKeyboardHeight(View rootView) {
return mIsShowing ? getStaticKeyboardHeight() : 0;
}
@Override
public int calculateKeyboardDetectionThreshold(Context context, View rootView) {
return 0;
}
/**
* Creates an inset observer view calculating the bottom inset based on the fake keyboard.
* @param context Context used to instantiate this view.
* @return a {@link InsetObserverView}
*/
InsetObserverView createInsetObserver(Context context) {
return new InsetObserverView(context) {
@Override
public int getSystemWindowInsetsBottom() {
return mIsShowing ? getStaticKeyboardHeight() : 0;
}
};
}
}
...@@ -383,7 +383,7 @@ public class ManualFillingIntegrationTest { ...@@ -383,7 +383,7 @@ public class ManualFillingIntegrationTest {
@Test @Test
@SmallTest @SmallTest
public void testInfobarStaysHiddenWhenOpeningSheet() public void testInfobarStaysHiddenWhenOpeningSheet()
throws InterruptedException, TimeoutException { throws InterruptedException, TimeoutException, ExecutionException {
mHelper.loadTestPage(false); mHelper.loadTestPage(false);
InfoBarTestAnimationListener listener = new InfoBarTestAnimationListener(); InfoBarTestAnimationListener listener = new InfoBarTestAnimationListener();
...@@ -413,7 +413,11 @@ public class ManualFillingIntegrationTest { ...@@ -413,7 +413,11 @@ public class ManualFillingIntegrationTest {
// Reopen the keyboard, then close it. // Reopen the keyboard, then close it.
whenDisplayed(withId(R.id.tabs)).perform(selectTabAtPosition(0)); whenDisplayed(withId(R.id.tabs)).perform(selectTabAtPosition(0));
mHelper.waitForKeyboard(); mHelper.waitForKeyboard();
mActivityTestRule.getKeyboardDelegate().hideKeyboard(null); ThreadUtils.runOnUiThreadBlocking(() -> {
mActivityTestRule.getKeyboardDelegate().hideKeyboard(
mActivityTestRule.getActivity().getCurrentFocus());
mActivityTestRule.getInfoBarContainer().requestLayout();
});
mHelper.waitToBeHidden(withId(R.id.keyboard_accessory_sheet)); mHelper.waitToBeHidden(withId(R.id.keyboard_accessory_sheet));
mHelper.waitToBeHidden(withId(R.id.keyboard_accessory)); mHelper.waitToBeHidden(withId(R.id.keyboard_accessory));
......
...@@ -17,9 +17,8 @@ import static org.chromium.chrome.test.util.ViewUtils.VIEW_NULL; ...@@ -17,9 +17,8 @@ import static org.chromium.chrome.test.util.ViewUtils.VIEW_NULL;
import static org.chromium.chrome.test.util.ViewUtils.waitForView; import static org.chromium.chrome.test.util.ViewUtils.waitForView;
import static org.chromium.ui.base.LocalizationUtils.setRtlForTesting; import static org.chromium.ui.base.LocalizationUtils.setRtlForTesting;
import android.content.Context; import android.app.Activity;
import android.support.design.widget.TabLayout; import android.support.design.widget.TabLayout;
import android.support.test.InstrumentationRegistry;
import android.support.test.espresso.PerformException; import android.support.test.espresso.PerformException;
import android.support.test.espresso.UiController; import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction; import android.support.test.espresso.ViewAction;
...@@ -34,7 +33,7 @@ import org.chromium.base.ThreadUtils; ...@@ -34,7 +33,7 @@ import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.UrlUtils; import org.chromium.base.test.util.UrlUtils;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeTabbedActivity; import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.InsetObserverView; import org.chromium.chrome.browser.ChromeWindow;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule; import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.content_public.browser.ImeAdapter; import org.chromium.content_public.browser.ImeAdapter;
import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.browser.WebContents;
...@@ -43,7 +42,6 @@ import org.chromium.content_public.browser.test.util.CriteriaHelper; ...@@ -43,7 +42,6 @@ import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.content_public.browser.test.util.DOMUtils; import org.chromium.content_public.browser.test.util.DOMUtils;
import org.chromium.content_public.browser.test.util.TestInputMethodManagerWrapper; import org.chromium.content_public.browser.test.util.TestInputMethodManagerWrapper;
import org.chromium.ui.DropdownPopupWindowInterface; import org.chromium.ui.DropdownPopupWindowInterface;
import org.chromium.ui.KeyboardVisibilityDelegate;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
...@@ -57,66 +55,16 @@ public class ManualFillingTestHelper { ...@@ -57,66 +55,16 @@ public class ManualFillingTestHelper {
private final AtomicReference<WebContents> mWebContentsRef = new AtomicReference<>(); private final AtomicReference<WebContents> mWebContentsRef = new AtomicReference<>();
private TestInputMethodManagerWrapper mInputMethodManagerWrapper; private TestInputMethodManagerWrapper mInputMethodManagerWrapper;
private class FakeKeyboard extends KeyboardVisibilityDelegate { public FakeKeyboard getKeyboard() {
static final int KEYBOARD_HEIGHT = 234; return (FakeKeyboard) mActivityTestRule.getKeyboardDelegate();
private boolean mIsShowing;
@Override
public void showKeyboard(View view) {
mIsShowing = true;
ThreadUtils.runOnUiThreadBlocking(() -> {
mActivityTestRule.getActivity()
.getManualFillingController()
.getMediatorForTesting()
.onKeyboardVisibilityChanged(mIsShowing);
});
}
@Override
public boolean hideKeyboard(View view) {
boolean keyboardWasVisible = mIsShowing;
mIsShowing = false;
ThreadUtils.runOnUiThreadBlocking(() -> {
mActivityTestRule.getActivity()
.getManualFillingController()
.getMediatorForTesting()
.onKeyboardVisibilityChanged(mIsShowing);
});
return keyboardWasVisible;
}
@Override
public int calculateKeyboardHeight(Context context, View rootView) {
return mIsShowing ? KEYBOARD_HEIGHT : 0;
} }
@Override
protected int calculateKeyboardDetectionThreshold(Context context, View rootView) {
return 0;
}
/**
* Creates an inset observer view calculating the bottom inset based on the fake keyboard.
* @param context Context used to instantiate this view.
* @return a {@link InsetObserverView}
*/
InsetObserverView createInsetObserver(Context context) {
return new InsetObserverView(context) {
@Override
public int getSystemWindowInsetsBottom() {
return mIsShowing ? KEYBOARD_HEIGHT : 0;
}
};
}
}
private final FakeKeyboard mKeyboard = new FakeKeyboard();
ManualFillingTestHelper(ChromeTabbedActivityTestRule activityTestRule) { ManualFillingTestHelper(ChromeTabbedActivityTestRule activityTestRule) {
mActivityTestRule = activityTestRule; mActivityTestRule = activityTestRule;
} }
public void loadTestPage(boolean isRtl) throws InterruptedException { public void loadTestPage(boolean isRtl) throws InterruptedException {
ChromeWindow.setKeyboardVisibilityDelegateFactory(FakeKeyboard::new);
mActivityTestRule.startMainActivityWithURL(UrlUtils.encodeHtmlDataUri("<html" mActivityTestRule.startMainActivityWithURL(UrlUtils.encodeHtmlDataUri("<html"
+ (isRtl ? " dir=\"rtl\"" : "") + "><head>" + (isRtl ? " dir=\"rtl\"" : "") + "><head>"
+ "<meta name=\"viewport\"" + "<meta name=\"viewport\""
...@@ -128,14 +76,14 @@ public class ManualFillingTestHelper { ...@@ -128,14 +76,14 @@ public class ManualFillingTestHelper {
+ "</form></body></html>")); + "</form></body></html>"));
setRtlForTesting(isRtl); setRtlForTesting(isRtl);
ThreadUtils.runOnUiThreadBlocking(() -> { ThreadUtils.runOnUiThreadBlocking(() -> {
ChromeTabbedActivity activity = mActivityTestRule.getActivity(); ChromeTabbedActivity activity = (ChromeTabbedActivity) mActivityTestRule.getActivity();
mWebContentsRef.set(activity.getActivityTab().getWebContents()); mWebContentsRef.set(activity.getActivityTab().getWebContents());
activity.getManualFillingController() activity.getManualFillingController()
.getMediatorForTesting() .getMediatorForTesting()
.setInsetObserverViewSupplier(() -> { .setInsetObserverViewSupplier(
return mKeyboard.createInsetObserver( ()
activity.getInsetObserverView().getContext()); -> getKeyboard().createInsetObserver(
}); activity.getInsetObserverView().getContext()));
// The TestInputMethodManagerWrapper intercepts showSoftInput so that a keyboard is // The TestInputMethodManagerWrapper intercepts showSoftInput so that a keyboard is
// never brought up. // never brought up.
final ImeAdapter imeAdapter = ImeAdapter.fromWebContents(mWebContentsRef.get()); final ImeAdapter imeAdapter = ImeAdapter.fromWebContents(mWebContentsRef.get());
...@@ -143,32 +91,31 @@ public class ManualFillingTestHelper { ...@@ -143,32 +91,31 @@ public class ManualFillingTestHelper {
imeAdapter.setInputMethodManagerWrapper(mInputMethodManagerWrapper); imeAdapter.setInputMethodManagerWrapper(mInputMethodManagerWrapper);
}); });
DOMUtils.waitForNonZeroNodeBounds(mWebContentsRef.get(), "password"); DOMUtils.waitForNonZeroNodeBounds(mWebContentsRef.get(), "password");
KeyboardVisibilityDelegate.setDelegateForTesting(mKeyboard); // Use a fake keyboard.
} }
public void clear() { public void clear() {
KeyboardVisibilityDelegate.clearDelegateForTesting(); ChromeWindow.resetKeyboardVisibilityDelegateFactory();
} }
public void waitForKeyboard() { public void waitForKeyboard() {
CriteriaHelper.pollUiThread(() -> { CriteriaHelper.pollUiThread(() -> {
return mActivityTestRule.getKeyboardDelegate().isKeyboardShowing( Activity activity = mActivityTestRule.getActivity();
InstrumentationRegistry.getContext(), return getKeyboard().isAndroidSoftKeyboardShowing(activity, activity.getCurrentFocus());
mActivityTestRule.getActivity().getCurrentFocus());
}); });
} }
public void waitForKeyboardToDisappear() { public void waitForKeyboardToDisappear() {
CriteriaHelper.pollUiThread( CriteriaHelper.pollUiThread(() -> {
() Activity activity = mActivityTestRule.getActivity();
-> !KeyboardVisibilityDelegate.getInstance().isKeyboardShowing( return !getKeyboard().isAndroidSoftKeyboardShowing(
InstrumentationRegistry.getContext(), activity, activity.getCurrentFocus());
mActivityTestRule.getActivity().getCurrentFocus())); });
} }
public void clickPasswordField() throws TimeoutException, InterruptedException { public void clickPasswordField() throws TimeoutException, InterruptedException {
DOMUtils.clickNode(mWebContentsRef.get(), "password"); DOMUtils.clickNode(mWebContentsRef.get(), "password");
requestShowKeyboardAccessory(); requestShowKeyboardAccessory();
mKeyboard.showKeyboard(null); getKeyboard().showKeyboard(mActivityTestRule.getActivity().getCurrentFocus());
} }
public void clickEmailField(boolean forceAccessory) public void clickEmailField(boolean forceAccessory)
...@@ -179,7 +126,7 @@ public class ManualFillingTestHelper { ...@@ -179,7 +126,7 @@ public class ManualFillingTestHelper {
} else { } else {
requestHideKeyboardAccessory(); requestHideKeyboardAccessory();
} }
mKeyboard.showKeyboard(null); getKeyboard().showKeyboard(mActivityTestRule.getActivity().getCurrentFocus());
} }
public DropdownPopupWindowInterface waitForAutofillPopup(String filterInput) public DropdownPopupWindowInterface waitForAutofillPopup(String filterInput)
...@@ -222,7 +169,7 @@ public class ManualFillingTestHelper { ...@@ -222,7 +169,7 @@ public class ManualFillingTestHelper {
*/ */
public void clickSubmit() throws TimeoutException, InterruptedException { public void clickSubmit() throws TimeoutException, InterruptedException {
DOMUtils.clickNode(mWebContentsRef.get(), "submit"); DOMUtils.clickNode(mWebContentsRef.get(), "submit");
mKeyboard.hideKeyboard(null); getKeyboard().hideAndroidSoftKeyboard(null);
} }
/** /**
...@@ -264,7 +211,7 @@ public class ManualFillingTestHelper { ...@@ -264,7 +211,7 @@ public class ManualFillingTestHelper {
.withCause(new Throwable("No tab at index " + tabIndex)) .withCause(new Throwable("No tab at index " + tabIndex))
.build(); .build();
} }
tabLayout.getTabAt(tabIndex).select(); ThreadUtils.runOnUiThread(() -> tabLayout.getTabAt(tabIndex).select());
} }
}; };
} }
......
...@@ -55,6 +55,7 @@ import org.chromium.chrome.test.util.browser.Features; ...@@ -55,6 +55,7 @@ import org.chromium.chrome.test.util.browser.Features;
import org.chromium.chrome.test.util.browser.Features.DisableFeatures; import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
import org.chromium.chrome.test.util.browser.Features.EnableFeatures; import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
import org.chromium.chrome.test.util.browser.modelutil.FakeViewProvider; import org.chromium.chrome.test.util.browser.modelutil.FakeViewProvider;
import org.chromium.ui.KeyboardVisibilityDelegate;
import org.chromium.ui.base.WindowAndroid; import org.chromium.ui.base.WindowAndroid;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
...@@ -71,6 +72,8 @@ public class ManualFillingControllerTest { ...@@ -71,6 +72,8 @@ public class ManualFillingControllerTest {
@Mock @Mock
private WindowAndroid mMockWindow; private WindowAndroid mMockWindow;
@Mock @Mock
private KeyboardVisibilityDelegate mMockKeyboard;
@Mock
private ChromeActivity mMockActivity; private ChromeActivity mMockActivity;
@Mock @Mock
private KeyboardAccessoryView mMockKeyboardAccessoryView; private KeyboardAccessoryView mMockKeyboardAccessoryView;
...@@ -99,6 +102,7 @@ public class ManualFillingControllerTest { ...@@ -99,6 +102,7 @@ public class ManualFillingControllerTest {
ShadowRecordHistogram.reset(); ShadowRecordHistogram.reset();
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
when(mMockWindow.getActivity()).thenReturn(new WeakReference<>(mMockActivity)); when(mMockWindow.getActivity()).thenReturn(new WeakReference<>(mMockActivity));
when(mMockWindow.getKeyboardDelegate()).thenReturn(mMockKeyboard);
when(mMockActivity.getTabModelSelector()).thenReturn(mMockTabModelSelector); when(mMockActivity.getTabModelSelector()).thenReturn(mMockTabModelSelector);
ChromeFullscreenManager fullscreenManager = new ChromeFullscreenManager(mMockActivity, 0); ChromeFullscreenManager fullscreenManager = new ChromeFullscreenManager(mMockActivity, 0);
when(mMockActivity.getFullscreenManager()).thenReturn(fullscreenManager); when(mMockActivity.getFullscreenManager()).thenReturn(fullscreenManager);
......
...@@ -260,6 +260,7 @@ android_library("ui_full_java") { ...@@ -260,6 +260,7 @@ android_library("ui_full_java") {
"java/src/org/chromium/ui/ViewProvider.java", "java/src/org/chromium/ui/ViewProvider.java",
"java/src/org/chromium/ui/VSyncMonitor.java", "java/src/org/chromium/ui/VSyncMonitor.java",
"java/src/org/chromium/ui/base/ActivityAndroidPermissionDelegate.java", "java/src/org/chromium/ui/base/ActivityAndroidPermissionDelegate.java",
"java/src/org/chromium/ui/base/ActivityKeyboardVisibilityDelegate.java",
"java/src/org/chromium/ui/base/ActivityWindowAndroid.java", "java/src/org/chromium/ui/base/ActivityWindowAndroid.java",
"java/src/org/chromium/ui/base/AndroidPermissionDelegate.java", "java/src/org/chromium/ui/base/AndroidPermissionDelegate.java",
"java/src/org/chromium/ui/base/Clipboard.java", "java/src/org/chromium/ui/base/Clipboard.java",
......
...@@ -15,7 +15,7 @@ import android.view.WindowInsets; ...@@ -15,7 +15,7 @@ import android.view.WindowInsets;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import org.chromium.base.Log; import org.chromium.base.Log;
import org.chromium.base.VisibleForTesting; import org.chromium.base.ObserverList;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
...@@ -38,8 +38,21 @@ public class KeyboardVisibilityDelegate { ...@@ -38,8 +38,21 @@ public class KeyboardVisibilityDelegate {
/** The delegate to determine keyboard visibility. */ /** The delegate to determine keyboard visibility. */
private static KeyboardVisibilityDelegate sInstance = new KeyboardVisibilityDelegate(); private static KeyboardVisibilityDelegate sInstance = new KeyboardVisibilityDelegate();
/** Set this delegate to replace the keyboard in tests. */ /**
private static KeyboardVisibilityDelegate sTestingInstance; * An interface to notify listeners of changes in the soft keyboard's visibility.
*/
public interface KeyboardVisibilityListener {
/**
* Called whenever the keyboard might have changed.
* @param isShowing A boolean that's true if the keyboard is now visible.
*/
void keyboardVisibilityChanged(boolean isShowing);
}
private final ObserverList<KeyboardVisibilityListener> mKeyboardVisibilityListeners =
new ObserverList<>();
protected void registerKeyboardVisibilityCallbacks() {}
protected void unregisterKeyboardVisibilityCallbacks() {}
/** /**
* Allows setting a new strategy to override the default {@link KeyboardVisibilityDelegate}. * Allows setting a new strategy to override the default {@link KeyboardVisibilityDelegate}.
...@@ -52,31 +65,12 @@ public class KeyboardVisibilityDelegate { ...@@ -52,31 +65,12 @@ public class KeyboardVisibilityDelegate {
sInstance = delegate; sInstance = delegate;
} }
/**
* Setting a test instance of the visibility delegate that won't affect the default instance.
*
* @param delegate A {@link KeyboardVisibilityDelegate} instance.
*/
@VisibleForTesting
public static void setDelegateForTesting(KeyboardVisibilityDelegate delegate) {
sTestingInstance = delegate;
}
/**
* Clears any previously set test instance.
*/
@VisibleForTesting
public static void clearDelegateForTesting() {
sTestingInstance = null;
}
/** /**
* Prefer using {@link org.chromium.ui.base.WindowAndroid#getKeyboardDelegate()} over this * Prefer using {@link org.chromium.ui.base.WindowAndroid#getKeyboardDelegate()} over this
* method. Both return a delegate which allows checking and influencing the keyboard state. * method. Both return a delegate which allows checking and influencing the keyboard state.
* @return the global {@link KeyboardVisibilityDelegate}. * @return the global {@link KeyboardVisibilityDelegate}.
*/ */
public static KeyboardVisibilityDelegate getInstance() { public static KeyboardVisibilityDelegate getInstance() {
if (sTestingInstance != null) return sTestingInstance;
return sInstance; return sInstance;
} }
...@@ -121,11 +115,21 @@ public class KeyboardVisibilityDelegate { ...@@ -121,11 +115,21 @@ public class KeyboardVisibilityDelegate {
} }
/** /**
* Hides the soft keyboard by using the {@link Context#INPUT_METHOD_SERVICE}. * Hides the soft keyboard.
* @param view The {@link View} that is currently accepting input. * @param view The {@link View} that is currently accepting input.
* @return Whether the keyboard was visible before. * @return Whether the keyboard was visible before.
*/ */
public boolean hideKeyboard(View view) { public boolean hideKeyboard(View view) {
return hideAndroidSoftKeyboard(view);
}
/**
* Hides the soft keyboard by using the {@link Context#INPUT_METHOD_SERVICE}.
* This template method simplifies mocking and the access to the soft keyboard in subclasses.
* @param view The {@link View} that is currently accepting input.
* @return Whether the keyboard was visible before.
*/
protected boolean hideAndroidSoftKeyboard(View view) {
InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService( InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(
Context.INPUT_METHOD_SERVICE); Context.INPUT_METHOD_SERVICE);
return imm.hideSoftInputFromWindow(view.getWindowToken(), 0); return imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
...@@ -134,11 +138,10 @@ public class KeyboardVisibilityDelegate { ...@@ -134,11 +138,10 @@ public class KeyboardVisibilityDelegate {
/** /**
* Calculates the keyboard height based on the bottom margin it causes for the given * Calculates the keyboard height based on the bottom margin it causes for the given
* rootView. It is used to determine whether the keyboard is visible. * rootView. It is used to determine whether the keyboard is visible.
* @param context A {@link Context} instance.
* @param rootView A {@link View}. * @param rootView A {@link View}.
* @return The size of the bottom margin which most likely is exactly the keyboard size. * @return The size of the bottom margin which most likely is exactly the keyboard size.
*/ */
public int calculateKeyboardHeight(Context context, View rootView) { public int calculateKeyboardHeight(View rootView) {
Rect appRect = new Rect(); Rect appRect = new Rect();
rootView.getWindowVisibleDisplayFrame(appRect); rootView.getWindowVisibleDisplayFrame(appRect);
...@@ -185,17 +188,61 @@ public class KeyboardVisibilityDelegate { ...@@ -185,17 +188,61 @@ public class KeyboardVisibilityDelegate {
return (int) (KEYBOARD_DETECT_BOTTOM_THRESHOLD_DP * density); return (int) (KEYBOARD_DETECT_BOTTOM_THRESHOLD_DP * density);
} }
/**
* Returns whether the keyboard is showing.
* @param context A {@link Context} instance.
* @param view A {@link View}.
* @return Whether or not the software keyboard is visible.
*/
public boolean isKeyboardShowing(Context context, View view) {
return isAndroidSoftKeyboardShowing(context, view);
}
/** /**
* Detects whether or not the keyboard is showing. This is a best guess based on the height * Detects whether or not the keyboard is showing. This is a best guess based on the height
* of the keyboard as there is no standardized/foolproof way to do this. * of the keyboard as there is no standardized/foolproof way to do this.
* This template method simplifies mocking and the access to the soft keyboard in subclasses.
* @param context A {@link Context} instance. * @param context A {@link Context} instance.
* @param view A {@link View}. * @param view A {@link View}.
* @return Whether or not the software keyboard is visible. * @return Whether or not the software keyboard is visible.
*/ */
public boolean isKeyboardShowing(Context context, View view) { protected boolean isAndroidSoftKeyboardShowing(Context context, View view) {
View rootView = view.getRootView(); View rootView = view.getRootView();
return rootView != null return rootView != null
&& calculateKeyboardHeight(context, rootView) && calculateKeyboardHeight(rootView)
> calculateKeyboardDetectionThreshold(context, rootView); > calculateKeyboardDetectionThreshold(context, rootView);
} }
/**
* To be called when the keyboard visibility state might have changed. Informs listeners of the
* state change IFF there actually was a change.
* @param isShowing The current (guesstimated) state of the keyboard.
*/
protected void notifyListeners(boolean isShowing) {
for (KeyboardVisibilityListener listener : mKeyboardVisibilityListeners) {
listener.keyboardVisibilityChanged(isShowing);
}
}
/**
* Adds a listener that is updated of keyboard visibility changes. This works as a best guess.
*
* @see org.chromium.ui.KeyboardVisibilityDelegate#isKeyboardShowing(Context, View)
*/
public void addKeyboardVisibilityListener(KeyboardVisibilityListener listener) {
if (mKeyboardVisibilityListeners.isEmpty()) {
registerKeyboardVisibilityCallbacks();
}
mKeyboardVisibilityListeners.addObserver(listener);
}
/**
* @see #addKeyboardVisibilityListener(KeyboardVisibilityListener)
*/
public void removeKeyboardVisibilityListener(KeyboardVisibilityListener listener) {
mKeyboardVisibilityListeners.removeObserver(listener);
if (mKeyboardVisibilityListeners.isEmpty()) {
unregisterKeyboardVisibilityCallbacks();
}
}
} }
// Copyright 2018 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.ui.base;
import android.app.Activity;
import android.support.annotation.Nullable;
import android.view.View;
import org.chromium.ui.KeyboardVisibilityDelegate;
import java.lang.ref.WeakReference;
/**
* A {@link KeyboardVisibilityDelegate} that listens to a given activity for layout changes. It
* notifies {@link KeyboardVisibilityDelegate.KeyboardVisibilityListener} whenever the layout change
* is suspected to be caused by a keyboard.
*/
public class ActivityKeyboardVisibilityDelegate
extends KeyboardVisibilityDelegate implements View.OnLayoutChangeListener {
private boolean mIsKeyboardShowing;
private WeakReference<Activity> mActivity;
/**
* Creates a new delegate listening to the given activity. If the activity is destroyed, it will
* continue to work as a regular {@link KeyboardVisibilityDelegate}.
* @param activity A {@link WeakReference} to an {@link Activity}.
*/
public ActivityKeyboardVisibilityDelegate(WeakReference<Activity> activity) {
mActivity = activity;
}
public @Nullable Activity getActivity() {
return mActivity.get();
}
@Override
public void registerKeyboardVisibilityCallbacks() {
Activity activity = getActivity();
if (activity == null) return;
View content = activity.findViewById(android.R.id.content);
mIsKeyboardShowing = isKeyboardShowing(activity, content);
content.addOnLayoutChangeListener(this);
}
@Override
public void unregisterKeyboardVisibilityCallbacks() {
Activity activity = getActivity();
if (activity == null) return;
activity.findViewById(android.R.id.content).removeOnLayoutChangeListener(this);
}
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
Activity activity = getActivity();
if (activity == null) return;
boolean isShowing = isKeyboardShowing(activity, v);
if (mIsKeyboardShowing == isShowing) return;
mIsKeyboardShowing = isShowing;
notifyListeners(isShowing);
}
}
\ No newline at end of file
...@@ -10,7 +10,6 @@ import android.content.ActivityNotFoundException; ...@@ -10,7 +10,6 @@ import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentSender.SendIntentException; import android.content.IntentSender.SendIntentException;
import android.view.View;
import org.chromium.base.ActivityState; import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus; import org.chromium.base.ApplicationStatus;
...@@ -25,8 +24,7 @@ import java.lang.ref.WeakReference; ...@@ -25,8 +24,7 @@ import java.lang.ref.WeakReference;
* Only instantiate this class when you need the implemented features. * Only instantiate this class when you need the implemented features.
*/ */
public class ActivityWindowAndroid public class ActivityWindowAndroid
extends WindowAndroid extends WindowAndroid implements ApplicationStatus.ActivityStateListener {
implements ApplicationStatus.ActivityStateListener, View.OnLayoutChangeListener {
// Constants used for intent request code bounding. // Constants used for intent request code bounding.
private static final int REQUEST_CODE_PREFIX = 1000; private static final int REQUEST_CODE_PREFIX = 1000;
private static final int REQUEST_CODE_RANGE_SIZE = 100; private static final int REQUEST_CODE_RANGE_SIZE = 100;
...@@ -61,6 +59,7 @@ public class ActivityWindowAndroid ...@@ -61,6 +59,7 @@ public class ActivityWindowAndroid
ApplicationStatus.registerStateListenerForActivity(this, activity); ApplicationStatus.registerStateListenerForActivity(this, activity);
} }
setKeyboardDelegate(createKeyboardVisibilityDelegate());
setAndroidPermissionDelegate(createAndroidPermissionDelegate()); setAndroidPermissionDelegate(createAndroidPermissionDelegate());
} }
...@@ -68,20 +67,13 @@ public class ActivityWindowAndroid ...@@ -68,20 +67,13 @@ public class ActivityWindowAndroid
return new ActivityAndroidPermissionDelegate(getActivity()); return new ActivityAndroidPermissionDelegate(getActivity());
} }
@Override protected ActivityKeyboardVisibilityDelegate createKeyboardVisibilityDelegate() {
protected void registerKeyboardVisibilityCallbacks() { return new ActivityKeyboardVisibilityDelegate(getActivity());
Activity activity = getActivity().get();
if (activity == null) return;
View content = activity.findViewById(android.R.id.content);
mIsKeyboardShowing = getKeyboardDelegate().isKeyboardShowing(getActivity().get(), content);
content.addOnLayoutChangeListener(this);
} }
@Override @Override
protected void unregisterKeyboardVisibilityCallbacks() { public ActivityKeyboardVisibilityDelegate getKeyboardDelegate() {
Activity activity = getActivity().get(); return (ActivityKeyboardVisibilityDelegate) super.getKeyboardDelegate();
if (activity == null) return;
activity.findViewById(android.R.id.content).removeOnLayoutChangeListener(this);
} }
@Override @Override
...@@ -167,7 +159,7 @@ public class ActivityWindowAndroid ...@@ -167,7 +159,7 @@ public class ActivityWindowAndroid
@Override @Override
public WeakReference<Activity> getActivity() { public WeakReference<Activity> getActivity() {
return new WeakReference<Activity>(activityFromContext(getContext().get())); return new WeakReference<>(activityFromContext(getContext().get()));
} }
@Override @Override
...@@ -190,13 +182,6 @@ public class ActivityWindowAndroid ...@@ -190,13 +182,6 @@ public class ActivityWindowAndroid
: super.getActivityState(); : super.getActivityState();
} }
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
keyboardVisibilityPossiblyChanged(
getKeyboardDelegate().isKeyboardShowing(getActivity().get(), v));
}
private int generateNextRequestCode() { private int generateNextRequestCode() {
int requestCode = REQUEST_CODE_PREFIX + mNextRequestCode; int requestCode = REQUEST_CODE_PREFIX + mNextRequestCode;
mNextRequestCode = (mNextRequestCode + 1) % REQUEST_CODE_RANGE_SIZE; mNextRequestCode = (mNextRequestCode + 1) % REQUEST_CODE_RANGE_SIZE;
......
...@@ -51,6 +51,8 @@ import java.util.HashSet; ...@@ -51,6 +51,8 @@ import java.util.HashSet;
@JNINamespace("ui") @JNINamespace("ui")
public class WindowAndroid implements AndroidPermissionDelegate { public class WindowAndroid implements AndroidPermissionDelegate {
private static final String TAG = "WindowAndroid"; private static final String TAG = "WindowAndroid";
private KeyboardVisibilityDelegate mKeyboardVisibilityDelegate =
KeyboardVisibilityDelegate.getInstance();
@TargetApi(Build.VERSION_CODES.KITKAT) @TargetApi(Build.VERSION_CODES.KITKAT)
private class TouchExplorationMonitor { private class TouchExplorationMonitor {
...@@ -100,8 +102,6 @@ public class WindowAndroid implements AndroidPermissionDelegate { ...@@ -100,8 +102,6 @@ public class WindowAndroid implements AndroidPermissionDelegate {
private HashSet<Animator> mAnimationsOverContent = new HashSet<>(); private HashSet<Animator> mAnimationsOverContent = new HashSet<>();
private View mAnimationPlaceholderView; private View mAnimationPlaceholderView;
protected boolean mIsKeyboardShowing;
// System accessibility service. // System accessibility service.
private final AccessibilityManager mAccessibilityManager; private final AccessibilityManager mAccessibilityManager;
...@@ -118,15 +118,6 @@ public class WindowAndroid implements AndroidPermissionDelegate { ...@@ -118,15 +118,6 @@ public class WindowAndroid implements AndroidPermissionDelegate {
private boolean mPendingVSyncRequest; private boolean mPendingVSyncRequest;
private boolean mVSyncPaused; private boolean mVSyncPaused;
/**
* An interface to notify listeners of changes in the soft keyboard's visibility.
*/
public interface KeyboardVisibilityListener {
public void keyboardVisibilityChanged(boolean isShowing);
}
private ObserverList<KeyboardVisibilityListener> mKeyboardVisibilityListeners =
new ObserverList<>();
/** /**
* An interface to notify listeners that a context menu is closed. * An interface to notify listeners that a context menu is closed.
*/ */
...@@ -691,40 +682,19 @@ public class WindowAndroid implements AndroidPermissionDelegate { ...@@ -691,40 +682,19 @@ public class WindowAndroid implements AndroidPermissionDelegate {
} }
} }
protected void registerKeyboardVisibilityCallbacks() {
}
protected void unregisterKeyboardVisibilityCallbacks() {
}
/**
* Adds a listener that is updated of keyboard visibility changes. This works as a best guess.
*
* @see org.chromium.ui.KeyboardVisibilityDelegate#isKeyboardShowing(Context, View)
*/
public void addKeyboardVisibilityListener(KeyboardVisibilityListener listener) {
if (mKeyboardVisibilityListeners.isEmpty()) {
registerKeyboardVisibilityCallbacks();
}
mKeyboardVisibilityListeners.addObserver(listener);
}
/**
* @see #addKeyboardVisibilityListener(KeyboardVisibilityListener)
*/
public void removeKeyboardVisibilityListener(KeyboardVisibilityListener listener) {
mKeyboardVisibilityListeners.removeObserver(listener);
if (mKeyboardVisibilityListeners.isEmpty()) {
unregisterKeyboardVisibilityCallbacks();
}
}
/** /**
* The returned {@link KeyboardVisibilityDelegate} can read and influence the soft keyboard. * The returned {@link KeyboardVisibilityDelegate} can read and influence the soft keyboard.
* @return a {@link KeyboardVisibilityDelegate} specific for this window. * @return a {@link KeyboardVisibilityDelegate} specific for this window.
*/ */
public KeyboardVisibilityDelegate getKeyboardDelegate() { public KeyboardVisibilityDelegate getKeyboardDelegate() {
return KeyboardVisibilityDelegate.getInstance(); return mKeyboardVisibilityDelegate;
}
@VisibleForTesting
public void setKeyboardDelegate(KeyboardVisibilityDelegate keyboardDelegate) {
mKeyboardVisibilityDelegate = keyboardDelegate;
// TODO(fhorschig): Remove - every caller should use the window to get the delegate.
KeyboardVisibilityDelegate.setInstance(keyboardDelegate);
} }
/** /**
...@@ -753,20 +723,6 @@ public class WindowAndroid implements AndroidPermissionDelegate { ...@@ -753,20 +723,6 @@ public class WindowAndroid implements AndroidPermissionDelegate {
} }
} }
/**
* To be called when the keyboard visibility state might have changed. Informs listeners of the
* state change IFF there actually was a change.
* @param isShowing The current (guesstimated) state of the keyboard.
*/
protected void keyboardVisibilityPossiblyChanged(boolean isShowing) {
if (mIsKeyboardShowing == isShowing) return;
mIsKeyboardShowing = isShowing;
for (KeyboardVisibilityListener listener : mKeyboardVisibilityListeners) {
listener.keyboardVisibilityChanged(isShowing);
}
}
/** /**
* Start a post-layout animation on top of web content. * Start a post-layout animation on top of web content.
* *
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment