Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 package org.mozilla.gecko.widget;
8 import org.mozilla.gecko.R;
9 import org.mozilla.gecko.Tabs;
10 import org.mozilla.gecko.prompts.PromptInput;
12 import org.json.JSONArray;
13 import org.json.JSONException;
14 import org.json.JSONObject;
16 import android.content.Context;
17 import android.content.res.Resources;
18 import android.graphics.Rect;
19 import android.os.Build;
20 import android.text.SpannableString;
21 import android.text.TextUtils;
22 import android.text.method.LinkMovementMethod;
23 import android.text.style.ForegroundColorSpan;
24 import android.text.style.URLSpan;
25 import android.util.Log;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.Button;
30 import android.widget.CheckBox;
31 import android.widget.ImageView;
32 import android.widget.LinearLayout;
33 import android.widget.Spinner;
34 import android.widget.SpinnerAdapter;
35 import android.widget.TextView;
37 import java.util.ArrayList;
38 import java.util.List;
40 public class DoorHanger extends LinearLayout {
41 private static final String LOGTAG = "GeckoDoorHanger";
43 private static int sInputPadding = -1;
44 private static int sSpinnerTextColor = -1;
45 private static int sSpinnerTextSize = -1;
47 private static LayoutParams sButtonParams;
48 static {
49 sButtonParams = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT, 1.0f);
50 }
52 private final TextView mTextView;
53 private final ImageView mIcon;
54 private final LinearLayout mChoicesLayout;
56 // Divider between doorhangers.
57 private final View mDivider;
59 // The tab associated with this notification.
60 private final int mTabId;
62 // Value used to identify the notification.
63 private final String mValue;
65 private Resources mResources;
67 private List<PromptInput> mInputs;
68 private CheckBox mCheckBox;
70 private int mPersistence = 0;
71 private boolean mPersistWhileVisible = false;
72 private long mTimeout = 0;
74 // Color used for dividers above and between buttons.
75 private int mDividerColor;
77 public static enum Theme {
78 LIGHT,
79 DARK
80 }
82 public interface OnButtonClickListener {
83 public void onButtonClick(DoorHanger dh, String tag);
84 }
86 public DoorHanger(Context context, Theme theme) {
87 this(context, 0, null, theme);
88 }
90 public DoorHanger(Context context, int tabId, String value) {
91 this(context, tabId, value, Theme.LIGHT);
92 }
94 private DoorHanger(Context context, int tabId, String value, Theme theme) {
95 super(context);
97 mTabId = tabId;
98 mValue = value;
99 mResources = getResources();
101 if (sInputPadding == -1) {
102 sInputPadding = mResources.getDimensionPixelSize(R.dimen.doorhanger_padding);
103 }
104 if (sSpinnerTextColor == -1) {
105 sSpinnerTextColor = mResources.getColor(R.color.text_color_primary_disable_only);
106 }
107 if (sSpinnerTextSize == -1) {
108 sSpinnerTextSize = mResources.getDimensionPixelSize(R.dimen.doorhanger_spinner_textsize);
109 }
111 setOrientation(VERTICAL);
113 LayoutInflater.from(context).inflate(R.layout.doorhanger, this);
114 mTextView = (TextView) findViewById(R.id.doorhanger_title);
115 mIcon = (ImageView) findViewById(R.id.doorhanger_icon);
116 mChoicesLayout = (LinearLayout) findViewById(R.id.doorhanger_choices);
117 mDivider = findViewById(R.id.divider_doorhanger);
119 setTheme(theme);
120 }
122 private void setTheme(Theme theme) {
123 if (theme == Theme.LIGHT) {
124 // The default styles declared in doorhanger.xml are light-themed, so we just
125 // need to set the divider color that we'll use in addButton.
126 mDividerColor = mResources.getColor(R.color.doorhanger_divider_light);
128 } else if (theme == Theme.DARK) {
129 mDividerColor = mResources.getColor(R.color.doorhanger_divider_dark);
131 // Set a dark background, and use a smaller text size for dark-themed DoorHangers.
132 setBackgroundColor(mResources.getColor(R.color.doorhanger_background_dark));
133 mTextView.setTextSize(mResources.getDimension(R.dimen.doorhanger_textsize_small));
134 }
135 }
137 public int getTabId() {
138 return mTabId;
139 }
141 public String getValue() {
142 return mValue;
143 }
145 public List<PromptInput> getInputs() {
146 return mInputs;
147 }
149 public CheckBox getCheckBox() {
150 return mCheckBox;
151 }
153 public void showDivider() {
154 mDivider.setVisibility(View.VISIBLE);
155 }
157 public void hideDivider() {
158 mDivider.setVisibility(View.GONE);
159 }
161 public void setMessage(String message) {
162 mTextView.setText(message);
163 }
165 public void setIcon(int resId) {
166 mIcon.setImageResource(resId);
167 mIcon.setVisibility(View.VISIBLE);
168 }
170 public void addLink(String label, String url, String delimiter) {
171 String title = mTextView.getText().toString();
172 SpannableString titleWithLink = new SpannableString(title + delimiter + label);
173 URLSpan linkSpan = new URLSpan(url) {
174 @Override
175 public void onClick(View view) {
176 Tabs.getInstance().loadUrlInTab(getURL());
177 }
178 };
180 // Prevent text outside the link from flashing when clicked.
181 ForegroundColorSpan colorSpan = new ForegroundColorSpan(mTextView.getCurrentTextColor());
182 titleWithLink.setSpan(colorSpan, 0, title.length(), 0);
184 titleWithLink.setSpan(linkSpan, title.length() + 1, titleWithLink.length(), 0);
185 mTextView.setText(titleWithLink);
186 mTextView.setMovementMethod(LinkMovementMethod.getInstance());
187 }
189 public void addButton(final String text, final String tag, final OnButtonClickListener listener) {
190 final Button button = (Button) LayoutInflater.from(getContext()).inflate(R.layout.doorhanger_button, null);
191 button.setText(text);
192 button.setTag(tag);
194 button.setOnClickListener(new Button.OnClickListener() {
195 @Override
196 public void onClick(View v) {
197 listener.onButtonClick(DoorHanger.this, tag);
198 }
199 });
201 if (mChoicesLayout.getChildCount() == 0) {
202 // If this is the first button we're adding, make the choices layout visible.
203 mChoicesLayout.setVisibility(View.VISIBLE);
204 // Make the divider above the buttons visible.
205 View divider = findViewById(R.id.divider_choices);
206 divider.setVisibility(View.VISIBLE);
207 divider.setBackgroundColor(mDividerColor);
208 } else {
209 // Add a vertical divider between additional buttons.
210 Divider divider = new Divider(getContext(), null);
211 divider.setOrientation(Divider.Orientation.VERTICAL);
212 divider.setBackgroundColor(mDividerColor);
213 mChoicesLayout.addView(divider);
214 }
216 mChoicesLayout.addView(button, sButtonParams);
217 }
219 public void setOptions(final JSONObject options) {
220 final int persistence = options.optInt("persistence");
221 if (persistence > 0) {
222 mPersistence = persistence;
223 }
225 mPersistWhileVisible = options.optBoolean("persistWhileVisible");
227 final long timeout = options.optLong("timeout");
228 if (timeout > 0) {
229 mTimeout = timeout;
230 }
232 final JSONObject link = options.optJSONObject("link");
233 if (link != null) {
234 try {
235 final String linkLabel = link.getString("label");
236 final String linkUrl = link.getString("url");
237 addLink(linkLabel, linkUrl, " ");
238 } catch (JSONException e) { }
239 }
241 final JSONArray inputs = options.optJSONArray("inputs");
242 if (inputs != null) {
243 mInputs = new ArrayList<PromptInput>();
245 final ViewGroup group = (ViewGroup) findViewById(R.id.doorhanger_inputs);
246 group.setVisibility(VISIBLE);
248 for (int i = 0; i < inputs.length(); i++) {
249 try {
250 PromptInput input = PromptInput.getInput(inputs.getJSONObject(i));
251 mInputs.add(input);
253 View v = input.getView(getContext());
254 styleInput(input, v);
255 group.addView(v);
256 } catch(JSONException ex) { }
257 }
258 }
260 final String checkBoxText = options.optString("checkbox");
261 if (!TextUtils.isEmpty(checkBoxText)) {
262 mCheckBox = (CheckBox) findViewById(R.id.doorhanger_checkbox);
263 mCheckBox.setText(checkBoxText);
264 mCheckBox.setVisibility(VISIBLE);
265 }
266 }
268 private void styleInput(PromptInput input, View view) {
269 if (input instanceof PromptInput.MenulistInput) {
270 styleSpinner(input, view);
271 } else {
272 // add some top and bottom padding to separate inputs
273 view.setPadding(0, sInputPadding,
274 0, sInputPadding);
275 }
276 }
278 private void styleSpinner(PromptInput input, View view) {
279 PromptInput.MenulistInput spinInput = (PromptInput.MenulistInput) input;
281 /* Spinners have some intrinsic padding. To force the spinner's text to line up with
282 * the doorhanger text, we have to take that padding into account.
283 *
284 * |-----A-------| <-- Normal doorhanger message
285 * |-B-|---C+D---| <-- (optional) Spinner Label
286 * |-B-|-C-|--D--| <-- Spinner
287 *
288 * A - Desired padding (sInputPadding)
289 * B - Final padding applied to input element (sInputPadding - rect.left - textPadding).
290 * C - Spinner background drawable padding (rect.left).
291 * D - Spinner inner TextView padding (textPadding).
292 */
294 // First get the padding of the selected view inside the spinner. Since the spinner
295 // hasn't been shown yet, we get this view directly from the adapter.
296 Spinner spinner = spinInput.spinner;
297 SpinnerAdapter adapter = spinner.getAdapter();
298 View dropView = adapter.getView(0, null, spinner);
299 int textPadding = 0;
300 if (dropView != null) {
301 textPadding = dropView.getPaddingLeft();
302 }
304 // Then get the intrinsic padding built into the background image of the spinner.
305 Rect rect = new Rect();
306 spinner.getBackground().getPadding(rect);
308 // Set the difference in padding to the spinner view to align it with doorhanger text.
309 view.setPadding(sInputPadding - rect.left - textPadding, 0, rect.right, sInputPadding);
311 if (spinInput.textView != null) {
312 spinInput.textView.setTextColor(sSpinnerTextColor);
313 spinInput.textView.setTextSize(sSpinnerTextSize);
315 // If this spinner has a label, offset it to also be aligned with the doorhanger text.
316 spinInput.textView.setPadding(rect.left + textPadding, 0, 0, 0);
317 }
318 }
321 /*
322 * Checks with persistence and timeout options to see if it's okay to remove a doorhanger.
323 *
324 * @param isShowing Whether or not this doorhanger is currently visible to the user.
325 * (e.g. the DoorHanger view might be VISIBLE, but its parent could be hidden)
326 */
327 public boolean shouldRemove(boolean isShowing) {
328 if (mPersistWhileVisible && isShowing) {
329 // We still want to decrement mPersistence, even if the popup is showing
330 if (mPersistence != 0)
331 mPersistence--;
332 return false;
333 }
335 // If persistence is set to -1, the doorhanger will never be
336 // automatically removed.
337 if (mPersistence != 0) {
338 mPersistence--;
339 return false;
340 }
342 if (System.currentTimeMillis() <= mTimeout) {
343 return false;
344 }
346 return true;
347 }
348 }