Bug 1388724 - 1. Support non-GeckoApp context in DoorHangerPopup; r=nechen
Make DoorHangerPopup support being used from non-GeckoApp contexts, so
we can reuse it from custom tabs and PWA.
MozReview-Commit-ID: BedxR92G7Vj
--- a/mobile/android/base/java/org/mozilla/gecko/DoorHangerPopup.java
+++ b/mobile/android/base/java/org/mozilla/gecko/DoorHangerPopup.java
@@ -1,15 +1,16 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko;
+import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.PopupWindow;
import org.mozilla.gecko.util.BundleEventListener;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoBundle;
@@ -30,38 +31,44 @@ public class DoorHangerPopup extends Anc
// Stores a set of all active DoorHanger notifications. A DoorHanger is
// uniquely identified by its tabId and value.
private final HashSet<DoorHanger> mDoorHangers;
// Whether or not the doorhanger popup is disabled.
private boolean mDisabled;
- private final GeckoApp geckoApp;
+ private final EventDispatcher eventDispatcher;
+ private final boolean isGeckoApp;
- public DoorHangerPopup(GeckoApp geckoApp) {
- super(geckoApp);
+ public DoorHangerPopup(final Context context, final EventDispatcher dispatcher) {
+ super(context);
- this.geckoApp = geckoApp;
+ eventDispatcher = dispatcher;
+ isGeckoApp = context instanceof GeckoApp;
mDoorHangers = new HashSet<DoorHanger>();
- geckoApp.getAppEventDispatcher().registerUiThreadListener(this,
+ eventDispatcher.registerUiThreadListener(this,
"Doorhanger:Add",
"Doorhanger:Remove");
- Tabs.registerOnTabsChangedListener(this);
+ if (isGeckoApp) {
+ Tabs.registerOnTabsChangedListener(this);
+ }
setOnDismissListener(this);
}
- void destroy() {
- geckoApp.getAppEventDispatcher().unregisterUiThreadListener(this,
+ public void destroy() {
+ eventDispatcher.unregisterUiThreadListener(this,
"Doorhanger:Add",
"Doorhanger:Remove");
- Tabs.unregisterOnTabsChangedListener(this);
+ if (isGeckoApp) {
+ Tabs.unregisterOnTabsChangedListener(this);
+ }
}
/**
* Temporarily disables the doorhanger popup. If the popup is disabled,
* it will not be shown to the user, but it will continue to process
* calls to add/remove doorhanger notifications.
*/
void disable() {
@@ -80,30 +87,30 @@ public class DoorHangerPopup extends Anc
@Override
public void handleMessage(final String event, final GeckoBundle geckoObject,
final EventCallback callback) {
if (event.equals("Doorhanger:Add")) {
final DoorhangerConfig config = makeConfigFromBundle(geckoObject);
addDoorHanger(config);
} else if (event.equals("Doorhanger:Remove")) {
- final int tabId = geckoObject.getInt("tabID");
+ final int tabId = geckoObject.getInt("tabID", -1);
final String value = geckoObject.getString("value");
DoorHanger doorHanger = getDoorHanger(tabId, value);
if (doorHanger == null) {
return;
}
removeDoorHanger(doorHanger);
updatePopup();
}
}
private DoorhangerConfig makeConfigFromBundle(final GeckoBundle bundle) {
- final int tabId = bundle.getInt("tabID");
+ final int tabId = bundle.getInt("tabID", -1);
final String id = bundle.getString("value");
final String typeString = bundle.getString("category");
final DoorHanger.Type doorhangerType = typeString == null ?
DoorHanger.Type.DEFAULT : DoorHanger.Type.valueOf(typeString);
final DoorhangerConfig config = new DoorhangerConfig(tabId, id, doorhangerType, this);
@@ -154,20 +161,20 @@ public class DoorHangerPopup extends Anc
}
}
/**
* Adds a doorhanger.
*
* This method must be called on the UI thread.
*/
- void addDoorHanger(DoorhangerConfig config) {
+ private void addDoorHanger(final DoorhangerConfig config) {
final int tabId = config.getTabId();
// Don't add a doorhanger for a tab that doesn't exist
- if (Tabs.getInstance().getTab(tabId) == null) {
+ if (isGeckoApp && Tabs.getInstance().getTab(tabId) == null) {
return;
}
// Replace the doorhanger if it already exists
DoorHanger oldDoorHanger = getDoorHanger(tabId, config.getId());
if (oldDoorHanger != null) {
removeDoorHanger(oldDoorHanger);
}
@@ -177,18 +184,19 @@ public class DoorHangerPopup extends Anc
}
final DoorHanger newDoorHanger = DoorHanger.Get(mContext, config);
mDoorHangers.add(newDoorHanger);
mContent.addView(newDoorHanger);
// Only update the popup if we're adding a notification to the selected tab
- if (tabId == Tabs.getInstance().getSelectedTab().getId())
+ if (!isGeckoApp || tabId == Tabs.getInstance().getSelectedTab().getId()) {
updatePopup();
+ }
}
/*
* DoorHanger.OnButtonClickListener implementation
*/
@Override
public void onButtonClick(final GeckoBundle response, DoorHanger doorhanger) {
@@ -197,46 +205,46 @@ public class DoorHangerPopup extends Anc
updatePopup();
}
/**
* Gets a doorhanger.
*
* This method must be called on the UI thread.
*/
- DoorHanger getDoorHanger(int tabId, String value) {
+ private DoorHanger getDoorHanger(int tabId, String value) {
for (DoorHanger dh : mDoorHangers) {
if (dh.getTabId() == tabId && dh.getIdentifier().equals(value))
return dh;
}
// If there's no doorhanger for the given tabId and value, return null
return null;
}
/**
* Removes a doorhanger.
*
* This method must be called on the UI thread.
*/
- void removeDoorHanger(final DoorHanger doorHanger) {
+ private void removeDoorHanger(final DoorHanger doorHanger) {
mDoorHangers.remove(doorHanger);
mContent.removeView(doorHanger);
}
/**
* Removes doorhangers for a given tab.
* @param tabId identifier of the tab to remove doorhangers from
* @param forceRemove boolean for force-removing tabs. If true, all doorhangers associated
* with the tab specified are removed; if false, only remove the doorhangers
* that are not persistent, as specified by the doorhanger options.
*
* This method must be called on the UI thread.
*/
- void removeTabDoorHangers(int tabId, boolean forceRemove) {
+ private void removeTabDoorHangers(int tabId, boolean forceRemove) {
// Make a temporary set to avoid a ConcurrentModificationException
HashSet<DoorHanger> doorHangersToRemove = new HashSet<DoorHanger>();
for (DoorHanger dh : mDoorHangers) {
// Only remove transient doorhangers for the given tab
if (dh.getTabId() == tabId
&& (forceRemove || (!forceRemove && dh.shouldRemove(isShowing())))) {
doorHangersToRemove.add(dh);
}
@@ -247,29 +255,30 @@ public class DoorHangerPopup extends Anc
}
}
/**
* Updates the popup state.
*
* This method must be called on the UI thread.
*/
- void updatePopup() {
+ private void updatePopup() {
// Bail if the selected tab is null, if there are no active doorhangers,
// if we haven't inflated the layout yet (this can happen if updatePopup()
// is called before the runnable from addDoorHanger() runs), or if the
// doorhanger popup is temporarily disabled.
- Tab tab = Tabs.getInstance().getSelectedTab();
- if (tab == null || mDoorHangers.size() == 0 || !mInflated || mDisabled) {
+ final Tab tab = isGeckoApp ? Tabs.getInstance().getSelectedTab() : null;
+ if ((isGeckoApp && tab == null) ||
+ mDoorHangers.size() == 0 || !mInflated || mDisabled) {
dismiss();
return;
}
// Show doorhangers for the selected tab
- int tabId = tab.getId();
+ int tabId = (tab != null) ? tab.getId() : -1;
boolean shouldShowPopup = false;
DoorHanger firstDoorhanger = null;
for (DoorHanger dh : mDoorHangers) {
if (dh.getTabId() == tabId) {
dh.setVisibility(View.VISIBLE);
shouldShowPopup = true;
if (firstDoorhanger == null) {
firstDoorhanger = dh;
@@ -284,17 +293,17 @@ public class DoorHangerPopup extends Anc
// Dismiss the popup if there are no doorhangers to show for this tab
if (!shouldShowPopup) {
dismiss();
return;
}
showDividers();
- final String baseDomain = tab.getBaseDomain();
+ final String baseDomain = tab != null ? tab.getBaseDomain() : null;
if (firstDoorhanger.getType() == Type.ADDON) {
firstDoorhanger.showTitle(null, mContext.getString(R.string.addons));
} else if (TextUtils.isEmpty(baseDomain)) {
firstDoorhanger.hideTitle();
} else {
firstDoorhanger.showTitle(tab.getFavicon(), baseDomain);
}
@@ -323,17 +332,17 @@ public class DoorHangerPopup extends Anc
}
if (lastVisibleDoorHanger != null) {
lastVisibleDoorHanger.hideDivider();
}
}
@Override
public void onDismiss() {
- final int tabId = Tabs.getInstance().getSelectedTab().getId();
+ final int tabId = isGeckoApp ? Tabs.getInstance().getSelectedTab().getId() : -1;
removeTabDoorHangers(tabId, true);
}
@Override
public void dismiss() {
// If the popup is focusable while it is hidden, we run into crashes
// on pre-ICS devices when the popup gets focus before it is shown.
setFocusable(false);
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -1499,17 +1499,17 @@ public abstract class GeckoApp extends G
// Allow onConfigurationChanged to take care of the rest.
// We don't call this.onConfigurationChanged, because (a) that does
// work that's unnecessary after this locale action, and (b) it can
// cause a loop! See Bug 1011008, Comment 12.
super.onConfigurationChanged(getResources().getConfiguration());
}
protected void initializeChrome() {
- mDoorHangerPopup = new DoorHangerPopup(this);
+ mDoorHangerPopup = new DoorHangerPopup(this, getAppEventDispatcher());
mDoorHangerPopup.setOnVisibilityChangeListener(this);
mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup);
}
@Override
public void onDoorHangerShow() {
final View overlay = getDoorhangerOverlay();
if (overlay != null) {