author | Chenxia Liu <liuche@mozilla.com> |
Thu, 07 May 2015 15:35:56 -0700 | |
changeset 243969 | 09b6e5f3e10d572c9c2b5950cfabfcc5bf20548b |
parent 243968 | 2a0c51e9039dfa61c747f086d3935417c5209bd6 |
child 243970 | acc34206a8ab3d111baff9d3e55007d84a43b76d |
push id | 28761 |
push user | cbook@mozilla.com |
push date | Fri, 15 May 2015 14:50:10 +0000 |
treeherder | mozilla-central@c0e709a5baca [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | ally |
bugs | 1126608 |
milestone | 41.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
|
--- 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; + } +}