// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.password_manager;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.support.v7.app.AlertDialog;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.signin.AccountManagementFragment;
import org.chromium.ui.base.WindowAndroid;
/**
* A dialog offers the user the ability to choose credentials for authentication. User is
* presented with username along with avatar and full name in case they are available.
* Native counterpart should be notified about credentials user have chosen and also if user
* haven't chosen anything.
*/
public class AccountChooserDialog
implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
private final Context mContext;
private final Credential[] mCredentials;
/**
* Title of the dialog, contains Smart Lock branding for the Smart Lock users.
*/
private final String mTitle;
private final int mTitleLinkStart;
private final int mTitleLinkEnd;
private final String mOrigin;
private final String mSigninButtonText;
private ArrayAdapter<Credential> mAdapter;
private boolean mIsDestroyed;
private boolean mWasDismissedByNative;
/**
* Holds the reference to the credentials which were chosen by the user.
*/
private Credential mCredential;
private long mNativeAccountChooserDialog;
private AlertDialog mDialog;
/**
* True, if credentials were selected via "Sign In" button instead of clicking on the credential
* itself.
*/
private boolean mSigninButtonClicked;
private AccountChooserDialog(Context context, long nativeAccountChooserDialog,
Credential[] credentials, String title, int titleLinkStart, int titleLinkEnd,
String origin, String signinButtonText) {
mNativeAccountChooserDialog = nativeAccountChooserDialog;
mContext = context;
mCredentials = credentials.clone();
mTitle = title;
mTitleLinkStart = titleLinkStart;
mTitleLinkEnd = titleLinkEnd;
mOrigin = origin;
mSigninButtonText = signinButtonText;
mSigninButtonClicked = false;
}
/**
* Creates and shows the dialog which allows user to choose credentials for login.
* @param credentials Credentials to display in the dialog.
* @param title Title message for the dialog, which can contain Smart Lock branding.
* @param titleLinkStart Start of a link in case title contains Smart Lock branding.
* @param titleLinkEnd End of a link in case title contains Smart Lock branding.
* @param origin Address of the web page, where dialog was triggered.
*/
@CalledByNative
private static AccountChooserDialog createAndShowAccountChooser(WindowAndroid windowAndroid,
long nativeAccountChooserDialog, Credential[] credentials, String title,
int titleLinkStart, int titleLinkEnd, String origin, String signinButtonText) {
Activity activity = windowAndroid.getActivity().get();
if (activity == null) return null;
AccountChooserDialog chooser =
new AccountChooserDialog(activity, nativeAccountChooserDialog, credentials, title,
titleLinkStart, titleLinkEnd, origin, signinButtonText);
chooser.show();
return chooser;
}
private ArrayAdapter<Credential> generateAccountsArrayAdapter(
Context context, Credential[] credentials) {
return new ArrayAdapter<Credential>(context, 0 /* resource */, credentials) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView =
inflater.inflate(R.layout.account_chooser_dialog_item, parent, false);
}
convertView.setTag(position);
Credential credential = getItem(position);
ImageView avatarView = (ImageView) convertView.findViewById(R.id.profile_image);
Bitmap avatar = credential.getAvatar();
if (avatar != null) {
avatarView.setImageBitmap(avatar);
} else {
avatarView.setImageResource(R.drawable.account_management_no_picture);
}
TextView mainNameView = (TextView) convertView.findViewById(R.id.main_name);
TextView secondaryNameView =
(TextView) convertView.findViewById(R.id.secondary_name);
if (credential.getFederation().isEmpty()) {
// Not federated credentials case
if (credential.getDisplayName().isEmpty()) {
mainNameView.setText(credential.getUsername());
secondaryNameView.setVisibility(View.GONE);
} else {
mainNameView.setText(credential.getDisplayName());
secondaryNameView.setText(credential.getUsername());
secondaryNameView.setVisibility(View.VISIBLE);
}
} else {
mainNameView.setText(credential.getUsername());
secondaryNameView.setText(credential.getFederation());
secondaryNameView.setVisibility(View.VISIBLE);
}
return convertView;
}
};
}
private void show() {
View titleView =
LayoutInflater.from(mContext).inflate(R.layout.account_chooser_dialog_title, null);
TextView origin = (TextView) titleView.findViewById(R.id.origin);
origin.setText(mOrigin);
TextView titleMessageText = (TextView) titleView.findViewById(R.id.title);
if (mTitleLinkStart != 0 && mTitleLinkEnd != 0) {
SpannableString spanableTitle = new SpannableString(mTitle);
spanableTitle.setSpan(new ClickableSpan() {
@Override
public void onClick(View view) {
nativeOnLinkClicked(mNativeAccountChooserDialog);
mDialog.dismiss();
}
}, mTitleLinkStart, mTitleLinkEnd, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
titleMessageText.setText(spanableTitle, TextView.BufferType.SPANNABLE);
titleMessageText.setMovementMethod(LinkMovementMethod.getInstance());
} else {
titleMessageText.setText(mTitle);
}
mAdapter = generateAccountsArrayAdapter(mContext, mCredentials);
final AlertDialog.Builder builder =
new AlertDialog.Builder(mContext, R.style.AlertDialogTheme)
.setCustomTitle(titleView)
.setNegativeButton(R.string.cancel, this)
.setAdapter(mAdapter, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
mCredential = mCredentials[item];
}
});
if (!TextUtils.isEmpty(mSigninButtonText)) {
builder.setPositiveButton(mSigninButtonText, this);
}
mDialog = builder.create();
mDialog.setOnDismissListener(this);
mDialog.show();
}
@CalledByNative
private void imageFetchComplete(int index, Bitmap avatarBitmap) {
if (mIsDestroyed) return;
assert index >= 0 && index < mCredentials.length;
assert mCredentials[index] != null;
avatarBitmap = AccountManagementFragment.makeRoundUserPicture(avatarBitmap);
mCredentials[index].setBitmap(avatarBitmap);
ListView view = mDialog.getListView();
if (index >= view.getFirstVisiblePosition() && index <= view.getLastVisiblePosition()) {
// Profile image is in the visible range.
View credentialView = view.getChildAt(index - view.getFirstVisiblePosition());
if (credentialView == null) return;
ImageView avatar = (ImageView) credentialView.findViewById(R.id.profile_image);
avatar.setImageBitmap(avatarBitmap);
}
}
private void destroy() {
assert mNativeAccountChooserDialog != 0;
assert !mIsDestroyed;
mIsDestroyed = true;
nativeDestroy(mNativeAccountChooserDialog);
mNativeAccountChooserDialog = 0;
mDialog = null;
}
@CalledByNative
private void dismissDialog() {
assert !mWasDismissedByNative;
mWasDismissedByNative = true;
mDialog.dismiss();
}
@Override
public void onClick(DialogInterface dialog, int whichButton) {
if (whichButton == DialogInterface.BUTTON_POSITIVE) {
mCredential = mCredentials[0];
mSigninButtonClicked = true;
}
}
@Override
public void onDismiss(DialogInterface dialog) {
if (!mWasDismissedByNative) {
if (mCredential != null) {
nativeOnCredentialClicked(mNativeAccountChooserDialog, mCredential.getIndex(),
mCredential.getType(), mSigninButtonClicked);
} else {
nativeCancelDialog(mNativeAccountChooserDialog);
}
}
destroy();
}
private native void nativeOnCredentialClicked(long nativeAccountChooserDialogAndroid,
int credentialId, int credentialType, boolean signinButtonClicked);
private native void nativeCancelDialog(long nativeAccountChooserDialogAndroid);
private native void nativeDestroy(long nativeAccountChooserDialogAndroid);
private native void nativeOnLinkClicked(long nativeAccountChooserDialogAndroid);
}