mobile/android/base/DoorHangerPopup.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

michael@0 1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
michael@0 2 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 package org.mozilla.gecko;
michael@0 7
michael@0 8 import org.mozilla.gecko.util.GeckoEventListener;
michael@0 9 import org.mozilla.gecko.widget.ArrowPopup;
michael@0 10 import org.mozilla.gecko.widget.DoorHanger;
michael@0 11 import org.mozilla.gecko.prompts.PromptInput;
michael@0 12
michael@0 13 import org.json.JSONArray;
michael@0 14 import org.json.JSONException;
michael@0 15 import org.json.JSONObject;
michael@0 16
michael@0 17 import android.os.Build;
michael@0 18 import android.util.Log;
michael@0 19 import android.view.View;
michael@0 20 import android.widget.CheckBox;
michael@0 21
michael@0 22 import java.util.HashSet;
michael@0 23 import java.util.List;
michael@0 24
michael@0 25 public class DoorHangerPopup extends ArrowPopup
michael@0 26 implements GeckoEventListener,
michael@0 27 Tabs.OnTabsChangedListener,
michael@0 28 DoorHanger.OnButtonClickListener {
michael@0 29 private static final String LOGTAG = "GeckoDoorHangerPopup";
michael@0 30
michael@0 31 // Stores a set of all active DoorHanger notifications. A DoorHanger is
michael@0 32 // uniquely identified by its tabId and value.
michael@0 33 private HashSet<DoorHanger> mDoorHangers;
michael@0 34
michael@0 35 // Whether or not the doorhanger popup is disabled.
michael@0 36 private boolean mDisabled;
michael@0 37
michael@0 38 DoorHangerPopup(GeckoApp activity) {
michael@0 39 super(activity);
michael@0 40
michael@0 41 mDoorHangers = new HashSet<DoorHanger>();
michael@0 42
michael@0 43 registerEventListener("Doorhanger:Add");
michael@0 44 registerEventListener("Doorhanger:Remove");
michael@0 45 Tabs.registerOnTabsChangedListener(this);
michael@0 46 }
michael@0 47
michael@0 48 void destroy() {
michael@0 49 unregisterEventListener("Doorhanger:Add");
michael@0 50 unregisterEventListener("Doorhanger:Remove");
michael@0 51 Tabs.unregisterOnTabsChangedListener(this);
michael@0 52 }
michael@0 53
michael@0 54 /**
michael@0 55 * Temporarily disables the doorhanger popup. If the popup is disabled,
michael@0 56 * it will not be shown to the user, but it will continue to process
michael@0 57 * calls to add/remove doorhanger notifications.
michael@0 58 */
michael@0 59 void disable() {
michael@0 60 mDisabled = true;
michael@0 61 updatePopup();
michael@0 62 }
michael@0 63
michael@0 64 /**
michael@0 65 * Re-enables the doorhanger popup.
michael@0 66 */
michael@0 67 void enable() {
michael@0 68 mDisabled = false;
michael@0 69 updatePopup();
michael@0 70 }
michael@0 71
michael@0 72 @Override
michael@0 73 public void handleMessage(String event, JSONObject geckoObject) {
michael@0 74 try {
michael@0 75 if (event.equals("Doorhanger:Add")) {
michael@0 76 final int tabId = geckoObject.getInt("tabID");
michael@0 77 final String value = geckoObject.getString("value");
michael@0 78 final String message = geckoObject.getString("message");
michael@0 79 final JSONArray buttons = geckoObject.getJSONArray("buttons");
michael@0 80 final JSONObject options = geckoObject.getJSONObject("options");
michael@0 81
michael@0 82 mActivity.runOnUiThread(new Runnable() {
michael@0 83 @Override
michael@0 84 public void run() {
michael@0 85 addDoorHanger(tabId, value, message, buttons, options);
michael@0 86 }
michael@0 87 });
michael@0 88 } else if (event.equals("Doorhanger:Remove")) {
michael@0 89 final int tabId = geckoObject.getInt("tabID");
michael@0 90 final String value = geckoObject.getString("value");
michael@0 91
michael@0 92 mActivity.runOnUiThread(new Runnable() {
michael@0 93 @Override
michael@0 94 public void run() {
michael@0 95 DoorHanger doorHanger = getDoorHanger(tabId, value);
michael@0 96 if (doorHanger == null)
michael@0 97 return;
michael@0 98
michael@0 99 removeDoorHanger(doorHanger);
michael@0 100 updatePopup();
michael@0 101 }
michael@0 102 });
michael@0 103 }
michael@0 104 } catch (Exception e) {
michael@0 105 Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
michael@0 106 }
michael@0 107 }
michael@0 108
michael@0 109 // This callback is automatically executed on the UI thread.
michael@0 110 @Override
michael@0 111 public void onTabChanged(final Tab tab, final Tabs.TabEvents msg, final Object data) {
michael@0 112 switch(msg) {
michael@0 113 case CLOSED:
michael@0 114 // Remove any doorhangers for a tab when it's closed (make
michael@0 115 // a temporary set to avoid a ConcurrentModificationException)
michael@0 116 HashSet<DoorHanger> doorHangersToRemove = new HashSet<DoorHanger>();
michael@0 117 for (DoorHanger dh : mDoorHangers) {
michael@0 118 if (dh.getTabId() == tab.getId())
michael@0 119 doorHangersToRemove.add(dh);
michael@0 120 }
michael@0 121 for (DoorHanger dh : doorHangersToRemove) {
michael@0 122 removeDoorHanger(dh);
michael@0 123 }
michael@0 124 break;
michael@0 125
michael@0 126 case LOCATION_CHANGE:
michael@0 127 // Only remove doorhangers if the popup is hidden or if we're navigating to a new URL
michael@0 128 if (!isShowing() || !data.equals(tab.getURL()))
michael@0 129 removeTransientDoorHangers(tab.getId());
michael@0 130
michael@0 131 // Update the popup if the location change was on the current tab
michael@0 132 if (Tabs.getInstance().isSelectedTab(tab))
michael@0 133 updatePopup();
michael@0 134 break;
michael@0 135
michael@0 136 case SELECTED:
michael@0 137 // Always update the popup when a new tab is selected. This will cover cases
michael@0 138 // where a different tab was closed, since we always need to select a new tab.
michael@0 139 updatePopup();
michael@0 140 break;
michael@0 141 }
michael@0 142 }
michael@0 143
michael@0 144 /**
michael@0 145 * Adds a doorhanger.
michael@0 146 *
michael@0 147 * This method must be called on the UI thread.
michael@0 148 */
michael@0 149 void addDoorHanger(final int tabId, final String value, final String message,
michael@0 150 final JSONArray buttons, final JSONObject options) {
michael@0 151 // Don't add a doorhanger for a tab that doesn't exist
michael@0 152 if (Tabs.getInstance().getTab(tabId) == null) {
michael@0 153 return;
michael@0 154 }
michael@0 155
michael@0 156 // Replace the doorhanger if it already exists
michael@0 157 DoorHanger oldDoorHanger = getDoorHanger(tabId, value);
michael@0 158 if (oldDoorHanger != null) {
michael@0 159 removeDoorHanger(oldDoorHanger);
michael@0 160 }
michael@0 161
michael@0 162 if (!mInflated) {
michael@0 163 init();
michael@0 164 }
michael@0 165
michael@0 166 final DoorHanger newDoorHanger = new DoorHanger(mActivity, tabId, value);
michael@0 167 newDoorHanger.setMessage(message);
michael@0 168 newDoorHanger.setOptions(options);
michael@0 169
michael@0 170 for (int i = 0; i < buttons.length(); i++) {
michael@0 171 try {
michael@0 172 JSONObject buttonObject = buttons.getJSONObject(i);
michael@0 173 String label = buttonObject.getString("label");
michael@0 174 String tag = String.valueOf(buttonObject.getInt("callback"));
michael@0 175 newDoorHanger.addButton(label, tag, this);
michael@0 176 } catch (JSONException e) {
michael@0 177 Log.e(LOGTAG, "Error creating doorhanger button", e);
michael@0 178 }
michael@0 179 }
michael@0 180
michael@0 181 mDoorHangers.add(newDoorHanger);
michael@0 182 mContent.addView(newDoorHanger);
michael@0 183
michael@0 184 // Only update the popup if we're adding a notifcation to the selected tab
michael@0 185 if (tabId == Tabs.getInstance().getSelectedTab().getId())
michael@0 186 updatePopup();
michael@0 187 }
michael@0 188
michael@0 189
michael@0 190 /*
michael@0 191 * DoorHanger.OnButtonClickListener implementation
michael@0 192 */
michael@0 193 @Override
michael@0 194 public void onButtonClick(DoorHanger dh, String tag) {
michael@0 195 JSONObject response = new JSONObject();
michael@0 196 try {
michael@0 197 response.put("callback", tag);
michael@0 198
michael@0 199 CheckBox checkBox = dh.getCheckBox();
michael@0 200 // If the checkbox is being used, pass its value
michael@0 201 if (checkBox != null) {
michael@0 202 response.put("checked", checkBox.isChecked());
michael@0 203 }
michael@0 204
michael@0 205 List<PromptInput> doorHangerInputs = dh.getInputs();
michael@0 206 if (doorHangerInputs != null) {
michael@0 207 JSONObject inputs = new JSONObject();
michael@0 208 for (PromptInput input : doorHangerInputs) {
michael@0 209 inputs.put(input.getId(), input.getValue());
michael@0 210 }
michael@0 211 response.put("inputs", inputs);
michael@0 212 }
michael@0 213 } catch (JSONException e) {
michael@0 214 Log.e(LOGTAG, "Error creating onClick response", e);
michael@0 215 }
michael@0 216
michael@0 217 GeckoEvent e = GeckoEvent.createBroadcastEvent("Doorhanger:Reply", response.toString());
michael@0 218 GeckoAppShell.sendEventToGecko(e);
michael@0 219 removeDoorHanger(dh);
michael@0 220 updatePopup();
michael@0 221 }
michael@0 222
michael@0 223 /**
michael@0 224 * Gets a doorhanger.
michael@0 225 *
michael@0 226 * This method must be called on the UI thread.
michael@0 227 */
michael@0 228 DoorHanger getDoorHanger(int tabId, String value) {
michael@0 229 for (DoorHanger dh : mDoorHangers) {
michael@0 230 if (dh.getTabId() == tabId && dh.getValue().equals(value))
michael@0 231 return dh;
michael@0 232 }
michael@0 233
michael@0 234 // If there's no doorhanger for the given tabId and value, return null
michael@0 235 return null;
michael@0 236 }
michael@0 237
michael@0 238 /**
michael@0 239 * Removes a doorhanger.
michael@0 240 *
michael@0 241 * This method must be called on the UI thread.
michael@0 242 */
michael@0 243 void removeDoorHanger(final DoorHanger doorHanger) {
michael@0 244 mDoorHangers.remove(doorHanger);
michael@0 245 mContent.removeView(doorHanger);
michael@0 246 }
michael@0 247
michael@0 248 /**
michael@0 249 * Removes doorhangers for a given tab.
michael@0 250 *
michael@0 251 * This method must be called on the UI thread.
michael@0 252 */
michael@0 253 void removeTransientDoorHangers(int tabId) {
michael@0 254 // Make a temporary set to avoid a ConcurrentModificationException
michael@0 255 HashSet<DoorHanger> doorHangersToRemove = new HashSet<DoorHanger>();
michael@0 256 for (DoorHanger dh : mDoorHangers) {
michael@0 257 // Only remove transient doorhangers for the given tab
michael@0 258 if (dh.getTabId() == tabId && dh.shouldRemove(isShowing()))
michael@0 259 doorHangersToRemove.add(dh);
michael@0 260 }
michael@0 261
michael@0 262 for (DoorHanger dh : doorHangersToRemove) {
michael@0 263 removeDoorHanger(dh);
michael@0 264 }
michael@0 265 }
michael@0 266
michael@0 267 /**
michael@0 268 * Updates the popup state.
michael@0 269 *
michael@0 270 * This method must be called on the UI thread.
michael@0 271 */
michael@0 272 void updatePopup() {
michael@0 273 // Bail if the selected tab is null, if there are no active doorhangers,
michael@0 274 // if we haven't inflated the layout yet (this can happen if updatePopup()
michael@0 275 // is called before the runnable from addDoorHanger() runs), or if the
michael@0 276 // doorhanger popup is temporarily disabled.
michael@0 277 Tab tab = Tabs.getInstance().getSelectedTab();
michael@0 278 if (tab == null || mDoorHangers.size() == 0 || !mInflated || mDisabled) {
michael@0 279 dismiss();
michael@0 280 return;
michael@0 281 }
michael@0 282
michael@0 283 // Show doorhangers for the selected tab
michael@0 284 int tabId = tab.getId();
michael@0 285 boolean shouldShowPopup = false;
michael@0 286 for (DoorHanger dh : mDoorHangers) {
michael@0 287 if (dh.getTabId() == tabId) {
michael@0 288 dh.setVisibility(View.VISIBLE);
michael@0 289 shouldShowPopup = true;
michael@0 290 } else {
michael@0 291 dh.setVisibility(View.GONE);
michael@0 292 }
michael@0 293 }
michael@0 294
michael@0 295 // Dismiss the popup if there are no doorhangers to show for this tab
michael@0 296 if (!shouldShowPopup) {
michael@0 297 dismiss();
michael@0 298 return;
michael@0 299 }
michael@0 300
michael@0 301 showDividers();
michael@0 302 if (isShowing()) {
michael@0 303 show();
michael@0 304 return;
michael@0 305 }
michael@0 306
michael@0 307 // Make the popup focusable for accessibility. This gets done here
michael@0 308 // so the node can be accessibility focused, but on pre-ICS devices this
michael@0 309 // causes crashes, so it is done after the popup is shown.
michael@0 310 if (Build.VERSION.SDK_INT >= 14) {
michael@0 311 setFocusable(true);
michael@0 312 }
michael@0 313
michael@0 314 show();
michael@0 315
michael@0 316 if (Build.VERSION.SDK_INT < 14) {
michael@0 317 // Make the popup focusable for keyboard accessibility.
michael@0 318 setFocusable(true);
michael@0 319 }
michael@0 320 }
michael@0 321
michael@0 322 //Show all inter-DoorHanger dividers (ie. Dividers on all visible DoorHangers except the last one)
michael@0 323 private void showDividers() {
michael@0 324 int count = mContent.getChildCount();
michael@0 325 DoorHanger lastVisibleDoorHanger = null;
michael@0 326
michael@0 327 for (int i = 0; i < count; i++) {
michael@0 328 DoorHanger dh = (DoorHanger) mContent.getChildAt(i);
michael@0 329 dh.showDivider();
michael@0 330 if (dh.getVisibility() == View.VISIBLE) {
michael@0 331 lastVisibleDoorHanger = dh;
michael@0 332 }
michael@0 333 }
michael@0 334 if (lastVisibleDoorHanger != null) {
michael@0 335 lastVisibleDoorHanger.hideDivider();
michael@0 336 }
michael@0 337 }
michael@0 338
michael@0 339 private void registerEventListener(String event) {
michael@0 340 GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
michael@0 341 }
michael@0 342
michael@0 343 private void unregisterEventListener(String event) {
michael@0 344 GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
michael@0 345 }
michael@0 346
michael@0 347 @Override
michael@0 348 public void dismiss() {
michael@0 349 // If the popup is focusable while it is hidden, we run into crashes
michael@0 350 // on pre-ICS devices when the popup gets focus before it is shown.
michael@0 351 setFocusable(false);
michael@0 352 super.dismiss();
michael@0 353 }
michael@0 354 }

mercurial