Bug 1144385 - Refactor buttons into DoorHanger. r=margaret

This commit is contained in:
Chenxia Liu 2015-03-30 19:26:14 -07:00
parent 92ec763d23
commit c75a82b501
6 changed files with 193 additions and 131 deletions

View File

@ -6,13 +6,10 @@
package org.mozilla.gecko;
import java.util.HashSet;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.prompts.PromptInput;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.widget.AnchoredPopup;
@ -21,7 +18,6 @@ import org.mozilla.gecko.widget.DoorHanger;
import android.content.Context;
import android.util.Log;
import android.view.View;
import android.widget.CheckBox;
import org.mozilla.gecko.widget.DoorhangerConfig;
public class DoorHangerPopup extends AnchoredPopup
@ -109,15 +105,16 @@ public class DoorHangerPopup extends AnchoredPopup
private DoorhangerConfig makeConfigFromJSON(JSONObject json) throws JSONException {
final int tabId = json.getInt("tabID");
final String id = json.getString("value");
final DoorhangerConfig config = new DoorhangerConfig(tabId, id);
final String typeString = json.optString("category");
final boolean isLogin = DoorHanger.Type.LOGIN.toString().equals(typeString);
final DoorHanger.Type doorhangerType = isLogin ? DoorHanger.Type.LOGIN : DoorHanger.Type.DEFAULT;
final DoorhangerConfig config = new DoorhangerConfig(tabId, id, doorhangerType, this);
config.setMessage(json.getString("message"));
config.setButtons(json.getJSONArray("buttons"));
config.appendButtonsFromJSON(json.getJSONArray("buttons"));
config.setOptions(json.getJSONObject("options"));
final String typeString = json.optString("category");
if (DoorHanger.Type.LOGIN.toString().equals(typeString)) {
config.setType(DoorHanger.Type.LOGIN);
}
return config;
}
@ -181,18 +178,6 @@ public class DoorHangerPopup extends AnchoredPopup
final DoorHanger newDoorHanger = DoorHanger.Get(mContext, config);
final JSONArray buttons = config.getButtons();
for (int i = 0; i < buttons.length(); i++) {
try {
JSONObject buttonObject = buttons.getJSONObject(i);
String label = buttonObject.getString("label");
String tag = String.valueOf(buttonObject.getInt("callback"));
newDoorHanger.addButton(label, tag, this);
} catch (JSONException e) {
Log.e(LOGTAG, "Error creating doorhanger button", e);
}
}
mDoorHangers.add(newDoorHanger);
mContent.addView(newDoorHanger);
@ -206,32 +191,10 @@ public class DoorHangerPopup extends AnchoredPopup
* DoorHanger.OnButtonClickListener implementation
*/
@Override
public void onButtonClick(DoorHanger dh, String tag) {
JSONObject response = new JSONObject();
try {
response.put("callback", tag);
CheckBox checkBox = dh.getCheckBox();
// If the checkbox is being used, pass its value
if (checkBox != null) {
response.put("checked", checkBox.isChecked());
}
List<PromptInput> doorHangerInputs = dh.getInputs();
if (doorHangerInputs != null) {
JSONObject inputs = new JSONObject();
for (PromptInput input : doorHangerInputs) {
inputs.put(input.getId(), input.getValue());
}
response.put("inputs", inputs);
}
} catch (JSONException e) {
Log.e(LOGTAG, "Error creating onClick response", e);
}
public void onButtonClick(JSONObject response, DoorHanger doorhanger) {
GeckoEvent e = GeckoEvent.createBroadcastEvent("Doorhanger:Reply", response.toString());
GeckoAppShell.sendEventToGecko(e);
removeDoorHanger(dh);
removeDoorHanger(doorhanger);
updatePopup();
}

View File

@ -17,7 +17,6 @@ import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.widget.AnchoredPopup;
import org.mozilla.gecko.widget.DoorHanger;
import org.mozilla.gecko.widget.DoorHanger.OnButtonClickListener;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
@ -34,6 +33,8 @@ import org.mozilla.gecko.widget.DoorhangerConfig;
* an arrow panel popup hanging from the lock icon in the browser toolbar.
*/
public class SiteIdentityPopup extends AnchoredPopup {
public static enum ButtonType { DISABLE, ENABLE, KEEP_BLOCKING };
private static final String LOGTAG = "GeckoSiteIdentityPopup";
private static final String MIXED_CONTENT_SUPPORT_URL =
@ -142,7 +143,7 @@ public class SiteIdentityPopup extends AnchoredPopup {
// Remove any existing mixed content notification.
removeMixedContentNotification();
final DoorhangerConfig config = new DoorhangerConfig();
final DoorhangerConfig config = new DoorhangerConfig(DoorHanger.Type.MIXED_CONTENT, mButtonClickListener);
int icon;
if (blocked) {
icon = R.drawable.shield_enabled_doorhanger;
@ -154,11 +155,11 @@ public class SiteIdentityPopup extends AnchoredPopup {
}
config.setLink(mContext.getString(R.string.learn_more), MIXED_CONTENT_SUPPORT_URL, "\n\n");
config.setType(DoorHanger.Type.SITE);
addNotificationButtons(config, blocked);
mMixedContentNotification = DoorHanger.Get(mContext, config);
mMixedContentNotification.setIcon(icon);
addNotificationButtons(mMixedContentNotification, blocked);
mContent.addView(mMixedContentNotification);
mDivider.setVisibility(View.VISIBLE);
@ -175,7 +176,7 @@ public class SiteIdentityPopup extends AnchoredPopup {
// Remove any existing tracking content notification.
removeTrackingContentNotification();
final DoorhangerConfig config = new DoorhangerConfig();
final DoorhangerConfig config = new DoorhangerConfig(DoorHanger.Type.TRACKING, mButtonClickListener);
int icon;
if (blocked) {
@ -189,12 +190,12 @@ public class SiteIdentityPopup extends AnchoredPopup {
}
config.setLink(mContext.getString(R.string.learn_more), TRACKING_CONTENT_SUPPORT_URL, "\n\n");
config.setType(DoorHanger.Type.SITE);
addNotificationButtons(config, blocked);
mTrackingContentNotification = DoorHanger.Get(mContext, config);
mTrackingContentNotification.setIcon(icon);
addNotificationButtons(mTrackingContentNotification, blocked);
mContent.addView(mTrackingContentNotification);
mDivider.setVisibility(View.VISIBLE);
@ -207,13 +208,12 @@ public class SiteIdentityPopup extends AnchoredPopup {
}
}
private void addNotificationButtons(DoorHanger dh, boolean blocked) {
// TODO: Add support for buttons in DoorHangerConfig.
private void addNotificationButtons(DoorhangerConfig config, boolean blocked) {
if (blocked) {
dh.addButton(mContext.getString(R.string.disable_protection), "disable", mButtonClickListener);
dh.addButton(mContext.getString(R.string.keep_blocking), "keepBlocking", mButtonClickListener);
config.appendButton(mContext.getString(R.string.disable_protection), ButtonType.DISABLE.ordinal());
config.appendButton(mContext.getString(R.string.keep_blocking), ButtonType.KEEP_BLOCKING.ordinal());
} else {
dh.addButton(mContext.getString(R.string.enable_protection), "enable", mButtonClickListener);
config.appendButton(mContext.getString(R.string.enable_protection), ButtonType.ENABLE.ordinal());
}
}
@ -290,18 +290,9 @@ public class SiteIdentityPopup extends AnchoredPopup {
private class PopupButtonListener implements OnButtonClickListener {
@Override
public void onButtonClick(DoorHanger dh, String tag) {
try {
JSONObject data = new JSONObject();
data.put("allowContent", tag.equals("disable"));
data.put("contentType", (dh == mMixedContentNotification ? "mixed" : "tracking"));
GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Reload", data.toString());
GeckoAppShell.sendEventToGecko(e);
} catch (JSONException e) {
Log.e(LOGTAG, "Exception creating message to enable/disable content blocking", e);
}
public void onButtonClick(JSONObject response, DoorHanger doorhanger) {
GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Reload", response.toString());
GeckoAppShell.sendEventToGecko(e);
dismiss();
}
}

View File

@ -5,6 +5,9 @@
package org.mozilla.gecko.widget;
import android.util.Log;
import android.view.LayoutInflater;
import android.widget.Button;
import org.mozilla.gecko.R;
import org.mozilla.gecko.prompts.PromptInput;
@ -18,6 +21,7 @@ import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import org.mozilla.gecko.toolbar.SiteIdentityPopup;
import java.util.ArrayList;
import java.util.List;
@ -31,10 +35,6 @@ public class DefaultDoorHanger extends DoorHanger {
private List<PromptInput> mInputs;
private CheckBox mCheckBox;
public DefaultDoorHanger(Context context, DoorhangerConfig config) {
this(context, config, Type.DEFAULT);
}
public DefaultDoorHanger(Context context, DoorhangerConfig config, Type type) {
super(context, config, type);
@ -62,15 +62,15 @@ public class DefaultDoorHanger extends DoorHanger {
if (link != null) {
addLink(link.label, link.url, link.delimiter);
}
setButtons(config);
}
@Override
public List<PromptInput> getInputs() {
private List<PromptInput> getInputs() {
return mInputs;
}
@Override
public CheckBox getCheckBox() {
private CheckBox getCheckBox() {
return mCheckBox;
}
@ -115,6 +115,54 @@ public class DefaultDoorHanger extends DoorHanger {
}
}
@Override
protected Button createButtonInstance(final String text, final int id) {
final Button button = (Button) LayoutInflater.from(getContext()).inflate(R.layout.doorhanger_button, null);
button.setText(text);
button.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View v) {
final JSONObject response = new JSONObject();
try {
// TODO: Bug 1149359 - Split this into each Doorhanger Type class.
switch (mType) {
case MIXED_CONTENT:
response.put("allowContent", (id == SiteIdentityPopup.ButtonType.DISABLE.ordinal()));
response.put("contentType", ("mixed"));
break;
case TRACKING:
response.put("allowContent", (id == SiteIdentityPopup.ButtonType.DISABLE.ordinal()));
response.put("contentType", ("tracking"));
break;
default:
response.put("callback", id);
CheckBox checkBox = getCheckBox();
// If the checkbox is being used, pass its value
if (checkBox != null) {
response.put("checked", checkBox.isChecked());
}
List<PromptInput> doorHangerInputs = getInputs();
if (doorHangerInputs != null) {
JSONObject inputs = new JSONObject();
for (PromptInput input : doorHangerInputs) {
inputs.put(input.getId(), input.getValue());
}
response.put("inputs", inputs);
}
}
mOnButtonClickListener.onButtonClick(response, DefaultDoorHanger.this);
} catch (JSONException e) {
Log.e(LOGTAG, "Error creating onClick response", e);
}
}
});
return button;
}
private void styleInput(PromptInput input, View view) {
if (input instanceof PromptInput.MenulistInput) {
styleDropdownInputs(input, view);

View File

@ -12,43 +12,40 @@ import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.text.style.ForegroundColorSpan;
import android.text.style.URLSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.prompts.PromptInput;
import java.util.List;
public abstract class DoorHanger extends LinearLayout {
public static DoorHanger Get(Context context, DoorhangerConfig config) {
final Type type = config.getType();
if (type != null) {
switch (type) {
case LOGIN:
return new LoginDoorHanger(context, config);
case SITE:
return new DefaultDoorHanger(context, config, type);
}
switch (type) {
case LOGIN:
return new LoginDoorHanger(context, config);
case TRACKING:
case MIXED_CONTENT:
return new DefaultDoorHanger(context, config, type);
}
return new DefaultDoorHanger(context, config);
return new DefaultDoorHanger(context, config, type);
}
public static enum Type { DEFAULT, LOGIN, SITE }
public static enum Type { DEFAULT, LOGIN, TRACKING, MIXED_CONTENT}
public interface OnButtonClickListener {
public void onButtonClick(DoorHanger dh, String tag);
public void onButtonClick(JSONObject response, DoorHanger doorhanger);
}
private static final LayoutParams sButtonParams;
protected static final LayoutParams sButtonParams;
static {
sButtonParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 1.0f);
}
@ -58,7 +55,8 @@ public abstract class DoorHanger extends LinearLayout {
// Divider between doorhangers.
private final View mDivider;
private final LinearLayout mButtonsContainer;
protected final LinearLayout mButtonsContainer;
protected final OnButtonClickListener mOnButtonClickListener;
// The tab this doorhanger is associated with.
private final int mTabId;
@ -66,6 +64,8 @@ public abstract class DoorHanger extends LinearLayout {
// DoorHanger identifier.
private final String mIdentifier;
protected final Type mType;
private final ImageView mIcon;
private final TextView mMessage;
@ -96,16 +96,22 @@ public abstract class DoorHanger extends LinearLayout {
mDivider = findViewById(R.id.divider_doorhanger);
mIcon = (ImageView) findViewById(R.id.doorhanger_icon);
mMessage = (TextView) findViewById(R.id.doorhanger_message);
if (type == Type.SITE) {
// TODO: Bug 1149359 - split this into DoorHanger subclasses.
if (type == Type.TRACKING || type == Type.MIXED_CONTENT) {
mMessage.setTextAppearance(getContext(), R.style.TextAppearance_DoorHanger_Small);
}
mType = type;
mButtonsContainer = (LinearLayout) findViewById(R.id.doorhanger_buttons);
mOnButtonClickListener = config.getButtonClickListener();
mDividerColor = getResources().getColor(R.color.divider_light);
setOrientation(VERTICAL);
}
abstract protected void loadConfig(DoorhangerConfig config);
protected abstract void loadConfig(DoorhangerConfig config);
protected void setOptions(final JSONObject options) {
final int persistence = options.optInt("persistence");
@ -119,6 +125,21 @@ public abstract class DoorHanger extends LinearLayout {
if (timeout > 0) {
mTimeout = timeout;
}
}
protected void setButtons(DoorhangerConfig config) {
final JSONArray buttons = config.getButtons();
final OnButtonClickListener listener = config.getButtonClickListener();
for (int i = 0; i < buttons.length(); i++) {
try {
final JSONObject buttonObject = buttons.getJSONObject(i);
final String label = buttonObject.getString("label");
final int callbackId = buttonObject.getInt("callback");
addButtonToLayout(label, callbackId);
} catch (JSONException e) {
Log.e(LOGTAG, "Error creating doorhanger button", e);
}
}
}
public int getTabId() {
@ -166,18 +187,13 @@ public abstract class DoorHanger extends LinearLayout {
mMessage.setMovementMethod(LinkMovementMethod.getInstance());
}
public void addButton(final String text, final String tag, final OnButtonClickListener listener) {
final Button button = (Button) LayoutInflater.from(getContext()).inflate(R.layout.doorhanger_button, null);
button.setText(text);
button.setTag(tag);
button.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View v) {
listener.onButtonClick(DoorHanger.this, tag);
}
});
/**
* Creates and adds a button into the DoorHanger.
* @param text Button text
* @param id Identifier associated with the button
*/
private void addButtonToLayout(String text, int id) {
final Button button = createButtonInstance(text, id);
if (mButtonsContainer.getChildCount() == 0) {
// If this is the first button we're adding, make the choices layout visible.
mButtonsContainer.setVisibility(View.VISIBLE);
@ -195,6 +211,8 @@ public abstract class DoorHanger extends LinearLayout {
mButtonsContainer.addView(button, sButtonParams);
}
protected abstract Button createButtonInstance(String text, int id);
/*
* Checks with persistence and timeout options to see if it's okay to remove a doorhanger.
*
@ -222,14 +240,4 @@ public abstract class DoorHanger extends LinearLayout {
return true;
}
// TODO: remove and expose through instance Button Handler.
public List<PromptInput> getInputs() {
return null;
}
public CheckBox getCheckBox() {
return null;
}
}

View File

@ -5,7 +5,9 @@
package org.mozilla.gecko.widget;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.widget.DoorHanger.Type;
@ -24,23 +26,29 @@ public class DoorhangerConfig {
}
}
private static final String LOGTAG = "DoorhangerConfig";
private final int tabId;
private final String id;
private DoorHanger.Type type;
private final DoorHanger.OnButtonClickListener buttonClickListener;
private final DoorHanger.Type type;
private String message;
private JSONObject options;
private Link link;
private JSONArray buttons;
private JSONArray buttons = new JSONArray();
public DoorhangerConfig() {
public DoorhangerConfig(Type type, DoorHanger.OnButtonClickListener listener) {
// XXX: This should only be used by SiteIdentityPopup doorhangers which
// don't need tab or id references, until bug 1141904 unifies doorhangers.
this(-1, null);
this(-1, null, type, listener);
}
public DoorhangerConfig(int tabId, String id) {
public DoorhangerConfig(int tabId, String id, DoorHanger.Type type, DoorHanger.OnButtonClickListener buttonClickListener) {
this.tabId = tabId;
this.id = id;
this.type = type;
this.buttonClickListener = buttonClickListener;
}
public int getTabId() {
@ -51,10 +59,6 @@ public class DoorhangerConfig {
return id;
}
public void setType(Type type) {
this.type = type;
}
public Type getType() {
return type;
}
@ -75,8 +79,33 @@ public class DoorhangerConfig {
return options;
}
public void setButtons(JSONArray buttons) {
this.buttons = buttons;
/**
* Add buttons from JSON to the Config object.
* @param buttons JSONArray of JSONObjects of the form { label: <label>, callback: <callback_id> }
*/
public void appendButtonsFromJSON(JSONArray buttons) {
try {
for (int i = 0; i < buttons.length(); i++) {
this.buttons.put(buttons.get(i));
}
} catch (JSONException e) {
Log.e(LOGTAG, "Error parsing buttons from JSON", e);
}
}
public void appendButton(String label, int callbackId) {
final JSONObject button = new JSONObject();
try {
button.put("label", label);
button.put("callback", callbackId);
this.buttons.put(button);
} catch (JSONException e) {
Log.e(LOGTAG, "Error creating button", e);
}
}
public DoorHanger.OnButtonClickListener getButtonClickListener() {
return this.buttonClickListener;
}
public JSONArray getButtons() {

View File

@ -9,7 +9,9 @@ import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import ch.boye.httpclientandroidlib.util.TextUtils;
import org.json.JSONException;
@ -37,7 +39,7 @@ public class LoginDoorHanger extends DoorHanger {
protected void loadConfig(DoorhangerConfig config) {
setOptions(config.getOptions());
setMessage(config.getMessage());
setButtons(config);
}
@Override
@ -76,4 +78,25 @@ public class LoginDoorHanger extends DoorHanger {
mLogin.setVisibility(View.GONE);
}
}
@Override
protected Button createButtonInstance(final String text, final int id) {
final Button button = (Button) LayoutInflater.from(getContext()).inflate(R.layout.doorhanger_button, null);
button.setText(text);
button.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View v) {
final JSONObject response = new JSONObject();
try {
response.put("callback", id);
} catch (JSONException e) {
Log.e(LOGTAG, "Error making doorhanger response message");
}
mOnButtonClickListener.onButtonClick(response, LoginDoorHanger.this);
}
});
return button;
}
}