// 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.sync.ui; import android.app.Dialog; import android.app.DialogFragment; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.customtabs.CustomTabsIntent; import android.support.v7.app.AlertDialog; import android.text.SpannableString; 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.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.CheckedTextView; import android.widget.ListView; import android.widget.TextView; import org.chromium.base.BuildInfo; import org.chromium.base.VisibleForTesting; import org.chromium.chrome.R; import org.chromium.chrome.browser.util.IntentUtils; import org.chromium.components.sync.PassphraseType; import org.chromium.ui.text.SpanApplier; import org.chromium.ui.text.SpanApplier.SpanInfo; import java.text.DateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Set; /** * Dialog to ask the user select what type of password to use for encryption. */ public class PassphraseTypeDialogFragment extends DialogFragment implements DialogInterface.OnClickListener, OnItemClickListener { private static final String TAG = "PassphraseTypeDialogFragment"; interface Listener { void onPassphraseTypeSelected(PassphraseType type); } private String[] getDisplayNames(List<PassphraseType> passphraseTypes) { String[] displayNames = new String[passphraseTypes.size()]; for (int i = 0; i < displayNames.length; i++) { displayNames[i] = textForPassphraseType(passphraseTypes.get(i)); } return displayNames; } private String textForPassphraseType(PassphraseType type) { switch (type) { case IMPLICIT_PASSPHRASE: // Intentional fall through. case KEYSTORE_PASSPHRASE: return getString(R.string.sync_passphrase_type_keystore); case FROZEN_IMPLICIT_PASSPHRASE: String passphraseDate = getPassphraseDateStringFromArguments(); String frozenPassphraseString = getString(R.string.sync_passphrase_type_frozen); return String.format(frozenPassphraseString, passphraseDate); case CUSTOM_PASSPHRASE: return getString(R.string.sync_passphrase_type_custom); default: return ""; } } private Adapter createAdapter(PassphraseType currentType) { List<PassphraseType> passphraseTypes = new ArrayList<PassphraseType>(currentType.getVisibleTypes()); return new Adapter(passphraseTypes, getDisplayNames(passphraseTypes)); } /** * The adapter for our ListView; only visible for testing purposes. */ @VisibleForTesting public class Adapter extends ArrayAdapter<String> { private final List<PassphraseType> mPassphraseTypes; /** * Do not call this constructor directly. Instead use * {@link PassphraseTypeDialogFragment#createAdapter}. */ private Adapter(List<PassphraseType> passphraseTypes, String[] displayStrings) { super(getActivity(), R.layout.passphrase_type_item, displayStrings); mPassphraseTypes = passphraseTypes; } @Override public boolean hasStableIds() { return true; } @Override public long getItemId(int position) { return getType(position).internalValue(); } public PassphraseType getType(int position) { return mPassphraseTypes.get(position); } public int getPositionForType(PassphraseType type) { return mPassphraseTypes.indexOf(type); } @Override public View getView(int position, View convertView, ViewGroup parent) { CheckedTextView view = (CheckedTextView) super.getView(position, convertView, parent); PassphraseType positionType = getType(position); PassphraseType currentType = getCurrentTypeFromArguments(); Set<PassphraseType> allowedTypes = currentType.getAllowedTypes(getIsEncryptEverythingAllowedFromArguments()); // Set the item to checked it if it is the currently selected encryption type. view.setChecked(positionType == currentType); // Allow user to click on enabled types for the current type. view.setEnabled(allowedTypes.contains(positionType)); return view; } } /** * This argument should contain a single value of type {@link PassphraseType}. */ private static final String ARG_CURRENT_TYPE = "arg_current_type"; private static final String ARG_PASSPHRASE_TIME = "arg_passphrase_time"; private static final String ARG_IS_ENCRYPT_EVERYTHING_ALLOWED = "arg_is_encrypt_everything_allowed"; static PassphraseTypeDialogFragment create( PassphraseType currentType, long passphraseTime, boolean isEncryptEverythingAllowed) { PassphraseTypeDialogFragment dialog = new PassphraseTypeDialogFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_CURRENT_TYPE, currentType); args.putLong(ARG_PASSPHRASE_TIME, passphraseTime); args.putBoolean(ARG_IS_ENCRYPT_EVERYTHING_ALLOWED, isEncryptEverythingAllowed); dialog.setArguments(args); return dialog; } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { LayoutInflater inflater = getActivity().getLayoutInflater(); View v = inflater.inflate(R.layout.sync_passphrase_types, null); // Configure the passphrase type list ListView list = (ListView) v.findViewById(R.id.passphrase_types); Adapter adapter = createAdapter(getCurrentTypeFromArguments()); list.setAdapter(adapter); list.setId(R.id.passphrase_type_list); list.setOnItemClickListener(this); list.setDividerHeight(0); PassphraseType currentType = getCurrentTypeFromArguments(); list.setSelection(adapter.getPositionForType(currentType)); // Configure the hint to reset the passphrase settings // Only show this hint if encryption has been set to use sync passphrase if (currentType == PassphraseType.CUSTOM_PASSPHRASE) { TextView instructionsView = (TextView) v.findViewById(R.id.reset_sync_text); instructionsView.setVisibility(View.VISIBLE); instructionsView.setMovementMethod(LinkMovementMethod.getInstance()); instructionsView.setText(getResetText()); } // Create and return the dialog return new AlertDialog.Builder(getActivity(), R.style.AlertDialogTheme) .setNegativeButton(R.string.cancel, this) .setTitle(R.string.sync_passphrase_type_title) .setView(v) .create(); } private SpannableString getResetText() { final Context context = getActivity(); return SpanApplier.applySpans( context.getString(R.string.sync_passphrase_encryption_reset_instructions), new SpanInfo("<resetlink>", "</resetlink>", new ClickableSpan() { @Override public void onClick(View view) { Uri syncDashboardUrl = Uri.parse( context.getText(R.string.sync_dashboard_url).toString()); Intent intent = new Intent(Intent.ACTION_VIEW, syncDashboardUrl); intent.setPackage(BuildInfo.getPackageName(context)); IntentUtils.safePutBinderExtra( intent, CustomTabsIntent.EXTRA_SESSION, null); context.startActivity(intent); } })); } @Override public void onClick(DialogInterface dialog, int which) { if (which == DialogInterface.BUTTON_NEGATIVE) { dismiss(); } } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long typeId) { PassphraseType currentType = getCurrentTypeFromArguments(); // We know this conversion from long to int is safe, because it represents very small // enum values. PassphraseType type = PassphraseType.fromInternalValue((int) typeId); boolean isEncryptEverythingAllowed = getIsEncryptEverythingAllowedFromArguments(); if (currentType.getAllowedTypes(isEncryptEverythingAllowed).contains(type)) { if (typeId != currentType.internalValue()) { Listener listener = (Listener) getTargetFragment(); listener.onPassphraseTypeSelected(type); } dismiss(); } } @VisibleForTesting public PassphraseType getCurrentTypeFromArguments() { PassphraseType currentType = getArguments().getParcelable(ARG_CURRENT_TYPE); if (currentType == null) { throw new IllegalStateException("Unable to find argument with current type."); } return currentType; } private String getPassphraseDateStringFromArguments() { long passphraseTime = getArguments().getLong(ARG_PASSPHRASE_TIME); DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM); return df.format(new Date(passphraseTime)); } private boolean getIsEncryptEverythingAllowedFromArguments() { return getArguments().getBoolean(ARG_IS_ENCRYPT_EVERYTHING_ALLOWED); } }