2011-11-18 10:28:17 -08:00
|
|
|
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
2012-05-21 04:12:37 -07:00
|
|
|
* 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/. */
|
2011-11-18 10:28:17 -08:00
|
|
|
|
|
|
|
package org.mozilla.gecko;
|
|
|
|
|
2012-08-02 17:13:40 -07:00
|
|
|
import org.mozilla.gecko.util.GeckoEventListener;
|
2013-06-20 08:06:15 -07:00
|
|
|
import org.mozilla.gecko.widget.ArrowPopup;
|
2012-08-02 17:13:40 -07:00
|
|
|
|
2012-07-27 17:53:54 -07:00
|
|
|
import org.json.JSONArray;
|
|
|
|
import org.json.JSONObject;
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2013-06-13 12:42:45 -07:00
|
|
|
import android.os.Build;
|
2011-11-18 10:28:17 -08:00
|
|
|
import android.util.Log;
|
2012-06-11 15:18:40 -07:00
|
|
|
import android.view.View;
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-08-04 16:34:44 -07:00
|
|
|
import java.util.HashSet;
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2013-06-20 08:06:15 -07:00
|
|
|
public class DoorHangerPopup extends ArrowPopup
|
2012-08-04 16:34:47 -07:00
|
|
|
implements GeckoEventListener, Tabs.OnTabsChangedListener {
|
2011-11-18 10:28:17 -08:00
|
|
|
private static final String LOGTAG = "GeckoDoorHangerPopup";
|
|
|
|
|
2012-08-04 16:34:44 -07:00
|
|
|
// Stores a set of all active DoorHanger notifications. A DoorHanger is
|
|
|
|
// uniquely identified by its tabId and value.
|
|
|
|
private HashSet<DoorHanger> mDoorHangers;
|
|
|
|
|
2012-08-04 16:34:42 -07:00
|
|
|
DoorHangerPopup(GeckoApp aActivity, View aAnchor) {
|
2013-06-20 08:06:15 -07:00
|
|
|
super(aActivity, aAnchor);
|
2012-07-09 15:05:53 -07:00
|
|
|
|
2012-08-04 16:34:44 -07:00
|
|
|
mDoorHangers = new HashSet<DoorHanger>();
|
2012-08-04 16:34:42 -07:00
|
|
|
|
2012-08-02 18:38:45 -07:00
|
|
|
registerEventListener("Doorhanger:Add");
|
|
|
|
registerEventListener("Doorhanger:Remove");
|
2012-08-04 16:34:47 -07:00
|
|
|
Tabs.registerOnTabsChangedListener(this);
|
2012-08-04 16:34:42 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void destroy() {
|
2012-08-02 18:38:45 -07:00
|
|
|
unregisterEventListener("Doorhanger:Add");
|
|
|
|
unregisterEventListener("Doorhanger:Remove");
|
2012-08-04 16:34:47 -07:00
|
|
|
Tabs.unregisterOnTabsChangedListener(this);
|
2012-08-04 16:34:42 -07:00
|
|
|
}
|
|
|
|
|
2013-02-26 21:48:00 -08:00
|
|
|
@Override
|
2012-08-04 16:34:42 -07:00
|
|
|
public void handleMessage(String event, JSONObject geckoObject) {
|
|
|
|
try {
|
|
|
|
if (event.equals("Doorhanger:Add")) {
|
2012-10-30 10:31:29 -07:00
|
|
|
final int tabId = geckoObject.getInt("tabID");
|
|
|
|
final String value = geckoObject.getString("value");
|
|
|
|
final String message = geckoObject.getString("message");
|
|
|
|
final JSONArray buttons = geckoObject.getJSONArray("buttons");
|
|
|
|
final JSONObject options = geckoObject.getJSONObject("options");
|
2012-08-04 16:34:42 -07:00
|
|
|
|
2012-10-30 10:31:29 -07:00
|
|
|
mActivity.runOnUiThread(new Runnable() {
|
2013-02-26 21:48:00 -08:00
|
|
|
@Override
|
2012-10-30 10:31:29 -07:00
|
|
|
public void run() {
|
|
|
|
addDoorHanger(tabId, value, message, buttons, options);
|
|
|
|
}
|
|
|
|
});
|
2012-08-04 16:34:42 -07:00
|
|
|
} else if (event.equals("Doorhanger:Remove")) {
|
2012-10-30 10:31:29 -07:00
|
|
|
final int tabId = geckoObject.getInt("tabID");
|
|
|
|
final String value = geckoObject.getString("value");
|
2012-08-04 16:34:42 -07:00
|
|
|
|
|
|
|
mActivity.runOnUiThread(new Runnable() {
|
2013-02-26 21:48:00 -08:00
|
|
|
@Override
|
2012-08-04 16:34:42 -07:00
|
|
|
public void run() {
|
2012-10-30 10:31:29 -07:00
|
|
|
DoorHanger doorHanger = getDoorHanger(tabId, value);
|
|
|
|
if (doorHanger == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
removeDoorHanger(doorHanger);
|
2012-08-04 16:34:42 -07:00
|
|
|
updatePopup();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
|
|
Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
|
|
|
|
}
|
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-10-30 10:31:29 -07:00
|
|
|
// This callback is automatically executed on the UI thread.
|
2013-02-26 21:48:00 -08:00
|
|
|
@Override
|
2012-10-30 10:31:29 -07:00
|
|
|
public void onTabChanged(final Tab tab, final Tabs.TabEvents msg, final Object data) {
|
2012-08-04 16:34:47 -07:00
|
|
|
switch(msg) {
|
|
|
|
case CLOSED:
|
2012-08-29 16:42:15 -07:00
|
|
|
// Remove any doorhangers for a tab when it's closed (make
|
|
|
|
// a temporary set to avoid a ConcurrentModificationException)
|
|
|
|
HashSet<DoorHanger> doorHangersToRemove = new HashSet<DoorHanger>();
|
2012-08-04 16:34:47 -07:00
|
|
|
for (DoorHanger dh : mDoorHangers) {
|
|
|
|
if (dh.getTabId() == tab.getId())
|
2012-08-29 16:42:15 -07:00
|
|
|
doorHangersToRemove.add(dh);
|
|
|
|
}
|
|
|
|
for (DoorHanger dh : doorHangersToRemove) {
|
|
|
|
removeDoorHanger(dh);
|
2012-08-04 16:34:47 -07:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LOCATION_CHANGE:
|
|
|
|
// Only remove doorhangers if the popup is hidden or if we're navigating to a new URL
|
|
|
|
if (!isShowing() || !data.equals(tab.getURL()))
|
|
|
|
removeTransientDoorHangers(tab.getId());
|
|
|
|
|
|
|
|
// Update the popup if the location change was on the current tab
|
|
|
|
if (Tabs.getInstance().isSelectedTab(tab))
|
|
|
|
updatePopup();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SELECTED:
|
|
|
|
// Always update the popup when a new tab is selected. This will cover cases
|
|
|
|
// where a different tab was closed, since we always need to select a new tab.
|
|
|
|
updatePopup();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-30 10:31:29 -07:00
|
|
|
/**
|
|
|
|
* Adds a doorhanger.
|
|
|
|
*
|
|
|
|
* This method must be called on the UI thread.
|
|
|
|
*/
|
2012-08-04 16:34:44 -07:00
|
|
|
void addDoorHanger(final int tabId, final String value, final String message,
|
|
|
|
final JSONArray buttons, final JSONObject options) {
|
|
|
|
// Don't add a doorhanger for a tab that doesn't exist
|
|
|
|
if (Tabs.getInstance().getTab(tabId) == null)
|
|
|
|
return;
|
2012-02-13 10:29:29 -08:00
|
|
|
|
2011-11-18 10:28:17 -08:00
|
|
|
// Replace the doorhanger if it already exists
|
2012-08-04 16:34:44 -07:00
|
|
|
DoorHanger oldDoorHanger = getDoorHanger(tabId, value);
|
|
|
|
if (oldDoorHanger != null)
|
|
|
|
removeDoorHanger(oldDoorHanger);
|
|
|
|
|
|
|
|
final DoorHanger newDoorHanger = new DoorHanger(mActivity, this, tabId, value);
|
|
|
|
mDoorHangers.add(newDoorHanger);
|
|
|
|
|
2012-10-30 10:31:29 -07:00
|
|
|
if (!mInflated)
|
|
|
|
init();
|
2012-08-04 16:34:44 -07:00
|
|
|
|
2012-10-30 10:31:29 -07:00
|
|
|
newDoorHanger.init(message, buttons, options);
|
|
|
|
mContent.addView(newDoorHanger);
|
2012-08-04 16:34:44 -07:00
|
|
|
|
2012-10-30 10:31:29 -07:00
|
|
|
// Only update the popup if we're adding a notifcation to the selected tab
|
|
|
|
if (tabId == Tabs.getInstance().getSelectedTab().getId())
|
|
|
|
updatePopup();
|
2012-08-04 16:34:44 -07:00
|
|
|
}
|
|
|
|
|
2012-10-30 10:31:29 -07:00
|
|
|
/**
|
|
|
|
* Gets a doorhanger.
|
|
|
|
*
|
|
|
|
* This method must be called on the UI thread.
|
|
|
|
*/
|
2012-08-04 16:34:44 -07:00
|
|
|
DoorHanger getDoorHanger(int tabId, String value) {
|
|
|
|
for (DoorHanger dh : mDoorHangers) {
|
|
|
|
if (dh.getTabId() == tabId && dh.getValue().equals(value))
|
|
|
|
return dh;
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
2012-08-04 16:34:44 -07:00
|
|
|
|
|
|
|
// If there's no doorhanger for the given tabId and value, return null
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2012-10-30 10:31:29 -07:00
|
|
|
/**
|
|
|
|
* Removes a doorhanger.
|
|
|
|
*
|
|
|
|
* This method must be called on the UI thread.
|
|
|
|
*/
|
2012-08-04 16:34:44 -07:00
|
|
|
void removeDoorHanger(final DoorHanger doorHanger) {
|
|
|
|
mDoorHangers.remove(doorHanger);
|
2012-10-30 10:31:29 -07:00
|
|
|
mContent.removeView(doorHanger);
|
2012-08-04 16:34:44 -07:00
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-10-30 10:31:29 -07:00
|
|
|
/**
|
|
|
|
* Removes doorhangers for a given tab.
|
|
|
|
*
|
|
|
|
* This method must be called on the UI thread.
|
|
|
|
*/
|
2012-08-04 16:34:44 -07:00
|
|
|
void removeTransientDoorHangers(int tabId) {
|
|
|
|
// 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 && dh.shouldRemove())
|
|
|
|
doorHangersToRemove.add(dh);
|
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-08-04 16:34:44 -07:00
|
|
|
for (DoorHanger dh : doorHangersToRemove) {
|
|
|
|
removeDoorHanger(dh);
|
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2012-10-30 10:31:29 -07:00
|
|
|
/**
|
|
|
|
* Updates the popup state.
|
|
|
|
*
|
|
|
|
* This method must be called on the UI thread.
|
|
|
|
*/
|
2012-08-04 16:34:44 -07:00
|
|
|
void updatePopup() {
|
|
|
|
// Bail if the selected tab is null, if there are no active doorhangers,
|
|
|
|
// or if we haven't inflated the layout yet (this can happen if updatePopup()
|
|
|
|
// is called before the runnable from addDoorHanger() runs).
|
2011-11-18 10:28:17 -08:00
|
|
|
Tab tab = Tabs.getInstance().getSelectedTab();
|
2012-08-04 16:34:44 -07:00
|
|
|
if (tab == null || mDoorHangers.size() == 0 || !mInflated) {
|
2012-08-04 16:34:42 -07:00
|
|
|
dismiss();
|
2011-11-21 14:24:24 -08:00
|
|
|
return;
|
|
|
|
}
|
2012-08-04 16:34:44 -07:00
|
|
|
|
|
|
|
// Show doorhangers for the selected tab
|
|
|
|
int tabId = tab.getId();
|
|
|
|
boolean shouldShowPopup = false;
|
|
|
|
for (DoorHanger dh : mDoorHangers) {
|
|
|
|
if (dh.getTabId() == tabId) {
|
|
|
|
dh.setVisibility(View.VISIBLE);
|
|
|
|
shouldShowPopup = true;
|
|
|
|
} else {
|
|
|
|
dh.setVisibility(View.GONE);
|
|
|
|
}
|
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-08-04 16:34:44 -07:00
|
|
|
// Dismiss the popup if there are no doorhangers to show for this tab
|
|
|
|
if (!shouldShowPopup) {
|
2012-08-04 16:34:42 -07:00
|
|
|
dismiss();
|
2011-11-18 10:28:17 -08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-03-11 14:07:11 -07:00
|
|
|
showDividers();
|
2012-07-09 15:05:53 -07:00
|
|
|
if (isShowing()) {
|
2011-11-18 10:28:17 -08:00
|
|
|
update();
|
2012-07-09 15:05:53 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-06-13 12:42:45 -07:00
|
|
|
// Make the popup focusable for accessibility. This gets done here
|
|
|
|
// so the node can be accessibility focused, but on pre-ICS devices this
|
|
|
|
// causes crashes, so it is done after the popup is shown.
|
|
|
|
if (Build.VERSION.SDK_INT >= 14) {
|
|
|
|
setFocusable(true);
|
|
|
|
}
|
|
|
|
|
2013-06-20 08:06:15 -07:00
|
|
|
show();
|
2012-08-04 16:34:42 -07:00
|
|
|
|
2013-06-13 12:42:45 -07:00
|
|
|
if (Build.VERSION.SDK_INT < 14) {
|
|
|
|
// Make the popup focusable for keyboard accessibility.
|
|
|
|
setFocusable(true);
|
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2013-03-11 14:07:11 -07:00
|
|
|
private void showDividers() {
|
|
|
|
int count = mContent.getChildCount();
|
|
|
|
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
|
|
DoorHanger dh = (DoorHanger) mContent.getChildAt(i);
|
|
|
|
dh.showDivider();
|
|
|
|
}
|
|
|
|
|
|
|
|
((DoorHanger) mContent.getChildAt(count-1)).hideDivider();
|
|
|
|
}
|
|
|
|
|
2012-08-02 18:38:45 -07:00
|
|
|
private void registerEventListener(String event) {
|
|
|
|
GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void unregisterEventListener(String event) {
|
|
|
|
GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
|
|
|
|
}
|
2012-09-07 22:31:54 -07:00
|
|
|
|
|
|
|
@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);
|
|
|
|
super.dismiss();
|
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|