From d2daf9f4f49494dc39ee522017d4053c8b440da6 Mon Sep 17 00:00:00 2001 From: Topher Date: Tue, 30 Oct 2018 16:04:00 -0700 Subject: [PATCH 1/3] Update to the latest build components and manage build error: Absolute path are not supported when setting an output file name --- Safe/build.gradle | 32 +++++++++++++----------- SafeDemo/build.gradle | 6 +++-- build.gradle | 10 ++++---- gradle/wrapper/gradle-wrapper.properties | 4 +-- 4 files changed, 28 insertions(+), 24 deletions(-) diff --git a/Safe/build.gradle b/Safe/build.gradle index 58ead690..cc4405bc 100644 --- a/Safe/build.gradle +++ b/Safe/build.gradle @@ -25,7 +25,7 @@ android { sourceSets { // Move the tests to tests/java, tests/res, etc... - instrumentTest.setRoot('tests') + androidTest.setRoot('tests') // Move the build types to build-types/ // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ... @@ -49,8 +49,10 @@ android { applicationVariants.all { variant -> variant.outputs.each { output -> + def relativeRootDir = output.packageApplication.outputDirectory.toPath() + .relativize(rootDir.toPath()).toFile() def file = output.outputFile - output.outputFileName = new File(file.parent, file.name.replace(".apk", "-" + defaultConfig.versionName + ".apk")) + output.outputFileName = new File("$relativeRootDir/release", file.name.replace(".apk", "-" + defaultConfig.versionName + ".apk")) } } } @@ -88,21 +90,21 @@ play { } dependencies { - compile fileTree(dir: 'libs', include: '*.jar') + implementation fileTree(dir: 'libs', include: '*.jar') implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" - compile 'com.github.openintents:distribution:3.0.1' - compile 'com.jakewharton:butterknife:8.5.1' + implementation 'com.github.openintents:distribution:3.0.1' + implementation 'com.jakewharton:butterknife:8.5.1' annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' - compile 'com.android.support:support-annotations:27.1.0' - compile 'com.android.support:support-fragment:27.1.0' - compile 'com.android.support:preference-v7:27.1.0' - compile 'com.android.support:design:27.1.0' - - androidTestCompile 'com.android.support:support-annotations:27.1.0' - androidTestCompile 'com.android.support.test:runner:1.0.1' - androidTestCompile 'com.android.support.test:rules:1.0.1' - androidTestCompile 'com.android.support.test.espresso:espresso-core:3.0.1' - androidTestCompile "com.android.support.test.espresso:espresso-intents:3.0.1" + implementation 'com.android.support:support-annotations:28.0.0' + implementation 'com.android.support:support-fragment:28.0.0' + implementation 'com.android.support:preference-v7:28.0.0' + implementation 'com.android.support:design:28.0.0' + + androidTestImplementation 'com.android.support:support-annotations:28.0.0' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test:rules:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + androidTestImplementation "com.android.support.test.espresso:espresso-intents:3.0.2" } repositories { mavenCentral() diff --git a/SafeDemo/build.gradle b/SafeDemo/build.gradle index f4711940..4fec2cc1 100644 --- a/SafeDemo/build.gradle +++ b/SafeDemo/build.gradle @@ -41,8 +41,10 @@ android { applicationVariants.all { variant -> variant.outputs.each { output -> + def relativeRootDir = output.packageApplication.outputDirectory.toPath() + .relativize(rootDir.toPath()).toFile() def file = output.outputFile - output.outputFileName = new File(file.parent, file.name.replace(".apk", "-" + defaultConfig.versionName + ".apk")) + output.outputFileName = new File("$relativeRootDir/release", file.name.replace(".apk", "-" + defaultConfig.versionName + ".apk")) } } } @@ -72,5 +74,5 @@ repositories { mavenCentral() } dependencies { - compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } diff --git a/build.gradle b/build.gradle index 1415b57b..527d049a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,11 @@ buildscript { - ext.kotlin_version = '1.2.30' + ext.kotlin_version = '1.2.71' repositories { jcenter() google() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.android.tools.build:gradle:3.2.1' classpath 'com.github.triplet.gradle:play-publisher:1.2.0' classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" @@ -25,9 +25,9 @@ ext { versionCode = versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionBuild versionName = "${versionMajor}.${versionMinor}.${versionPatch}" - compileSdkVersion = 27 - buildToolsVersion = "27.0.3" - targetSdkVersion = 27 + compileSdkVersion = 28 + buildToolsVersion = "28.0.3" + targetSdkVersion = 28 } allprojects { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 758ac157..1803c0ea 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Mar 15 16:40:57 CET 2018 +#Tue Oct 30 10:19:33 PDT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip From 97accb98651af7fcc4940e5f181f8ea66884fb46 Mon Sep 17 00:00:00 2001 From: Topher Date: Tue, 30 Oct 2018 16:06:02 -0700 Subject: [PATCH 2/3] Increase size of Notes field for easier editing. --- Safe/src/main/res/layout/pass_edit_fragment.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/Safe/src/main/res/layout/pass_edit_fragment.xml b/Safe/src/main/res/layout/pass_edit_fragment.xml index 0d98b138..df6f5e22 100644 --- a/Safe/src/main/res/layout/pass_edit_fragment.xml +++ b/Safe/src/main/res/layout/pass_edit_fragment.xml @@ -128,6 +128,7 @@ android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1" + android:minHeight="100dp" android:gravity="top" android:hint="@string/notes" android:inputType="textMultiLine" From 45bb8de28fa3e5cdf080d052b05efe9072d19f4c Mon Sep 17 00:00:00 2001 From: Topher Date: Tue, 30 Oct 2018 16:59:06 -0700 Subject: [PATCH 3/3] Only prompt for autobackup if something has changed. --- .../org/openintents/safe/AskPassword.java | 4 + .../org/openintents/safe/CategoryList.java | 68 ++++++----- .../java/org/openintents/safe/DBHelper.java | 106 +++++++++++++++++- 3 files changed, 144 insertions(+), 34 deletions(-) diff --git a/Safe/src/main/java/org/openintents/safe/AskPassword.java b/Safe/src/main/java/org/openintents/safe/AskPassword.java index 1eb9cdac..3f29fd67 100644 --- a/Safe/src/main/java/org/openintents/safe/AskPassword.java +++ b/Safe/src/main/java/org/openintents/safe/AskPassword.java @@ -149,6 +149,10 @@ public void onCreate(Bundle icicle) { switch (dbHelper.fetchVersion()) { case 2: databaseVersionError(); + break; + case 4: + dbHelper.updateDbVersion4to5(); + break; } } dbSalt = dbHelper.fetchSalt(); diff --git a/Safe/src/main/java/org/openintents/safe/CategoryList.java b/Safe/src/main/java/org/openintents/safe/CategoryList.java index d559698d..e0bf5c4d 100644 --- a/Safe/src/main/java/org/openintents/safe/CategoryList.java +++ b/Safe/src/main/java/org/openintents/safe/CategoryList.java @@ -302,39 +302,44 @@ private void checkForAutoBackup() { if (BuildConfig.DEBUG) { Log.d(TAG, "in need of backup"); } - // used a custom layout in order to get the checkbox - AlertDialog.Builder builder; - - LayoutInflater inflater = (LayoutInflater) this.getSystemService(LAYOUT_INFLATER_SERVICE); - View layout = inflater.inflate(R.layout.auto_backup, null); - - builder = new AlertDialog.Builder(this); - builder.setView(layout); - builder.setTitle(getString(R.string.autobackup)); - builder.setIcon(android.R.drawable.ic_menu_save); - builder.setNegativeButton( - R.string.no, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - checkAutobackupTurnoff(); + + boolean hasUnsavedChange = new DBHelper(this).getDbHasChanged(); + + if (hasUnsavedChange) { + // used a custom layout in order to get the checkbox + AlertDialog.Builder builder; + + LayoutInflater inflater = (LayoutInflater) this.getSystemService(LAYOUT_INFLATER_SERVICE); + View layout = inflater.inflate(R.layout.auto_backup, null); + + builder = new AlertDialog.Builder(this); + builder.setView(layout); + builder.setTitle(getString(R.string.autobackup)); + builder.setIcon(android.R.drawable.ic_menu_save); + builder.setNegativeButton( + R.string.no, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + checkAutobackupTurnoff(); + } } - } - ); - builder.setPositiveButton( - R.string.yes, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - checkAutobackupTurnoff(); - backupThreadStart(); + ); + builder.setPositiveButton( + R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + checkAutobackupTurnoff(); + backupThreadStart(); + } } - } - ); - if (daysSinceLastBackup == julianDay) { - builder.setMessage(R.string.backup_never); - } else { - String backupInDays = getString(R.string.backup_in_days, daysSinceLastBackup); - builder.setMessage(backupInDays); + ); + if (daysSinceLastBackup == julianDay) { + builder.setMessage(R.string.backup_never); + } else { + String backupInDays = getString(R.string.backup_in_days, daysSinceLastBackup); + builder.setMessage(backupInDays); + } + autobackupDialog = builder.create(); + autobackupDialog.show(); } - autobackupDialog = builder.create(); - autobackupDialog.show(); } } @@ -662,7 +667,8 @@ private void launchPassList(long id) { private String backupDatabase(String filename, OutputStream str) { Backup backup = new Backup(this); - backup.write(filename, str); + boolean changesSaved = backup.write(filename, str); + if (changesSaved) new DBHelper(this).setChangesSaved(); return backup.getResult(); } diff --git a/Safe/src/main/java/org/openintents/safe/DBHelper.java b/Safe/src/main/java/org/openintents/safe/DBHelper.java index f4550a93..538d3f7d 100644 --- a/Safe/src/main/java/org/openintents/safe/DBHelper.java +++ b/Safe/src/main/java/org/openintents/safe/DBHelper.java @@ -54,7 +54,8 @@ public class DBHelper { private static final String TABLE_SALT = "salt"; private static final String TABLE_PACKAGE_ACCESS = "package_access"; private static final String TABLE_CIPHER_ACCESS = "cipher_access"; - private static final int DATABASE_VERSION = 4; + private static final String TABLE_HAS_UNBACKED_CHANGE = "has_change_to_backup"; + private static final int DATABASE_VERSION = 5; private static String TAG = "DBHelper"; Context myCtx; @@ -77,6 +78,10 @@ public class DBHelper { private static final String PASSWORDS_DROP = "drop table " + TABLE_PASSWORDS + ";"; + private static final String HAS_UNBACKED_CHANGE_CREATE = + "create table if not exists " + TABLE_HAS_UNBACKED_CHANGE + " (" + + "has_change boolean default 1);"; + private static final String PACKAGE_ACCESS_CREATE = "create table " + TABLE_PACKAGE_ACCESS + " (" + "id integer not null, " @@ -190,6 +195,9 @@ private void CreateDatabase(SQLiteDatabase db) { db.execSQL(CIPHER_ACCESS_CREATE); db.execSQL(MASTER_KEY_CREATE); db.execSQL(SALT_CREATE); + + createHasChangeTable(); + } catch (SQLException e) { Log.d(TAG, "SQLite exception: " + e.getLocalizedMessage()); } @@ -210,10 +218,43 @@ public void deleteDatabase() { } } - public boolean needsUpgrade() { + boolean needsUpgrade() { return needsUpgrade; } + /** + * Create {@link DBHelper#TABLE_HAS_UNBACKED_CHANGE} table of simple boolean, to facilitate + * quick determination as to whether an autobackup should be prompted. + * + * Default value is false so we don't prompt for a backup before any data has been stored in the db. + * + * @throws SQLException + */ + private void createHasChangeTable() throws SQLException { + + db.execSQL(HAS_UNBACKED_CHANGE_CREATE); + + ContentValues defaultValue = new ContentValues(); + defaultValue.put("has_change", 0); + db.insert(TABLE_HAS_UNBACKED_CHANGE, null, defaultValue); + } + + /** + * Apply to DB ver 4 to update to DB ver 5. + * Calls {@link DBHelper#createHasChangeTable()} and updates the DB version to 5. + */ + void updateDbVersion4to5() { + try { + createHasChangeTable(); + + ContentValues dbVersion = new ContentValues(); + dbVersion.put("version", DATABASE_VERSION); + db.update(TABLE_DBVERSION, dbVersion, "version=" + fetchVersion(), null); + } catch (SQLException e) { + Log.d(TAG, "SQLite exception: " + e.getLocalizedMessage()); + } + } + public boolean getPrePopulate() { return needsPrePopulation; } @@ -236,7 +277,7 @@ public void close() { } } - public int fetchVersion() { + int fetchVersion() { int version = 0; try { Cursor c = db.query( @@ -362,6 +403,7 @@ public long addCategory(CategoryEntry entry) { try { rowID = db.insert(TABLE_CATEGORIES, null, initialValues); + setDbHasChanged(); } catch (SQLException e) { Log.d(TAG, "SQLite exception: " + e.getLocalizedMessage()); } @@ -376,6 +418,7 @@ public long addCategory(CategoryEntry entry) { public void deleteCategory(long Id) { try { db.delete(TABLE_CATEGORIES, "id=" + Id, null); + setDbHasChanged(); } catch (SQLException e) { Log.d(TAG, "SQLite exception: " + e.getLocalizedMessage()); } @@ -466,6 +509,7 @@ public void updateCategory(long Id, CategoryEntry entry) { try { db.update(TABLE_CATEGORIES, args, "id=" + Id, null); + setDbHasChanged(); } catch (SQLException e) { Log.d(TAG, "SQLite exception: " + e.getLocalizedMessage()); } @@ -498,6 +542,57 @@ public int countPasswords(long categoryId) { return count; } + /** + * To be called only after backup is successful. + */ + void setChangesSaved() { + setDbHasChangedFlag(false); + } + + /** + * Set the flag to indicate that an autobackup should be prompted at the next opportunity. + */ + private void setDbHasChanged() { + setDbHasChangedFlag(true); + } + + /** + * Sets has_change bit. + */ + private void setDbHasChangedFlag(boolean hasChanged) { + ContentValues value = new ContentValues(); + value.put("has_change", hasChanged); + try { + db.update(TABLE_HAS_UNBACKED_CHANGE, value, null, null); + } catch (SQLException e) { + Log.d(TAG, "SQLite exception: " + e.getLocalizedMessage()); + } + } + + /** + * + * @return true if a change has been registered in the db. + */ + boolean getDbHasChanged() { + boolean hasChanged = true; + if (db != null) { + try { + Cursor c = db.query( + true, TABLE_HAS_UNBACKED_CHANGE, new String[]{"has_change"}, + null, null, null, null, null, null); + if (c.getCount() > 0) { + c.moveToFirst(); + hasChanged = c.getShort(0) == 1; + } else setDbHasChanged(); // Bit was never set? Set it now. + c.close(); + } catch (SQLException e) { + Log.d(TAG, "SQLite exception: " + e.getLocalizedMessage()); + setDbHasChanged(); // Bit was never set? Set it now. + } + } + return hasChanged; + } + /** * @return A list of all password entries filtered by the CategoryId. * If CategoryId is 0, then return all entries in the database. @@ -724,6 +819,7 @@ public long updatePassword(long Id, PassEntry entry) { args.put("lastdatetimeedit", dateOut); try { db.update(TABLE_PASSWORDS, args, "id=" + Id, null); + setDbHasChanged(); } catch (SQLException e) { Log.d(TAG, "updatePassword: SQLite exception: " + e.getLocalizedMessage()); return -1; @@ -748,6 +844,7 @@ public void updatePasswordCategory(long Id, long newCategoryId) { try { db.update(TABLE_PASSWORDS, args, "id=" + Id, null); + setDbHasChanged(); } catch (SQLException e) { Log.d(TAG, "SQLite exception: " + e.getLocalizedMessage()); } @@ -784,6 +881,7 @@ public long addPassword(PassEntry entry) { try { id = db.insertOrThrow(TABLE_PASSWORDS, null, initialValues); + setDbHasChanged(); } catch (SQLException e) { Log.d(TAG, "SQLite exception: " + e.getLocalizedMessage()); id = -1; @@ -798,6 +896,7 @@ public void deletePassword(long Id) { try { db.delete(TABLE_PASSWORDS, "id=" + Id, null); db.delete(TABLE_PACKAGE_ACCESS, "id=" + Id, null); + setDbHasChanged(); } catch (SQLException e) { Log.d(TAG, "SQLite exception: " + e.getLocalizedMessage()); } @@ -825,6 +924,7 @@ public void addCipherAccess(String packageToAdd, long expiration) { initialValues.put("dateadded", dateOut); try { db.insert(TABLE_CIPHER_ACCESS, null, initialValues); + setDbHasChanged(); } catch (SQLException e) { Log.d(TAG, "SQLite exception: " + e.getLocalizedMessage()); }