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.

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

mercurial