/* * Universal Password Manager * Copyright (c) 2010-2011 Adrian Smith * * This file is part of Universal Password Manager. * * Universal Password Manager is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Universal Password Manager is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Universal Password Manager; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package com.u17od.upm; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.channels.FileChannel; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.ArrayAdapter; import android.widget.TextView; import android.widget.Toast; public class FullAccountList extends AccountsList { private static final int CONFIRM_RESTORE_DIALOG = 0; private static final int CONFIRM_OVERWRITE_BACKUP_FILE = 1; private static final int DIALOG_ABOUT = 2; private static final int CONFIRM_DELETE_DB_DIALOG = 3; private static final int IMPORT_CERT_DIALOG = 4; public static final int RESULT_EXIT = 0; public static final int RESULT_ENTER_PW = 1; public static final String CERT_FILE_NAME = "upm.cer"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); registerForContextMenu(getListView()); populateAccountList(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { switch(requestCode) { case AddEditAccount.EDIT_ACCOUNT_REQUEST_CODE: case ViewAccountDetails.VIEW_ACCOUNT_REQUEST_CODE: if (resultCode == AddEditAccount.EDIT_ACCOUNT_RESULT_CODE_TRUE) { populateAccountList(); } break; case SyncDatabaseActivity.SYNC_DB_REQUEST_CODE: if (resultCode == SyncDatabaseActivity.RESULT_REFRESH) { populateAccountList(); } break; } } private void populateAccountList() { if (getPasswordDatabase() == null) { // If the UPM process was restarted since AppEntryActivity was last // run then databaseFileToDecrypt won't be set so set it here. EnterMasterPassword.databaseFileToDecrypt = Utilities.getDatabaseFile(this); setResult(RESULT_ENTER_PW); finish(); } else { setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, getPasswordDatabase().getAccountNames())); } } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.main, menu); return true; } @Override public boolean onPrepareOptionsMenu (Menu menu) { super.onPrepareOptionsMenu(menu); if (getPasswordDatabase() == null) { // If the UPM process was restarted since AppEntryActivity was last // run then databaseFileToDecrypt won't be set so set it here. EnterMasterPassword.databaseFileToDecrypt = Utilities.getDatabaseFile(this); setResult(RESULT_ENTER_PW); finish(); } else { if (Utilities.getSyncMethod(this).equals(Prefs.SyncMethod.DISABLED)) { menu.findItem(R.id.sync).setEnabled(false); } else { menu.findItem(R.id.sync).setEnabled(true); } } return true; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { new AlertDialog.Builder(this) .setIcon(android.R.drawable.ic_dialog_alert) .setTitle(R.string.confirm_exit_title) .setMessage(R.string.confirm_exit_message) .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { FullAccountList.this.setResult(RESULT_EXIT); FullAccountList.this.finish(); } }) .setNegativeButton(R.string.no, null) .show(); return true; } else { return super.onKeyDown(keyCode, event); } } @Override public boolean onOptionsItemSelected(MenuItem item) { boolean optionConsumed = false; switch (item.getItemId()) { case R.id.search: onSearchRequested(); optionConsumed = true; break; case R.id.add: if (Utilities.isSyncRequired(this)) { UIUtilities.showToast(this, R.string.sync_required); } else { Intent i = new Intent(FullAccountList.this, AddEditAccount.class); i.putExtra(AddEditAccount.MODE, AddEditAccount.ADD_MODE); startActivityForResult(i, AddEditAccount.EDIT_ACCOUNT_REQUEST_CODE); } break; case R.id.change_master_password: if (Utilities.isSyncRequired(this)) { UIUtilities.showToast(this, R.string.sync_required); } else { startActivity(new Intent(FullAccountList.this, ChangeMasterPassword.class)); } break; case R.id.restore: // Check to ensure there's a file to restore File restoreFile = new File(Environment.getExternalStorageDirectory(), Utilities.DEFAULT_DATABASE_FILE); if (restoreFile.exists()) { showDialog(CONFIRM_RESTORE_DIALOG); } else { String messageRes = getString(R.string.restore_file_doesnt_exist); String message = String.format(messageRes, restoreFile.getAbsolutePath()); Toast.makeText(this, message, Toast.LENGTH_LONG).show(); } break; case R.id.backup: // If there's already a backup file prompt the user if they want to overwrite File backupFile = new File(Environment.getExternalStorageDirectory(), Utilities.DEFAULT_DATABASE_FILE); if (backupFile.exists()) { showDialog(CONFIRM_OVERWRITE_BACKUP_FILE); } else { backupDatabase(); } break; case R.id.about: showDialog(DIALOG_ABOUT); break; case R.id.sync: if (Utilities.getSyncMethod(this).equals(Prefs.SyncMethod.HTTP)) { Intent i = new Intent(FullAccountList.this, SyncDatabaseViaHttpActivity.class); startActivityForResult(i, SyncDatabaseActivity.SYNC_DB_REQUEST_CODE); } else if (Utilities.getSyncMethod(this).equals(Prefs.SyncMethod.DROPBOX)) { Intent i = new Intent(FullAccountList.this, SyncDatabaseViaDropboxActivity.class); startActivityForResult(i, SyncDatabaseActivity.SYNC_DB_REQUEST_CODE); } break; case R.id.preferences: startActivity(new Intent(this, Prefs.class)); break; case R.id.delete_db: showDialog(CONFIRM_DELETE_DB_DIALOG); break; case R.id.import_certificate: showDialog(IMPORT_CERT_DIALOG); break; case R.id.delete_certificate: deleteCertificate(); break; case R.id.donate: launchDonatePage(); break; } return optionConsumed; } @Override protected Dialog onCreateDialog(int id) { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); switch(id) { case CONFIRM_RESTORE_DIALOG: dialogBuilder.setMessage(getString(R.string.confirm_restore_overwrite)) .setCancelable(false) .setPositiveButton("Yes", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { ((UPMApplication) getApplication()).restoreDatabase(FullAccountList.this); // Clear the activity stack and bring up AppEntryActivity // This is effectively restarting the application Intent i = new Intent(FullAccountList.this, AppEntryActivity.class); i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(i); finish(); } }) .setNegativeButton("No", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); break; case CONFIRM_OVERWRITE_BACKUP_FILE: File backupFile = new File(Environment.getExternalStorageDirectory(), Utilities.DEFAULT_DATABASE_FILE); String messageRes = getString(R.string.backup_file_exists); String message = String.format(messageRes, backupFile.getAbsolutePath()); dialogBuilder.setMessage(message) .setCancelable(false) .setPositiveButton("Yes", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { backupDatabase(); } }) .setNegativeButton("No", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); break; case DIALOG_ABOUT: PackageInfo pinfo; String versionName = "<unknown>"; try { pinfo = getPackageManager().getPackageInfo(getPackageName(), 0); versionName = pinfo.versionName; } catch (NameNotFoundException e) { Log.e("FullAccountList", e.getMessage(), e); } View v = LayoutInflater.from(this).inflate(R.layout.dialog, null); TextView text = (TextView) v.findViewById(R.id.dialogText); text.setText(getString(R.string.aboutText, versionName)); dialogBuilder .setTitle(R.string.about) .setIcon(android.R.drawable.ic_menu_info_details) .setPositiveButton(R.string.homepage, new OnClickListener() { public void onClick(DialogInterface dialog, int which) { Uri uri = Uri.parse(getString(R.string.homepageUrl)); startActivity(new Intent(Intent.ACTION_VIEW, uri)); } }) .setNeutralButton(R.string.donate, new OnClickListener() { public void onClick(DialogInterface arg0, int arg1) { launchDonatePage(); } }) .setNegativeButton(R.string.close, null) .setView(v); break; case CONFIRM_DELETE_DB_DIALOG: dialogBuilder.setMessage(getString(R.string.confirm_delete_db)) .setPositiveButton("Yes", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { deleteDatabase(); // Clear the activity stack and bring up AppEntryActivity // This is effectively restarting the application Intent i = new Intent(FullAccountList.this, AppEntryActivity.class); i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(i); finish(); } }) .setNegativeButton("No", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); break; case IMPORT_CERT_DIALOG: String importCertMessageRes = getString(R.string.import_cert_message); String importCertMessage = String.format(importCertMessageRes, CERT_FILE_NAME); dialogBuilder .setCancelable(false) .setTitle(R.string.import_cert) .setMessage(importCertMessage) .setPositiveButton(R.string.import_cert, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { importCert(); } }) .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); break; } return dialogBuilder.create(); } private void deleteDatabase() { Utilities.getDatabaseFile(this).delete(); Utilities.setDatabaseFileName(null, this); } private void backupDatabase() { File fileOnSDCard = new File(Environment.getExternalStorageDirectory(), Utilities.DEFAULT_DATABASE_FILE); File databaseFile = Utilities.getDatabaseFile(this); if (((UPMApplication) getApplication()).copyFile(databaseFile, fileOnSDCard, this)) { String message = String.format(getString(R.string.backup_complete), fileOnSDCard.getAbsolutePath()); UIUtilities.showToast(this, message, false); } } private void launchDonatePage() { Uri uri = Uri.parse(getString(R.string.donateURL)); startActivity(new Intent(Intent.ACTION_VIEW, uri)); } private void deleteCertificate() { File privateCertFile = getFileStreamPath(CERT_FILE_NAME); if (privateCertFile.exists()) { privateCertFile.delete(); UIUtilities.showToast(this, R.string.cert_deleted, false); } else { UIUtilities.showToast(this, R.string.no_cert_available, false); } } /* * Import a Certifcate at a known location into the applications private files area. * This file will be used in the HTTPTransport. */ private void importCert() { File certFile = new File(Environment.getExternalStorageDirectory(), CERT_FILE_NAME); File privateCertFile = getFileStreamPath(CERT_FILE_NAME); try { copyFile(certFile, privateCertFile); UIUtilities.showToast(this, R.string.cert_imported, false); } catch (IOException e) { Log.e(this.getClass().getName(), e.toString(), e); Toast.makeText(this, e.toString(), Toast.LENGTH_LONG).show(); } } private void copyFile(File source, File dest) throws IOException { FileChannel sourceChannel = null; FileChannel destinationChannel = null; FileInputStream is = null; FileOutputStream os = null; try { is = new FileInputStream(source); sourceChannel = is.getChannel(); File destFile = null; if (dest.isDirectory()) { destFile = new File(dest, source.getName()); } else { destFile = dest; } os = new FileOutputStream(destFile); destinationChannel = os.getChannel(); destinationChannel.transferFrom(sourceChannel, 0, sourceChannel.size()); } finally { if (sourceChannel != null) { sourceChannel.close(); } if (is != null) { is.close(); } if (destinationChannel != null) { destinationChannel.close(); } if (os != null) { os.close(); } } } }