Java tutorial
/* * Copyright 2009-2018 Brian Pellin. * * This file is part of KeePassDroid. * * KeePassDroid 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. * * KeePassDroid 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 KeePassDroid. If not, see <http://www.gnu.org/licenses/>. * */ package com.keepassdroid; import android.Manifest; import android.app.Activity; import android.app.backup.BackupManager; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.ContextWrapper; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v4.hardware.fingerprint.FingerprintManagerCompat; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.ImageButton; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import com.android.keepass.KeePass; import com.android.keepass.R; import com.keepassdroid.app.App; import com.keepassdroid.compat.ClipDataCompat; import com.keepassdroid.compat.StorageAF; import com.keepassdroid.database.edit.LoadDB; import com.keepassdroid.database.edit.OnFinish; import com.keepassdroid.dialog.PasswordEncodingDialogHelper; import com.keepassdroid.fileselect.BrowserDialog; import com.keepassdroid.fingerprint.FingerPrintHelper; import com.keepassdroid.intents.Intents; import com.keepassdroid.settings.AppSettingsActivity; import com.keepassdroid.utils.EmptyUtils; import com.keepassdroid.utils.Interaction; import com.keepassdroid.utils.UriUtil; import com.keepassdroid.utils.Util; import org.spongycastle.util.Pack; import java.io.File; import java.io.FileNotFoundException; import javax.crypto.Cipher; public class PasswordActivity extends LockingActivity implements FingerPrintHelper.FingerPrintCallback { public static final String KEY_DEFAULT_FILENAME = "defaultFileName"; private static final String KEY_FILENAME = "fileName"; private static final String KEY_KEYFILE = "keyFile"; private static final String KEY_PASSWORD = "password"; private static final String KEY_LAUNCH_IMMEDIATELY = "launchImmediately"; private static final String VIEW_INTENT = "android.intent.action.VIEW"; private static final int FILE_BROWSE = 256; public static final int GET_CONTENT = 257; private static final int OPEN_DOC = 258; private static final String[] READ_WRITE_PERMISSIONS = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }; private static final int PERMISSION_REQUEST_ID = 1; private Uri mDbUri = null; private Uri mKeyUri = null; private boolean mRememberKeyfile; SharedPreferences prefs; SharedPreferences prefsNoBackup; private Uri storedKeyUri = null; private String storedPassword = null; private FingerPrintHelper fingerPrintHelper; private int mode; private static final String PREF_KEY_VALUE_PREFIX = "valueFor_"; // key is a combination of db file name and this prefix private static final String PREF_KEY_IV_PREFIX = "ivFor_"; // key is a combination of db file name and this prefix private View fingerprintView; private TextView confirmationView; private EditText passwordView; private Button confirmButton; public static void Launch(Activity act, String fileName) throws FileNotFoundException { Launch(act, fileName, ""); } public static void Launch(Activity act, String fileName, String keyFile) throws FileNotFoundException { if (EmptyUtils.isNullOrEmpty(fileName)) { throw new FileNotFoundException(); } Uri uri = UriUtil.parseDefaultFile(fileName); String scheme = uri.getScheme(); if (!EmptyUtils.isNullOrEmpty(scheme) && scheme.equalsIgnoreCase("file")) { File dbFile = new File(uri.getPath()); if (!dbFile.exists()) { throw new FileNotFoundException(); } } Intent i = new Intent(act, PasswordActivity.class); i.putExtra(KEY_FILENAME, fileName); i.putExtra(KEY_KEYFILE, keyFile); act.startActivityForResult(i, 0); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case KeePass.EXIT_NORMAL: setEditText(R.id.password, ""); App.getDB().clear(); break; case KeePass.EXIT_LOCK: setResult(KeePass.EXIT_LOCK); setEditText(R.id.password, ""); finish(); App.getDB().clear(); break; case FILE_BROWSE: if (resultCode == RESULT_OK) { String filename = data.getDataString(); if (filename != null) { EditText fn = (EditText) findViewById(R.id.pass_keyfile); fn.setText(filename); mKeyUri = UriUtil.parseDefaultFile(filename); } } break; case GET_CONTENT: case OPEN_DOC: if (resultCode == RESULT_OK) { if (data != null) { Uri uri = data.getData(); if (uri != null) { if (requestCode == GET_CONTENT) { uri = UriUtil.translate(this, uri); } String path = uri.toString(); if (path != null) { EditText fn = (EditText) findViewById(R.id.pass_keyfile); fn.setText(path); } mKeyUri = uri; } } } break; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent i = getIntent(); prefs = PreferenceManager.getDefaultSharedPreferences(this); prefsNoBackup = getSharedPreferences("nobackup", Context.MODE_PRIVATE); mRememberKeyfile = prefs.getBoolean(getString(R.string.keyfile_key), getResources().getBoolean(R.bool.keyfile_default)); setContentView(R.layout.password); confirmButton = (Button) findViewById(R.id.pass_ok); fingerprintView = findViewById(R.id.fingerprint); confirmationView = (TextView) findViewById(R.id.fingerprint_label); passwordView = (EditText) findViewById(R.id.password); new InitTask().execute(i); initForFingerprint(); } @Override protected void onResume() { super.onResume(); // If the application was shutdown make sure to clear the password field, if it // was saved in the instance state if (App.isShutdown()) { TextView password = (TextView) findViewById(R.id.password); password.setText(""); } // Clear the shutdown flag App.clearShutdown(); // checks if fingerprint is available, will also start listening for fingerprints when available checkAvailability(); } private void retrieveSettings() { String defaultFilename = prefs.getString(KEY_DEFAULT_FILENAME, ""); if (!EmptyUtils.isNullOrEmpty(mDbUri.getPath()) && UriUtil.equalsDefaultfile(mDbUri, defaultFilename)) { CheckBox checkbox = (CheckBox) findViewById(R.id.default_database); checkbox.setChecked(true); } } private Uri getKeyFile(Uri dbUri) { if (mRememberKeyfile) { return App.getFileHistory().getFileByName(dbUri); } else { return null; } } private void populateView() { String db = (mDbUri == null) ? "" : mDbUri.toString(); setEditText(R.id.filename, db); String key = (mKeyUri == null) ? "" : mKeyUri.toString(); setEditText(R.id.pass_keyfile, key); } /* private void errorMessage(CharSequence text) { Toast.makeText(this, text, Toast.LENGTH_LONG).show(); } */ private void errorMessage(int resId) { Toast.makeText(this, resId, Toast.LENGTH_LONG).show(); } // fingerprint related code here private void initForFingerprint() { fingerPrintHelper = new FingerPrintHelper(this, this); if (fingerPrintHelper.hasEnrolledFingerprints()) { // when text entered we can enable the logon/purchase button and if required update encryption/decryption mode passwordView.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { } @Override public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { } @Override public void afterTextChanged(final Editable s) { final boolean validInput = s.length() > 0; // encrypt or decrypt mode based on how much input or not int messageId; if (validInput) { messageId = R.string.store_with_fingerprint; } else if (EmptyUtils.isNullOrEmpty(prefsNoBackup.getString(getPreferenceKeyValue(), null))) { messageId = R.string.no_password_stored; } else { messageId = R.string.scanning_fingerprint; } confirmationView.setText(messageId); mode = validInput ? toggleMode(Cipher.ENCRYPT_MODE) : toggleMode(Cipher.DECRYPT_MODE); } }); // callback for fingerprint findings fingerPrintHelper.setAuthenticationCallback(new FingerprintManagerCompat.AuthenticationCallback() { @Override public void onAuthenticationError(final int errorCode, final CharSequence errString) { if (errorCode != 5) { // FINGERPRINT_ERROR_CANCELLED (not defined in support library) onException(errString); } } @Override public void onAuthenticationHelp(final int helpCode, final CharSequence helpString) { onException(); confirmationView.setText(helpString); } @Override public void onAuthenticationSucceeded(final FingerprintManagerCompat.AuthenticationResult result) { if (mode == Cipher.ENCRYPT_MODE) { // newly store the entered password in encrypted way final String password = passwordView.getText().toString(); fingerPrintHelper.encryptData(password); } else if (mode == Cipher.DECRYPT_MODE) { // retrieve the encrypted value from preferences final String encryptedValue = prefsNoBackup.getString(getPreferenceKeyValue(), null); if (encryptedValue != null) { fingerPrintHelper.decryptData(encryptedValue); } } } @Override public void onAuthenticationFailed() { onException(R.string.fingerprint_notrecognized); } }); } } private String getPreferenceKeyValue() { // makes it possible to store passwords uniqly per database return PREF_KEY_VALUE_PREFIX + (mDbUri != null ? mDbUri.getPath() : ""); } private String getPreferenceKeyIvSpec() { return PREF_KEY_IV_PREFIX + (mDbUri != null ? mDbUri.getPath() : ""); } private int toggleMode(final int newMode) { if (mode != newMode) { mode = newMode; switch (mode) { case Cipher.ENCRYPT_MODE: fingerPrintHelper.initEncryptData(); break; case Cipher.DECRYPT_MODE: final String ivSpecValue = prefsNoBackup.getString(getPreferenceKeyIvSpec(), null); if (ivSpecValue != null) { fingerPrintHelper.initDecryptData(ivSpecValue); } break; } } else { fingerPrintHelper.stopListening(); fingerPrintHelper.startListening(); } return mode; } @Override protected void onPause() { super.onPause(); // stop listening when we go in background if (fingerPrintHelper != null) { fingerPrintHelper.stopListening(); } } private void setFingerPrintVisibilty(int vis) { ImageButton browse = (ImageButton) findViewById(R.id.browse_button); EditText fn = (EditText) findViewById(R.id.pass_keyfile); RelativeLayout.LayoutParams browseParams = (RelativeLayout.LayoutParams) browse.getLayoutParams(); RelativeLayout.LayoutParams fnParams = (RelativeLayout.LayoutParams) fn.getLayoutParams(); int layoutBelow; if (vis == View.GONE) { layoutBelow = R.id.password; } else { layoutBelow = R.id.fingerprint_label; } browseParams.addRule(RelativeLayout.BELOW, layoutBelow); fnParams.addRule(RelativeLayout.BELOW, layoutBelow); fingerprintView.setVisibility(vis); confirmationView.setVisibility(vis); } private void checkAvailability() { // fingerprint not supported (by API level or hardware) so keep option hidden if (!fingerPrintHelper.isHardwareDetected()) { setFingerPrintVisibilty(View.GONE); } // fingerprint is available but not configured show icon but in disabled state with some information else if (!fingerPrintHelper.hasEnrolledFingerprints()) { setFingerPrintVisibilty(View.VISIBLE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { fingerprintView.setAlpha(0.3f); } // This happens when no fingerprints are registered. Listening won't start confirmationView.setText(R.string.configure_fingerprint); } // finally fingerprint available and configured so we can use it else { setFingerPrintVisibilty(View.VISIBLE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { fingerprintView.setAlpha(1f); } // fingerprint available but no stored password found yet for this DB so show info don't listen if (prefsNoBackup.getString(getPreferenceKeyValue(), null) == null) { confirmationView.setText(R.string.no_password_stored); } // all is set here so we can confirm to user and start listening for fingerprints else { confirmationView.setText(R.string.scanning_fingerprint); // listen for decryption by default toggleMode(Cipher.DECRYPT_MODE); } } } @Override public void handleEncryptedResult(final String value, final String ivSpec) { prefsNoBackup.edit().putString(getPreferenceKeyValue(), value).putString(getPreferenceKeyIvSpec(), ivSpec) .commit(); // and remove visual input to reset UI confirmButton.performClick(); confirmationView.setText(R.string.encrypted_value_stored); } @Override public void handleDecryptedResult(final String value) { // on decrypt enter it for the purchase/login action passwordView.setText(value); confirmButton.performClick(); } @Override public void onInvalidKeyException() { Toast.makeText(this, R.string.fingerprint_invalid_key, Toast.LENGTH_SHORT).show(); } @Override public void onException() { onException(true); } @Override public void onException(boolean showMessage) { if (showMessage) { onException(R.string.fingerprint_error); } } @Override public void onException(int resId) { Toast.makeText(this, resId, Toast.LENGTH_SHORT).show(); } @Override public void onException(CharSequence message) { Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); } @Override public void onKeyInvalidated() { prefsNoBackup.edit().remove(getPreferenceKeyValue()).remove(getPreferenceKeyIvSpec()).commit(); confirmationView.setText(R.string.fingerprint_key_invalidated); } private class DefaultCheckChange implements CompoundButton.OnCheckedChangeListener { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { String newDefaultFileName; if (isChecked) { newDefaultFileName = mDbUri.toString(); } else { newDefaultFileName = ""; } SharedPreferences.Editor editor = prefs.edit(); editor.putString(KEY_DEFAULT_FILENAME, newDefaultFileName); editor.apply(); BackupManager backupManager = new BackupManager(PasswordActivity.this); backupManager.dataChanged(); } } private class OkClickHandler implements View.OnClickListener { public void onClick(View view) { String pass = getEditText(R.id.password); String key = getEditText(R.id.pass_keyfile); loadDatabase(pass, key); } } private void loadDatabase(String pass, String keyfile) { loadDatabase(pass, UriUtil.parseDefaultFile(keyfile)); } private boolean hasFileUri(Uri uri) { try { if (uri == null) { return false; } return uri.getScheme().equalsIgnoreCase("file"); } catch (Exception e) { return false; } } private boolean checkFilePermissions(Uri db, Uri keyfile) { boolean hasFileUri = hasFileUri(db) || hasFileUri(keyfile); if (!hasFileUri) return true; boolean hasRead = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; boolean hasWrite = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; if (!hasRead || !hasWrite) { ActivityCompat.requestPermissions(this, READ_WRITE_PERMISSIONS, PERMISSION_REQUEST_ID); return false; } return true; } public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (requestCode == PERMISSION_REQUEST_ID && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) { loadDatabaseWithPermission(); } else { errorMessage(R.string.no_external_permissions); } } private void loadDatabase(String pass, Uri keyfile) { if (pass.length() == 0 && (keyfile == null || keyfile.toString().length() == 0)) { errorMessage(R.string.error_nopass); return; } storedPassword = pass; storedKeyUri = keyfile; if (checkFilePermissions(mDbUri, keyfile)) { loadDatabaseWithPermission(); } } private void loadDatabaseWithPermission() { String pass = storedPassword; storedPassword = null; Uri keyfile = storedKeyUri; storedKeyUri = null; // Clear before we load Database db = App.getDB(); db.clear(); // Clear the shutdown flag App.clearShutdown(); Handler handler = new Handler(); LoadDB task = new LoadDB(db, PasswordActivity.this, mDbUri, pass, keyfile, new AfterLoad(handler, db)); ProgressTask pt = new ProgressTask(PasswordActivity.this, task, R.string.loading_database); pt.run(); } private String getEditText(int resId) { return Util.getEditText(this, resId); } private void setEditText(int resId, String str) { TextView te = (TextView) findViewById(resId); assert (te == null); if (te != null) { te.setText(str); } } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); MenuInflater inflate = getMenuInflater(); inflate.inflate(R.menu.password, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_about: AboutDialog dialog = new AboutDialog(this); dialog.show(); return true; case R.id.menu_app_settings: AppSettingsActivity.Launch(this); return true; } return super.onOptionsItemSelected(item); } private final class AfterLoad extends OnFinish { private Database db; public AfterLoad(Handler handler, Database db) { super(handler); this.db = db; } @Override public void run() { if (db.passwordEncodingError) { PasswordEncodingDialogHelper dialog = new PasswordEncodingDialogHelper(); dialog.show(PasswordActivity.this, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { GroupActivity.Launch(PasswordActivity.this); } }); } else if (mSuccess) { GroupActivity.Launch(PasswordActivity.this); } else { displayMessage(PasswordActivity.this); } } } private class InitTask extends AsyncTask<Intent, Void, Integer> { String password = ""; boolean launch_immediately = false; @Override protected Integer doInBackground(Intent... args) { Intent i = args[0]; String action = i.getAction(); ; if (action != null && action.equals(VIEW_INTENT)) { Uri incoming = i.getData(); mDbUri = incoming; mKeyUri = ClipDataCompat.getUriFromIntent(i, KEY_KEYFILE); if (incoming == null) { return R.string.error_can_not_handle_uri; } else if (incoming.getScheme().equals("file")) { String fileName = incoming.getPath(); if (fileName.length() == 0) { // No file name return R.string.FileNotFound; } File dbFile = new File(fileName); if (!dbFile.exists()) { // File does not exist return R.string.FileNotFound; } if (mKeyUri == null) { mKeyUri = getKeyFile(mDbUri); } } else if (incoming.getScheme().equals("content")) { if (mKeyUri == null) { mKeyUri = getKeyFile(mDbUri); } } else { return R.string.error_can_not_handle_uri; } password = i.getStringExtra(KEY_PASSWORD); launch_immediately = i.getBooleanExtra(KEY_LAUNCH_IMMEDIATELY, false); } else { mDbUri = UriUtil.parseDefaultFile(i.getStringExtra(KEY_FILENAME)); mKeyUri = UriUtil.parseDefaultFile(i.getStringExtra(KEY_KEYFILE)); password = i.getStringExtra(KEY_PASSWORD); launch_immediately = i.getBooleanExtra(KEY_LAUNCH_IMMEDIATELY, false); if (mKeyUri == null || mKeyUri.toString().length() == 0) { mKeyUri = getKeyFile(mDbUri); } } return null; } public void onPostExecute(Integer result) { if (result != null) { Toast.makeText(PasswordActivity.this, result, Toast.LENGTH_LONG).show(); finish(); return; } populateView(); confirmButton.setOnClickListener(new OkClickHandler()); CheckBox checkBox = (CheckBox) findViewById(R.id.show_password); // Show or hide password checkBox.setOnCheckedChangeListener(new OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { TextView password = (TextView) findViewById(R.id.password); if (isChecked) { password.setInputType( InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); } else { password.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); } } }); if (password != null) { TextView tv_password = (TextView) findViewById(R.id.password); tv_password.setText(password); } CheckBox defaultCheck = (CheckBox) findViewById(R.id.default_database); defaultCheck.setOnCheckedChangeListener(new DefaultCheckChange()); ImageButton browse = (ImageButton) findViewById(R.id.browse_button); browse.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if (StorageAF.useStorageFramework(PasswordActivity.this)) { Intent i = new Intent(StorageAF.ACTION_OPEN_DOCUMENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("*/*"); startActivityForResult(i, OPEN_DOC); } else { Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("*/*"); try { startActivityForResult(i, GET_CONTENT); } catch (ActivityNotFoundException e) { lookForOpenIntentsFilePicker(); } } } private void lookForOpenIntentsFilePicker() { if (Interaction.isIntentAvailable(PasswordActivity.this, Intents.OPEN_INTENTS_FILE_BROWSE)) { Intent i = new Intent(Intents.OPEN_INTENTS_FILE_BROWSE); // Get file path parent if possible try { if (mDbUri != null && mDbUri.toString().length() > 0) { if (mDbUri.getScheme().equals("file")) { File keyfile = new File(mDbUri.getPath()); File parent = keyfile.getParentFile(); if (parent != null) { i.setData(Uri.parse("file://" + parent.getAbsolutePath())); } } } } catch (Exception e) { // Ignore } try { startActivityForResult(i, FILE_BROWSE); } catch (ActivityNotFoundException e) { showBrowserDialog(); } } else { showBrowserDialog(); } } private void showBrowserDialog() { BrowserDialog diag = new BrowserDialog(PasswordActivity.this); diag.show(); } }); retrieveSettings(); if (launch_immediately) { loadDatabase(password, mKeyUri); } } } }