Commit 0711fb3f authored by Friedrich Horschig's avatar Friedrich Horschig Committed by Commit Bot

[Android] Ensure filling component isn't used before initialization

This CL ensures that any access to the ManualFillingCoordinator checks
whether the latter exists in the first place. Because its initialization
happens after the native initialization, this is a possible source of
crashes.

Also, this CL handles that cases in which the bottom control space
changes even if there is no accessory to fill it.

Bug: 876215, 877687
Change-Id: I713ab1343742c858d6ab0277b94cf3190e88e5e2
Reviewed-on: https://chromium-review.googlesource.com/1189805
Commit-Queue: Friedrich Horschig [CEST] <fhorschig@chromium.org>
Reviewed-by: default avatarBernhard Bauer <bauerb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#586732}
parent a56c7856
...@@ -261,7 +261,6 @@ public abstract class ChromeActivity extends AsyncInitializationActivity ...@@ -261,7 +261,6 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
private BottomSheet mBottomSheet; private BottomSheet mBottomSheet;
private ContextualSuggestionsCoordinator mContextualSuggestionsCoordinator; private ContextualSuggestionsCoordinator mContextualSuggestionsCoordinator;
private ScrimView mScrimView; private ScrimView mScrimView;
private ManualFillingCoordinator mManualFillingController;
private float mStatusBarScrimFraction; private float mStatusBarScrimFraction;
private int mBaseStatusBarColor; private int mBaseStatusBarColor;
private int mScrimColor; private int mScrimColor;
...@@ -278,6 +277,8 @@ public abstract class ChromeActivity extends AsyncInitializationActivity ...@@ -278,6 +277,8 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
private Runnable mRecordMultiWindowModeScreenWidthRunnable; private Runnable mRecordMultiWindowModeScreenWidthRunnable;
private final DiscardableReferencePool mReferencePool = new DiscardableReferencePool(); private final DiscardableReferencePool mReferencePool = new DiscardableReferencePool();
private final ManualFillingCoordinator mManualFillingController =
new ManualFillingCoordinator();
private AssistStatusHandler mAssistStatusHandler; private AssistStatusHandler mAssistStatusHandler;
...@@ -688,13 +689,6 @@ public abstract class ChromeActivity extends AsyncInitializationActivity ...@@ -688,13 +689,6 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
return mFindToolbarManager; return mFindToolbarManager;
} }
/**
* @return The {@link KeyboardAccessoryCoordinator} that belongs to this activity.
*/
public KeyboardAccessoryCoordinator getKeyboardAccessory() {
return mManualFillingController.getKeyboardAccessory();
}
/** /**
* @return The {@link KeyboardAccessoryCoordinator} that belongs to this activity. * @return The {@link KeyboardAccessoryCoordinator} that belongs to this activity.
*/ */
...@@ -939,7 +933,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity ...@@ -939,7 +933,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
OfflineIndicatorController.onUpdate(); OfflineIndicatorController.onUpdate();
if (getManualFillingController() != null) getManualFillingController().onResume(); getManualFillingController().onResume();
} }
@Override @Override
...@@ -957,7 +951,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity ...@@ -957,7 +951,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
RecordUserAction.record("MobileGoToBackground"); RecordUserAction.record("MobileGoToBackground");
Tab tab = getActivityTab(); Tab tab = getActivityTab();
if (tab != null) getTabContentManager().cacheTabThumbnail(tab); if (tab != null) getTabContentManager().cacheTabThumbnail(tab);
if (getManualFillingController() != null) getManualFillingController().onPause(); getManualFillingController().onPause();
VrModuleProvider.getDelegate().maybeUnregisterVrEntryHook(); VrModuleProvider.getDelegate().maybeUnregisterVrEntryHook();
markSessionEnd(); markSessionEnd();
...@@ -1269,10 +1263,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity ...@@ -1269,10 +1263,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
mTabContentManager = null; mTabContentManager = null;
} }
if (mManualFillingController != null) {
mManualFillingController.destroy(); mManualFillingController.destroy();
mManualFillingController = null;
}
if (mActivityTabStartupMetricsTracker != null) { if (mActivityTabStartupMetricsTracker != null) {
mActivityTabStartupMetricsTracker.destroy(); mActivityTabStartupMetricsTracker.destroy();
...@@ -1383,7 +1374,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity ...@@ -1383,7 +1374,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
setStatusBarColor(null, mBaseStatusBarColor); setStatusBarColor(null, mBaseStatusBarColor);
}, coordinator); }, coordinator);
mManualFillingController = new ManualFillingCoordinator(getWindowAndroid(), mManualFillingController.initialize(getWindowAndroid(),
findViewById(R.id.keyboard_accessory_stub), findViewById(R.id.keyboard_accessory_stub),
findViewById(R.id.keyboard_accessory_sheet_stub)); findViewById(R.id.keyboard_accessory_sheet_stub));
......
...@@ -1759,8 +1759,7 @@ public class ChromeTabbedActivity ...@@ -1759,8 +1759,7 @@ public class ChromeTabbedActivity
// BottomSheet can be opened before native is initialized. // BottomSheet can be opened before native is initialized.
if (!mUIInitialized) return getBottomSheet() != null && getBottomSheet().handleBackPress(); if (!mUIInitialized) return getBottomSheet() != null && getBottomSheet().handleBackPress();
if (getManualFillingController() != null && getManualFillingController().handleBackPress()) if (getManualFillingController().handleBackPress()) return true;
return true;
final Tab currentTab = getActivityTab(); final Tab currentTab = getActivityTab();
......
...@@ -57,7 +57,7 @@ public class AutofillKeyboardAccessoryBridge ...@@ -57,7 +57,7 @@ public class AutofillKeyboardAccessoryBridge
@Override @Override
public void suggestionSelected(int listIndex) { public void suggestionSelected(int listIndex) {
KeyboardAccessoryMetricsRecorder.recordActionSelected(AccessoryAction.AUTOFILL_SUGGESTION); KeyboardAccessoryMetricsRecorder.recordActionSelected(AccessoryAction.AUTOFILL_SUGGESTION);
if (mManualFillingCoordinator != null) mManualFillingCoordinator.dismiss(); mManualFillingCoordinator.dismiss();
if (mNativeAutofillKeyboardAccessory == 0) return; if (mNativeAutofillKeyboardAccessory == 0) return;
nativeSuggestionSelected(mNativeAutofillKeyboardAccessory, listIndex); nativeSuggestionSelected(mNativeAutofillKeyboardAccessory, listIndex);
} }
...@@ -96,9 +96,11 @@ public class AutofillKeyboardAccessoryBridge ...@@ -96,9 +96,11 @@ public class AutofillKeyboardAccessoryBridge
assert mContext != null; assert mContext != null;
if (mContext instanceof ChromeActivity) { if (mContext instanceof ChromeActivity) {
mManualFillingCoordinator = ((ChromeActivity) mContext).getManualFillingController(); mManualFillingCoordinator = ((ChromeActivity) mContext).getManualFillingController();
if (mManualFillingCoordinator.getKeyboardAccessory() != null) {
mManualFillingCoordinator.getKeyboardAccessory().registerActionListProvider( mManualFillingCoordinator.getKeyboardAccessory().registerActionListProvider(
mChipProvider); mChipProvider);
} }
}
mNativeAutofillKeyboardAccessory = nativeAutofillKeyboardAccessory; mNativeAutofillKeyboardAccessory = nativeAutofillKeyboardAccessory;
} }
...@@ -118,7 +120,6 @@ public class AutofillKeyboardAccessoryBridge ...@@ -118,7 +120,6 @@ public class AutofillKeyboardAccessoryBridge
private void dismiss() { private void dismiss() {
mChipProvider.notifyObservers(new KeyboardAccessoryData.Action[0]); mChipProvider.notifyObservers(new KeyboardAccessoryData.Action[0]);
mContext = null; mContext = null;
mManualFillingCoordinator = null;
} }
/** /**
......
...@@ -191,6 +191,15 @@ public class KeyboardAccessoryCoordinator { ...@@ -191,6 +191,15 @@ public class KeyboardAccessoryCoordinator {
return mMediator.isShown(); return mMediator.isShown();
} }
/**
* This method returns whether the accessory has any contents that justify showing it. A single
* tab, action or suggestion chip would already allow that.
* @return True if there is any content to be shown. False otherwise.
*/
public boolean hasContents() {
return mMediator.hasContents();
}
/** /**
* Returns whether the active tab is non-null. The returned property reflects the latest change * Returns whether the active tab is non-null. The returned property reflects the latest change
* while the view might still be in progress of being updated accordingly. * while the view might still be in progress of being updated accordingly.
......
...@@ -171,9 +171,13 @@ class KeyboardAccessoryMediator ...@@ -171,9 +171,13 @@ class KeyboardAccessoryMediator
} }
} }
boolean hasContents() {
return mModel.getActionList().size() > 0 || mModel.getTabList().size() > 0;
}
private boolean shouldShowAccessory() { private boolean shouldShowAccessory() {
if (!mShowIfNotEmpty && mModel.activeTab() == null) return false; if (!mShowIfNotEmpty && mModel.activeTab() == null) return false;
return mModel.getActionList().size() > 0 || mModel.getTabList().size() > 0; return hasContents();
} }
private void updateVisibility() { private void updateVisibility() {
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory; package org.chromium.chrome.browser.autofill.keyboard_accessory;
import android.support.annotation.Nullable;
import android.view.ViewStub; import android.view.ViewStub;
import org.chromium.base.VisibleForTesting; import org.chromium.base.VisibleForTesting;
...@@ -23,12 +24,13 @@ public class ManualFillingCoordinator { ...@@ -23,12 +24,13 @@ public class ManualFillingCoordinator {
private final ManualFillingMediator mMediator = new ManualFillingMediator(); private final ManualFillingMediator mMediator = new ManualFillingMediator();
/** /**
* Creates a the manual filling controller. * Initializes the manual filling component. Calls to this class are NoOps until
* {@link #initialize(WindowAndroid, ViewStub, ViewStub)} is called.
* @param windowAndroid The window needed to listen to the keyboard and to connect to activity. * @param windowAndroid The window needed to listen to the keyboard and to connect to activity.
* @param keyboardAccessoryStub The view stub for keyboard accessory bar. * @param keyboardAccessoryStub The view stub for keyboard accessory bar.
* @param accessorySheetStub The view stub for the keyboard accessory bottom sheet. * @param accessorySheetStub The view stub for the keyboard accessory bottom sheet.
*/ */
public ManualFillingCoordinator(WindowAndroid windowAndroid, ViewStub keyboardAccessoryStub, public void initialize(WindowAndroid windowAndroid, ViewStub keyboardAccessoryStub,
ViewStub accessorySheetStub) { ViewStub accessorySheetStub) {
KeyboardAccessoryCoordinator keyboardAccessory = KeyboardAccessoryCoordinator keyboardAccessory =
new KeyboardAccessoryCoordinator(keyboardAccessoryStub, mMediator); new KeyboardAccessoryCoordinator(keyboardAccessoryStub, mMediator);
...@@ -59,6 +61,10 @@ public class ManualFillingCoordinator { ...@@ -59,6 +61,10 @@ public class ManualFillingCoordinator {
mMediator.dismiss(); mMediator.dismiss();
} }
/**
* Notifies the component that a popup window exists so it can be dismissed if necessary.
* @param popup A {@link DropdownPopupWindow} that might be dismissed later.
*/
public void notifyPopupAvailable(DropdownPopupWindow popup) { public void notifyPopupAvailable(DropdownPopupWindow popup) {
mMediator.notifyPopupOpened(popup); mMediator.notifyPopupOpened(popup);
} }
...@@ -101,7 +107,7 @@ public class ManualFillingCoordinator { ...@@ -101,7 +107,7 @@ public class ManualFillingCoordinator {
* the keyboard accessory (e.g. by providing suggestions or actions). * the keyboard accessory (e.g. by providing suggestions or actions).
* @return The coordinator of the Keyboard accessory component. * @return The coordinator of the Keyboard accessory component.
*/ */
public KeyboardAccessoryCoordinator getKeyboardAccessory() { public @Nullable KeyboardAccessoryCoordinator getKeyboardAccessory() {
return mMediator.getKeyboardAccessory(); return mMediator.getKeyboardAccessory();
} }
......
...@@ -172,10 +172,15 @@ class ManualFillingMediator ...@@ -172,10 +172,15 @@ class ManualFillingMediator
} }
} }
boolean isInitialized() {
return mWindowAndroid != null;
}
// TODO(fhorschig): Look for stronger signals than |keyboardVisibilityChanged|. // TODO(fhorschig): Look for stronger signals than |keyboardVisibilityChanged|.
// This variable remembers the last state of |keyboardVisibilityChanged| which might not be // This variable remembers the last state of |keyboardVisibilityChanged| which might not be
// sufficient for edge cases like hardware keyboards, floating keyboards, etc. // sufficient for edge cases like hardware keyboards, floating keyboards, etc.
private void onKeyboardVisibilityChanged(boolean isShowing) { private void onKeyboardVisibilityChanged(boolean isShowing) {
if (!mKeyboardAccessory.hasContents()) return; // Exit early to not affect the layout.
if (isShowing) { if (isShowing) {
mKeyboardAccessory.requestShowing(); mKeyboardAccessory.requestShowing();
mActivity.getFullscreenManager().setBottomControlsHeight(calculateAccessoryBarHeight()); mActivity.getFullscreenManager().setBottomControlsHeight(calculateAccessoryBarHeight());
...@@ -184,22 +189,21 @@ class ManualFillingMediator ...@@ -184,22 +189,21 @@ class ManualFillingMediator
mAccessorySheet.hide(); mAccessorySheet.hide();
} else { } else {
mKeyboardAccessory.close(); mKeyboardAccessory.close();
onBottomControlSpaceChanged();
if (mKeyboardAccessory.hasActiveTab()) { if (mKeyboardAccessory.hasActiveTab()) {
mKeyboardAccessory.setBottomOffset(mAccessorySheet.getHeight());
mAccessorySheet.show(); mAccessorySheet.show();
onBottomControlSpaceChanged();
} else {
mActivity.getFullscreenManager().setBottomControlsHeight(mPreviousControlHeight);
} }
} }
} }
void registerPasswordProvider(Provider<KeyboardAccessoryData.Item> itemProvider) { void registerPasswordProvider(Provider<KeyboardAccessoryData.Item> itemProvider) {
if (!isInitialized()) return;
assert getPasswordAccessorySheet() != null : "No password sheet available!"; assert getPasswordAccessorySheet() != null : "No password sheet available!";
getPasswordAccessorySheet().registerItemProvider(itemProvider); getPasswordAccessorySheet().registerItemProvider(itemProvider);
} }
void registerActionProvider(KeyboardAccessoryData.PropertyProvider<Action> actionProvider) { void registerActionProvider(KeyboardAccessoryData.PropertyProvider<Action> actionProvider) {
if (!isInitialized()) return;
ActionProviderCacheAdapter adapter = ActionProviderCacheAdapter adapter =
new ActionProviderCacheAdapter(mActiveBrowserTab, actionProvider, new Action[0]); new ActionProviderCacheAdapter(mActiveBrowserTab, actionProvider, new Action[0]);
mModel.get(mActiveBrowserTab).mActionsProvider = adapter; mModel.get(mActiveBrowserTab).mActionsProvider = adapter;
...@@ -207,6 +211,7 @@ class ManualFillingMediator ...@@ -207,6 +211,7 @@ class ManualFillingMediator
} }
void destroy() { void destroy() {
if (!isInitialized()) return;
if (mWindowAndroid != null) { if (mWindowAndroid != null) {
mWindowAndroid.removeKeyboardVisibilityListener(mVisibilityListener); mWindowAndroid.removeKeyboardVisibilityListener(mVisibilityListener);
} }
...@@ -214,7 +219,7 @@ class ManualFillingMediator ...@@ -214,7 +219,7 @@ class ManualFillingMediator
} }
boolean handleBackPress() { boolean handleBackPress() {
if (mAccessorySheet.isShown()) { if (isInitialized() && mAccessorySheet.isShown()) {
mKeyboardAccessory.dismiss(); mKeyboardAccessory.dismiss();
return true; return true;
} }
...@@ -222,6 +227,7 @@ class ManualFillingMediator ...@@ -222,6 +227,7 @@ class ManualFillingMediator
} }
void dismiss() { void dismiss() {
if (!isInitialized()) return;
mKeyboardAccessory.dismiss(); mKeyboardAccessory.dismiss();
if (getContentView() != null) UiUtils.hideKeyboard(getContentView()); if (getContentView() != null) UiUtils.hideKeyboard(getContentView());
} }
...@@ -231,10 +237,12 @@ class ManualFillingMediator ...@@ -231,10 +237,12 @@ class ManualFillingMediator
} }
public void pause() { public void pause() {
if (!isInitialized()) return;
mKeyboardAccessory.dismiss(); mKeyboardAccessory.dismiss();
} }
void resume() { void resume() {
if (!isInitialized()) return;
restoreCachedState(mActiveBrowserTab); restoreCachedState(mActiveBrowserTab);
} }
...@@ -266,7 +274,7 @@ class ManualFillingMediator ...@@ -266,7 +274,7 @@ class ManualFillingMediator
* Opens the keyboard which implicitly dismisses the sheet. Without open sheet, this is a NoOp. * Opens the keyboard which implicitly dismisses the sheet. Without open sheet, this is a NoOp.
*/ */
void swapSheetWithKeyboard() { void swapSheetWithKeyboard() {
if (mAccessorySheet.isShown()) onOpenKeyboard(); if (isInitialized() && mAccessorySheet.isShown()) onOpenKeyboard();
} }
@Override @Override
...@@ -278,12 +286,15 @@ class ManualFillingMediator ...@@ -278,12 +286,15 @@ class ManualFillingMediator
@Override @Override
public void onBottomControlSpaceChanged() { public void onBottomControlSpaceChanged() {
@Px
int newControlsHeight = calculateAccessoryBarHeight(); int newControlsHeight = calculateAccessoryBarHeight();
if (mAccessorySheet.isShown()) { int newControlsOffset = 0;
if (mKeyboardAccessory.hasActiveTab()) {
newControlsHeight += mAccessorySheet.getHeight(); newControlsHeight += mAccessorySheet.getHeight();
newControlsOffset += mAccessorySheet.getHeight();
} }
mActivity.getFullscreenManager().setBottomControlsHeight(newControlsHeight); mKeyboardAccessory.setBottomOffset(newControlsOffset);
mActivity.getFullscreenManager().setBottomControlsHeight(
mKeyboardAccessory.isShown() ? newControlsHeight : mPreviousControlHeight);
} }
/** /**
...@@ -342,6 +353,7 @@ class ManualFillingMediator ...@@ -342,6 +353,7 @@ class ManualFillingMediator
@VisibleForTesting @VisibleForTesting
void addTab(KeyboardAccessoryData.Tab tab) { void addTab(KeyboardAccessoryData.Tab tab) {
if (!isInitialized()) return;
// TODO(fhorschig): This should add the tab only to the state. Sheet and accessory should be // TODO(fhorschig): This should add the tab only to the state. Sheet and accessory should be
// using a |set| method or even observe the state. // using a |set| method or even observe the state.
mKeyboardAccessory.addTab(tab); mKeyboardAccessory.addTab(tab);
...@@ -351,6 +363,7 @@ class ManualFillingMediator ...@@ -351,6 +363,7 @@ class ManualFillingMediator
@VisibleForTesting @VisibleForTesting
@Nullable @Nullable
PasswordAccessorySheetCoordinator getPasswordAccessorySheet() { PasswordAccessorySheetCoordinator getPasswordAccessorySheet() {
if (!isInitialized()) return null;
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.EXPERIMENTAL_UI) if (!ChromeFeatureList.isEnabled(ChromeFeatureList.EXPERIMENTAL_UI)
&& !ChromeFeatureList.isEnabled(ChromeFeatureList.PASSWORDS_KEYBOARD_ACCESSORY)) { && !ChromeFeatureList.isEnabled(ChromeFeatureList.PASSWORDS_KEYBOARD_ACCESSORY)) {
return null; return null;
...@@ -365,6 +378,7 @@ class ManualFillingMediator ...@@ -365,6 +378,7 @@ class ManualFillingMediator
} }
// TODO(fhorschig): Should be @VisibleForTesting. // TODO(fhorschig): Should be @VisibleForTesting.
@Nullable
KeyboardAccessoryCoordinator getKeyboardAccessory() { KeyboardAccessoryCoordinator getKeyboardAccessory() {
return mKeyboardAccessory; return mKeyboardAccessory;
} }
......
...@@ -86,7 +86,7 @@ public class ManualFillingControllerTest { ...@@ -86,7 +86,7 @@ public class ManualFillingControllerTest {
@Rule @Rule
public Features.JUnitProcessor mFeaturesProcessor = new Features.JUnitProcessor(); public Features.JUnitProcessor mFeaturesProcessor = new Features.JUnitProcessor();
private ManualFillingCoordinator mController; private ManualFillingCoordinator mController = new ManualFillingCoordinator();
@Before @Before
public void setUp() { public void setUp() {
...@@ -100,7 +100,7 @@ public class ManualFillingControllerTest { ...@@ -100,7 +100,7 @@ public class ManualFillingControllerTest {
when(mMockActivity.getResources()).thenReturn(mMockResources); when(mMockActivity.getResources()).thenReturn(mMockResources);
when(mMockResources.getDimensionPixelSize(anyInt())).thenReturn(48); when(mMockResources.getDimensionPixelSize(anyInt())).thenReturn(48);
PasswordAccessorySheetCoordinator.IconProvider.getInstance().setIconForTesting(mMockIcon); PasswordAccessorySheetCoordinator.IconProvider.getInstance().setIconForTesting(mMockIcon);
mController = new ManualFillingCoordinator(mMockWindow, mMockViewStub, mMockViewStub); mController.initialize(mMockWindow, mMockViewStub, mMockViewStub);
} }
@Test @Test
......
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