Bug 1126608 - Add select login doorhanger to SiteIdentityPopup. r=ally
authorChenxia Liu <liuche@mozilla.com>
Thu, 07 May 2015 15:35:56 -0700
changeset 243969 09b6e5f3e10d572c9c2b5950cfabfcc5bf20548b
parent 243968 2a0c51e9039dfa61c747f086d3935417c5209bd6
child 243970 acc34206a8ab3d111baff9d3e55007d84a43b76d
push id28761
push usercbook@mozilla.com
push dateFri, 15 May 2015 14:50:10 +0000
treeherdermozilla-central@c0e709a5baca [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersally
bugs1126608
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1126608 - Add select login doorhanger to SiteIdentityPopup. r=ally
mobile/android/base/Tab.java
mobile/android/base/locales/en-US/android_strings.dtd
mobile/android/base/moz.build
mobile/android/base/resources/layout/login_doorhanger.xml
mobile/android/base/strings.xml.in
mobile/android/base/toolbar/SiteIdentityPopup.java
mobile/android/base/widget/LoginDoorHanger.java
mobile/android/base/widget/SiteLogins.java
--- a/mobile/android/base/Tab.java
+++ b/mobile/android/base/Tab.java
@@ -31,16 +31,17 @@ import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.drawable.BitmapDrawable;
 import android.os.Build;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.widget.Toast;
+import org.mozilla.gecko.widget.SiteLogins;
 
 public class Tab {
     private static final String LOGTAG = "GeckoTab";
 
     private static Pattern sColorPattern;
     private final int mId;
     private final BrowserDB mDB;
     private long mLastUsed;
@@ -52,16 +53,17 @@ public class Tab {
     private String mFaviconUrl;
     private String mApplicationId; // Intended to be null after explicit user action.
 
     // The set of all available Favicons for this tab, sorted by attractiveness.
     final TreeSet<RemoteFavicon> mAvailableFavicons = new TreeSet<>();
     private boolean mHasFeeds;
     private boolean mHasOpenSearch;
     private final SiteIdentity mSiteIdentity;
+    private SiteLogins mSiteLogins;
     private BitmapDrawable mThumbnail;
     private final int mParentId;
     private final boolean mExternal;
     private boolean mBookmark;
     private boolean mIsInReadingList;
     private int mFaviconLoadId;
     private String mContentType;
     private boolean mHasTouchListeners;
@@ -275,16 +277,20 @@ public class Tab {
     public boolean hasOpenSearch() {
         return mHasOpenSearch;
     }
 
     public SiteIdentity getSiteIdentity() {
         return mSiteIdentity;
     }
 
+    public SiteLogins getSiteLogins() {
+        return mSiteLogins;
+    }
+
     public boolean isBookmark() {
         return mBookmark;
     }
 
     public boolean isInReadingList() {
         return mIsInReadingList;
     }
 
@@ -489,16 +495,20 @@ public class Tab {
     public void setHasOpenSearch(boolean hasOpenSearch) {
         mHasOpenSearch = hasOpenSearch;
     }
 
     public void updateIdentityData(JSONObject identityData) {
         mSiteIdentity.update(identityData);
     }
 
+    public void setSiteLogins(SiteLogins siteLogins) {
+        mSiteLogins = siteLogins;
+    }
+
     void updateBookmark() {
         if (getURL() == null) {
             return;
         }
 
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
@@ -686,16 +696,17 @@ public class Tab {
 
         setContentType(message.getString("contentType"));
         updateUserRequested(message.getString("userRequested"));
         mBaseDomain = message.optString("baseDomain");
 
         setHasFeeds(false);
         setHasOpenSearch(false);
         mSiteIdentity.reset();
+        setSiteLogins(null);
         setZoomConstraints(new ZoomConstraints(true));
         setHasTouchListeners(false);
         setBackgroundColor(DEFAULT_BACKGROUND_COLOR);
         setErrorType(ErrorType.NONE);
         setLoadProgressIfLoading(LOAD_PROGRESS_LOCATION_CHANGE);
 
         Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.LOCATION_CHANGE, oldUrl);
     }
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -374,16 +374,17 @@ size. -->
      where normally a username would be displayed. In this case, no username was found, and this placeholder
      contains brackets to indicate this is not actually a username, but rather a placeholder -->
 <!ENTITY doorhanger_login_no_username "[No username]">
 <!ENTITY doorhanger_login_edit_title "Edit login">
 <!ENTITY doorhanger_login_edit_username_hint "Username">
 <!ENTITY doorhanger_login_edit_password_hint "Password">
 <!ENTITY doorhanger_login_edit_toggle "Show password">
 <!ENTITY doorhanger_login_edit_toast_error "Failed to save login">
+<!ENTITY doorhanger_login_select_message "Copy password from &formatS;?">
 
 <!ENTITY pref_titlebar_mode "Title bar">
 <!ENTITY pref_titlebar_mode_title "Show page title">
 <!ENTITY pref_titlebar_mode_url "Show page address">
 
 <!-- Localization note (pref_scroll_title_bar2): Label for setting that controls
      whether or not the dynamic toolbar is enabled. -->
 <!ENTITY pref_scroll_title_bar2 "Full-screen browsing">
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -513,16 +513,17 @@ gbjar.sources += [
     'widget/FlowLayout.java',
     'widget/GeckoActionProvider.java',
     'widget/GeckoPopupMenu.java',
     'widget/GeckoSwipeRefreshLayout.java',
     'widget/GeckoViewFlipper.java',
     'widget/IconTabWidget.java',
     'widget/LoginDoorHanger.java',
     'widget/ResizablePathDrawable.java',
+    'widget/SiteLogins.java',
     'widget/SquaredImageView.java',
     'widget/SwipeDismissListViewTouchListener.java',
     'widget/TabThumbnailWrapper.java',
     'widget/ThumbnailView.java',
     'widget/TwoWayView.java',
     'ZoomConstraints.java',
     'ZoomedView.java',
 ]
--- a/mobile/android/base/resources/layout/login_doorhanger.xml
+++ b/mobile/android/base/resources/layout/login_doorhanger.xml
@@ -30,17 +30,17 @@
 
             <TextView android:id="@+id/doorhanger_message"
                       android:focusable="true"
                       android:layout_width="match_parent"
                       android:layout_height="wrap_content"
                       android:paddingBottom="@dimen/doorhanger_section_padding_large"
                       android:textAppearance="@style/TextAppearance.DoorHanger.Medium"/>
 
-            <TextView android:id="@+id/doorhanger_login"
+            <TextView android:id="@+id/doorhanger_link"
                       android:layout_width="match_parent"
                       android:layout_height="wrap_content"
                       android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
                       android:textColor="@color/link_blue"
                       android:paddingBottom="@dimen/doorhanger_section_padding_large"
                       android:visibility="gone"/>
 
         </LinearLayout>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -341,16 +341,17 @@
   <string name="contextmenu_add_search_engine">&contextmenu_add_search_engine;</string>
 
   <string name="doorhanger_login_no_username">&doorhanger_login_no_username;</string>
   <string name="doorhanger_login_edit_title">&doorhanger_login_edit_title;</string>
   <string name="doorhanger_login_edit_username_hint">&doorhanger_login_edit_username_hint;</string>
   <string name="doorhanger_login_edit_password_hint">&doorhanger_login_edit_password_hint;</string>
   <string name="doorhanger_login_edit_toggle">&doorhanger_login_edit_toggle;</string>
   <string name="doorhanger_login_edit_toast_error">&doorhanger_login_edit_toast_error;</string>
+  <string name="doorhanger_login_select_message">&doorhanger_login_select_message;</string>
 
   <string name="pref_titlebar_mode">&pref_titlebar_mode;</string>
   <string name="pref_titlebar_mode_title">&pref_titlebar_mode_title;</string>
   <string name="pref_titlebar_mode_url">&pref_titlebar_mode_url;</string>
 
   <string name="pref_scroll_title_bar2">&pref_scroll_title_bar2;</string>
   <string name="pref_scroll_title_bar_summary">&pref_scroll_title_bar_summary;</string>
 
--- a/mobile/android/base/toolbar/SiteIdentityPopup.java
+++ b/mobile/android/base/toolbar/SiteIdentityPopup.java
@@ -13,29 +13,31 @@ import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.SiteIdentity;
 import org.mozilla.gecko.SiteIdentity.SecurityMode;
 import org.mozilla.gecko.SiteIdentity.MixedMode;
 import org.mozilla.gecko.SiteIdentity.TrackingMode;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.util.GeckoEventListener;
+import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.AnchoredPopup;
 import org.mozilla.gecko.widget.DoorHanger;
 import org.mozilla.gecko.widget.DoorHanger.OnButtonClickListener;
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 import org.mozilla.gecko.widget.DoorhangerConfig;
+import org.mozilla.gecko.widget.SiteLogins;
 
 /**
  * SiteIdentityPopup is a singleton class that displays site identity data in
  * an arrow panel popup hanging from the lock icon in the browser toolbar.
  */
 public class SiteIdentityPopup extends AnchoredPopup implements GeckoEventListener {
 
     public static enum ButtonType { DISABLE, ENABLE, KEEP_BLOCKING };
@@ -43,32 +45,36 @@ public class SiteIdentityPopup extends A
     private static final String LOGTAG = "GeckoSiteIdentityPopup";
 
     private static final String MIXED_CONTENT_SUPPORT_URL =
         "https://support.mozilla.org/kb/how-does-insecure-content-affect-safety-android";
 
     private static final String TRACKING_CONTENT_SUPPORT_URL =
         "https://support.mozilla.org/kb/firefox-android-tracking-protection";
 
+    // Placeholder string.
+    private final static String FORMAT_S = "%s";
+
     private SiteIdentity mSiteIdentity;
 
     private LinearLayout mIdentity;
 
     private LinearLayout mIdentityKnownContainer;
     private LinearLayout mIdentityUnknownContainer;
 
     private TextView mHost;
     private TextView mOwnerLabel;
     private TextView mOwner;
     private TextView mVerifier;
 
     private View mDivider;
 
     private DoorHanger mMixedContentNotification;
     private DoorHanger mTrackingContentNotification;
+    private DoorHanger mSelectLoginDoorhanger;
 
     private final OnButtonClickListener mButtonClickListener;
 
     public SiteIdentityPopup(Context context) {
         super(context);
 
         mButtonClickListener = new PopupButtonListener();
         EventDispatcher.getInstance().registerGeckoThreadListener(this, "Doorhanger:Logins");
@@ -119,26 +125,94 @@ public class SiteIdentityPopup extends A
             updateIdentityInformation(siteIdentity);
         }
     }
 
     @Override
     public void handleMessage(String event, JSONObject geckoObject) {
         if ("Doorhanger:Logins".equals(event)) {
             try {
-                final JSONArray logins = geckoObject.getJSONArray("logins");
-                addSelectLoginDoorhanger(logins);
+                final Tab selectedTab = Tabs.getInstance().getSelectedTab();
+                if (selectedTab != null) {
+                    final JSONObject data = geckoObject.getJSONObject("data");
+                    addLoginsToTab(data);
+                }
+                if (isShowing()) {
+                    addSelectLoginDoorhanger(selectedTab);
+                }
             } catch (JSONException e) {
                 Log.e(LOGTAG, "Error accessing logins in Doorhanger:Logins message", e);
             }
         }
     }
 
-    private void addSelectLoginDoorhanger(JSONArray logins) {
-        // TODO: add doorhanger + link (if there is more than one login).
+    private void addLoginsToTab(JSONObject data) throws JSONException {
+        final JSONObject titleObj = data.getJSONObject("title");
+        final JSONArray logins = data.getJSONArray("logins");
+
+        final SiteLogins siteLogins = new SiteLogins(titleObj, logins);
+        Tabs.getInstance().getSelectedTab().setSiteLogins(siteLogins);
+    }
+
+    private void addSelectLoginDoorhanger(Tab tab) throws JSONException {
+        final SiteLogins siteLogins = tab.getSiteLogins();
+        if (siteLogins == null) {
+            return;
+        }
+
+        final JSONArray logins = siteLogins.getLogins();
+        if (logins.length() == 0) {
+            return;
+        }
+
+        final DoorhangerConfig config = new DoorhangerConfig(DoorHanger.Type.LOGIN, null);
+
+        // Set message.
+        String username = ((JSONObject) logins.get(0)).getString("username");
+        if (TextUtils.isEmpty(username)) {
+            username = mContext.getString(R.string.doorhanger_login_no_username);
+        }
+
+        final String message = mContext.getString(R.string.doorhanger_login_select_message).replace(FORMAT_S, username);
+        config.setMessage(message);
+
+        // Set options.
+        final JSONObject options = new JSONObject();
+        final JSONObject titleObj = siteLogins.getTitle();
+        options.put("title", titleObj);
+
+        if (logins.length() > 1) {
+            // TODO: Fetch actionText if there is more than one login.
+            final JSONObject actionText = null;
+            options.put("actionText", actionText);
+        }
+
+        config.setOptions(options);
+
+        ThreadUtils.postToUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (!mInflated) {
+                    init();
+                }
+
+                removeSelectLoginDoorhanger();
+
+                mSelectLoginDoorhanger = DoorHanger.Get(mContext, config);
+                mContent.addView(mSelectLoginDoorhanger);
+                mDivider.setVisibility(View.VISIBLE);
+            }
+        });
+    }
+
+    private void removeSelectLoginDoorhanger() {
+        if (mSelectLoginDoorhanger != null) {
+            mContent.removeView(mSelectLoginDoorhanger);
+            mSelectLoginDoorhanger = null;
+        }
     }
 
     private void toggleIdentityKnownContainerVisibility(final boolean isIdentityKnown) {
         if (isIdentityKnown) {
             mIdentityKnownContainer.setVisibility(View.VISIBLE);
             mIdentityUnknownContainer.setVisibility(View.GONE);
         } else {
             mIdentityKnownContainer.setVisibility(View.GONE);
@@ -276,16 +350,22 @@ public class SiteIdentityPopup extends A
             addMixedContentNotification(mixedMode == MixedMode.MIXED_CONTENT_BLOCKED);
         }
 
         final TrackingMode trackingMode = mSiteIdentity.getTrackingMode();
         if (trackingMode != TrackingMode.UNKNOWN) {
             addTrackingContentNotification(trackingMode == TrackingMode.TRACKING_CONTENT_BLOCKED);
         }
 
+        try {
+            addSelectLoginDoorhanger(selectedTab);
+        } catch (JSONException e) {
+            Log.e(LOGTAG, "Error adding selectLogin doorhanger", e);
+        }
+
         showDividers();
 
         super.show();
     }
 
     // Show the right dividers
     private void showDividers() {
         final int count = mContent.getChildCount();
@@ -314,16 +394,17 @@ public class SiteIdentityPopup extends A
         EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "Doorhanger:Logins");
     }
 
     @Override
     public void dismiss() {
         super.dismiss();
         removeMixedContentNotification();
         removeTrackingContentNotification();
+        removeSelectLoginDoorhanger();
         mDivider.setVisibility(View.GONE);
     }
 
     private class PopupButtonListener implements OnButtonClickListener {
         @Override
         public void onButtonClick(JSONObject response, DoorHanger doorhanger) {
             GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Reload", response.toString());
             GeckoAppShell.sendEventToGecko(e);
--- a/mobile/android/base/widget/LoginDoorHanger.java
+++ b/mobile/android/base/widget/LoginDoorHanger.java
@@ -25,27 +25,27 @@ import ch.boye.httpclientandroidlib.util
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 
 public class LoginDoorHanger extends DoorHanger {
     private static final String LOGTAG = "LoginDoorHanger";
-    private enum ActionType { EDIT };
+    private enum ActionType { EDIT, SELECT }
 
     private final TextView mTitle;
-    private final TextView mLogin;
+    private final TextView mLink;
     private int mCallbackID;
 
     public LoginDoorHanger(Context context, DoorhangerConfig config) {
         super(context, config, Type.LOGIN);
 
         mTitle = (TextView) findViewById(R.id.doorhanger_title);
-        mLogin = (TextView) findViewById(R.id.doorhanger_login);
+        mLink = (TextView) findViewById(R.id.doorhanger_link);
 
         loadConfig(config);
     }
 
     @Override
     protected void loadConfig(DoorhangerConfig config) {
         setOptions(config.getOptions());
         setMessage(config.getMessage());
@@ -112,29 +112,20 @@ public class LoginDoorHanger extends Doo
      * Add sub-text to the doorhanger and add the click action.
      *
      * If the parsing the action from the JSON throws, the text is left visible, but there is no
      * click action.
      * @param actionTextObj JSONObject containing blob for making an action.
      */
     private void addActionText(JSONObject actionTextObj) {
         if (actionTextObj == null) {
-            mLogin.setVisibility(View.GONE);
+            mLink.setVisibility(View.GONE);
             return;
         }
 
-        boolean hasUsername = true;
-        String text = actionTextObj.optString("text");
-        if (TextUtils.isEmpty(text)) {
-            hasUsername = false;
-            text = mResources.getString(R.string.doorhanger_login_no_username);
-        }
-        mLogin.setText(text);
-        mLogin.setVisibility(View.VISIBLE);
-
         // Make action.
         try {
             final JSONObject bundle = actionTextObj.getJSONObject("bundle");
             final ActionType type = ActionType.valueOf(actionTextObj.getString("type"));
             final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
 
             switch (type) {
                 case EDIT:
@@ -177,27 +168,34 @@ public class LoginDoorHanger extends Doo
                         }
                     });
                     builder.setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
                         @Override
                         public void onClick(DialogInterface dialog, int which) {
                             dialog.dismiss();
                         }
                     });
+                    String text = actionTextObj.optString("text");
+                    if (TextUtils.isEmpty(text)) {
+                        text = mResources.getString(R.string.doorhanger_login_no_username);
+                    }
+                    mLink.setText(text);
+                    mLink.setVisibility(View.VISIBLE);
+                    break;
+
+                case SELECT:
+
+                    break;
             }
 
             final Dialog dialog = builder.create();
-            mLogin.setOnClickListener(new OnClickListener() {
+            mLink.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
                     dialog.show();
                 }
             });
 
         } catch (JSONException e) {
-            // Log an error, but leave the text visible if there was a username.
             Log.e(LOGTAG, "Error fetching actionText from JSON", e);
-            if (!hasUsername) {
-                mLogin.setVisibility(View.GONE);
-            }
         }
     }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/SiteLogins.java
@@ -0,0 +1,22 @@
+package org.mozilla.gecko.widget;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+public class SiteLogins {
+    private final JSONObject titleObj;
+    private final JSONArray logins;
+
+    public SiteLogins(JSONObject titleObj, JSONArray logins) {
+        this.logins = logins;
+        this.titleObj = titleObj;
+    }
+
+    public JSONArray getLogins() {
+        return logins;
+    }
+
+    public JSONObject getTitle() {
+        return titleObj;
+    }
+}