From fffbd2b15d1d515771a4d496b1c7cccc97c4bc5b Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Thu, 10 Oct 2019 13:51:06 +0200 Subject: [PATCH 01/14] Ask to import account on migration --- .../activity/NotesListViewActivity.java | 20 ++--- .../persistence/NoteSQLiteOpenHelper.java | 90 +++++++++++++++++-- .../persistence/NoteServerSyncHelper.java | 8 +- .../owncloud/notes/util/NotesClient.java | 18 +--- 4 files changed, 95 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java index eae160fd..7cf17572 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java @@ -200,9 +200,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap try { // to get current account from SingleAccountHelper selectAccount(SingleAccountHelper.getCurrentSingleSignOnAccount(getApplicationContext()).name); Log.v(getClass().getSimpleName(), "NextcloudRequest account: " + localAccount); - } catch (NextcloudFilesAppAccountNotFoundException e) { - e.printStackTrace(); - } catch (NoCurrentAccountSelectedException e) { + } catch (NoCurrentAccountSelectedException | NextcloudFilesAppAccountNotFoundException e) { if (db.hasAccounts()) { // If nothing is stored in SingleAccountHelper, check db for accounts selectAccount(db.getAccounts().get(0).getAccountName()); } else { @@ -740,14 +738,6 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - AccountImporter.onActivityResult(requestCode, resultCode, data, this, (SingleSignOnAccount account) -> { - Log.v(getClass().getSimpleName(), "Added account: " + "name:" + account.name + ", " + account.url + ", userId" + account.userId); - db.addAccount(account.url, account.userId, account.name); - selectAccount(account.name); - clickHeader(); - drawerLayout.closeDrawer(GravityCompat.START); - }); - // Check which request we're responding to if (requestCode == create_note_cmd) { // Make sure the request was successful @@ -771,6 +761,14 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap // Recreate activity completely, because theme switchting makes problems when only invalidating the views. // @see https://github.com/stefan-niedermann/nextcloud-notes/issues/529 recreate(); + } else { + AccountImporter.onActivityResult(requestCode, resultCode, data, this, (SingleSignOnAccount account) -> { + Log.v(getClass().getSimpleName(), "Added account: " + "name:" + account.name + ", " + account.url + ", userId" + account.userId); + db.addAccount(account.url, account.userId, account.name); + selectAccount(account.name); + clickHeader(); + drawerLayout.closeDrawer(GravityCompat.START); + }); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index c7241574..8da56ebc 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -1,9 +1,14 @@ package it.niedermann.owncloud.notes.persistence; +import android.Manifest; +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.Activity; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.content.pm.ShortcutManager; import android.content.res.Resources; import android.database.Cursor; @@ -18,7 +23,13 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; +import androidx.core.content.ContextCompat; +import com.nextcloud.android.sso.Constants; +import com.nextcloud.android.sso.exceptions.AndroidGetAccountsPermissionNotGranted; + +import java.net.MalformedURLException; +import java.net.URL; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; @@ -27,6 +38,7 @@ import java.util.List; import java.util.Map; import it.niedermann.owncloud.notes.R; +import it.niedermann.owncloud.notes.android.activity.NotesListViewActivity; import it.niedermann.owncloud.notes.android.appwidget.NoteListWidget; import it.niedermann.owncloud.notes.android.appwidget.SingleNoteWidget; import it.niedermann.owncloud.notes.model.CloudNote; @@ -37,6 +49,8 @@ import it.niedermann.owncloud.notes.model.NavigationAdapter; import it.niedermann.owncloud.notes.util.ICallback; import it.niedermann.owncloud.notes.util.NoteUtil; +import static com.nextcloud.android.sso.AccountImporter.CHOOSE_ACCOUNT_SSO; + /** * Helps to add, get, update and delete Notes with the option to trigger a Resync with the Server. */ @@ -72,13 +86,13 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { private NoteSQLiteOpenHelper(Context context) { super(context, database_name, null, database_version); - this.context = context.getApplicationContext(); + this.context = context; serverSyncHelper = NoteServerSyncHelper.getInstance(this); } public static NoteSQLiteOpenHelper getInstance(Context context) { if (instance == null) - return instance = new NoteSQLiteOpenHelper(context.getApplicationContext()); + return instance = new NoteSQLiteOpenHelper(context); else return instance; } @@ -163,10 +177,74 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { values.put(key_account_id, 1); db.update(table_notes, values, key_account_id + " = ?", new String[]{"NULL"}); - SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); - editor.remove("notes_last_etag"); - editor.remove("notes_last_modified"); - editor.apply(); + + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + + String username = sharedPreferences.getString("settingsUsername", ""); + String url = sharedPreferences.getString("settingsUrl", ""); + if (url != null && url.endsWith("/")) { + url = url.substring(0, url.length() - 1); + try { + String accountName = username + "@" + new URL(url).getHost(); + + ContentValues migratedAccountValues = new ContentValues(); + migratedAccountValues.put(key_url, url); + migratedAccountValues.put(key_username, username); + migratedAccountValues.put(key_account_name, accountName); + db.insert(table_accounts, null, migratedAccountValues); + + if (context instanceof NotesListViewActivity) { + // Partially copied from AccountImporter + Activity activity = (Activity) context; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + // Do something for lollipop and above versions + Log.d(getClass().getSimpleName(), "Permission not granted!"); + throw new AndroidGetAccountsPermissionNotGranted(); + } else { + if (ContextCompat.checkSelfPermission(context, Manifest.permission.GET_ACCOUNTS) != PackageManager.PERMISSION_GRANTED) { + Log.d(getClass().getSimpleName(), "Permission granted!"); + ArrayList accs = new ArrayList<>(); + accs.add(new Account(accountName, Constants.ACCOUNT_TYPE_PROD)); + accs.add(new Account(accountName, Constants.ACCOUNT_TYPE_DEV)); + Intent intent = AccountManager.newChooseAccountIntent(accs.get(0), accs, new String[]{Constants.ACCOUNT_TYPE_PROD, Constants.ACCOUNT_TYPE_DEV}, + true, null, null, null, null); + activity.startActivityForResult(intent, CHOOSE_ACCOUNT_SSO); + } + } +// ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.GET_ACCOUNTS}, +// REQUEST_GET_ACCOUNTS_PERMISSION); +// Account ac = AccountImporter.getAccountForName(context, accountName); +// ArrayList li = new ArrayList<>(); +// li.add(ac); +// Intent intent = AccountManager.newChooseAccountIntent(ac, +// li, +// new String[]{Constants.ACCOUNT_TYPE_PROD, Constants.ACCOUNT_TYPE_DEV}, +// true, +// null, +// null, +// null, +// null); +// ((NotesListViewActivity) context).startActivityForResult(intent, CHOOSE_ACCOUNT_SSO); + } + + + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.remove("notes_last_etag"); + editor.remove("notes_last_modified"); + editor.remove("settingsUrl"); + editor.remove("settingsUsername"); + editor.remove("settingsPassword"); + editor.apply(); + } catch (MalformedURLException e) { + e.printStackTrace(); +// } catch (NextcloudFilesAppNotSupportedException e) { +// e.printStackTrace(); +// } catch (NextcloudFilesAppAccountPermissionNotGrantedException e) { +// e.printStackTrace(); + } catch (AndroidGetAccountsPermissionNotGranted androidGetAccountsPermissionNotGranted) { + androidGetAccountsPermissionNotGranted.printStackTrace(); + } + } } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java index 79f84dc6..ca6a2d72 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java @@ -98,13 +98,7 @@ public class NoteServerSyncHelper { private NoteServerSyncHelper(NoteSQLiteOpenHelper db) { this.dbHelper = db; this.appContext = db.getContext().getApplicationContext(); - try { - this.localAccount = db.getLocalAccountByAccountName(SingleAccountHelper.getCurrentSingleSignOnAccount(appContext).name); - notesClient = new NotesClient(appContext); - Log.v(getClass().getSimpleName(), "NextcloudRequest account: " + localAccount); - } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) { - e.printStackTrace(); - } + updateAccount(); this.syncOnlyOnWifiKey = appContext.getResources().getString(R.string.pref_key_wifi_only); // Registers BroadcastReceiver to track network connection changes. diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java index ba5c8c04..f5cee393 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java @@ -76,23 +76,7 @@ public class NotesClient { public NotesClient(Context context) { this.context = context; - try { - SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(context); - Log.v(getClass().getSimpleName(), "NextcloudRequest account: " + ssoAccount.name); - mNextcloudAPI = new NextcloudAPI(context, ssoAccount, new GsonBuilder().create(), new NextcloudAPI.ApiConnectedListener() { - @Override - public void onConnected() { - Log.v(getClass().getSimpleName(), "SSO API connected"); - } - - @Override - public void onError(Exception ex) { - ex.printStackTrace(); - } - }); - } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) { - e.printStackTrace(); - } + updateAccount(); } public void updateAccount() { From 981b059402e78181b28a1b771e606eb0c64ff701 Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Thu, 10 Oct 2019 17:29:45 +0200 Subject: [PATCH 02/14] Move unauthorized account detection and handling to NotesListViewActivity --- .../activity/NotesListViewActivity.java | 56 ++++++++++++---- .../android/fragment/BaseNoteFragment.java | 6 +- .../persistence/NoteSQLiteOpenHelper.java | 64 ++----------------- .../persistence/NoteServerSyncHelper.java | 10 ++- 4 files changed, 63 insertions(+), 73 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java index 7cf17572..a91423a7 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java @@ -1,7 +1,11 @@ package it.niedermann.owncloud.notes.android.activity; +import android.Manifest; +import android.accounts.Account; +import android.accounts.AccountManager; import android.app.SearchManager; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.content.res.Configuration; @@ -9,6 +13,7 @@ import android.graphics.Canvas; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.util.Log; @@ -30,6 +35,7 @@ import androidx.appcompat.view.ActionMode; import androidx.appcompat.widget.AppCompatImageView; import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.recyclerview.widget.ItemTouchHelper; @@ -43,6 +49,7 @@ import com.bumptech.glide.request.RequestOptions; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.snackbar.Snackbar; import com.nextcloud.android.sso.AccountImporter; +import com.nextcloud.android.sso.Constants; import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException; import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException; import com.nextcloud.android.sso.helper.SingleAccountHelper; @@ -70,6 +77,7 @@ import it.niedermann.owncloud.notes.util.ICallback; import it.niedermann.owncloud.notes.util.NoteUtil; import it.niedermann.owncloud.notes.util.NotesClientUtil; +import static com.nextcloud.android.sso.AccountImporter.CHOOSE_ACCOUNT_SSO; import static it.niedermann.owncloud.notes.android.activity.EditNoteActivity.ACTION_SHORTCUT; import static it.niedermann.owncloud.notes.util.SSOUtil.askForNewAccount; @@ -197,15 +205,10 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap setupNavigationMenu(); setupNotesList(); - try { // to get current account from SingleAccountHelper + try { selectAccount(SingleAccountHelper.getCurrentSingleSignOnAccount(getApplicationContext()).name); - Log.v(getClass().getSimpleName(), "NextcloudRequest account: " + localAccount); } catch (NoCurrentAccountSelectedException | NextcloudFilesAppAccountNotFoundException e) { - if (db.hasAccounts()) { // If nothing is stored in SingleAccountHelper, check db for accounts - selectAccount(db.getAccounts().get(0).getAccountName()); - } else { - askForNewAccount(this); - } + handleNotAuthorizedAccount(); } //SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); @@ -267,14 +270,43 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap private void selectAccount(String accountName) { SingleAccountHelper.setCurrentAccount(getApplicationContext(), accountName); localAccount = db.getLocalAccountByAccountName(accountName); - db.getNoteServerSyncHelper().updateAccount(); - synchronize(); - refreshLists(); + try { + db.getNoteServerSyncHelper().updateAccount(); + synchronize(); + refreshLists(); + } catch (NextcloudFilesAppAccountNotFoundException e) { + handleNotAuthorizedAccount(); + } setupHeader(); setupNavigationList(ADAPTER_KEY_RECENT); updateUsernameInDrawer(); } + private void handleNotAuthorizedAccount() { + swipeRefreshLayout.setRefreshing(false); + if (db.hasAccounts()) { // If nothing is stored in SingleAccountHelper, check db for accounts + String notAuthorizedAccount = db.getAccounts().get(0).getAccountName(); + + // Partially copied from AccountImporter + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + // FIXME Do something for lollipop and above versions + Log.e(getClass().getSimpleName(), "Permission not granted."); + } else { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.GET_ACCOUNTS) != PackageManager.PERMISSION_GRANTED) { + Log.d(getClass().getSimpleName(), "Permission granted!"); + ArrayList possiblePreviousAccounts = new ArrayList<>(); + possiblePreviousAccounts.add(new Account(notAuthorizedAccount, Constants.ACCOUNT_TYPE_PROD)); + possiblePreviousAccounts.add(new Account(notAuthorizedAccount, Constants.ACCOUNT_TYPE_DEV)); + Intent intent = AccountManager.newChooseAccountIntent(possiblePreviousAccounts.get(0), possiblePreviousAccounts, new String[]{Constants.ACCOUNT_TYPE_PROD, Constants.ACCOUNT_TYPE_DEV}, + true, "Choose the same account you were already using.", null, null, null); + startActivityForResult(intent, CHOOSE_ACCOUNT_SSO); + } + } + } else { + askForNewAccount(this); + } + } + private void setupHeader() { accountChooser.removeAllViews(); for (LocalAccount account : db.getAccounts()) { @@ -287,9 +319,9 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap .apply(RequestOptions.circleCropTransform()) .into(((ImageView) v.findViewById(R.id.accountItemAvatar))); v.setOnClickListener(clickedView -> { - selectAccount(account.getAccountName()); clickHeader(); drawerLayout.closeDrawer(GravityCompat.START); + selectAccount(account.getAccountName()); }); v.findViewById(R.id.delete).setOnClickListener(clickedView -> { db.deleteAccount(account.getId()); @@ -390,7 +422,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap // update views if (closeNavigation) { - drawerLayout.closeDrawers(); + drawerLayout.closeDrawer(GravityCompat.START); } refreshLists(true); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java index 909e6236..b9d134db 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java @@ -107,7 +107,11 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo /* Switch account if account id has been provided */ this.localAccount = db.getAccount(accountId); SingleAccountHelper.setCurrentAccount(getActivity().getApplicationContext(), localAccount.getAccountName()); - db.getNoteServerSyncHelper().updateAccount(); + try { + db.getNoteServerSyncHelper().updateAccount(); + } catch(NextcloudFilesAppAccountNotFoundException e) { + e.printStackTrace(); + } } note = originalNote = db.getNote(localAccount.getId(), id); } else { diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index 8da56ebc..ae890d68 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -1,14 +1,9 @@ package it.niedermann.owncloud.notes.persistence; -import android.Manifest; -import android.accounts.Account; -import android.accounts.AccountManager; -import android.app.Activity; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.PackageManager; import android.content.pm.ShortcutManager; import android.content.res.Resources; import android.database.Cursor; @@ -23,10 +18,6 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; -import androidx.core.content.ContextCompat; - -import com.nextcloud.android.sso.Constants; -import com.nextcloud.android.sso.exceptions.AndroidGetAccountsPermissionNotGranted; import java.net.MalformedURLException; import java.net.URL; @@ -38,7 +29,6 @@ import java.util.List; import java.util.Map; import it.niedermann.owncloud.notes.R; -import it.niedermann.owncloud.notes.android.activity.NotesListViewActivity; import it.niedermann.owncloud.notes.android.appwidget.NoteListWidget; import it.niedermann.owncloud.notes.android.appwidget.SingleNoteWidget; import it.niedermann.owncloud.notes.model.CloudNote; @@ -49,8 +39,6 @@ import it.niedermann.owncloud.notes.model.NavigationAdapter; import it.niedermann.owncloud.notes.util.ICallback; import it.niedermann.owncloud.notes.util.NoteUtil; -import static com.nextcloud.android.sso.AccountImporter.CHOOSE_ACCOUNT_SSO; - /** * Helps to add, get, update and delete Notes with the option to trigger a Resync with the Server. */ @@ -177,9 +165,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { values.put(key_account_id, 1); db.update(table_notes, values, key_account_id + " = ?", new String[]{"NULL"}); - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); - String username = sharedPreferences.getString("settingsUsername", ""); String url = sharedPreferences.getString("settingsUrl", ""); if (url != null && url.endsWith("/")) { @@ -193,41 +179,6 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { migratedAccountValues.put(key_account_name, accountName); db.insert(table_accounts, null, migratedAccountValues); - if (context instanceof NotesListViewActivity) { - // Partially copied from AccountImporter - Activity activity = (Activity) context; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - // Do something for lollipop and above versions - Log.d(getClass().getSimpleName(), "Permission not granted!"); - throw new AndroidGetAccountsPermissionNotGranted(); - } else { - if (ContextCompat.checkSelfPermission(context, Manifest.permission.GET_ACCOUNTS) != PackageManager.PERMISSION_GRANTED) { - Log.d(getClass().getSimpleName(), "Permission granted!"); - ArrayList accs = new ArrayList<>(); - accs.add(new Account(accountName, Constants.ACCOUNT_TYPE_PROD)); - accs.add(new Account(accountName, Constants.ACCOUNT_TYPE_DEV)); - Intent intent = AccountManager.newChooseAccountIntent(accs.get(0), accs, new String[]{Constants.ACCOUNT_TYPE_PROD, Constants.ACCOUNT_TYPE_DEV}, - true, null, null, null, null); - activity.startActivityForResult(intent, CHOOSE_ACCOUNT_SSO); - } - } -// ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.GET_ACCOUNTS}, -// REQUEST_GET_ACCOUNTS_PERMISSION); -// Account ac = AccountImporter.getAccountForName(context, accountName); -// ArrayList li = new ArrayList<>(); -// li.add(ac); -// Intent intent = AccountManager.newChooseAccountIntent(ac, -// li, -// new String[]{Constants.ACCOUNT_TYPE_PROD, Constants.ACCOUNT_TYPE_DEV}, -// true, -// null, -// null, -// null, -// null); -// ((NotesListViewActivity) context).startActivityForResult(intent, CHOOSE_ACCOUNT_SSO); - } - - SharedPreferences.Editor editor = sharedPreferences.edit(); editor.remove("notes_last_etag"); editor.remove("notes_last_modified"); @@ -236,14 +187,13 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { editor.remove("settingsPassword"); editor.apply(); } catch (MalformedURLException e) { + Log.e(getClass().getSimpleName(), "Previous URL could not be parsed. Recreating database..."); e.printStackTrace(); -// } catch (NextcloudFilesAppNotSupportedException e) { -// e.printStackTrace(); -// } catch (NextcloudFilesAppAccountPermissionNotGrantedException e) { -// e.printStackTrace(); - } catch (AndroidGetAccountsPermissionNotGranted androidGetAccountsPermissionNotGranted) { - androidGetAccountsPermissionNotGranted.printStackTrace(); + recreateDatabase(db); } + } else { + Log.e(getClass().getSimpleName(), "Previous URL is null. Recreating database..."); + recreateDatabase(db); } } } @@ -751,9 +701,9 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { db.insert(table_accounts, null, values); } - public LocalAccount getAccount(long i) { + public LocalAccount getAccount(long accountId) { SQLiteDatabase db = getReadableDatabase(); - Cursor cursor = db.query(table_accounts, new String[]{key_id, key_url, key_account_name, key_username, key_etag, key_modified}, key_id + " = ?", new String[]{i + ""}, null, null, null, null); + Cursor cursor = db.query(table_accounts, new String[]{key_id, key_url, key_account_name, key_username, key_etag, key_modified}, key_id + " = ?", new String[]{accountId + ""}, null, null, null, null); LocalAccount account = new LocalAccount(); while (cursor.moveToNext()) { account.setId(cursor.getLong(0)); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java index ca6a2d72..c182af1a 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java @@ -98,7 +98,11 @@ public class NoteServerSyncHelper { private NoteServerSyncHelper(NoteSQLiteOpenHelper db) { this.dbHelper = db; this.appContext = db.getContext().getApplicationContext(); - updateAccount(); + try { + updateAccount(); + } catch (NextcloudFilesAppAccountNotFoundException e) { + e.printStackTrace(); + } this.syncOnlyOnWifiKey = appContext.getResources().getString(R.string.pref_key_wifi_only); // Registers BroadcastReceiver to track network connection changes. @@ -111,7 +115,7 @@ public class NoteServerSyncHelper { updateNetworkStatus(); } - public void updateAccount() { + public void updateAccount() throws NextcloudFilesAppAccountNotFoundException { try { this.localAccount = dbHelper.getLocalAccountByAccountName(SingleAccountHelper.getCurrentSingleSignOnAccount(appContext).name); if (notesClient == null) { @@ -122,7 +126,7 @@ public class NoteServerSyncHelper { notesClient.updateAccount(); } Log.v(getClass().getSimpleName(), "NextcloudRequest account: " + localAccount); - } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) { + } catch (NoCurrentAccountSelectedException e) { e.printStackTrace(); } Log.v(getClass().getSimpleName(), "Reinstanziation NotesClient because of SSO acc changed"); From 84df5aef47b68d0ab15d4c78387b33deb725c14e Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Thu, 10 Oct 2019 17:33:06 +0200 Subject: [PATCH 03/14] Allow importing other accounts while migrating --- .../notes/android/activity/NotesListViewActivity.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java index a91423a7..db2e8f78 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java @@ -295,9 +295,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap if (ContextCompat.checkSelfPermission(this, Manifest.permission.GET_ACCOUNTS) != PackageManager.PERMISSION_GRANTED) { Log.d(getClass().getSimpleName(), "Permission granted!"); ArrayList possiblePreviousAccounts = new ArrayList<>(); - possiblePreviousAccounts.add(new Account(notAuthorizedAccount, Constants.ACCOUNT_TYPE_PROD)); - possiblePreviousAccounts.add(new Account(notAuthorizedAccount, Constants.ACCOUNT_TYPE_DEV)); - Intent intent = AccountManager.newChooseAccountIntent(possiblePreviousAccounts.get(0), possiblePreviousAccounts, new String[]{Constants.ACCOUNT_TYPE_PROD, Constants.ACCOUNT_TYPE_DEV}, + Intent intent = AccountManager.newChooseAccountIntent(new Account(notAuthorizedAccount, Constants.ACCOUNT_TYPE_PROD), null, new String[]{Constants.ACCOUNT_TYPE_PROD, Constants.ACCOUNT_TYPE_DEV}, true, "Choose the same account you were already using.", null, null, null); startActivityForResult(intent, CHOOSE_ACCOUNT_SSO); } From 811ea0eda71cf7c3e96df55e6b9d1260ac1712b8 Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Thu, 10 Oct 2019 17:41:50 +0200 Subject: [PATCH 04/14] hide fab when account is not yet authorized --- .../owncloud/notes/android/activity/NotesListViewActivity.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java index db2e8f78..041176cb 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java @@ -268,12 +268,14 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap } private void selectAccount(String accountName) { + fabCreate.hide(); SingleAccountHelper.setCurrentAccount(getApplicationContext(), accountName); localAccount = db.getLocalAccountByAccountName(accountName); try { db.getNoteServerSyncHelper().updateAccount(); synchronize(); refreshLists(); + fabCreate.show(); } catch (NextcloudFilesAppAccountNotFoundException e) { handleNotAuthorizedAccount(); } @@ -283,6 +285,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap } private void handleNotAuthorizedAccount() { + fabCreate.hide(); swipeRefreshLayout.setRefreshing(false); if (db.hasAccounts()) { // If nothing is stored in SingleAccountHelper, check db for accounts String notAuthorizedAccount = db.getAccounts().get(0).getAccountName(); From 09f4bfb11443890b6130c80f504ef70f565cceb6 Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Thu, 10 Oct 2019 18:35:11 +0200 Subject: [PATCH 05/14] Handle direct creation via widget and shortcut graceful if account is not authorized yet --- .../android/activity/EditNoteActivity.java | 10 ++- .../android/fragment/BaseNoteFragment.java | 76 ++++++++++--------- .../android/fragment/NoteEditFragment.java | 8 +- 3 files changed, 52 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java index 6bd9988f..0f4796cb 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java @@ -13,6 +13,7 @@ import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import java.util.Calendar; +import java.util.Objects; import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.android.fragment.BaseNoteFragment; @@ -211,12 +212,17 @@ public class EditNoteActivity extends AppCompatActivity implements BaseNoteFragm @Override public void onNoteUpdated(DBNote note) { - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { + ActionBar actionBar = Objects.requireNonNull(getSupportActionBar()); + if (note != null) { actionBar.setTitle(note.getTitle()); if (!note.getCategory().isEmpty()) { actionBar.setSubtitle(NoteUtil.extendCategory(note.getCategory())); } + } else { + // Maybe account is not authenticated -> note == null + Log.e(getClass().getSimpleName(), "note is null, start NotesListViewActivity"); + startActivity(new Intent(this, NotesListViewActivity.class)); + finish(); } } } \ No newline at end of file diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java index b9d134db..65f3f017 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java @@ -94,40 +94,40 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo try { this.localAccount = db.getLocalAccountByAccountName(SingleAccountHelper.getCurrentSingleSignOnAccount(getActivity().getApplicationContext()).name); + + if (savedInstanceState == null) { + isNew = true; + long id = getArguments().getLong(PARAM_NOTE_ID); + if (id > 0) { + long accountId = getArguments().getLong(PARAM_ACCOUNT_ID); + if(accountId > 0) { + /* Switch account if account id has been provided */ + this.localAccount = db.getAccount(accountId); + SingleAccountHelper.setCurrentAccount(getActivity().getApplicationContext(), localAccount.getAccountName()); + try { + db.getNoteServerSyncHelper().updateAccount(); + } catch(NextcloudFilesAppAccountNotFoundException e) { + e.printStackTrace(); + } + } + note = originalNote = db.getNote(localAccount.getId(), id); + } else { + CloudNote cloudNote = (CloudNote) getArguments().getSerializable(PARAM_NEWNOTE); + if (cloudNote == null) { + throw new IllegalArgumentException(PARAM_NOTE_ID + " is not given and argument " + PARAM_NEWNOTE + " is missing."); + } + note = db.getNote(localAccount.getId(), db.addNoteAndSync(localAccount.getId(), cloudNote)); + originalNote = null; + } + } else { + isNew = false; + note = (DBNote) savedInstanceState.getSerializable(SAVEDKEY_NOTE); + originalNote = (DBNote) savedInstanceState.getSerializable(SAVEDKEY_ORIGINAL_NOTE); + } + setHasOptionsMenu(true); } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) { e.printStackTrace(); } - - if (savedInstanceState == null) { - isNew = true; - long id = getArguments().getLong(PARAM_NOTE_ID); - if (id > 0) { - long accountId = getArguments().getLong(PARAM_ACCOUNT_ID); - if(accountId > 0) { - /* Switch account if account id has been provided */ - this.localAccount = db.getAccount(accountId); - SingleAccountHelper.setCurrentAccount(getActivity().getApplicationContext(), localAccount.getAccountName()); - try { - db.getNoteServerSyncHelper().updateAccount(); - } catch(NextcloudFilesAppAccountNotFoundException e) { - e.printStackTrace(); - } - } - note = originalNote = db.getNote(localAccount.getId(), id); - } else { - CloudNote cloudNote = (CloudNote) getArguments().getSerializable(PARAM_NEWNOTE); - if (cloudNote == null) { - throw new IllegalArgumentException(PARAM_NOTE_ID + " is not given and argument " + PARAM_NEWNOTE + " is missing."); - } - note = db.getNote(localAccount.getId(), db.addNoteAndSync(localAccount.getId(), cloudNote)); - originalNote = null; - } - } else { - isNew = false; - note = (DBNote) savedInstanceState.getSerializable(SAVEDKEY_NOTE); - originalNote = (DBNote) savedInstanceState.getSerializable(SAVEDKEY_ORIGINAL_NOTE); - } - setHasOptionsMenu(true); } @Override @@ -336,12 +336,16 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo */ protected void saveNote(@Nullable ICallback callback) { Log.d(getClass().getSimpleName(), "saveData()"); - String newContent = getContent(); - if (note.getContent().equals(newContent)) { - Log.v(getClass().getSimpleName(), "... not saving, since nothing has changed"); + if(note != null) { + String newContent = getContent(); + if (note.getContent().equals(newContent)) { + Log.v(getClass().getSimpleName(), "... not saving, since nothing has changed"); + } else { + note = db.updateNoteAndSync(localAccount.getId(), note, newContent, callback); + listener.onNoteUpdated(note); + } } else { - note = db.updateNoteAndSync(localAccount.getId(), note, newContent, callback); - listener.onNoteUpdated(note); + Log.e(getClass().getSimpleName(), "note is null"); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NoteEditFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NoteEditFragment.java index 81134cd2..ba5faa12 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NoteEditFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NoteEditFragment.java @@ -25,6 +25,8 @@ import com.yydcdut.markdown.syntax.edit.EditFactory; import com.yydcdut.rxmarkdown.RxMDEditText; import com.yydcdut.rxmarkdown.RxMarkdown; +import java.util.Objects; + import butterknife.BindView; import butterknife.ButterKnife; import it.niedermann.owncloud.notes.R; @@ -114,9 +116,9 @@ public class NoteEditFragment extends BaseNoteFragment { public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - if(getView() != null) { - ButterKnife.bind(this, getView()); + ButterKnife.bind(this, Objects.requireNonNull(getView())); + if(note != null) { setActiveTextView(editContent); if (note.getContent().isEmpty()) { @@ -161,8 +163,6 @@ public class NoteEditFragment extends BaseNoteFragment { if (sp.getBoolean(getString(R.string.pref_key_font), false)) { editContent.setTypeface(Typeface.MONOSPACE); } - } else { - Log.e(NoteEditFragment.class.getSimpleName(), "getView() is null"); } } From a0699986cc1937f76eec6dcb7571ab89cd677572 Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Thu, 10 Oct 2019 20:29:20 +0200 Subject: [PATCH 06/14] Some refactoring --- .../activity/NotesListViewActivity.java | 47 +++-------------- .../owncloud/notes/util/SSOUtil.java | 50 +++++++++++++++++-- 2 files changed, 54 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java index 041176cb..bccee9ca 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java @@ -1,11 +1,7 @@ package it.niedermann.owncloud.notes.android.activity; -import android.Manifest; -import android.accounts.Account; -import android.accounts.AccountManager; import android.app.SearchManager; import android.content.Intent; -import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.content.res.Configuration; @@ -13,7 +9,6 @@ import android.graphics.Canvas; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.AsyncTask; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.util.Log; @@ -35,7 +30,6 @@ import androidx.appcompat.view.ActionMode; import androidx.appcompat.widget.AppCompatImageView; import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.Toolbar; -import androidx.core.content.ContextCompat; import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.recyclerview.widget.ItemTouchHelper; @@ -49,14 +43,11 @@ import com.bumptech.glide.request.RequestOptions; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.snackbar.Snackbar; import com.nextcloud.android.sso.AccountImporter; -import com.nextcloud.android.sso.Constants; import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException; import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException; import com.nextcloud.android.sso.helper.SingleAccountHelper; import com.nextcloud.android.sso.model.SingleSignOnAccount; -import java.net.MalformedURLException; -import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -76,8 +67,8 @@ import it.niedermann.owncloud.notes.util.ExceptionHandler; import it.niedermann.owncloud.notes.util.ICallback; import it.niedermann.owncloud.notes.util.NoteUtil; import it.niedermann.owncloud.notes.util.NotesClientUtil; +import it.niedermann.owncloud.notes.util.SSOUtil; -import static com.nextcloud.android.sso.AccountImporter.CHOOSE_ACCOUNT_SSO; import static it.niedermann.owncloud.notes.android.activity.EditNoteActivity.ACTION_SHORTCUT; import static it.niedermann.owncloud.notes.util.SSOUtil.askForNewAccount; @@ -287,22 +278,8 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap private void handleNotAuthorizedAccount() { fabCreate.hide(); swipeRefreshLayout.setRefreshing(false); - if (db.hasAccounts()) { // If nothing is stored in SingleAccountHelper, check db for accounts - String notAuthorizedAccount = db.getAccounts().get(0).getAccountName(); - - // Partially copied from AccountImporter - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - // FIXME Do something for lollipop and above versions - Log.e(getClass().getSimpleName(), "Permission not granted."); - } else { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.GET_ACCOUNTS) != PackageManager.PERMISSION_GRANTED) { - Log.d(getClass().getSimpleName(), "Permission granted!"); - ArrayList possiblePreviousAccounts = new ArrayList<>(); - Intent intent = AccountManager.newChooseAccountIntent(new Account(notAuthorizedAccount, Constants.ACCOUNT_TYPE_PROD), null, new String[]{Constants.ACCOUNT_TYPE_PROD, Constants.ACCOUNT_TYPE_DEV}, - true, "Choose the same account you were already using.", null, null, null); - startActivityForResult(intent, CHOOSE_ACCOUNT_SSO); - } - } + if (db.hasAccounts()) { + SSOUtil.authorizeExistingAccount(this, db.getAccounts().get(0).getAccountName()); } else { askForNewAccount(this); } @@ -460,7 +437,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap private class LoadCategoryListTask extends AsyncTask> { @Override protected List doInBackground(Void... voids) { - if(localAccount == null) { + if (localAccount == null) { return new ArrayList<>(); } List categories = db.getCategories(localAccount.getId()); @@ -726,9 +703,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap if (currentVisibility == View.VISIBLE) { fabCreate.hide(); } else { - new Handler().postDelayed(() -> { - fabCreate.show(); - }, 150); + new Handler().postDelayed(() -> fabCreate.show(), 150); } oldVisibility = currentVisibility; @@ -809,13 +784,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap try { String url = localAccount.getUrl(); if (url != null) { - String croppedUrl = url; - try { - croppedUrl = new URL(url).getHost(); - } catch (MalformedURLException e) { - e.printStackTrace(); - } - this.account.setText(localAccount.getUserName() + "@" + croppedUrl); + this.account.setText(localAccount.getAccountName()); Glide .with(this) .load(url + "/index.php/avatar/" + Uri.encode(localAccount.getUserName()) + "/64") @@ -825,7 +794,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap } else { Log.w(NotesListViewActivity.class.getSimpleName(), "url is null"); } - } catch (NullPointerException e) { + } catch (NullPointerException e) { // No local account - show generic header this.account.setText(R.string.app_name_long); Glide .with(this) @@ -846,7 +815,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap v.setSelected(true); } int size = adapter.getSelected().size(); - mActionMode.setTitle(String.valueOf(getResources().getQuantityString(R.plurals.ab_selected, size, size))); + mActionMode.setTitle(getResources().getQuantityString(R.plurals.ab_selected, size, size)); int checkedItemCount = adapter.getSelected().size(); boolean hasCheckedItems = checkedItemCount > 0; diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java index 41b86026..67469aa0 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java @@ -1,14 +1,23 @@ package it.niedermann.owncloud.notes.util; +import android.Manifest; +import android.accounts.Account; +import android.accounts.AccountManager; import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; + import com.nextcloud.android.sso.AccountImporter; +import com.nextcloud.android.sso.Constants; import com.nextcloud.android.sso.exceptions.AndroidGetAccountsPermissionNotGranted; import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotInstalledException; import com.nextcloud.android.sso.ui.UiExceptionManager; -import it.niedermann.owncloud.notes.android.activity.NotesListViewActivity; +import static com.nextcloud.android.sso.AccountImporter.CHOOSE_ACCOUNT_SSO; public class SSOUtil { @@ -16,16 +25,49 @@ public class SSOUtil { } - public static void askForNewAccount(Activity activity) { + /** + * Opens a dialog which allows the user to pick a Nextcloud account (which previously has to be configured in the files app). + * Also allows to configure a new Nextcloud account in the files app and directly import it. + * + * @param activity should implement AccountImporter.onActivityResult + */ + public static void askForNewAccount(@NonNull Activity activity) { try { AccountImporter.pickNewAccount(activity); } catch (NextcloudFilesAppNotInstalledException e1) { UiExceptionManager.showDialogForException(activity, e1); - Log.w(NotesListViewActivity.class.toString(), "============================================================="); - Log.w(NotesListViewActivity.class.toString(), "Nextcloud app is not installed. Cannot choose account"); + Log.w(SSOUtil.class.toString(), "============================================================="); + Log.w(SSOUtil.class.toString(), "Nextcloud app is not installed. Cannot choose account"); e1.printStackTrace(); } catch (AndroidGetAccountsPermissionNotGranted e2) { AccountImporter.requestAndroidAccountPermissionsAndPickAccount(activity); } } + + /** + * Opens the same dialog like AccountImporter.pickNewAccount() but preselects the given account + * + * // FIXME does not work on Android 4 (and maybe others) yet + * + * @param activity should implement CHOOSE_ACCOUNT_SSO in onActivityResult + * @param accountName account that should be preselected + */ + public static void authorizeExistingAccount(@NonNull Activity activity, @NonNull String accountName) { + if (ContextCompat.checkSelfPermission(activity, Manifest.permission.GET_ACCOUNTS) != PackageManager.PERMISSION_GRANTED) { + Log.d(SSOUtil.class.getSimpleName(), "Permission granted!"); + Intent intent = AccountManager.newChooseAccountIntent( + new Account(accountName, Constants.ACCOUNT_TYPE_PROD), + null, + new String[]{Constants.ACCOUNT_TYPE_PROD, Constants.ACCOUNT_TYPE_DEV}, + true, + null, + null, + null, + null + ); + activity.startActivityForResult(intent, CHOOSE_ACCOUNT_SSO); + } else { + AccountImporter.requestAndroidAccountPermissionsAndPickAccount(activity); + } + } } From 08ee1dc75518e8219e86507853462299a1e08ee5 Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Fri, 11 Oct 2019 09:27:27 +0200 Subject: [PATCH 07/14] Add Foreign Key also on db upgrade process --- .../persistence/NoteSQLiteOpenHelper.java | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index ae890d68..ec196083 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -96,8 +96,8 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { */ @Override public void onCreate(SQLiteDatabase db) { - createNotesTable(db, table_notes); createAccountTable(db, table_accounts); + createNotesTable(db, table_notes); } private void createNotesTable(@NonNull SQLiteDatabase db, @NonNull String tableName) { @@ -111,7 +111,8 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { key_content + " TEXT, " + key_favorite + " INTEGER DEFAULT 0, " + key_category + " TEXT NOT NULL DEFAULT '', " + - key_etag + " TEXT)"); + key_etag + " TEXT," + + "FOREIGN KEY(" + key_account_id + ") REFERENCES " + table_accounts + "(" + key_id + "))"); createNotesIndexes(db); } @@ -158,13 +159,14 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { db.execSQL(String.format("ALTER TABLE %s RENAME TO %s", table_temp, table_notes)); } if (oldVersion < 9) { + // Create accounts table + createAccountTable(db, table_accounts); + + // Add accountId to notes table db.execSQL("ALTER TABLE " + table_notes + " ADD COLUMN " + key_account_id + " INTEGER NOT NULL DEFAULT 0"); createIndex(db, table_notes, key_account_id); - createAccountTable(db, table_accounts); - ContentValues values = new ContentValues(); - values.put(key_account_id, 1); - db.update(table_notes, values, key_account_id + " = ?", new String[]{"NULL"}); + // Migrate existing account from SharedPreferences SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); String username = sharedPreferences.getString("settingsUsername", ""); String url = sharedPreferences.getString("settingsUrl", ""); @@ -179,6 +181,21 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { migratedAccountValues.put(key_account_name, accountName); db.insert(table_accounts, null, migratedAccountValues); + // After successful insertion of migrated account, set accountId to 1 in each note + ContentValues values = new ContentValues(); + values.put(key_account_id, 1); + db.update(table_notes, values, key_account_id + " = ?", new String[]{"NULL"}); + + // Add FOREIGN_KEY constraint + final String table_temp = "NOTES_TEMP"; + db.execSQL(String.format("ALTER TABLE %s RENAME TO %s", table_notes, table_temp)); + createNotesTable(db, table_notes); + + db.execSQL(String.format("INSERT INTO %s(%s,%s, %s,%s,%s,%s,%s,%s,%s,%s) ", table_notes, key_id, key_account_id, key_remote_id, key_status, key_title, key_modified, key_content, key_favorite, key_category, key_etag) + + String.format("SELECT %s,%s,%s, %s,%s,strftime('%%s',%s),%s,%s,%s,%s FROM %s", key_id, key_account_id, key_remote_id, key_status, key_title, key_modified, key_content, key_favorite, key_category, key_etag, table_temp)); + db.execSQL(String.format("DROP TABLE %s;", table_temp)); + + // Clean up no longer needed SharedPreferences SharedPreferences.Editor editor = sharedPreferences.edit(); editor.remove("notes_last_etag"); editor.remove("notes_last_modified"); @@ -247,23 +264,11 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { return context; } - /** - * Creates a new Note in the Database and adds a Synchronization Flag. - * - * @param content String - */ - @SuppressWarnings("UnusedReturnValue") - public long addNoteAndSync(String content, String category, boolean favorite, long accountId) { - CloudNote note = new CloudNote(0, Calendar.getInstance(), NoteUtil.generateNonEmptyNoteTitle(content, getContext()), content, favorite, category, null); - return addNoteAndSync(accountId, note); - } - /** * Creates a new Note in the Database and adds a Synchronization Flag. * * @param note Note */ - @SuppressWarnings("UnusedReturnValue") public long addNoteAndSync(long accountId, CloudNote note) { DBNote dbNote = new DBNote(0, 0, note.getModified(), note.getTitle(), note.getContent(), note.isFavorite(), note.getCategory(), note.getEtag(), DBStatus.LOCAL_EDITED, accountId); long id = addNote(accountId, dbNote); From 2de611ef1bd6fdf83c40c21103ccdeb92ecf97da Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Fri, 11 Oct 2019 10:22:01 +0200 Subject: [PATCH 08/14] Display snackbar when importing account twice --- .../notes/android/activity/NotesListViewActivity.java | 10 +++++++++- .../notes/persistence/NoteSQLiteOpenHelper.java | 2 +- app/src/main/res/layout/activity_notes_list_view.xml | 1 + app/src/main/res/values/strings.xml | 1 + 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java index bccee9ca..5eda3489 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java @@ -5,6 +5,7 @@ import android.content.Intent; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.content.res.Configuration; +import android.database.sqlite.SQLiteConstraintException; import android.graphics.Canvas; import android.graphics.drawable.Icon; import android.net.Uri; @@ -30,6 +31,7 @@ import androidx.appcompat.view.ActionMode; import androidx.appcompat.widget.AppCompatImageView; import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.Toolbar; +import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.recyclerview.widget.ItemTouchHelper; @@ -91,6 +93,8 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap private LocalAccount localAccount; + @BindView(R.id.coordinatorLayout) + CoordinatorLayout coordinatorLayout; @BindView(R.id.accountNavigation) LinearLayout accountNavigation; @BindView(R.id.accountChooser) @@ -772,7 +776,11 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap } else { AccountImporter.onActivityResult(requestCode, resultCode, data, this, (SingleSignOnAccount account) -> { Log.v(getClass().getSimpleName(), "Added account: " + "name:" + account.name + ", " + account.url + ", userId" + account.userId); - db.addAccount(account.url, account.userId, account.name); + try { + db.addAccount(account.url, account.userId, account.name); + } catch(SQLiteConstraintException e) { + Snackbar.make(coordinatorLayout, R.string.account_already_imported, Snackbar.LENGTH_LONG).show(); + } selectAccount(account.name); clickHeader(); drawerLayout.closeDrawer(GravityCompat.START); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index ec196083..5583ded5 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -703,7 +703,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { values.put(key_url, url); values.put(key_username, username); values.put(key_account_name, accountName); - db.insert(table_accounts, null, values); + db.insertOrThrow(table_accounts, null, values); } public LocalAccount getAccount(long accountId) { diff --git a/app/src/main/res/layout/activity_notes_list_view.xml b/app/src/main/res/layout/activity_notes_list_view.xml index e2076b4f..95d63523 100644 --- a/app/src/main/res/layout/activity_notes_list_view.xml +++ b/app/src/main/res/layout/activity_notes_list_view.xml @@ -1,6 +1,7 @@ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6602eae8..7f0b8a71 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -146,6 +146,7 @@ The Notes app will beginning with the next major version use the great Single-Sign-On-Feature of Nextcloud. This will increase the security, reliability and comfort for you. Please make sure, you have installed at least version 3.8.0 of the files app and select at the first run the same account which you are already using. More information Understood + Account has already been imported From bd0b374ed6610b33b2a7a3009ea54400e3ce4a6d Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Fri, 11 Oct 2019 10:34:01 +0200 Subject: [PATCH 09/14] Do not show "already imported" message on migrated accounts --- .../notes/android/activity/NotesListViewActivity.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java index 5eda3489..340ebb6d 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java @@ -779,7 +779,9 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap try { db.addAccount(account.url, account.userId, account.name); } catch(SQLiteConstraintException e) { - Snackbar.make(coordinatorLayout, R.string.account_already_imported, Snackbar.LENGTH_LONG).show(); + if(db.getAccounts().size() > 1) { // TODO ideally only show snackbar when this is a not migrated account + Snackbar.make(coordinatorLayout, R.string.account_already_imported, Snackbar.LENGTH_LONG).show(); + } } selectAccount(account.name); clickHeader(); From 877dcfb64cede4995a6bd6e960438ef0b540d2b8 Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Fri, 11 Oct 2019 10:46:36 +0200 Subject: [PATCH 10/14] Harmonize logging tag --- .../android/AlwaysAutoCompleteTextView.java | 7 ++- .../android/activity/EditNoteActivity.java | 6 +- .../activity/NotesListViewActivity.java | 16 +++--- .../android/appwidget/NoteListWidget.java | 5 +- .../android/fragment/BaseNoteFragment.java | 8 ++- .../fragment/CategoryDialogFragment.java | 4 +- .../android/fragment/NotePreviewFragment.java | 4 +- .../android/fragment/PreferencesFragment.java | 5 +- .../persistence/NoteSQLiteOpenHelper.java | 28 +++++----- .../persistence/NoteServerSyncHelper.java | 56 ++++++++++--------- .../owncloud/notes/util/NotesClient.java | 12 ++-- 11 files changed, 87 insertions(+), 64 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/AlwaysAutoCompleteTextView.java b/app/src/main/java/it/niedermann/owncloud/notes/android/AlwaysAutoCompleteTextView.java index 6a6a312b..1ee1b884 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/AlwaysAutoCompleteTextView.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/AlwaysAutoCompleteTextView.java @@ -1,16 +1,19 @@ package it.niedermann.owncloud.notes.android; import android.content.Context; -import androidx.appcompat.widget.AppCompatAutoCompleteTextView; import android.util.AttributeSet; import android.util.Log; import android.view.WindowManager; +import androidx.appcompat.widget.AppCompatAutoCompleteTextView; + /** * Extension of the {@link AppCompatAutoCompleteTextView}, but this one is always open, i.e. you can see the list of suggestions even the TextView is empty. */ public class AlwaysAutoCompleteTextView extends AppCompatAutoCompleteTextView { + private static final String TAG = AlwaysAutoCompleteTextView.class.getSimpleName(); + private int myThreshold; public AlwaysAutoCompleteTextView(Context context) { @@ -50,7 +53,7 @@ public class AlwaysAutoCompleteTextView extends AppCompatAutoCompleteTextView { } catch (WindowManager.BadTokenException e) { // https://github.com/stefan-niedermann/nextcloud-notes/issues/366 e.printStackTrace(); - Log.e(AlwaysAutoCompleteTextView.class.getSimpleName(), "Exception", e); + Log.e(TAG, "Exception", e); } } } \ No newline at end of file diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java index 0f4796cb..5ea53197 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java @@ -27,6 +27,8 @@ import it.niedermann.owncloud.notes.util.NoteUtil; public class EditNoteActivity extends AppCompatActivity implements BaseNoteFragment.NoteFragmentListener { + private static final String TAG = EditNoteActivity.class.getSimpleName(); + public static final String ACTION_SHORTCUT = "it.niedermann.owncloud.notes.shortcut"; private static final String INTENT_GOOGLE_ASSISTANT = "com.google.android.gm.action.AUTO_SEND"; private static final String MIMETYPE_TEXT_PLAIN = "text/plain"; @@ -55,7 +57,7 @@ public class EditNoteActivity extends AppCompatActivity implements BaseNoteFragm @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); - Log.d(getClass().getSimpleName(), "onNewIntent: " + intent.getLongExtra(PARAM_NOTE_ID, 0)); + Log.d(TAG, "onNewIntent: " + intent.getLongExtra(PARAM_NOTE_ID, 0)); setIntent(intent); if (fragment != null) { getFragmentManager().beginTransaction().detach(fragment).commit(); @@ -220,7 +222,7 @@ public class EditNoteActivity extends AppCompatActivity implements BaseNoteFragm } } else { // Maybe account is not authenticated -> note == null - Log.e(getClass().getSimpleName(), "note is null, start NotesListViewActivity"); + Log.e(TAG, "note is null, start NotesListViewActivity"); startActivity(new Intent(this, NotesListViewActivity.class)); finish(); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java index 340ebb6d..8ec83c19 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java @@ -76,6 +76,8 @@ import static it.niedermann.owncloud.notes.util.SSOUtil.askForNewAccount; public class NotesListViewActivity extends AppCompatActivity implements ItemAdapter.NoteClickListener { + private static final String TAG = NotesListViewActivity.class.getSimpleName(); + public static final String CREATED_NOTE = "it.niedermann.owncloud.notes.created_notes"; public static final String ADAPTER_KEY_RECENT = "recent"; public static final String ADAPTER_KEY_STARRED = "starred"; @@ -155,7 +157,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap .setIntent(intent) .build()); } - Log.d(getClass().getSimpleName(), "Update dynamic shortcuts"); + Log.d(TAG, "Update dynamic shortcuts"); shortcutManager.removeAllDynamicShortcuts(); shortcutManager.addDynamicShortcuts(newShortcuts); } @@ -595,7 +597,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap db.deleteNoteAndSync((dbNote).getId()); adapter.remove(dbNote); refreshLists(); - Log.v(getClass().getSimpleName(), "Item deleted through swipe ----------------------------------------------"); + Log.v(TAG, "Item deleted through swipe ----------------------------------------------"); Snackbar.make(swipeRefreshLayout, R.string.action_note_deleted, Snackbar.LENGTH_LONG) .setAction(R.string.action_undo, (View v) -> { db.addNoteAndSync(dbNote.getAccountId(), dbNote); @@ -762,10 +764,10 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap if (createdNote != null) { adapter.add(createdNote); } else { - Log.w(NotesListViewActivity.class.getSimpleName(), "createdNote is null"); + Log.w(TAG, "createdNote is null"); } } else { - Log.w(NotesListViewActivity.class.getSimpleName(), "bundle is null"); + Log.w(TAG, "bundle is null"); } } listView.scrollToPosition(0); @@ -775,7 +777,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap recreate(); } else { AccountImporter.onActivityResult(requestCode, resultCode, data, this, (SingleSignOnAccount account) -> { - Log.v(getClass().getSimpleName(), "Added account: " + "name:" + account.name + ", " + account.url + ", userId" + account.userId); + Log.v(TAG, "Added account: " + "name:" + account.name + ", " + account.url + ", userId" + account.userId); try { db.addAccount(account.url, account.userId, account.name); } catch(SQLiteConstraintException e) { @@ -802,7 +804,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap .apply(RequestOptions.circleCropTransform()) .into(this.currentAccountImage); } else { - Log.w(NotesListViewActivity.class.getSimpleName(), "url is null"); + Log.w(TAG, "url is null"); } } catch (NullPointerException e) { // No local account - show generic header this.account.setText(R.string.app_name_long); @@ -811,7 +813,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap .load(R.mipmap.ic_launcher) .apply(RequestOptions.circleCropTransform()) .into(this.currentAccountImage); - Log.w(getClass().getSimpleName(), "Tried to update username in drawer, but localAccount was null"); + Log.w(TAG, "Tried to update username in drawer, but localAccount was null"); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/NoteListWidget.java b/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/NoteListWidget.java index b0b18f33..e5f25e1a 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/NoteListWidget.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/NoteListWidget.java @@ -17,6 +17,7 @@ import it.niedermann.owncloud.notes.android.activity.EditNoteActivity; import it.niedermann.owncloud.notes.android.activity.NotesListViewActivity; public class NoteListWidget extends AppWidgetProvider { + private static final String TAG = NoteListWidget.class.getSimpleName(); public static final String WIDGET_MODE_KEY = "NLW_mode"; public static final String WIDGET_CATEGORY_KEY = "NLW_cat"; public static final String DARK_THEME_KEY = "NLW_darkTheme"; @@ -111,14 +112,14 @@ public class NoteListWidget extends AppWidgetProvider { if (intent.getExtras() != null) { updateAppWidget(context, awm, new int[]{intent.getExtras().getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)}); } else { - Log.w(NoteListWidget.class.getSimpleName(), "intent.getExtras() is null"); + Log.w(TAG, "intent.getExtras() is null"); } } else { updateAppWidget(context, awm, awm.getAppWidgetIds(new ComponentName(context, NoteListWidget.class))); } } } else { - Log.w(NoteListWidget.class.getSimpleName(), "intent.getAction() is null"); + Log.w(TAG, "intent.getAction() is null"); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java index 65f3f017..86f63135 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java @@ -46,6 +46,8 @@ import static it.niedermann.owncloud.notes.android.activity.EditNoteActivity.ACT public abstract class BaseNoteFragment extends Fragment implements CategoryDialogFragment.CategoryDialogListener { + private static final String TAG = BaseNoteFragment.class.getSimpleName(); + public interface NoteFragmentListener { void close(); @@ -335,17 +337,17 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo * @param callback Observer which is called after save/synchronization */ protected void saveNote(@Nullable ICallback callback) { - Log.d(getClass().getSimpleName(), "saveData()"); + Log.d(TAG, "saveData()"); if(note != null) { String newContent = getContent(); if (note.getContent().equals(newContent)) { - Log.v(getClass().getSimpleName(), "... not saving, since nothing has changed"); + Log.v(TAG, "... not saving, since nothing has changed"); } else { note = db.updateNoteAndSync(localAccount.getId(), note, newContent, callback); listener.onNoteUpdated(note); } } else { - Log.e(getClass().getSimpleName(), "note is null"); + Log.e(TAG, "note is null"); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/CategoryDialogFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/CategoryDialogFragment.java index b1d3d256..dd0e5ce9 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/CategoryDialogFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/CategoryDialogFragment.java @@ -32,6 +32,8 @@ import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; */ public class CategoryDialogFragment extends DialogFragment { + private static final String TAG = CategoryDialogFragment.class.getSimpleName(); + /** * Interface that must be implemented by the calling Activity. */ @@ -91,7 +93,7 @@ public class CategoryDialogFragment extends DialogFragment { if (getDialog().getWindow() != null) { getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); } else { - Log.w(CategoryDialogFragment.class.getSimpleName(), "can not set SOFT_INPUT_STATE_ALWAYAS_VISIBLE because getWindow() == null"); + Log.w(TAG, "can not set SOFT_INPUT_STATE_ALWAYAS_VISIBLE because getWindow() == null"); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java index 0ab8d7b0..d3b9dc07 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java @@ -28,6 +28,8 @@ import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; public class NotePreviewFragment extends BaseNoteFragment { + + private static final String TAG = NotePreviewFragment.class.getSimpleName(); @BindView(R.id.single_note_content) RxMDTextView noteContent; @@ -91,7 +93,7 @@ public class NotePreviewFragment extends BaseNoteFragment { @Override public void onError(Throwable e) { - Log.v(getClass().getSimpleName(), "RxMarkdown error", e); + Log.v(TAG, "RxMarkdown error", e); } @Override diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/PreferencesFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/PreferencesFragment.java index dba2218e..14fd900c 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/PreferencesFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/PreferencesFragment.java @@ -14,6 +14,9 @@ import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.util.Notes; public class PreferencesFragment extends PreferenceFragment { + + private static final String TAG = PreferencesFragment.class.getSimpleName(); + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -37,7 +40,7 @@ public class PreferencesFragment extends PreferenceFragment { final SwitchPreference wifiOnlyPref = (SwitchPreference) findPreference(getString(R.string.pref_key_wifi_only)); wifiOnlyPref.setOnPreferenceChangeListener((Preference preference, Object newValue) -> { Boolean syncOnWifiOnly = (Boolean) newValue; - Log.v(getClass().getSimpleName(), "syncOnWifiOnly: " + syncOnWifiOnly); + Log.v(TAG, "syncOnWifiOnly: " + syncOnWifiOnly); return true; }); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index 5583ded5..7ba9c878 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -44,6 +44,8 @@ import it.niedermann.owncloud.notes.util.NoteUtil; */ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { + private static final String TAG = NoteSQLiteOpenHelper.class.getSimpleName(); + private static final int database_version = 9; private static final String database_name = "OWNCLOUD_NOTES"; private static final String table_notes = "NOTES"; @@ -204,12 +206,12 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { editor.remove("settingsPassword"); editor.apply(); } catch (MalformedURLException e) { - Log.e(getClass().getSimpleName(), "Previous URL could not be parsed. Recreating database..."); + Log.e(TAG, "Previous URL could not be parsed. Recreating database..."); e.printStackTrace(); recreateDatabase(db); } } else { - Log.e(getClass().getSimpleName(), "Previous URL is null. Recreating database..."); + Log.e(TAG, "Previous URL is null. Recreating database..."); recreateDatabase(db); } } @@ -339,7 +341,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { private List getNotesCustom(long accountId, @NonNull String selection, @NonNull String[] selectionArgs, @Nullable String orderBy, @Nullable String limit) { SQLiteDatabase db = getReadableDatabase(); if (selectionArgs.length > 2) { - Log.v(getClass().getSimpleName(), selection + " ---- " + selectionArgs[0] + " " + selectionArgs[1] + " " + selectionArgs[2]); + Log.v(TAG, selection + " ---- " + selectionArgs[0] + " " + selectionArgs[1] + " " + selectionArgs[2]); } Cursor cursor = db.query(table_notes, columns, selection, selectionArgs, null, null, orderBy, limit); List notes = new ArrayList<>(); @@ -365,9 +367,9 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { public void debugPrintFullDB(long accountId) { List notes = getNotesCustom(accountId, "", new String[]{}, default_order); - Log.v(getClass().getSimpleName(), "Full Database (" + notes.size() + " notes):"); + Log.v(TAG, "Full Database (" + notes.size() + " notes):"); for (DBNote note : notes) { - Log.v(getClass().getSimpleName(), " " + note); + Log.v(TAG, " " + note); } } @@ -617,7 +619,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { whereArgs = new String[]{String.valueOf(id), DBStatus.VOID.getTitle(), Long.toString(remoteNote.getModified().getTimeInMillis() / 1000), remoteNote.getTitle(), remoteNote.isFavorite() ? "1" : "0", remoteNote.getCategory(), remoteNote.getEtag(), remoteNote.getContent()}; } int i = db.update(table_notes, values, whereClause, whereArgs); - Log.d(getClass().getSimpleName(), "updateNote: " + remoteNote + " || forceUnchangedDBNoteState: " + forceUnchangedDBNoteState + " => " + i + " rows updated"); + Log.d(TAG, "updateNote: " + remoteNote + " || forceUnchangedDBNoteState: " + forceUnchangedDBNoteState + " => " + i + " rows updated"); return i; } @@ -645,7 +647,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { shortcutManager.getPinnedShortcuts().forEach((shortcut) -> { String shortcutId = id + ""; if (shortcut.getId().equals(shortcutId)) { - Log.v(NoteSQLiteOpenHelper.class.getSimpleName(), "Removing shortcut for " + shortcutId); + Log.v(TAG, "Removing shortcut for " + shortcutId); shortcutManager.disableShortcuts(Collections.singletonList(shortcutId), context.getResources().getString(R.string.note_has_been_deleted)); } }); @@ -765,10 +767,10 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { if (deletedAccounts < 1) { throw new IllegalArgumentException("The given accountId does not delete any row"); } else if (deletedAccounts > 1) { - Log.e(getClass().getSimpleName(), "AccountId '" + accountId + "' deleted unexpectedly '" + deletedAccounts + "' accounts"); + Log.e(TAG, "AccountId '" + accountId + "' deleted unexpectedly '" + deletedAccounts + "' accounts"); } final int deletedNotes = db.delete(table_notes, key_account_id + " = ?", new String[]{accountId + ""}); - Log.v(getClass().getSimpleName(), "Deleted " + deletedNotes + " notes from account " + accountId); + Log.v(TAG, "Deleted " + deletedNotes + " notes from account " + accountId); } void updateETag(long accountId, String etag) { @@ -777,9 +779,9 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { values.put(key_etag, etag); final int updatedRows = db.update(table_accounts, values, key_id + " = ?", new String[]{accountId + ""}); if (updatedRows == 1) { - Log.v(getClass().getSimpleName(), "Updated etag to " + etag + " for accountId = " + accountId); + Log.v(TAG, "Updated etag to " + etag + " for accountId = " + accountId); } else { - Log.e(getClass().getSimpleName(), "Updated " + updatedRows + " but expected only 1 for accountId = " + accountId + " and etag = " + etag); + Log.e(TAG, "Updated " + updatedRows + " but expected only 1 for accountId = " + accountId + " and etag = " + etag); } } @@ -789,9 +791,9 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { values.put(key_modified, modified); final int updatedRows = db.update(table_accounts, values, key_id + " = ?", new String[]{accountId + ""}); if (updatedRows == 1) { - Log.v(getClass().getSimpleName(), "Updated modified to " + modified + " for accountId = " + accountId); + Log.v(TAG, "Updated modified to " + modified + " for accountId = " + accountId); } else { - Log.e(getClass().getSimpleName(), "Updated " + updatedRows + " but expected only 1 for accountId = " + accountId + " and modified = " + modified); + Log.e(TAG, "Updated " + updatedRows + " but expected only 1 for accountId = " + accountId + " and modified = " + modified); } } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java index c182af1a..7c70fe4e 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java @@ -39,6 +39,8 @@ import it.niedermann.owncloud.notes.util.ServerResponse; */ public class NoteServerSyncHelper { + private static final String TAG = NoteServerSyncHelper.class.getSimpleName(); + private static NoteServerSyncHelper instance; /** @@ -125,11 +127,11 @@ public class NoteServerSyncHelper { } else { notesClient.updateAccount(); } - Log.v(getClass().getSimpleName(), "NextcloudRequest account: " + localAccount); + Log.v(TAG, "NextcloudRequest account: " + localAccount); } catch (NoCurrentAccountSelectedException e) { e.printStackTrace(); } - Log.v(getClass().getSimpleName(), "Reinstanziation NotesClient because of SSO acc changed"); + Log.v(TAG, "Reinstanziation NotesClient because of SSO acc changed"); } @Override @@ -193,9 +195,9 @@ public class NoteServerSyncHelper { * @param onlyLocalChanges Whether to only push local changes to the server or to also load the whole list of notes from the server. */ public void scheduleSync(boolean onlyLocalChanges) { - Log.d(getClass().getSimpleName(), "Sync requested (" + (onlyLocalChanges ? "onlyLocalChanges" : "full") + "; " + (syncActive ? "sync active" : "sync NOT active") + ") ..."); + Log.d(TAG, "Sync requested (" + (onlyLocalChanges ? "onlyLocalChanges" : "full") + "; " + (syncActive ? "sync active" : "sync NOT active") + ") ..."); if (isSyncPossible() && (!syncActive || onlyLocalChanges)) { - Log.d(getClass().getSimpleName(), "... starting now"); + Log.d(TAG, "... starting now"); SyncTask syncTask = new SyncTask(onlyLocalChanges); syncTask.addCallbacks(callbacksPush); callbacksPush = new ArrayList<>(); @@ -205,13 +207,13 @@ public class NoteServerSyncHelper { } syncTask.execute(); } else if (!onlyLocalChanges) { - Log.d(getClass().getSimpleName(), "... scheduled"); + Log.d(TAG, "... scheduled"); syncScheduled = true; for (ICallback callback : callbacksPush) { callback.onScheduled(); } } else { - Log.d(getClass().getSimpleName(), "... do nothing"); + Log.d(TAG, "... do nothing"); for (ICallback callback : callbacksPush) { callback.onScheduled(); } @@ -229,13 +231,13 @@ public class NoteServerSyncHelper { .getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected(); if (networkConnected) { - Log.d(NoteServerSyncHelper.class.getSimpleName(), "Network connection established."); + Log.d(TAG, "Network connection established."); } else { - Log.d(NoteServerSyncHelper.class.getSimpleName(), "Network connected, but not used because only synced on wifi."); + Log.d(TAG, "Network connected, but not used because only synced on wifi."); } } else { networkConnected = false; - Log.d(NoteServerSyncHelper.class.getSimpleName(), "No network connection."); + Log.d(TAG, "No network connection."); } } @@ -267,7 +269,7 @@ public class NoteServerSyncHelper { @Override protected LoginStatus doInBackground(Void... voids) { - Log.i(getClass().getSimpleName(), "STARTING SYNCHRONIZATION"); + Log.i(TAG, "STARTING SYNCHRONIZATION"); //dbHelper.debugPrintFullDB(); LoginStatus status = LoginStatus.OK; pushLocalChanges(); @@ -275,7 +277,7 @@ public class NoteServerSyncHelper { status = pullRemoteChanges(); } //dbHelper.debugPrintFullDB(); - Log.i(getClass().getSimpleName(), "SYNCHRONIZATION FINISHED"); + Log.i(TAG, "SYNCHRONIZATION FINISHED"); return status; } @@ -286,34 +288,34 @@ public class NoteServerSyncHelper { if (localAccount == null) { return; } - Log.d(getClass().getSimpleName(), "pushLocalChanges()"); + Log.d(TAG, "pushLocalChanges()"); List notes = dbHelper.getLocalModifiedNotes(localAccount.getId()); for (DBNote note : notes) { - Log.d(getClass().getSimpleName(), " Process Local Note: " + note); + Log.d(TAG, " Process Local Note: " + note); try { CloudNote remoteNote = null; switch (note.getStatus()) { case LOCAL_EDITED: - Log.v(getClass().getSimpleName(), " ...create/edit"); + Log.v(TAG, " ...create/edit"); // if note is not new, try to edit it. if (note.getRemoteId() > 0) { - Log.v(getClass().getSimpleName(), " ...try to edit"); + Log.v(TAG, " ...try to edit"); remoteNote = notesClient.editNote(note).getNote(); } // However, the note may be deleted on the server meanwhile; or was never synchronized -> (re)create // Please note, thas dbHelper.updateNote() realizes an optimistic conflict resolution, which is required for parallel changes of this Note from the UI. if (remoteNote == null) { - Log.v(getClass().getSimpleName(), " ...Note does not exist on server -> (re)create"); + Log.v(TAG, " ...Note does not exist on server -> (re)create"); remoteNote = notesClient.createNote(note).getNote(); } dbHelper.updateNote(note.getId(), remoteNote, note); break; case LOCAL_DELETED: if (note.getRemoteId() > 0) { - Log.v(getClass().getSimpleName(), " ...delete (from server and local)"); + Log.v(TAG, " ...delete (from server and local)"); notesClient.deleteNote(note.getRemoteId()); } else { - Log.v(getClass().getSimpleName(), " ...delete (only local, since it was not synchronized)"); + Log.v(TAG, " ...delete (only local, since it was not synchronized)"); } // Please note, thas dbHelper.deleteNote() realizes an optimistic conflict resolution, which is required for parallel changes of this Note from the UI. dbHelper.deleteNote(note.getId(), DBStatus.LOCAL_DELETED); @@ -322,7 +324,7 @@ public class NoteServerSyncHelper { throw new IllegalStateException("Unknown State of Note: " + note); } } catch (JSONException e) { - Log.e(getClass().getSimpleName(), "Exception", e); + Log.e(TAG, "Exception", e); exceptions.add(e); } } @@ -335,7 +337,7 @@ public class NoteServerSyncHelper { if (localAccount == null) { return LoginStatus.NO_NETWORK; } - Log.d(getClass().getSimpleName(), "pullRemoteChanges() for account " + localAccount.getAccountName()); + Log.d(TAG, "pullRemoteChanges() for account " + localAccount.getAccountName()); LoginStatus status; try { Map idMap = dbHelper.getIdMap(localAccount.getId()); @@ -344,23 +346,23 @@ public class NoteServerSyncHelper { Set remoteIDs = new HashSet<>(); // pull remote changes: update or create each remote note for (CloudNote remoteNote : remoteNotes) { - Log.v(getClass().getSimpleName(), " Process Remote Note: " + remoteNote); + Log.v(TAG, " Process Remote Note: " + remoteNote); remoteIDs.add(remoteNote.getRemoteId()); if (remoteNote.getModified() == null) { - Log.v(getClass().getSimpleName(), " ... unchanged"); + Log.v(TAG, " ... unchanged"); } else if (idMap.containsKey(remoteNote.getRemoteId())) { - Log.v(getClass().getSimpleName(), " ... found -> Update"); + Log.v(TAG, " ... found -> Update"); dbHelper.updateNote(idMap.get(remoteNote.getRemoteId()), remoteNote, null); } else { - Log.v(getClass().getSimpleName(), " ... create"); + Log.v(TAG, " ... create"); dbHelper.addNote(localAccount.getId(), remoteNote); } } - Log.d(getClass().getSimpleName(), " Remove remotely deleted Notes (only those without local changes)"); + Log.d(TAG, " Remove remotely deleted Notes (only those without local changes)"); // remove remotely deleted notes (only those without local changes) for (Map.Entry entry : idMap.entrySet()) { if (!remoteIDs.contains(entry.getKey())) { - Log.v(getClass().getSimpleName(), " ... remove " + entry.getValue()); + Log.v(TAG, " ... remove " + entry.getValue()); dbHelper.deleteNote(entry.getValue(), DBStatus.VOID); } } @@ -372,7 +374,7 @@ public class NoteServerSyncHelper { dbHelper.updateModified(localAccount.getId(), localAccount.getModified()); return LoginStatus.OK; } catch (JSONException e) { - Log.e(getClass().getSimpleName(), "Exception", e); + Log.e(TAG, "Exception", e); exceptions.add(e); status = LoginStatus.JSON_FAILED; } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java index f5cee393..da343bb1 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java @@ -31,6 +31,8 @@ import it.niedermann.owncloud.notes.util.ServerResponse.NotesResponse; @WorkerThread public class NotesClient { + private static final String TAG = NotesClient.class.getSimpleName(); + private final Context context; private NextcloudAPI mNextcloudAPI; @@ -85,11 +87,11 @@ public class NotesClient { } try { SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(context); - Log.v(getClass().getSimpleName(), "NextcloudRequest account: " + ssoAccount.name); + Log.v(TAG, "NextcloudRequest account: " + ssoAccount.name); mNextcloudAPI = new NextcloudAPI(context, ssoAccount, new GsonBuilder().create(), new NextcloudAPI.ApiConnectedListener() { @Override public void onConnected() { - Log.v(getClass().getSimpleName(), "SSO API connected"); + Log.v(TAG, "SSO API connected"); } @Override @@ -178,9 +180,9 @@ public class NotesClient { StringBuilder result = new StringBuilder(); try { - Log.v(getClass().getSimpleName(), "NextcloudRequest: " + nextcloudRequest.toString()); + Log.v(TAG, "NextcloudRequest: " + nextcloudRequest.toString()); InputStream inputStream = mNextcloudAPI.performNetworkRequest(nextcloudRequest); - Log.v(getClass().getSimpleName(), "NextcloudRequest: " + nextcloudRequest.toString()); + Log.v(TAG, "NextcloudRequest: " + nextcloudRequest.toString()); BufferedReader rd = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = rd.readLine()) != null) { @@ -197,7 +199,7 @@ public class NotesClient { long lastModified = 0; if (nextcloudRequest.getHeader().get("Last-Modified") != null) lastModified = Long.parseLong(nextcloudRequest.getHeader().get("Last-Modified").get(0)) / 1000; - Log.d(getClass().getSimpleName(), "ETag: " + etag + "; Last-Modified: " + lastModified + " (" + lastModified + ")"); + Log.d(TAG, "ETag: " + etag + "; Last-Modified: " + lastModified + " (" + lastModified + ")"); // return these header fields since they should only be saved after successful processing the result! return new ResponseData(result.toString(), etag, lastModified); } From 64ef38f9665ba393be8e4b8d8d1a4d2e8efad0eb Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Fri, 11 Oct 2019 11:13:30 +0200 Subject: [PATCH 11/14] Some code cleanup --- .../notes/android/activity/AboutActivity.java | 12 ++++------ .../android/activity/ExceptionActivity.java | 2 ++ .../activity/SelectSingleNoteActivity.java | 1 + .../android/appwidget/SingleNoteWidget.java | 3 +-- .../android/fragment/BaseNoteFragment.java | 1 + .../android/fragment/NotePreviewFragment.java | 4 +++- .../persistence/NoteSQLiteOpenHelper.java | 4 +--- .../owncloud/notes/util/DisplayUtils.java | 6 ++--- .../owncloud/notes/util/NotesClient.java | 23 ++++++------------- .../owncloud/notes/util/NotesClientUtil.java | 1 - .../owncloud/notes/util/SupportUtil.java | 2 +- 11 files changed, 25 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/AboutActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/AboutActivity.java index e86409ab..6c5cbf82 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/AboutActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/AboutActivity.java @@ -2,6 +2,7 @@ package it.niedermann.owncloud.notes.android.activity; import android.os.Bundle; +import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; @@ -37,26 +38,23 @@ public class AboutActivity extends AppCompatActivity { } private class TabsPagerAdapter extends FragmentPagerAdapter { - private final int PAGE_COUNT = 3; - public TabsPagerAdapter(FragmentManager fragmentManager) { + TabsPagerAdapter(FragmentManager fragmentManager) { super(fragmentManager); } @Override public int getCount() { - return PAGE_COUNT; + return 3; } /** * return the right fragment for the given position */ + @NonNull @Override public Fragment getItem(int position) { switch (position) { - case 0: - return new AboutFragmentCreditsTab(); - case 1: return new AboutFragmentContributingTab(); @@ -64,7 +62,7 @@ public class AboutActivity extends AppCompatActivity { return new AboutFragmentLicenseTab(); default: - return null; + return new AboutFragmentCreditsTab(); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/ExceptionActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/ExceptionActivity.java index 1f41b724..32b4b503 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/ExceptionActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/ExceptionActivity.java @@ -1,5 +1,6 @@ package it.niedermann.owncloud.notes.android.activity; +import android.annotation.SuppressLint; import android.content.ClipData; import android.content.ClipboardManager; import android.os.Bundle; @@ -30,6 +31,7 @@ public class ExceptionActivity extends AppCompatActivity { public static final String KEY_THROWABLE = "T"; + @SuppressLint("SetTextI18n") // only used for logging @Override protected void onCreate(@Nullable Bundle savedInstanceState) { setContentView(R.layout.activity_exception); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java index ec4f24f1..e1512c52 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java @@ -62,6 +62,7 @@ public class SelectSingleNoteActivity extends NotesListViewActivity { finish(); } + assert extras != null; int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); SharedPreferences.Editor sp = PreferenceManager.getDefaultSharedPreferences(this).edit(); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/SingleNoteWidget.java b/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/SingleNoteWidget.java index 229f4746..19afe345 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/SingleNoteWidget.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/SingleNoteWidget.java @@ -15,7 +15,6 @@ import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.android.activity.EditNoteActivity; public class SingleNoteWidget extends AppWidgetProvider { - private static boolean darkTheme; public static final String DARK_THEME_KEY = "SNW_darkTheme"; public static final String WIDGET_KEY = "single_note_widget"; @@ -32,7 +31,7 @@ public class SingleNoteWidget extends AppWidgetProvider { return; } - darkTheme = sp.getBoolean(DARK_THEME_KEY + appWidgetId, false); + boolean darkTheme = sp.getBoolean(DARK_THEME_KEY + appWidgetId, false); PendingIntent templatePendingIntent = PendingIntent.getActivity(context, appWidgetId, templateIntent, PendingIntent.FLAG_UPDATE_CURRENT); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java index 86f63135..19571a41 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java @@ -357,6 +357,7 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo final String prefValueLarge = getString(R.string.pref_value_font_size_large); String fontSize = sp.getString(getString(R.string.pref_key_font_size), prefValueMedium); + assert fontSize != null; if (fontSize.equals(prefValueSmall)) { return getResources().getDimension(R.dimen.note_font_size_small); } else if (fontSize.equals(prefValueMedium)) { diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java index d3b9dc07..839dbb1a 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java @@ -19,6 +19,8 @@ import com.yydcdut.markdown.syntax.text.TextFactory; import com.yydcdut.rxmarkdown.RxMDTextView; import com.yydcdut.rxmarkdown.RxMarkdown; +import java.util.Objects; + import butterknife.BindView; import butterknife.ButterKnife; import it.niedermann.owncloud.notes.R; @@ -59,7 +61,7 @@ public class NotePreviewFragment extends BaseNoteFragment { @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - ButterKnife.bind(this, getView()); + ButterKnife.bind(this, Objects.requireNonNull(getView())); setActiveTextView(noteContent); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index 7ba9c878..fb33e3bb 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -583,9 +583,8 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { * @param id local ID of Note * @param remoteNote Note from the server. * @param forceUnchangedDBNoteState is not null, then the local note is updated only if it was not modified meanwhile - * @return The number of the Rows affected. */ - int updateNote(long id, @NonNull CloudNote remoteNote, @Nullable DBNote forceUnchangedDBNoteState) { + void updateNote(long id, @NonNull CloudNote remoteNote, @Nullable DBNote forceUnchangedDBNoteState) { SQLiteDatabase db = this.getWritableDatabase(); // First, update the remote ID, since this field cannot be changed in parallel, but have to be updated always. @@ -620,7 +619,6 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { } int i = db.update(table_notes, values, whereClause, whereArgs); Log.d(TAG, "updateNote: " + remoteNote + " || forceUnchangedDBNoteState: " + forceUnchangedDBNoteState + " => " + i + " rows updated"); - return i; } /** diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/DisplayUtils.java b/app/src/main/java/it/niedermann/owncloud/notes/util/DisplayUtils.java index 53d58bac..52b5219c 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/util/DisplayUtils.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/util/DisplayUtils.java @@ -26,16 +26,16 @@ import android.text.style.CharacterStyle; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; +import androidx.annotation.ColorInt; + import java.util.regex.Matcher; import java.util.regex.Pattern; -import androidx.annotation.ColorInt; - public class DisplayUtils { public static Spannable searchAndColor(String text, Spannable spannable, String searchText, @ColorInt int color) { - Object spansToRemove[] = spannable.getSpans(0, text.length(), Object.class); + Object[] spansToRemove = spannable.getSpans(0, text.length(), Object.class); for(Object span: spansToRemove){ if(span instanceof CharacterStyle) spannable.removeSpan(span); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java index da343bb1..fe54607e 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import it.niedermann.owncloud.notes.model.CloudNote; import it.niedermann.owncloud.notes.util.ServerResponse.NoteResponse; @@ -63,6 +64,8 @@ public class NotesClient { } } + private static final String HEADER_ETAG = "ETag"; + private static final String HEADER_LAST_MODIFIED = "Last-Modified"; public static final String METHOD_GET = "GET"; public static final String METHOD_PUT = "PUT"; public static final String METHOD_POST = "POST"; @@ -112,17 +115,6 @@ public class NotesClient { return new NotesResponse(requestServer(url, METHOD_GET, null, lastETag)); } - /** - * Fetches a Note by ID from Server - * - * @param id long - ID of the wanted note - * @return Requested Note - */ - @SuppressWarnings("unused") - public NoteResponse getNoteById(long id) { - return new NoteResponse(requestServer("notes/" + id, METHOD_GET, null, null)); - } - private NoteResponse putNote(CloudNote note, String path, String method) throws JSONException { JSONObject paramObject = new JSONObject(); paramObject.accumulate(JSON_CONTENT, note.getContent()); @@ -132,7 +124,6 @@ public class NotesClient { return new NoteResponse(requestServer(path, method, paramObject, null)); } - /** * Creates a Note on the Server * @@ -193,12 +184,12 @@ public class NotesClient { e.printStackTrace(); } String etag = ""; - if (nextcloudRequest.getHeader().get("ETag") != null) { - etag = nextcloudRequest.getHeader().get("ETag").get(0); + if (nextcloudRequest.getHeader().get(HEADER_ETAG) != null) { + etag = Objects.requireNonNull(nextcloudRequest.getHeader().get(HEADER_ETAG)).get(0); } long lastModified = 0; - if (nextcloudRequest.getHeader().get("Last-Modified") != null) - lastModified = Long.parseLong(nextcloudRequest.getHeader().get("Last-Modified").get(0)) / 1000; + if (nextcloudRequest.getHeader().get(HEADER_LAST_MODIFIED) != null) + lastModified = Long.parseLong(Objects.requireNonNull(nextcloudRequest.getHeader().get(HEADER_LAST_MODIFIED)).get(0)) / 1000; Log.d(TAG, "ETag: " + etag + "; Last-Modified: " + lastModified + " (" + lastModified + ")"); // return these header fields since they should only be saved after successful processing the result! return new ResponseData(result.toString(), etag, lastModified); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClientUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClientUtil.java index 97eb0bdb..5098e324 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClientUtil.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClientUtil.java @@ -12,7 +12,6 @@ public class NotesClientUtil { public enum LoginStatus { OK(0), - CONNECTION_FAILED(R.string.error_io), NO_NETWORK(R.string.error_no_network), JSON_FAILED(R.string.error_json); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/SupportUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/util/SupportUtil.java index 4fcea792..da1a4a42 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/util/SupportUtil.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/util/SupportUtil.java @@ -20,7 +20,7 @@ public class SupportUtil { * @see Html#fromHtml(String) * @see Html#fromHtml(String, int) */ - public static Spanned fromHtml(String source) { + private static Spanned fromHtml(String source) { if (Build.VERSION.SDK_INT >= 24) { return Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY); } else { From b4a83475024a38acdbfbc60dc0500f030a6e2989 Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Fri, 11 Oct 2019 13:51:51 +0200 Subject: [PATCH 12/14] Fix account import for Android 4 (and maybe others) --- .../owncloud/notes/util/SSOUtil.java | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java index 67469aa0..3709747c 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java @@ -6,6 +6,7 @@ import android.accounts.AccountManager; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; +import android.os.Build; import android.util.Log; import androidx.annotation.NonNull; @@ -21,6 +22,8 @@ import static com.nextcloud.android.sso.AccountImporter.CHOOSE_ACCOUNT_SSO; public class SSOUtil { + private static final String TAG = SSOUtil.class.getSimpleName(); + private SSOUtil() { } @@ -47,27 +50,29 @@ public class SSOUtil { /** * Opens the same dialog like AccountImporter.pickNewAccount() but preselects the given account * - * // FIXME does not work on Android 4 (and maybe others) yet - * * @param activity should implement CHOOSE_ACCOUNT_SSO in onActivityResult * @param accountName account that should be preselected */ public static void authorizeExistingAccount(@NonNull Activity activity, @NonNull String accountName) { - if (ContextCompat.checkSelfPermission(activity, Manifest.permission.GET_ACCOUNTS) != PackageManager.PERMISSION_GRANTED) { - Log.d(SSOUtil.class.getSimpleName(), "Permission granted!"); - Intent intent = AccountManager.newChooseAccountIntent( - new Account(accountName, Constants.ACCOUNT_TYPE_PROD), - null, - new String[]{Constants.ACCOUNT_TYPE_PROD, Constants.ACCOUNT_TYPE_DEV}, - true, - null, - null, - null, - null - ); - activity.startActivityForResult(intent, CHOOSE_ACCOUNT_SSO); - } else { - AccountImporter.requestAndroidAccountPermissionsAndPickAccount(activity); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + if (ContextCompat.checkSelfPermission(activity, Manifest.permission.GET_ACCOUNTS) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "Permission not granted."); + // Well... do you want to use this SSO account or not? + return; + } } + + Log.d(TAG, "Permission granted."); + Intent intent = AccountManager.newChooseAccountIntent( + new Account(accountName, Constants.ACCOUNT_TYPE_PROD), + null, + new String[]{Constants.ACCOUNT_TYPE_PROD, Constants.ACCOUNT_TYPE_DEV}, + true, + null, + null, + null, + null + ); + activity.startActivityForResult(intent, CHOOSE_ACCOUNT_SSO); } } From bd3421f83572ab877fae86812b284284fefecd87 Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Fri, 11 Oct 2019 14:13:23 +0200 Subject: [PATCH 13/14] Fix account import for Android 7 (and maybe 6) --- .../main/java/it/niedermann/owncloud/notes/util/SSOUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java index 3709747c..dddf3f0e 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java @@ -54,7 +54,7 @@ public class SSOUtil { * @param accountName account that should be preselected */ public static void authorizeExistingAccount(@NonNull Activity activity, @NonNull String accountName) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { if (ContextCompat.checkSelfPermission(activity, Manifest.permission.GET_ACCOUNTS) != PackageManager.PERMISSION_GRANTED) { Log.w(TAG, "Permission not granted."); // Well... do you want to use this SSO account or not? From 16b98435da0b9252e6b897e4508c293228c5f22d Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Fri, 11 Oct 2019 15:58:40 +0200 Subject: [PATCH 14/14] More defensive coding style on database level --- .../persistence/NoteSQLiteOpenHelper.java | 117 ++++++++++-------- .../notes/util/DatabaseIndexUtil.java | 32 +++++ .../owncloud/notes/util/SupportUtil.java | 30 +++-- 3 files changed, 116 insertions(+), 63 deletions(-) create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/util/DatabaseIndexUtil.java diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index fb33e3bb..aa13268c 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -8,6 +8,7 @@ import android.content.pm.ShortcutManager; import android.content.res.Resources; import android.database.Cursor; import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.os.Build; @@ -36,6 +37,7 @@ import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.DBStatus; import it.niedermann.owncloud.notes.model.LocalAccount; import it.niedermann.owncloud.notes.model.NavigationAdapter; +import it.niedermann.owncloud.notes.util.DatabaseIndexUtil; import it.niedermann.owncloud.notes.util.ICallback; import it.niedermann.owncloud.notes.util.NoteUtil; @@ -129,14 +131,14 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { createAccountIndexes(db); } - @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion < 3) { recreateDatabase(db); } if (oldVersion < 4) { - clearDatabase(db); + db.delete(table_notes, null, null); + db.delete(table_accounts, null, null); } if (oldVersion < 5) { db.execSQL("ALTER TABLE " + table_notes + " ADD COLUMN " + key_remote_id + " INTEGER"); @@ -147,7 +149,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { db.execSQL("ALTER TABLE " + table_notes + " ADD COLUMN " + key_favorite + " INTEGER DEFAULT 0"); } if (oldVersion < 7) { - dropIndexes(db); + DatabaseIndexUtil.dropIndexes(db); db.execSQL("ALTER TABLE " + table_notes + " ADD COLUMN " + key_category + " TEXT NOT NULL DEFAULT ''"); db.execSQL("ALTER TABLE " + table_notes + " ADD COLUMN " + key_etag + " TEXT"); createNotesIndexes(db); @@ -166,7 +168,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { // Add accountId to notes table db.execSQL("ALTER TABLE " + table_notes + " ADD COLUMN " + key_account_id + " INTEGER NOT NULL DEFAULT 0"); - createIndex(db, table_notes, key_account_id); + DatabaseIndexUtil.createIndex(db, table_notes, key_account_id); // Migrate existing account from SharedPreferences SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); @@ -222,44 +224,19 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { recreateDatabase(db); } - private void clearDatabase(SQLiteDatabase db) { - db.delete(table_notes, null, null); - } - private void recreateDatabase(SQLiteDatabase db) { - dropIndexes(db); - db.execSQL("DROP TABLE " + table_notes); + DatabaseIndexUtil.dropIndexes(db); + db.execSQL("DROP TABLE IF EXISTS " + table_notes); + db.execSQL("DROP TABLE IF EXISTS " + table_accounts); onCreate(db); } - private void dropIndexes(SQLiteDatabase db) { - Cursor c = db.query("sqlite_master", new String[]{"name"}, "type=?", new String[]{"index"}, null, null, null); - while (c.moveToNext()) { - db.execSQL("DROP INDEX " + c.getString(0)); - } - c.close(); + private static void createNotesIndexes(@NonNull SQLiteDatabase db) { + DatabaseIndexUtil.createIndex(db, table_notes, key_remote_id, key_account_id, key_status, key_favorite, key_category, key_modified); } - private void createNotesIndexes(SQLiteDatabase db) { - createIndex(db, table_notes, key_remote_id); - createIndex(db, table_notes, key_account_id); - createIndex(db, table_notes, key_status); - createIndex(db, table_notes, key_favorite); - createIndex(db, table_notes, key_category); - createIndex(db, table_notes, key_modified); - } - - private void createAccountIndexes(SQLiteDatabase db) { - createIndex(db, table_accounts, key_url); - createIndex(db, table_accounts, key_username); - createIndex(db, table_accounts, key_account_name); - createIndex(db, table_accounts, key_etag); - createIndex(db, table_accounts, key_modified); - } - - private void createIndex(SQLiteDatabase db, String table, String column) { - String indexName = table + "_" + column + "_idx"; - db.execSQL("CREATE INDEX IF NOT EXISTS " + indexName + " ON " + table + "(" + column + ")"); + private static void createAccountIndexes(@NonNull SQLiteDatabase db) { + DatabaseIndexUtil.createIndex(db, table_accounts, key_url, key_username, key_account_name, key_etag, key_modified); } public Context getContext() { @@ -360,12 +337,14 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { */ @NonNull private DBNote getNoteFromCursor(long accountId, @NonNull Cursor cursor) { + validateAccountId(accountId); Calendar modified = Calendar.getInstance(); modified.setTimeInMillis(cursor.getLong(4) * 1000); return new DBNote(cursor.getLong(0), cursor.getLong(1), modified, cursor.getString(3), cursor.getString(5), cursor.getInt(6) > 0, cursor.getString(7), cursor.getString(8), DBStatus.parse(cursor.getString(2)), accountId); } public void debugPrintFullDB(long accountId) { + validateAccountId(accountId); List notes = getNotesCustom(accountId, "", new String[]{}, default_order); Log.v(TAG, "Full Database (" + notes.size() + " notes):"); for (DBNote note : notes) { @@ -376,6 +355,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { @NonNull @WorkerThread public Map getIdMap(long accountId) { + validateAccountId(accountId); Map result = new HashMap<>(); SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.query(table_notes, new String[]{key_remote_id, key_id}, key_status + " != ? AND " + key_account_id + " = ? ", new String[]{DBStatus.LOCAL_DELETED.getTitle(), "" + accountId}, null, null, null); @@ -394,12 +374,14 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { @NonNull @WorkerThread public List getNotes(long accountId) { + validateAccountId(accountId); return getNotesCustom(accountId, key_status + " != ? AND " + key_account_id + " = ?", new String[]{DBStatus.LOCAL_DELETED.getTitle(), "" + accountId}, default_order); } @NonNull @WorkerThread public List getRecentNotes(long accountId) { + validateAccountId(accountId); return getNotesCustom(accountId, key_status + " != ? AND " + key_account_id + " = ?", new String[]{DBStatus.LOCAL_DELETED.getTitle(), "" + accountId}, key_modified + " DESC", "4"); } @@ -452,13 +434,15 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { */ @NonNull @WorkerThread - public List getLocalModifiedNotes(long accountId) { + List getLocalModifiedNotes(long accountId) { + validateAccountId(accountId); return getNotesCustom(accountId, key_status + " != ? AND " + key_account_id + " = ?", new String[]{DBStatus.VOID.getTitle(), "" + accountId}, null); } @NonNull @WorkerThread public Map getFavoritesCount(long accountId) { + validateAccountId(accountId); SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.query( table_notes, @@ -479,6 +463,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { @NonNull @WorkerThread public List getCategories(long accountId) { + validateAccountId(accountId); SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.query( table_notes, @@ -626,10 +611,8 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { * from the Server. * * @param id long - ID of the Note that should be deleted - * @return Affected rows */ - @SuppressWarnings("UnusedReturnValue") - public int deleteNoteAndSync(long id) { + public void deleteNoteAndSync(long id) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(key_status, DBStatus.LOCAL_DELETED.getTitle()); @@ -650,7 +633,6 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { } }); } - return i; } /** @@ -671,33 +653,40 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { * Notify about changed notes. */ void notifyNotesChanged() { - updateSingleNoteWidgets(); - updateNoteListWidgets(); + updateSingleNoteWidgets(getContext()); + updateNoteListWidgets(getContext()); } /** * Update single note widget, if the note data was changed. */ - private void updateSingleNoteWidgets() { - Intent intent = new Intent(getContext(), SingleNoteWidget.class); + private static void updateSingleNoteWidgets(Context context) { + Intent intent = new Intent(context, SingleNoteWidget.class); intent.setAction("android.appwidget.action.APPWIDGET_UPDATE"); - getContext().sendBroadcast(intent); + context.sendBroadcast(intent); } /** * Update note list widgets, if the note data was changed. */ - private void updateNoteListWidgets() { - Intent intent = new Intent(getContext(), NoteListWidget.class); + private static void updateNoteListWidgets(Context context) { + Intent intent = new Intent(context, NoteListWidget.class); intent.setAction("android.appwidget.action.APPWIDGET_UPDATE"); - getContext().sendBroadcast(intent); + context.sendBroadcast(intent); } public boolean hasAccounts() { return DatabaseUtils.queryNumEntries(getReadableDatabase(), table_accounts) > 0; } - public void addAccount(String url, String username, String accountName) { + /** + * + * @param url URL to the root of the used Nextcloud instance without trailing slash + * @param username Username of the account + * @param accountName Composed by the username and the host of the URL, separated by @-sign + * @throws SQLiteConstraintException in case accountName already exists + */ + public void addAccount(@NonNull String url, @NonNull String username, @NonNull String accountName) throws SQLiteConstraintException { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(key_url, url); @@ -706,7 +695,13 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { db.insertOrThrow(table_accounts, null, values); } + /** + * + * @param accountId account which should be read + * @return a LocalAccount object for the given accountId + */ public LocalAccount getAccount(long accountId) { + validateAccountId(accountId); SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.query(table_accounts, new String[]{key_id, key_url, key_account_name, key_username, key_etag, key_modified}, key_id + " = ?", new String[]{accountId + ""}, null, null, null, null); LocalAccount account = new LocalAccount(); @@ -740,8 +735,10 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { return accounts; } + @Nullable public LocalAccount getLocalAccountByAccountName(String accountName) { if (accountName == null) { + Log.e(TAG, "accountName is null"); return null; } SQLiteDatabase db = getReadableDatabase(); @@ -759,10 +756,17 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { return account; } - public void deleteAccount(long accountId) { + /** + * + * @param accountId the id of the account that should be deleted + * @throws IllegalArgumentException if no account has been deleted by the given accountId + */ + public void deleteAccount(long accountId) throws IllegalArgumentException { + validateAccountId(accountId); SQLiteDatabase db = this.getWritableDatabase(); int deletedAccounts = db.delete(table_accounts, key_id + " = ?", new String[]{accountId + ""}); if (deletedAccounts < 1) { + Log.e(TAG, "AccountId '" + accountId + "' did not delete any account"); throw new IllegalArgumentException("The given accountId does not delete any row"); } else if (deletedAccounts > 1) { Log.e(TAG, "AccountId '" + accountId + "' deleted unexpectedly '" + deletedAccounts + "' accounts"); @@ -772,6 +776,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { } void updateETag(long accountId, String etag) { + validateAccountId(accountId); SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(key_etag, etag); @@ -784,6 +789,10 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { } void updateModified(long accountId, long modified) { + validateAccountId(accountId); + if(modified < 0) { + throw new IllegalArgumentException("modified must be greater or equal 0"); + } SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(key_modified, modified); @@ -794,4 +803,10 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { Log.e(TAG, "Updated " + updatedRows + " but expected only 1 for accountId = " + accountId + " and modified = " + modified); } } + + private static void validateAccountId(long accountId) { + if(accountId < 1) { + throw new IllegalArgumentException("accountId must be greater than 0"); + } + } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/DatabaseIndexUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/util/DatabaseIndexUtil.java new file mode 100644 index 00000000..74fb97de --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/util/DatabaseIndexUtil.java @@ -0,0 +1,32 @@ +package it.niedermann.owncloud.notes.util; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import androidx.annotation.NonNull; + +public class DatabaseIndexUtil { + + private DatabaseIndexUtil() { + + } + + public static void createIndex(@NonNull SQLiteDatabase db, @NonNull String table, @NonNull String ...columns) { + for (String column: columns) { + createIndex(db, table, column); + } + } + + public static void createIndex(@NonNull SQLiteDatabase db, @NonNull String table, @NonNull String column) { + String indexName = table + "_" + column + "_idx"; + db.execSQL("CREATE INDEX IF NOT EXISTS " + indexName + " ON " + table + "(" + column + ")"); + } + + public static void dropIndexes(@NonNull SQLiteDatabase db) { + Cursor c = db.query("sqlite_master", new String[]{"name"}, "type=?", new String[]{"index"}, null, null, null); + while (c.moveToNext()) { + db.execSQL("DROP INDEX " + c.getString(0)); + } + c.close(); + } +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/SupportUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/util/SupportUtil.java index da1a4a42..0beb4bca 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/util/SupportUtil.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/util/SupportUtil.java @@ -6,12 +6,30 @@ import android.text.Spanned; import android.text.method.LinkMovementMethod; import android.widget.TextView; +import androidx.annotation.NonNull; + /** * Some helper functionality in alike the Android support library. * Currently, it offers methods for working with HTML string resources. */ public class SupportUtil { + private SupportUtil() { + + } + + /** + * Fills a {@link TextView} with HTML content and activates links in that {@link TextView}. + * + * @param view The {@link TextView} which should be filled. + * @param stringId The string resource containing HTML tags (escaped by <) + * @param formatArgs Arguments for the string resource. + */ + public static void setHtml(@NonNull TextView view, int stringId, Object... formatArgs) { + view.setText(SupportUtil.fromHtml(view.getResources().getString(stringId, formatArgs))); + view.setMovementMethod(LinkMovementMethod.getInstance()); + } + /** * Creates a {@link Spanned} from a HTML string on all SDK versions. * @@ -27,16 +45,4 @@ public class SupportUtil { return Html.fromHtml(source); } } - - /** - * Fills a {@link TextView} with HTML content and activates links in that {@link TextView}. - * - * @param view The {@link TextView} which should be filled. - * @param stringId The string resource containing HTML tags (escaped by <) - * @param formatArgs Arguments for the string resource. - */ - public static void setHtml(TextView view, int stringId, Object... formatArgs) { - view.setText(SupportUtil.fromHtml(view.getResources().getString(stringId, formatArgs))); - view.setMovementMethod(LinkMovementMethod.getInstance()); - } }