package de.devland.masterpassword.ui; import android.app.Activity; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.graphics.Typeface; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.NavUtils; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.text.Editable; import android.text.TextWatcher; import android.util.Pair; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.EditText; import android.widget.ScrollView; import android.widget.Spinner; import android.widget.TextView; import com.lyndir.masterpassword.MPSiteType; import com.lyndir.masterpassword.MPSiteVariant; import com.lyndir.masterpassword.MasterKey; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; import butterknife.OnTouch; import de.devland.esperandro.Esperandro; import de.devland.masterpassword.App; import de.devland.masterpassword.R; import de.devland.masterpassword.base.ui.BaseFragment; import de.devland.masterpassword.model.Category; import de.devland.masterpassword.model.Site; import de.devland.masterpassword.prefs.DefaultPrefs; import de.devland.masterpassword.service.ClearClipboardService; import de.devland.masterpassword.ui.view.SiteCounterView; import de.devland.masterpassword.util.MasterPasswordHolder; import de.devland.masterpassword.util.ShowCaseManager; import lombok.NoArgsConstructor; import static de.devland.masterpassword.util.MPUtils.extractMPSiteParameters; /** * A simple {@link Fragment} subclass. */ @NoArgsConstructor public class EditFragment extends BaseFragment { public static final String ARG_SITE_ID = "de.devland.masterpassword.EditFragment.siteId"; public static final String ARG_HOSTNAME = "de.devland.masterpassword.EditFragment.hostnamae"; protected DefaultPrefs defaultPrefs; @BindView(R.id.scrollView) protected ScrollView scrollView; @BindView(R.id.editText_siteName) protected EditText siteName; @BindView(R.id.editText_userName) protected AutoCompleteTextView userName; @BindView(R.id.spinner_passwordType) protected Spinner passwordType; @BindView(R.id.spinner_algorithmVersion) protected Spinner algorithmVersion; @BindView(R.id.spinner_category) protected Spinner categorySpinner; @BindView(R.id.editText_notes) protected EditText notes; @BindView(R.id.numberPicker_siteCounter) protected SiteCounterView siteCounter; @BindView(R.id.password) protected TextView password; @BindView(R.id.checkbox_generateUsername) protected CheckBox generatedUsername; private String[] passwordTypeKeys; private String[] algorithmVersionKeys; private ArrayAdapter<String> categoryAdapter; private long siteId = -1; private Site site; private TextWatcher updatePasswordTextWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { updatePasswordAndLogin(); } }; private AdapterView.OnItemSelectedListener updatePasswordItemSelectedListener = new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { updatePasswordAndLogin(); } @Override public void onNothingSelected(AdapterView<?> parent) { } }; private CompoundButton.OnCheckedChangeListener updateGeneratedUserNameCheckedListener = new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { updatePasswordAndLogin(); } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); defaultPrefs = Esperandro.getPreferences(DefaultPrefs.class, getActivity()); Bundle arguments = getArguments(); String hostname = null; if (arguments != null) { siteId = arguments.getLong(ARG_SITE_ID, -1); hostname = arguments.getString(ARG_HOSTNAME, null); } site = Site.findById(Site.class, siteId); if (site == null) { site = new Site(); Pair<MPSiteType, MPSiteVariant> passwordTypeParameters = extractMPSiteParameters(defaultPrefs.defaultPasswordType()); site.setPasswordType(passwordTypeParameters.first); site.setPasswordVariant(passwordTypeParameters.second); } if (hostname != null) { site.setSiteName(hostname); } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.edit, menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); switch (id) { case android.R.id.home: case R.id.action_save: writeValues(); case R.id.action_cancel: NavUtils.navigateUpFromSameTask(getActivity()); } return super.onOptionsItemSelected(item); } @Override public void onAttach(Activity activity) { super.onAttach(activity); ActionBar actionBar = ((AppCompatActivity) activity).getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(true); } passwordTypeKeys = getResources().getStringArray(R.array.passwordTypeKeys); algorithmVersionKeys = getResources().getStringArray(R.array.algorithmVersionKeys); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_edit, container, false); ButterKnife.bind(this, rootView); SortedSet<String> userNames = new TreeSet<>(); Iterator<Site> siteIterator = Site.findAll(Site.class); while (siteIterator.hasNext()) { Site site = siteIterator.next(); String siteUserName = site.getUserName(); if (siteUserName != null && !siteUserName.isEmpty()) { userNames.add(siteUserName); } } List<String> sortedUserNames = new ArrayList<>(); for (String siteUserName : userNames) { sortedUserNames.add(siteUserName); } ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(), android.R.layout.select_dialog_item, sortedUserNames); userName.setAdapter(adapter); userName.setThreshold(1); List<Category> categories = defaultPrefs.categories(); Collections.sort(categories); List<String> categoryNames = new ArrayList<>(); categoryNames.add(""); for (Category cat : categories) { categoryNames.add(cat.getName()); } categoryAdapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_spinner_item, categoryNames); categoryAdapter.setDropDownViewResource(R.layout.support_simple_spinner_dropdown_item); categorySpinner.setAdapter(categoryAdapter); return rootView; } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); Typeface typeface = Typeface .createFromAsset(getActivity().getAssets(), "fonts/RobotoSlab-Light.ttf"); password.setTypeface(typeface); readValues(); updatePasswordAndLogin(); siteName.addTextChangedListener(updatePasswordTextWatcher); siteCounter.setOnChangeListener(updatePasswordTextWatcher); passwordType.setOnItemSelectedListener(updatePasswordItemSelectedListener); algorithmVersion.setOnItemSelectedListener(updatePasswordItemSelectedListener); generatedUsername.setOnCheckedChangeListener(updateGeneratedUserNameCheckedListener); ShowCaseManager.INSTANCE.showEditShowCase(getActivity(), userName); } private void updatePasswordAndLogin() { int passwordTypeIndex = passwordType.getSelectedItemPosition(); String passwordTypeKey = passwordTypeKeys[passwordTypeIndex]; Pair<MPSiteType, MPSiteVariant> siteParameters = extractMPSiteParameters(passwordTypeKey); MPSiteType siteType = siteParameters.first; MPSiteVariant variant = siteParameters.second; String name = siteName.getText().toString(); int counter = siteCounter.getValue(); int algorithmVersionIndex = algorithmVersion.getSelectedItemPosition(); MasterKey.Version version = MasterKey.Version.valueOf(algorithmVersionKeys[algorithmVersionIndex]); if (name.length() > 0 && MasterPasswordHolder.INSTANCE.getMasterKey(version) != null) { //password.setVisibility(View.VISIBLE); String generatedPassword = MasterPasswordHolder.INSTANCE.generate(siteType, variant, name, counter, version); password.setText(generatedPassword); if (generatedUsername.isChecked()) { String generatedUserName = MasterPasswordHolder.INSTANCE.generate(MPSiteType.GeneratedName, MPSiteVariant.Login, name, counter, version); userName.setText(generatedUserName); } } else { password.setText(R.string.msg_previewNotAvailable); if (generatedUsername.isChecked()) { userName.setText(R.string.msg_previewNotAvailable); } } if (generatedUsername.isChecked()) { userName.setEnabled(false); } else { userName.setEnabled(true); } } @OnClick(R.id.password) void copyPasswordToClipboard() { final ClipboardManager clipboard = (ClipboardManager) App.get() .getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newPlainText("password", password.getText()); clipboard.setPrimaryClip(clip); Intent service = new Intent(App.get(), ClearClipboardService.class); App.get().startService(service); } private void readValues() { siteName.setText(site.getSiteName()); updatePasswordTypeSpinner(site.getPasswordType(), site.getPasswordVariant()); updateCategorySpinner(site.getCategory()); updateAlgorithmVersionSpinner(site.getAlgorithmVersion()); siteCounter.setValue(site.getSiteCounter()); generatedUsername.setChecked(site.isGeneratedUserName()); userName.setEnabled(!site.isGeneratedUserName()); userName.setText(site.getCurrentUserName()); notes.setText(site.getNotes()); } private void updateCategorySpinner(String category) { for (int i = 0; i < categoryAdapter.getCount(); i++) { String spinnerCategory = categoryAdapter.getItem(i); if (spinnerCategory.equals(category)) { categorySpinner.setSelection(i, true); break; } } } private void updatePasswordTypeSpinner(MPSiteType passwordTypeEnum, MPSiteVariant passwordVariant) { String passwordTypeName = passwordTypeEnum.toString(); String passwordVariantName = passwordVariant.toString(); String spinnerValue = passwordTypeName + ":" + passwordVariantName; for (int i = 0; i < passwordTypeKeys.length; i++) { String passwordTypeKey = passwordTypeKeys[i]; if (passwordTypeKey.equals(spinnerValue)) { passwordType.setSelection(i, true); break; } } } private void updateAlgorithmVersionSpinner(MasterKey.Version algorithmVersionEnum) { String algorithmVersionName = algorithmVersionEnum.toString(); for (int i = 0; i < algorithmVersionKeys.length; i++) { String algorithmVersionKey = algorithmVersionKeys[i]; if (algorithmVersionKey.equals(algorithmVersionName)) { algorithmVersion.setSelection(i, true); break; } } } private void writeValues() { site.setSiteName(siteName.getText().toString()); site.setGeneratedUserName(generatedUsername.isChecked()); if (site.isGeneratedUserName()) { site.setUserName(null); } else { site.setUserName(userName.getText().toString()); } int passwordTypeIndex = passwordType.getSelectedItemPosition(); String passwordTypeVariant = passwordTypeKeys[passwordTypeIndex]; Pair<MPSiteType, MPSiteVariant> passwordTypeParameters = extractMPSiteParameters(passwordTypeVariant); site.setPasswordType(passwordTypeParameters.first); site.setPasswordVariant(passwordTypeParameters.second); int algorithmVersionIndex = algorithmVersion.getSelectedItemPosition(); site.setAlgorithmVersion(MasterKey.Version.valueOf(algorithmVersionKeys[algorithmVersionIndex])); site.setSiteCounter(siteCounter.getValue()); site.setCategory(categorySpinner.getSelectedItem().toString()); site.setNotes(notes.getText().toString()); if (site.complete()) { site.change(); } } public void onBackPressed() { writeValues(); } // http://stackoverflow.com/a/20520755 @OnTouch(R.id.editText_notes) public boolean scrollViewFix(View v, MotionEvent event) { if (v.getId() == R.id.editText_notes) { v.getParent().requestDisallowInterceptTouchEvent(true); switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_UP: v.getParent().requestDisallowInterceptTouchEvent(false); break; } } return false; } }