diff --git a/.idea/.name b/.idea/.name index 382e0406e..634ba0111 100644 --- a/.idea/.name +++ b/.idea/.name @@ -1 +1 @@ -KeePassDroid \ No newline at end of file +keepassdroid \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 3cff213df..000000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 3b312839b..000000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 7e40dfca2..5d1998103 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -43,20 +43,4 @@ - - - - - 1.7 - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index c3c5f5663..58620619b 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,8 +2,8 @@ - + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7f4..6564d52db 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/app.iml b/app/app.iml index d7577ac80..1ff741123 100644 --- a/app/app.iml +++ b/app/app.iml @@ -1,5 +1,5 @@ - + @@ -87,7 +87,6 @@ - @@ -109,5 +108,7 @@ + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index b7c64b230..e6a6d9cea 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,7 +7,7 @@ model { defaultConfig.with { applicationId = "com.android.keepass" - minSdkVersion.apiLevel = 3 + minSdkVersion.apiLevel = 9 targetSdkVersion.apiLevel = 12 versionCode = 151 versionName = "2.0.6.1" @@ -71,6 +71,8 @@ model { dependencies { androidTestCompile files('libs/junit4.jar') + compile 'com.journeyapps:zxing-android-embedded:3.0.0@aar' + compile 'com.google.zxing:core:3.2.0' // Set this dependency if you want to use Hamcrest matching //androidTestCompile 'org.hamcrest:hamcrest-library:1.3' } diff --git a/app/src/main/java/com/keepassdroid/EntryActivity.java b/app/src/main/java/com/keepassdroid/EntryActivity.java index c8ba6c318..3ef8f3629 100644 --- a/app/src/main/java/com/keepassdroid/EntryActivity.java +++ b/app/src/main/java/com/keepassdroid/EntryActivity.java @@ -28,6 +28,7 @@ import android.app.Activity; import android.app.AlertDialog; +import android.app.Dialog; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -38,6 +39,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.graphics.Bitmap; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; @@ -49,6 +51,7 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; +import android.view.Window; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; @@ -56,6 +59,7 @@ import com.android.keepass.KeePass; import com.android.keepass.R; +import com.google.zxing.WriterException; import com.keepassdroid.app.App; import com.keepassdroid.compat.ActivityCompat; import com.keepassdroid.database.PwDatabase; @@ -64,6 +68,7 @@ import com.keepassdroid.database.exception.SamsungClipboardException; import com.keepassdroid.intents.Intents; import com.keepassdroid.utils.EmptyUtils; +import com.keepassdroid.utils.QrCodeGenerator; import com.keepassdroid.utils.Types; import com.keepassdroid.utils.Util; @@ -73,22 +78,24 @@ public class EntryActivity extends LockCloseHideActivity { public static final int NOTIFY_USERNAME = 1; public static final int NOTIFY_PASSWORD = 2; - + public static final int NOTIFY_QRCODE = 3; + + public static void Launch(Activity act, PwEntry pw, int pos) { Intent i; - + if ( pw instanceof PwEntryV4 ) { i = new Intent(act, EntryActivityV4.class); } else { i = new Intent(act, EntryActivity.class); } - + i.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID())); i.putExtra(KEY_REFRESH_POS, pos); - + act.startActivityForResult(i,0); } - + protected PwEntry mEntry; private Timer mTimer = new Timer(); private boolean mShowPassword; @@ -96,14 +103,14 @@ public static void Launch(Activity act, PwEntry pw, int pos) { private NotificationManager mNM; private BroadcastReceiver mIntentReceiver; protected boolean readOnly = false; - + private DateFormat dateFormat; private DateFormat timeFormat; - + protected void setEntryView() { setContentView(R.layout.entry_view); } - + protected void setupEditButtons() { Button edit = (Button) findViewById(R.id.entry_edit); edit.setOnClickListener(new View.OnClickListener() { @@ -111,12 +118,12 @@ protected void setupEditButtons() { public void onClick(View v) { EntryEditActivity.Launch(EntryActivity.this, mEntry); } - + }); - + if (readOnly) { edit.setVisibility(View.GONE); - + View divider = findViewById(R.id.entry_divider2); divider.setVisibility(View.GONE); } @@ -126,10 +133,10 @@ public void onClick(View v) { protected void onCreate(Bundle savedInstanceState) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); mShowPassword = ! prefs.getBoolean(getString(R.string.maskpass_key), getResources().getBoolean(R.bool.maskpass_default)); - + super.onCreate(savedInstanceState); setEntryView(); - + Context appCtx = getApplicationContext(); dateFormat = android.text.format.DateFormat.getDateFormat(appCtx); timeFormat = android.text.format.DateFormat.getTimeFormat(appCtx); @@ -148,41 +155,49 @@ protected void onCreate(Bundle savedInstanceState) { UUID uuid = Types.bytestoUUID(i.getByteArrayExtra(KEY_ENTRY)); mPos = i.getIntExtra(KEY_REFRESH_POS, -1); assert(uuid != null); - + mEntry = db.pm.entries.get(uuid); if (mEntry == null) { Toast.makeText(this, R.string.entry_not_found, Toast.LENGTH_LONG).show(); finish(); return; } - + // Refresh Menu contents in case onCreateMenuOptions was called before mEntry was set ActivityCompat.invalidateOptionsMenu(this); - + // Update last access time. mEntry.touch(false, false); - + fillData(false); setupEditButtons(); - + // Notification Manager mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - + if ( mEntry.getPassword().length() > 0 ) { // only show notification if password is available Notification password = getNotification(Intents.COPY_PASSWORD, R.string.copy_password); mNM.notify(NOTIFY_PASSWORD, password); } - + if ( mEntry.getUsername().length() > 0 ) { // only show notification if username is available Notification username = getNotification(Intents.COPY_USERNAME, R.string.copy_username); mNM.notify(NOTIFY_USERNAME, username); } - + + //Ajouter la notification pour le qr code + if ( mEntry.getPassword().length() > 0 ) { + // only show notification if password is available + Notification qrcode = getNotification(Intents.SHOW_QRCODE, R.string.notify_qrcode); + mNM.notify(NOTIFY_QRCODE, qrcode); + } + + mIntentReceiver = new BroadcastReceiver() { - + @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -197,70 +212,88 @@ public void onReceive(Context context, Intent intent) { if ( password.length() > 0 ) { timeoutCopyToClipboard(new String(mEntry.getPassword())); } + } else if (action.equals(Intents.SHOW_QRCODE)) { + String password = new String(mEntry.getPassword()); + if ( password.length() > 0 ) { + //Open a dialog with qrcode + try { + Bitmap qrCode = QrCodeGenerator.genQrCodeBitmap(password); + Dialog dialogQrCode = new Dialog(context); + dialogQrCode.getWindow().requestFeature(Window.FEATURE_NO_TITLE); + dialogQrCode.setContentView(getLayoutInflater().inflate(R.layout.qrcode + , null)); + dialogQrCode.show(); + ImageView qrCodeView = (ImageView) dialogQrCode.findViewById(R.id.qrcode); + qrCodeView.setImageBitmap(qrCode); + } catch (WriterException e) { + e.printStackTrace(); + } + } } } }; - + IntentFilter filter = new IntentFilter(); filter.addAction(Intents.COPY_USERNAME); filter.addAction(Intents.COPY_PASSWORD); + filter.addAction(Intents.SHOW_QRCODE); registerReceiver(mIntentReceiver, filter); } - + @Override protected void onDestroy() { // These members might never get initialized if the app timed out if ( mIntentReceiver != null ) { unregisterReceiver(mIntentReceiver); } - + if ( mNM != null ) { try { - mNM.cancelAll(); + mNM.cancelAll(); } catch (SecurityException e) { // Some android devices give a SecurityException when trying to cancel notifications without the WAKE_LOCK permission, // we'll ignore these. } } - + super.onDestroy(); } private Notification getNotification(String intentText, int descResId) { String desc = getString(descResId); Notification notify = new Notification(R.drawable.notify, desc, System.currentTimeMillis()); - + Intent intent = new Intent(intentText); PendingIntent pending = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); - + notify.setLatestEventInfo(this, getString(R.string.app_name), desc, pending); - + return notify; } - + private String getDateTime(Date dt) { return dateFormat.format(dt) + " " + timeFormat.format(dt); - + } - + protected void fillData(boolean trimList) { ImageView iv = (ImageView) findViewById(R.id.entry_icon); Database db = App.getDB(); db.drawFactory.assignDrawableTo(iv, getResources(), mEntry.getIcon()); - + PwDatabase pm = db.pm; populateText(R.id.entry_title, mEntry.getTitle(true, pm)); populateText(R.id.entry_user_name, mEntry.getUsername(true, pm)); - + populateText(R.id.entry_url, mEntry.getUrl(true, pm)); populateText(R.id.entry_password, mEntry.getPassword(true, pm)); setPasswordStyle(); - + populateText(R.id.entry_created, getDateTime(mEntry.getCreationTime())); populateText(R.id.entry_modified, getDateTime(mEntry.getLastModificationTime())); populateText(R.id.entry_accessed, getDateTime(mEntry.getLastAccessTime())); - + Date expires = mEntry.getExpiryTime(); if ( mEntry.expires() ) { populateText(R.id.entry_expires, getDateTime(expires)); @@ -270,7 +303,7 @@ protected void fillData(boolean trimList) { populateText(R.id.entry_comment, mEntry.getNotes(true, pm)); } - + private void populateText(int viewId, int resId) { TextView tv = (TextView) findViewById(viewId); tv.setText(resId); @@ -297,21 +330,21 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - + MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.entry, menu); - + MenuItem togglePassword = menu.findItem(R.id.menu_toggle_pass); if ( mShowPassword ) { togglePassword.setTitle(R.string.menu_hide_password); } else { togglePassword.setTitle(R.string.menu_showpass); } - + MenuItem gotoUrl = menu.findItem(R.id.menu_goto_url); MenuItem copyUser = menu.findItem(R.id.menu_copy_user); MenuItem copyPass = menu.findItem(R.id.menu_copy_pass); - + // In API >= 11 onCreateOptionsMenu may be called before onCreate completes // so mEntry may not be set if (mEntry == null) { @@ -334,10 +367,10 @@ public boolean onCreateOptionsMenu(Menu menu) { copyPass.setVisible(false); } } - + return true; } - + private void setPasswordStyle() { TextView password = (TextView) findViewById(R.id.entry_password); @@ -351,61 +384,61 @@ private void setPasswordStyle() { @Override public boolean onOptionsItemSelected(MenuItem item) { switch ( item.getItemId() ) { - case R.id.menu_donate: - try { - Util.gotoUrl(this, R.string.donate_url); - } catch (ActivityNotFoundException e) { - Toast.makeText(this, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show(); - return false; - } - - return true; - case R.id.menu_toggle_pass: - if ( mShowPassword ) { - item.setTitle(R.string.menu_showpass); - mShowPassword = false; - } else { - item.setTitle(R.string.menu_hide_password); - mShowPassword = true; - } - setPasswordStyle(); - - return true; - - case R.id.menu_goto_url: - String url; - url = mEntry.getUrl(); - - // Default http:// if no protocol specified - if ( ! url.contains("://") ) { - url = "http://" + url; - } - - try { - Util.gotoUrl(this, url); - } catch (ActivityNotFoundException e) { - Toast.makeText(this, R.string.no_url_handler, Toast.LENGTH_LONG).show(); - } - return true; - - case R.id.menu_copy_user: - timeoutCopyToClipboard(mEntry.getUsername(true, App.getDB().pm)); - return true; - - case R.id.menu_copy_pass: - timeoutCopyToClipboard(new String(mEntry.getPassword(true, App.getDB().pm))); - return true; - - case R.id.menu_lock: - App.setShutdown(); - setResult(KeePass.EXIT_LOCK); - finish(); - return true; + case R.id.menu_donate: + try { + Util.gotoUrl(this, R.string.donate_url); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show(); + return false; + } + + return true; + case R.id.menu_toggle_pass: + if ( mShowPassword ) { + item.setTitle(R.string.menu_showpass); + mShowPassword = false; + } else { + item.setTitle(R.string.menu_hide_password); + mShowPassword = true; + } + setPasswordStyle(); + + return true; + + case R.id.menu_goto_url: + String url; + url = mEntry.getUrl(); + + // Default http:// if no protocol specified + if ( ! url.contains("://") ) { + url = "http://" + url; + } + + try { + Util.gotoUrl(this, url); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.no_url_handler, Toast.LENGTH_LONG).show(); + } + return true; + + case R.id.menu_copy_user: + timeoutCopyToClipboard(mEntry.getUsername(true, App.getDB().pm)); + return true; + + case R.id.menu_copy_pass: + timeoutCopyToClipboard(new String(mEntry.getPassword(true, App.getDB().pm))); + return true; + + case R.id.menu_lock: + App.setShutdown(); + setResult(KeePass.EXIT_LOCK); + finish(); + return true; } - + return super.onOptionsItemSelected(item); } - + private void timeoutCopyToClipboard(String text) { try { Util.copyToClipboard(this, text); @@ -413,36 +446,36 @@ private void timeoutCopyToClipboard(String text) { showSamsungDialog(); return; } - + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); String sClipClear = prefs.getString(getString(R.string.clipboard_timeout_key), getString(R.string.clipboard_timeout_default)); - + long clipClearTime = Long.parseLong(sClipClear); - + if ( clipClearTime > 0 ) { mTimer.schedule(new ClearClipboardTask(this, text), clipClearTime); } } - + // Setup to allow the toast to happen in the foreground final Handler uiThreadCallback = new Handler(); // Task which clears the clipboard, and sends a toast to the foreground. private class ClearClipboardTask extends TimerTask { - + private final String mClearText; private final Context mCtx; - + ClearClipboardTask(Context ctx, String clearText) { mClearText = clearText; mCtx = ctx; } - + @Override public void run() { String currentClip = Util.getClipboard(mCtx); - + if ( currentClip.equals(mClearText) ) { try { Util.copyToClipboard(mCtx, ""); @@ -453,7 +486,7 @@ public void run() { } } } - + private void showSamsungDialog() { String text = getString(R.string.clipboard_error).concat(System.getProperty("line.separator")).concat(getString(R.string.clipboard_error_url)); SpannableString s = new SpannableString(text); @@ -462,17 +495,17 @@ private void showSamsungDialog() { tv.setAutoLinkMask(RESULT_OK); tv.setMovementMethod(LinkMovementMethod.getInstance()); Linkify.addLinks(s, Linkify.WEB_URLS); - + AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.clipboard_error_title) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }) - .setView(tv) - .show(); - + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .setView(tv) + .show(); + } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/keepassdroid/intents/Intents.java b/app/src/main/java/com/keepassdroid/intents/Intents.java index a214c3a63..ed2f6f3be 100644 --- a/app/src/main/java/com/keepassdroid/intents/Intents.java +++ b/app/src/main/java/com/keepassdroid/intents/Intents.java @@ -20,9 +20,10 @@ public class Intents { public static final String TIMEOUT = "com.keepassdroid.timeout"; - + public static final String COPY_USERNAME = "com.keepassdroid.copy_username"; public static final String COPY_PASSWORD = "com.keepassdroid.copy_password"; + public static final String SHOW_QRCODE = "com.keepassdroid.show_qrcode"; public static final String OPEN_INTENTS_FILE_BROWSE = "org.openintents.action.PICK_FILE"; -} +} \ No newline at end of file diff --git a/app/src/main/java/com/keepassdroid/utils/QrCodeGenerator.java b/app/src/main/java/com/keepassdroid/utils/QrCodeGenerator.java new file mode 100644 index 000000000..1fe198747 --- /dev/null +++ b/app/src/main/java/com/keepassdroid/utils/QrCodeGenerator.java @@ -0,0 +1,38 @@ +package com.keepassdroid.utils; + +import android.graphics.Bitmap; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.MultiFormatWriter; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; + +public class QrCodeGenerator { + + private static final int WIDTH = 800; + private static final int WHITE = 0xFFFFFFFF; + private static final int BLACK = 0xFF000000; + + public static Bitmap genQrCodeBitmap(String str) throws WriterException { + BitMatrix result; + try { + result = new MultiFormatWriter().encode(str, + BarcodeFormat.QR_CODE, WIDTH, WIDTH, null); + } catch (IllegalArgumentException iae) { + // Unsupported format + return null; + } + int w = result.getWidth(); + int h = result.getHeight(); + int[] pixels = new int[w * h]; + for (int y = 0; y < h; y++) { + int offset = y * w; + for (int x = 0; x < w; x++) { + pixels[offset + x] = result.get(x, y) ? BLACK : WHITE; + } + } + Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + bitmap.setPixels(pixels, 0, WIDTH, 0, 0, w, h); + return bitmap; + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/qrcode.xml b/app/src/main/res/layout/qrcode.xml new file mode 100644 index 000000000..53829399c --- /dev/null +++ b/app/src/main/res/layout/qrcode.xml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 800ecb19b..cf3012e60 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -50,6 +50,7 @@ Temps avant le vidage du presse-papier après copie du nom d\'utilisateur ou du mot de passe Copier le nom d\'utilisateur dans le presse-papier Copier le mot de passe dans le presse-papier + Générer le mot de passe sous forme de qr code Création de la clé de base de données… Groupe actuel\u00A0: Groupe actuel\u00A0: Racine diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f1126abd6..ed5234868 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -50,6 +50,7 @@ Time before clearing clipboard after copying username or password Select to copy username to clipboard Select to copy password to clipboard + Select to generate a qrcode Creating database key… Current Group: Current Group: Root diff --git a/keepassdroid.iml b/keepassdroid.iml new file mode 100644 index 000000000..6b5c6bb4a --- /dev/null +++ b/keepassdroid.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file